你在初始化期间如何访问UserControl的XAML设置属性?

9
我们正在编写一个自定义UserControl(与无样式的控件相对),并且我们需要根据消费者在XAML中设置的属性执行一些初始化操作。
现在,在大多数情况下,您会使用Initialized事件(或OnInitialized重载),因为此时所有通过XAML设置的属性都已应用,但在UserControl的情况下,情况并非如此。当Initialized事件触发时,所有属性仍处于默认值。
我没有注意到其他控件,只有UserControls不同,它们在构造函数中调用InitializeComponent(),所以我注释了那行代码并运行了代码,果然,在Initialized事件期间,属性被设置了。
以下是一些代码和测试结果说明这一点...
在构造函数中调用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 

并且属性已经被改变

所有这些都说了,我可以使用什么来基于用户在XAML中设置的属性初始化我的控件?(注意:Loaded太晚了,因为控件应该已经被初始化了。)

XAML片段

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

TestControl.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);
    }
}
2个回答

13

太棒了!我搞定了!

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

问题在于该调用在构造函数中,这反过来会调用OnInitialized(从而引发Initialized事件),但这发生在XAML设置的属性被应用之前,这意味着您还没有访问它们,而我需要访问它们。

有人可能认为这是Loaded事件的一个好用法-根据这些属性完成初始化-但是如果您正在那里执行其他初始化,则会与您的使用者创建潜在的竞争条件,因为如果他们订阅了您的Loaded事件,并在您之前收到它,则在其处理程序中尝试访问您的控件时,他们将访问未初始化的控件。

然后有一些事情发生了... 如上所示,如果您从构造函数中删除InitializeComponent调用,则Initialized事件现在可以按照您的期望工作,但是当然由于您尚未调用InitializeComponent,因此您的UI尚未填充。

那么,如果将该调用移动到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之前无法通过名称访问组成控件,但这只意味着你必须计划将基于名称的初始化移动到InitializeComponentbase.OnInitialize调用之间,那么一切都会正常工作。

1
在类似的情况下,这对我很管用。感谢你的发现! - Jcl
1
很高兴能够帮助。这是当我想通时那种恍然大悟的时刻之一。 - Mark A. Donohoe
1
哇,这个hack真是太棒了。非常感谢你,它完美地解决了我的问题。我差点就要放弃了。 - julian bechtold
是的,可能有点疯狂,但我很高兴我解决了它。知道这个技巧让我的生活变得更轻松了。 - Mark A. Donohoe

0
请在调用InitializeComponent之前注册intialized事件。该事件将在EndInit或OnVisualParentChanges方法被调用后即InitializeComponent之后被触发。
public TestControl()
{
    this.Initialized += TestControl_Initialized;
    InitializeComponent();
}

不完全正确!我刚刚更新了我的问题,显示InitializeComponent引发了该事件。问题在于,在此时XAML设置的属性仍未设置。请参见上面的新输出。 - Mark A. Donohoe
为什么不使用Loaded事件?属性值将在那个时候设置。 - Nitin
正如我之前所说,Loaded事件对于其他各种原因来说太晚了。我们的控件应该在此事件触发时完全构建,如果其他监听我们的Loaded事件的东西先于我们获取它,那么我们就会失去同步。不过,我认为我找到了一个似乎有效的解决方案。请参见上文... - Mark A. Donohoe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接