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

Posted

技术标签:

【中文标题】如何在初始化期间访问 UserControl 的 XAML 集属性?【英文标题】:How can you access XAML-set properties of a UserControl during initialization? 【发布时间】:2013-09-06 03:38:27 【问题描述】:

我们正在编写一个自定义的 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 and the property has been changed

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

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);
    

【问题讨论】:

【参考方案1】:

棒极了!我想通了!

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

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

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

然后我发生了一些事情...如上所示,如果您从构造函数中删除 InitializeComponent 调用,则 Initialized 事件现在可以按预期工作,但是当然您的 UI 还没有水合因为你还没有打电话给InitializeComponent

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

是的!那行得通! :)

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

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

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 的调用之间移动这种基于名称的初始化一切都会好起来的。

【讨论】:

在类似的情况下为我工作。感谢您的发现! 很高兴我能帮上忙。这是我想通的那些时刻之一。【参考方案2】:

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

public TestControl()

    this.Initialized += TestControl_Initialized;
    InitializeComponent();

【讨论】:

不完全!我刚刚更新了我的问题,表明当我发现 InitializeComponent 引发了该事件时。问题是 XAML 设置的属性此时尚未设置。请参阅上面的新输出。 为什么不使用Loaded Event?届时将设置属性值。 正如我上面所说,由于各种其他原因,加载为时已晚。我们的控件应该在触发时完全构建,并且如果其他侦听我们的 Loaded 事件的东西在我们之前得到它,那么我们就不同步了。不过,我想我找到了一个似乎可行的解决方案。见上文...

以上是关于如何在初始化期间访问 UserControl 的 XAML 集属性?的主要内容,如果未能解决你的问题,请参考以下文章

WINFORM UserControl属性的特性

如何从UserControl访问父级的DataContext

如何从其他 UserControl 访问 SimpleChildWindow

如何从 xaml 访问 UserControl 内的按钮?

如何从 UserControl 访问 WPF 页面中的公共变量?

如何从代码隐藏中访问UserControl中样式的Setter中ControlTemplate中定义的Control实例?