[英]How can you access XAML-set properties of a UserControl during initialization?
We're writing a custom UserControl (as opposed to a lookless control) and we need to perform some initialization based on what properties our consumers set on our control in XAML. 我们正在编写自定义UserControl(而不是无形控件),我们需要根据消费者在XAML中控制的属性执行一些初始化。
Now in most cases, you'd use the Initialized event (or the OnInitialized override) since by the time that fires, all the XAML-set properties have been applied, but in the case of a UserControl, that isn't the case. 现在在大多数情况下,您将使用Initialized事件(或OnInitialized覆盖),因为在触发时,已应用了所有XAML集属性,但在UserControl的情况下,情况并非如此。 When the Initialized event fires, all properties are still at their default values. 触发Initialized事件时,所有属性仍处于默认值。
I didn't notice this for other controls, just UserControls, which are different in that they call InitializeComponent() in their constructor, so as a test, I commented that line out and ran the code and sure enough, this time during the Initialized event, the properties were set. 我没有注意到其他控件,只是UserControls,不同之处在于它们在构造函数中调用InitializeComponent(),所以作为测试,我评论说该行并运行代码,果然,这次是在Initialized期间事件的性质设定。
Here is some code and test results demonstrating this... 以下是一些代码和测试结果,证明了这一点......
Result with InitializeComponent called in the constructor: 在构造函数中调用InitializeComponent的结果:
(Note: The values still have not been set) (注意:值仍未设置)
TestValue (Pre-OnInitialized): Original Value
TestValue (Initialized Event): Original Value
TestValue (Post-OnInitialized): Original Value
Result with InitializeComponent completely commented out: InitializeComponent的结果已完全注释掉:
(Note: While the values have been set, the control isn't loaded as it needs 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
All this said, what can I use to initialize my control based on user-set properties in the XAML? 所有这些说,我可以使用什么来初始化我的控件基于XAML中的用户设置属性? (Note: Loaded is too late as the control should already have been initialized by then.) (注意:加载已经太晚了,因为到那时控件应该已经初始化了。)
XAML Snippet XAML片段
<local:TestControl TestValue="New Value!" />
TestControl.cs 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(
"TestValue",
typeof(string),
typeof(TestControl),
new UIPropertyMetadata("Original Value"));
public string TestValue
{
get { return (string)GetValue(TestValueProperty); }
set { SetValue(TestValueProperty, value); }
}
}
Awesomesausage! Awesomesausage! I figured it out! 我想到了!
Normally when you receive the Initialized
event (or are inside the OnInitialized
override) you have access to XAML-set property values. 通常,当您收到Initialized
事件(或在OnInitialized
覆盖内)时,您可以访问XAML设置的属性值。 However, UserControl
classes work a little differently as they depend on InitializeComponent
being called to hydrate the UI and set the related member variables, etc. 但是, UserControl
类的工作方式略有不同,因为它们依赖于调用InitializeComponent
来水合UI并设置相关的成员变量等。
The problem is that call is in the constructor, which in turn ends up calling OnInitialized
(and thus raising the Initialized
event) but that happens way before the XAML-set properties have been applied, meaning you don't have access to them yet, which I needed. 问题是调用是在构造函数中,而构造函数最终调用OnInitialized
(从而引发Initialized
事件)但是在应用XAML集属性之前发生了这种情况,这意味着您还没有访问它们,我需要的。
One may think that's a good use for the Loaded
event--to finish initialization based on those properties--but if you're performing additional initialization there, you're creating a potential race condition with your consumers in that if they subscribe to your Loaded
event and get it before you, then in their handler try to access your control, they will be accessing an uninitialized control. 有人可能会认为这对Loaded
事件很有用 - 根据这些属性完成初始化 - 但是如果你在那里进行额外的初始化,那么如果他们订阅了你的消费者就会产生潜在的竞争条件Loaded
事件并在你之前得到它,然后在他们的处理程序中尝试访问你的控件,他们将访问一个未初始化的控件。
Then something occurred to me... As I showed above, if you remove the InitializeComponent
call from the constructor, the Initialized
event now works as you would expect, but of course your UI isn't hydrated yet since you haven't yet called InitializeComponent
. 然后我发生了一些事情......正如我在上面所示,如果你从构造函数中删除了InitializeComponent
调用,那么Initialized
事件现在可以正常工作了,但是当然你的UI还没有被水合,因为你还没有调用InitializeComponent
。
So what would happen if you moved that call to the beginning of the OnInitialized
override, before the call to base.OnInitialized
, and thus before the Initialized
event was raised? 那么如果在调用base.OnInitialized
之前将调用移动到OnInitialized
覆盖的开头,从而在Initialized
事件被引发之前,会发生什么?
Yep! 是的! That worked! 那很有效! :) :)
This way not only do you have the XAML-set properties, but you'd also have the UI fully loaded before anyone gets the Initialized
event (let alone the Loaded
event), which is how the Initialized
event is supposed to be used. 这样你不仅拥有XAML集属性,而且在任何人获得Initialized
事件(更不用说Loaded
事件)之前你也会完全加载UI,这就是应该使用Initialized
事件的方式。
Below is the revised code... 以下是修订后的代码......
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
call, but that just means you have to plan to move such name-based initialization between InitializeComponent
and that call to base.OnInitialize
and things will work just fine. 如果你这样做,只记得在InitializeComponent
调用之前你不能按名称访问组成控件,但这只是意味着你必须计划在InitializeComponent
和对base.OnInitialize
调用之间移动这种基于名称的初始化,事情会起作用正好。 Please register the intialized event before calling InitializeComponent. 请在调用InitializeComponent之前注册初始化事件。 This event will be raised whenever the EndInit or OnVisualParentChanges methods are called ie after InitializeComponent. 只要调用EndInit或OnVisualParentChanges方法(即在InitializeComponent之后),就会引发此事件。
public TestControl()
{
this.Initialized += TestControl_Initialized;
InitializeComponent();
}
Thanks 谢谢
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.