Santiago Palladino

Santiago Palladino

Silverlight Loaded Events

9 min
Aug 5 2008
cpdomg
9 min
Aug 5 2008

A common scenario in the development of GUI applications is the presence of different events fired by the engine as the visual tree is constructed and rendered. And Silverlight, of course, is no exception.

While WPF provides Initialized and Loaded events for all framework elements, Silverlight has only the latter. There is also a LayoutUpdated method, but (as we will see in a few moments) is not very useful.

To check the order of the events fired by Silverlight when rendering the visual tree, I created a small test: it consisted in a grid containing a border containing a textbox. I wrote handlers for the Loaded, SizeChanged and Layout events, and outputted them to the console.

Note that MSDN recommends NOT to use javascript's alert method to check the order of the events fired, since it could not match the real one. Don't ask how this might happen.

Also, if you are dealing with a User Control you built yourself, there is also the possibility to override the Measure and Arrange methods, which correspond to the two passes the Silverlight engine makes when deciding the layout of the visual tree. Here I will limit the test to events visible from the outside.

Here is the output of the first test run. I'm writing the control's name (level means the level within the tree), its render size, and margin set. The last one will serve as a witness to know when it is actually set, being specified in XAML.

Loaded LevelThreeTextbox render size 0x0 margin 1
Loaded LevelTwoBorder render size 0x0 margin 1
Loaded LevelOneGrid render size 0x0 margin 1
Size   LevelOneGrid render size 300x300 margin 1
Size   LevelTwoBorder render size 98x98 margin 1
Size   LevelThreeTextbox render size 20x96 margin 1
Layout Sender Null
Layout Sender Null
Layout Sender Null

As expected, the Loaded event gets fired first, since (according to the documentation) it is fired when all properties are set (note that margin's value is 1). Also notice that it is fired in the leaves first, since there is no point in setting the "Children" property of a control and raising its loaded event, having unloaded children.

Then, SizeChanged is fired, which makes sense if you consider that size has gone to a valid value from no size at all. The problem here is that the root fires first. You will soon see why this is a problem.

Finally, Layout methods are fired, without any information of the sender at all (just a null reference), which renders it completely unusable.

Another test I decided to undertake was adding more controls to the grid, and check in which order they were loaded/drawn: position in grid or order of creation in XAML.

Here is the XAML I used:

<Grid x:Name="LevelOneGrid" Height="300" Width="300" Loaded="Element_Loaded" SizeChanged="Element_SizeChanged" LayoutUpdated="Element_LayoutUpdated" Margin="1">
    <Grid.RowDefinitions>
        <RowDefinition Height="*" />
        <RowDefinition Height="*"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>

    <Border x:Name="LevelTwoBorderAlpha" Grid.Column="1" Grid.Row="1" Background="Beige" Loaded="Element_Loaded" SizeChanged="Element_SizeChanged" LayoutUpdated="Element_LayoutUpdated" Margin="1">
        <TextBox x:Name="LevelThreeTextboxAlpha" Text="Test" TextAlignment="Center" HorizontalAlignment="Stretch" VerticalAlignment="Center" Loaded="Element_Loaded" SizeChanged="Element_SizeChanged" LayoutUpdated="Element_LayoutUpdated" Margin="1"/>
    </Border>

    <Border x:Name="LevelTwoBorderBeta" Grid.Column="0" Grid.Row="0" Background="Beige" Loaded="Element_Loaded" SizeChanged="Element_SizeChanged" LayoutUpdated="Element_LayoutUpdated" Margin="1">
        <TextBox x:Name="LevelThreeTextboxBeta" Text="Test" TextAlignment="Center" HorizontalAlignment="Stretch" VerticalAlignment="Center" Loaded="Element_Loaded" SizeChanged="Element_SizeChanged" LayoutUpdated="Element_LayoutUpdated" Margin="1"/>
    </Border>

    <Border x:Name="LevelTwoBorderGamma" Grid.Column="2" Grid.Row="2" Background="Beige" Loaded="Element_Loaded" SizeChanged="Element_SizeChanged" LayoutUpdated="Element_LayoutUpdated" Margin="1">
        <TextBox x:Name="LevelThreeTextboxGamma" Text="Test" TextAlignment="Center" HorizontalAlignment="Stretch" VerticalAlignment="Center" Loaded="Element_Loaded" SizeChanged="Element_SizeChanged" LayoutUpdated="Element_LayoutUpdated" Margin="1"/>
    </Border>

    <Border x:Name="LevelTwoBorderDelta" Grid.Column="0" Grid.Row="2" Background="Beige" Loaded="Element_Loaded" SizeChanged="Element_SizeChanged" LayoutUpdated="Element_LayoutUpdated" Margin="1">
        <TextBox x:Name="LevelThreeTextboxDelta" Text="Test" TextAlignment="Center" HorizontalAlignment="Stretch" VerticalAlignment="Center" Loaded="Element_Loaded" SizeChanged="Element_SizeChanged" LayoutUpdated="Element_LayoutUpdated" Margin="1"/>
    </Border>

</Grid>

And here the output:

Loaded LevelThreeTextboxAlpha render size 0x0 margin 1
Loaded LevelTwoBorderAlpha render size 0x0 margin 1 (1,1)
Loaded LevelThreeTextboxBeta render size 0x0 margin 1
Loaded LevelTwoBorderBeta render size 0x0 margin 1 (0,0)
Loaded LevelThreeTextboxGamma render size 0x0 margin 1
Loaded LevelTwoBorderGamma render size 0x0 margin 1 (2,2)
Loaded LevelThreeTextboxDelta render size 0x0 margin 1
Loaded LevelTwoBorderDelta render size 0x0 margin 1 (2,0)
Loaded LevelOneGrid render size 0x0 margin 1

Size   LevelOneGrid render size 300x300 margin 1
Size   LevelTwoBorderDelta render size 98x98 margin 1 (2,0)
Size   LevelThreeTextboxDelta render size 20x96 margin 1
Size   LevelTwoBorderGamma render size 98x98 margin 1 (2,2)
Size   LevelThreeTextboxGamma render size 20x96 margin 1
Size   LevelTwoBorderBeta render size 98x98 margin 1 (0,0)
Size   LevelThreeTextboxBeta render size 20x96 margin 1
Size   LevelTwoBorderAlpha render size 98x98 margin 1 (1,1)
Size   LevelThreeTextboxAlpha render size 20x96 margin 1

Layout Sender Null
Layout Sender Null
Layout Sender Null
Layout Sender Null
Layout Sender Null
Layout Sender Null
Layout Sender Null
Layout Sender Null
Layout Sender Null 

Loaded events are fired in order of creation in XAML, which makes sense. The big surprise is that size changed events are fired in exactly the opposite order. And Layout keeps the same behaviour as usual: none.

Now, to the motivation of this post. I had a relatively complex user control, which contained several children, and I needed to position a selection window over it, based on the position of the children.

The problem is that there was no event I could attach to in the user control that could notify that all children were fully rendered, since the control's SizeChanged fired before its children's. And I definitively wanted to avoid LayoutUpdated.

So I wrote a small class, the RenderNotifier, that attaches itself to a control and receives a list of children to monitor. When all of them raise their size changed events, the  RenderNotifier raises its own RenderComplete event.

It contains a list of children and attaches itself to each SizerChanged event, and detaches on remove.

public RenderNotifier(FrameworkElement targetElement)
{
    this.TargetElement = targetElement;
    TargetElement.SizeChanged += new SizeChangedEventHandler(TargetElement_SizeChanged);
}

public void AddObservedChild(FrameworkElement child)
{
    if (!observedChildren.Contains(child))
    {
        child.SizeChanged += Child_SizeChanged;
        observedChildren.Add(child);
    }
}

public void RemoveObservedChild(FrameworkElement child)
{
    if (observedChildren.Remove(child))
    {
        child.SizeChanged -= Child_SizeChanged;
    }
}

public void ClearObservedChildren()
{
    foreach (FrameworkElement elem in observedChildren)
    {
        elem.SizeChanged -= Child_SizeChanged;
    }

    observedChildren.Clear();
}

And whenever the parent control fires its size changed, the Notifier counts the children's notifications and raises its own event after all have been fired.

int notificationsPending = 0;

void Child_SizeChanged(object sender, SizeChangedEventArgs e)
{
    if (--notificationsPending == 0)
    {
        OnRenderComplete();
    }
}

void TargetElement_SizeChanged(object sender, SizeChangedEventArgs e)
{
    notificationsPending = observedChildren.Count;
}

Quite a lot of work just to know when the tree is completely rendered. But at least it works.

And returns a non-null sender in its callbacks.