Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Specifically for a User Control, is there a place after the constructor, but before the Loaded event where you can access properties set in XAML?

Tags:

c#

wpf

loaded

We have an issue where our user control is initialized based on what the user sets in XAML when utilizing our control. Currently we were using the 'Loaded' event to act on what the user had set or not.

However, the issue with using the Loaded event is another sibling of this control is using their loaded event to set something on ours, which isn't yet fully initialized since our Loaded event hasn't yet fired. (It's a UI race condition if you will.)

Again, we can't move our code to the constructor as the WPF system hasn't yet set the properties specified by the XAML of the consumer of our control. We can't use the Loaded event for the reasons stated above. Initialized doesn't seem to work either.

I've also looked into ISupportsInitialize, but that's where we would be batch-setting the control's properties, not something externally, so that doesn't seem to be a fit either.

Thoughts?

Update

I've since found out this is an anomaly specifically with UserControls. They handle initialization differently. You can find more details in my follow-up question here...

  • How can you access XAML-set properties of a UserControl during initialization?

...but the short version is calling InitializeComponent in the constructor actually raises the Initialized event, but does so before the XAML-defined properties have actually been set. Comment it out and the properties are now set when Initialized fires, but of course your control's UI isn't loaded! Kinda frustrating actually.

Still looking for a solution. Code examples and more details can be found there.

like image 683
Mark A. Donohoe Avatar asked Sep 02 '13 20:09

Mark A. Donohoe


3 Answers

[Copying my answer from my other question here.]

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. 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.

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.

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.

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.

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?

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.

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

}
  • Note: You don't need the constructor anymore unless you have a specific need to do other things there. And if you do, just remember you can't access constituent controls by name until after the 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.
like image 110
Mark A. Donohoe Avatar answered Nov 10 '22 03:11

Mark A. Donohoe


There is the Window.Initialized event which comes after the constructor is called and before the Window.Loaded event. Properties will be set by then, but DynamicResource and Binding values won't. From the Object Lifetime Events page on the MSDN website:

Initialized is raised first, and roughly corresponds to the initialization of the object by the call to its constructor. Because the event happens in response to initialization, you are guaranteed that all properties of the object are set. (An exception is expression usages such as dynamic resources or binding; these will be unevaluated expressions.) As a consequence of the requirement that all properties are set, the sequence of Initialized being raised by nested elements that are defined in markup appears to occur in order of deepest elements in the element tree first, then parent elements toward the root. This order is because the parent-child relationships and containment are properties, and therefore the parent cannot report initialization until the child elements that fill the property are also completely initialized.

When you are writing handlers in response to the Initialized event, you must consider that there is no guarantee that all other elements in the element tree (either logical tree or visual tree) around where the handler is attached have been created, particularly parent elements. Member variables may be null, or data sources might not yet be populated by the underlying binding (even at the expression level).

like image 41
Sheridan Avatar answered Nov 10 '22 03:11

Sheridan


How coincidental that an 8 month old post is answered 2 days ago with the exact problem I am currently having (but with WinRT XAML, not WPF).

Ran into this same issue with a UserControl that contains an Image control. UserControl has a custom integer dependency property whose change handler sets the Image.Source to a new BitmapImage (the integer value determine which image to show).

Had a race condition where the image was not always being displayed properly. Very intermittent and unfortunately I published a Win 8.1 and WinPhone 8.1 app with the bug. Ugh.

Thank you for your contributions MarqueIV and Sheridanless.

like image 40
Art Dumas Avatar answered Nov 10 '22 01:11

Art Dumas