UIElement 在什么时候加载?

At what point is a UIElement loaded?

我有以下代码:

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

}

由于 lastRectangle.ActualWidth 为 0,因此没有按预期工作(将每个矩形彼此对角放置)。据我了解 this answer,这是因为 lastRectangle 尚未测量和排列。

我很好奇,如果不添加到已经可见并已加载的容器中,将在什么时候进行测量和排列?

window的测量整理工作已经完成。但是您现在正在创建新的子控件,这些控件通常不会 measured/arranged 直到 window 的下一个布局传递。

您可以通过在循环结束时调用每个矩形的 Measure 方法来强制立即发生这种情况。

元素的 Framework.Loaded 事件在测量和排列布局传递之后但在呈现元素之前引发。

当在元素上调用 UIElement.InvalidateMeasure 异步布局过程或 UIElement.UpdateLayout 同步布局过程时,将初始化完整的布局过程。

在您的场景中,调用了 Window.Loaded 事件处理程序,这意味着 WindowCanvas 都已加载(但未呈现)。
现在开始向 Canvas.

添加新的 UIElement 元素

Canvas.Children.Add 应该调用 InvalidateMeasure 方法。因为 InvalidateMeasure 触发异步布局传递,所以 Canvas 和当前 child 元素将被排入布局队列并且上下文继续执行(添加更多矩形)。
由于新添加的元素已经存在挂起的布局传递,因此您应该避免手动调用 UIElement.Measure(这是递归调用,考虑到性能时相当昂贵)。

上下文完成后,将处理队列中等待布局传递和最终渲染的元素,并对这些元素递归调用 MeasureOverrideArrangeOverride ( Canvas及其children).
结果,UIElement.RenderSize可以通过布局系统计算出来。
此时,新的FrameworkElement.ActualWidth将可用。

这是添加元素(矩形)的 FrameworkElement.Loaded 事件最终引发的时刻。

要解决您的问题,您必须改用 Rectangle.Width
或者在添加下一个之前等待每个 Rectangle.Laoded 事件:

private int ShapeCount { get; set; }
private const int MaxShapes = 5;
private Point ShapeBPosition { get; set; }

private void MainWindow_Loaded(Object sender, RoutedEventArgs e)
{
  this.ShapePosition = new Point();
  AddRectangle(this.ShapePosition);
}

private void AddRectangle(Point position)
{
  Random random = new Random();
  Rectangle rectangle = new Rectangle();
  rectangle.Fill = Brushes.Blue;

  rectangle.Width = random.Next(100, 200);
  rectangle.Height = ++this.ShapeCount * 100;

  Canvas_Main.Children.Add(rectangle);
  Canvas.SetLeft(rectangle, position.X);
  Canvas.SetTop(rectangle, position.Y);

  rectangle.Loaded += OnRectangleLoaded;
}

private void OnRectangleLoaded(object sender, RoutedEventArgs e)
{
  var rectangle = sender as Rectangle;

  rectangle.Loaded -= OnRectangleLoaded;
  if (this.ShapeCount == MainWindow.MaxShapes)
  {
    return;
  }

  // this.ShapePosition is struct => modify copy
  Point position = this.ShapePosition; 
  position.Offset(rectangle.ActualWidth, 0);
  this.ShapePosition = position;
  AddRectangle(this.ShapePosition);
}