I have the following code:
<Window x:Class="Demo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Canvas Name="Canvas_Main" />
</Window>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(Object sender, RoutedEventArgs e)
{
Rectangle lastRectangle = null;
Random random = new Random(0);
for (Int32 counter = 0; counter < 5; counter++)
{
Rectangle rectangle = new Rectangle();
rectangle.Fill = Brushes.Blue;
rectangle.Width = random.Next(100, 200);
rectangle.Height = counter * 100;
Canvas_Main.Children.Add(rectangle);
if (lastRectangle == null) {
Canvas.SetLeft(rectangle, 0);
Canvas.SetTop(rectangle, 0);
}
else
{
Canvas.SetLeft(rectangle, lastRectangle.ActualWidth);
Canvas.SetTop(rectangle, 0);
}
lastRectangle = rectangle;
}
}
}
This isn't working as expected (laying each rectangle diagonally next to each other), as lastRectangle.ActualWidth
is 0. As I understand things from this answer, it is because lastRectangle has not been measured and arranged.
I am curious, at what point would the measuring and arranging be done, if not when added to a container that is already visible and loaded?
The
Framework.Loaded
event of an element is raised after the measure and arrange layout pass, but before the rendering of the element.The complete layout pass is initialized when
UIElement.InvalidateMeasure
for an asynchronous layout pass orUIElement.UpdateLayout
for a synchronous layout pass was invoked on the element.In your scenario the
Window.Loaded
event handler is invoked, which means that theWindow
and theCanvas
are both loaded (but not rendered).Now you start to add new
UIElement
elements to theCanvas
.Canvas.Children.Add
should invoke theInvalidateMeasure
method. BecauseInvalidateMeasure
triggers an asynchronous layout pass, theCanvas
and therefore the current child element will be enqueued into the layout queue and the context continues execution (adding more rectangles).Because there is already a pending layout pass due to the freshly added element, you should avoid calling
UIElement.Measure
manually (this are recursive calls and quite expensive when considering performance).Once the context has completed, the elements in the queue, that are waiting for their layout pass and final rendering, will be handled and
MeasureOverride
andArrangeOverride
are invoked recursively on those elements (Canvas
and its children).As a result,
UIElement.RenderSize
can be calculated by the layout system.At this moment, the new
FrameworkElement.ActualWidth
will be available.This is the moment the
FrameworkElement.Loaded
event of the added elements (the rectangles) is finally raised.To solve your problem you have to either use
Rectangle.Width
insteador wait for each
Rectangle.Laoded
event before adding the next: