Layout Events - SizeChanged and LayoutUpdated

Executive Summary: Most of the time, SizeChanged is the right event to use, and LayoutUpdated is the wrong event. 

The Silverlight 2 layout system offers two events: SizeChanged and LayoutUpdated. They look the same...here is how they are hooked up in C#:

public Page()

{

    InitializeComponent();

    LayoutRoot.SizeChanged += LayoutRoot_SizeChanged;

    LayoutRoot.LayoutUpdated += LayoutRoot_LayoutUpdated;

}

The handlers are a bit different, though. Note that the LayoutUpdated event just has a standard EventArgs, while the SizeChanged event has the SizeChangedEventArgs, which very helpfully contains the old and new sizes.

void LayoutRoot_LayoutUpdated(object sender, EventArgs e)

{

}

private void LayoutRoot_SizeChanged(object sender, SizeChangedEventArgs e)

{

}

So far, they look pretty much the same, except for the additional information provided by the SizeChanged event. But this is not the case.

SizeChanged

The SizeChanged event is an "instance event" that is raised whenever the size of the element you have attached the handler to has changed. Note that if the position of the element on the screen changes, but not its size, this event does not get raised.

LayoutUpdated

The LayoutUpdated event is a "static event" that is fired every time layout had anything to do anywhere in the tree. You may find that it is getting fired much more often than you thought it would be. That's the reason why.

Sequence of events

The two events are also raised at different times. Here is roughly the way that the layout loop works:

while any element needs to be measured or arranged
{
while any element needs to be measured
{
measure everything that needs measuring
}

    while anything element needs to be arranged and no element needs to be measured
{
arrange everything that needs to be arranged
}

if any element needs to be measured or arranged
{
continue
}

    if there are any SizeChanged events to raise
{
raise the SizeChanged events
}

    if any element needs to be measured or arranged
{
continue
}

    if layout did anything
{
raise the LayoutUpdated event
}
}

Layout keeps looping until there was nothing for it to do or until the cycle detection kicks in. This means that you can change properties that affect layout in the SizeChanged and LayoutUpdated event handlers. In fact, it is kind of assumed that you'll be doing that in your SizeChanged handlers. The LayoutUpdated event fires when the layout system thinks it is all finished. I used it for my Snapper element only because I needed to be notified when the Snapper moved, not just when it changed size, and there is no other way to get that information.

Example

Here's an example of an app the handles the SizeChanged and LayoutUpdated events.

Don't forget to modify the x:Class to match your app.

<UserControl x:Class="LayoutEvents.Page"

xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml">

<Grid x:Name="LayoutRoot" Background="BurlyWood">

<Grid.RowDefinitions>

<RowDefinition Height="*"/>

<RowDefinition Height="Auto"/>

</Grid.RowDefinitions>

<Rectangle Margin="4" Fill="AliceBlue"/>

<StackPanel x:Name="buttonPanel" Width="150" Margin="8" MinWidth="100" MaxWidth="200">

<Button Content="Width += 10" Margin="2" Click="ButtonAdd_Click"/>

<Button Content="Width -= 10" Margin="2" Click="ButtonSubtract_Click"/>

</StackPanel>

<Rectangle Fill="Coral" Grid.Row="1"/>

<TextBlock x:Name="caption" Margin="4" Grid.Row="1" Text="Caption"/>

</Grid>

</UserControl>

...and here's the code. Don't forget to make sure that the namespace matches the XAML.

 

using System;

using System.Windows;

using System.Windows.Controls;

namespace LayoutEvents

{

    public partial class Page : UserControl

    {

        public Page()

        {

            InitializeComponent();

            LayoutRoot.SizeChanged += LayoutRoot_SizeChanged;

            LayoutRoot.LayoutUpdated += LayoutRoot_LayoutUpdated;

        }

        void LayoutRoot_LayoutUpdated(object sender, EventArgs e)

        {

            if (!updating)

            {

                updating = true;

                ++layoutUpdated;

                UpdateCaption();

            }

            else

                updating = false;

        }

        private void LayoutRoot_SizeChanged(object sender, SizeChangedEventArgs e)

        {

            caption.Visibility = (e.NewSize.Width < 300 || e.NewSize.Height < 300) ? Visibility.Collapsed : Visibility.Visible;

            if (!updating)

            {

                ++sizeChanged;

                UpdateCaption();

            }

        }

        private void ButtonAdd_Click(object sender, RoutedEventArgs e)

        {

            buttonPanel.Width += 10;

        }

        private void ButtonSubtract_Click(object sender, RoutedEventArgs e)

        {

            buttonPanel.Width -= 10;

        }

        private void UpdateCaption()

        {

            caption.Text = "LayoutUpdated: " + layoutUpdated + " SizeChanged: " + sizeChanged;

        }

        int layoutUpdated, sizeChanged;

        bool updating;

    }

}

 

You will see that the caption area only appears when the browser is large enough. By resizing the browser (assuming that your HTML page autosizes the Silverlight plugin to be fill the page) you can see both events being raised. But when you click on the Buttons, you will notice that the LayoutUpdated event is raised, even though you attached the handler to the LayoutRoot and the LayoutRoot has not changed at all.