8

我们有一个问题,我们的用户控件是根据用户在使用我们的控件时在 XAML 中设置的内容来初始化的。目前我们正在使用“加载”事件来处理用户设置的内容。

然而,使用 Loaded 事件的问题是这个控件的另一个兄弟是使用他们的加载事件在我们的上设置一些东西,因为我们的Loaded 事件还没有触发,所以它还没有完全初始化。(如果你愿意,这是一个 UI 竞争条件。)

同样,我们不能将代码移至构造函数,因为 WPF 系统尚未设置控件使用者的 XAML 指定的属性。由于上述原因,我们不能使用 Loaded 事件。初始化似乎也不起作用。

我还研究了 ISupportsInitialize,但这就是我们要批量设置控件属性的地方,而不是外部的东西,所以这似乎也不合适。

想法?

更新

从那以后,我发现这是 UserControls 的异常情况。它们以不同的方式处理初始化。您可以在我的后续问题中找到更多详细信息...

...但简短的版本是在构造函数中调用 InitializeComponent 实际上引发 Initialized 事件,但在实际设置 XAML 定义的属性之前这样做。将其注释掉,现在在 Initialized 触发时设置属性,但是当然您的控件的 UI 没有加载!实际上有点令人沮丧。

仍在寻找解决方案。可以在此处找到代码示例和更多详细信息。

4

3 回答 3

8

[从这里复制我的另一个问题的答案。]

真棒香肠!我想到了!

通常,当您收到Initialized事件(或在OnInitialized覆盖内)时,您可以访问 XAML 设置的属性值。然而,UserControl类的工作方式略有不同,因为它们依赖于InitializeComponent被调用来水合 UI 并设置相关的成员变量等。

问题是调用在构造函数中,最终调用OnInitialized(并因此引发Initialized事件),但这发生在应用 XAML 设置属性之前,这意味着您还没有访问它们的权限,我需要。

有人可能认为这对Loaded事件很有用——根据这些属性完成初始化——但如果你在那里执行额外的初始化,你就会与你的消费者创建一个潜在的竞争条件,如果他们订阅你的Loaded事件并在你面前得到它,然后在他们的处理程序中尝试访问你的控件,他们将访问一个未初始化的控件。

然后我发生了一些事情......正如我在上面展示的那样,如果你InitializeComponent从构造函数中删除调用,Initialized事件现在会按你的预期工作,但当然你的 UI 还没有水合,因为你还没有调用InitializeComponent.

那么,如果您将该调用移到OnInitialized覆盖的开头,在调用之前base.OnInitialized,因此在Initialized引发事件之前会发生什么?

是的!那行得通!:)

这样,您不仅拥有了 XAML 设置的属性,而且还可以在任何人收到Initialized事件(更不用说Loaded事件)之前完全加载 UI,这就是Initialized应该使用事件的方式。

下面是修改后的代码...

public partial class TestControl : UserControl
{
    protected override void OnInitialized(EventArgs e)
    {
        InitializeComponent();
        base.OnInitialized(e);
    }

    public static readonly DependencyProperty TestValueProperty = DependencyProperty.Register(
        "TestValue",
        typeof(string),
        typeof(TestControl),
        new UIPropertyMetadata("Original Value"));

    public string TestValue
    {
        get { return (string)GetValue(TestValueProperty); }
        set { SetValue(TestValueProperty, value); }
    }

}
  • 注意:您不再需要构造函数,除非您有特殊需要在那里做其他事情。如果你这样做了,请记住在调用之后你才能通过名称访问组成控件InitializeComponent,但这只是意味着你必须计划在InitializeComponent调用和调用之间移动这种基于名称的初始化,base.OnInitialize并且一切都会正常工作。
于 2014-05-21T14:02:16.457 回答
2

有一个Window.Initialized事件发生在调用构造函数之后和Window.Loaded事件之前。届时将设置属性,但不会设置DynamicResourceBinding。从 MSDN 网站上的对象生命周期事件页面:

Initialized 首先被引发,并且大致对应于通过调用其构造函数来初始化对象。因为事件是响应初始化而发生的,所以可以保证对象的所有属性都已设置。(一个例外是表达式使用,例如动态资源或绑定;这些将是未计算的表达式。)由于要求设置所有属性,因此由标记中定义的嵌套元素引发的 Initialized 序列似乎发生在首先是元素树中最深的元素的顺序,然后是根元素的父元素。这个顺序是因为父子关系和包含是属性,所以父子不能报告初始化,直到填充属性的子元素也完全初始化。

当您编写处理程序以响应 Initialized 事件时,您必须考虑不能保证已创建附加处理程序的元素树(逻辑树或可视树)中的所有其他元素,尤其是父元素。成员变量可能为空,或者数据源可能尚未由底层绑定填充(即使在表达式级别)。

于 2013-09-02T21:50:54.653 回答
2

2 天前回答了一个 8 个月大的帖子与我目前遇到的确切问题(但使用 WinRT XAML,而不是 WPF),这是多么巧合。

包含 Image 控件的 UserControl 遇到了同样的问题。UserControl 有一个自定义整数依赖属性,其更改处理程序将 Image.Source 设置为新的 BitmapImage(整数值确定要显示的图像)。

有一个竞争条件,图像并不总是正确显示。非常断断续续,不幸的是我发布了一个带有错误的 Win 8.1 和 WinPhone 8.1 应用程序。啊。

感谢您对 MarqueIV 和 Sheridanless 的贡献。

于 2014-05-24T04:17:30.470 回答