7

我们正在编写一个自定义的 UserControl(与无外观的控件相反),我们需要根据我们的消费者在 XAML 中为我们的控件设置的属性执行一些初始化。

现在,在大多数情况下,您将使用 Initialized 事件(或 OnInitialized 覆盖),因为在触发时,所有 XAML 设置的属性都已应用,但对于 UserControl,情况并非如此。当 Initialized 事件触发时,所有属性仍为其默认值。

对于其他控件,我没有注意到这一点,只是 UserControls,它们的不同之处在于它们在构造函数中调用 InitializeComponent(),因此作为测试,我注释掉了该行并运行了代码,果然,这次是在初始化期间事件,属性设置。

这里有一些代码和测试结果证明了这一点......

在构造函数中调用 InitializeComponent 的结果:(
注意:尚未设置值)

TestValue (Pre-OnInitialized): Original Value
TestValue (Initialized Event): Original Value
TestValue (Post-OnInitialized): Original Value

InitializeComponent 完全注释掉的结果:(
注意:虽然值已设置,但控件未加载,因为它需要 InitializeComponent)

TestValue (Pre-OnInitialized): New Value!
TestValue (Initialized Event): New Value!
TestValue (Post-OnInitialized): New Value! // Event *was* called and the property has been changed

综上所述,我可以使用什么来根据 XAML 中的用户设置属性来初始化我的控件?(注意:加载为时已晚,因为此时控件应该已经初始化。)

XAML 代码段

<local:TestControl TestValue="New Value!" />

测试控制.cs

public partial class TestControl : UserControl {

    public TestControl() {
        this.Initialized += TestControl_Initialized;
        InitializeComponent();
    }

    protected override void OnInitialized(EventArgs e) {
        Console.WriteLine("TestValue (Pre-OnInitialized): " + TestValue);
        base.OnInitialized(e);
        Console.WriteLine("TestValue (Post-OnInitialized): " + TestValue);
    }

    void TestControl_Initialized(object sender, EventArgs e) {
        Console.WriteLine("TestValue (Initialized Event): " + TestValue);
    }

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

    public string TestValue {
        get => (string)GetValue(TestValueProperty);
        set => SetValue(TestValueProperty, value);
    }
}
4

2 回答 2

10

真棒香肠!我想到了!

通常,当您收到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(
        nameof(TestValue),
        typeof(string),
        typeof(TestControl),
        new UIPropertyMetadata("Original Value"));

    public string TestValue {
        get => (string)GetValue(TestValueProperty);
        set => SetValue(TestValueProperty, value);
    }
}
  • 注意:您不再需要构造函数,除非您有特殊需要在那里做其他事情。如果你这样做了,请记住在调用之后你才能通过名称访问组成控件InitializeComponent,但这只是意味着你必须计划在InitializeComponent调用和调用之间移动这种基于名称的初始化,base.OnInitialize并且一切都会正常工作。
于 2013-09-03T03:26:14.083 回答
0

请在调用 InitializeComponent 之前注册已初始化的事件。每当调用 EndInit 或 OnVisualParentChanges 方法时,即在 InitializeComponent 之后,都会引发此事件。

public TestControl()
{
    this.Initialized += TestControl_Initialized;
    InitializeComponent();
}
于 2013-09-03T02:53:12.157 回答