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.Loadedevent 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.InvalidateMeasurefor an asynchronous layout pass orUIElement.UpdateLayoutfor a synchronous layout pass was invoked on the element.In your scenario the
Window.Loadedevent handler is invoked, which means that theWindowand theCanvasare both loaded (but not rendered).Now you start to add new
UIElementelements to theCanvas.Canvas.Children.Addshould invoke theInvalidateMeasuremethod. BecauseInvalidateMeasuretriggers an asynchronous layout pass, theCanvasand 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.Measuremanually (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
MeasureOverrideandArrangeOverrideare invoked recursively on those elements (Canvasand its children).As a result,
UIElement.RenderSizecan be calculated by the layout system.At this moment, the new
FrameworkElement.ActualWidthwill be available.This is the moment the
FrameworkElement.Loadedevent of the added elements (the rectangles) is finally raised.To solve your problem you have to either use
Rectangle.Widthinsteador wait for each
Rectangle.Laodedevent before adding the next: