问题描述
<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并且安排。
我很好奇,如果不添加到已经可见并已装载的容器中,什么时候可以进行测量和安排?
解决方法
在度量和排列布局通过之后但在呈现元素之前引发元素的Framework.Loaded
事件。
在元素上调用UIElement.InvalidateMeasure
进行异步布局遍历或UIElement.UpdateLayout
进行同步布局遍历时,将初始化完整的布局遍历。
在您的方案中,将调用Window.Loaded
事件处理程序,这意味着Window
和Canvas
都已加载(但未呈现)。
现在,您开始向UIElement
添加新的Canvas
元素。
Canvas.Children.Add
应该调用InvalidateMeasure
方法。由于InvalidateMeasure
触发了异步布局传递,因此Canvas
以及当前的子元素将被排入布局队列,并且上下文继续执行(添加更多矩形)。
由于新添加的元素已经存在待处理的布局传递,因此您应该避免手动调用UIElement.Measure
(这是递归调用,考虑性能时非常昂贵)。
上下文完成后,将等待队列中等待布局通过和最终渲染的元素,并在这些元素上递归调用MeasureOverride
和ArrangeOverride
({{ 1}}及其子对象。
结果,Canvas
可以由布局系统计算。
此时,新的UIElement.RenderSize
将可用。
这是添加的元素(矩形)的FrameworkElement.ActualWidth
事件最终引发的时刻。
要解决您的问题,您必须改用FrameworkElement.Loaded
或等待每个Rectangle.Width
事件,然后再添加下一个事件:
Rectangle.Laoded
,
该窗口的测量和布置已完成。但是,您现在正在创建新的子控件,通常直到窗口的下一个布局通过时才进行测量/布置。
您可以通过在循环末尾调用每个矩形的Measure
方法来强制执行此操作。