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
事件处理程序,这意味着 Window
和 Canvas
都已加载(但未呈现)。
现在开始向 Canvas
.
添加新的 UIElement
元素
Canvas.Children.Add
应该调用 InvalidateMeasure
方法。因为 InvalidateMeasure
触发异步布局传递,所以 Canvas
和当前 child 元素将被排入布局队列并且上下文继续执行(添加更多矩形)。
由于新添加的元素已经存在挂起的布局传递,因此您应该避免手动调用 UIElement.Measure
(这是递归调用,考虑到性能时相当昂贵)。
上下文完成后,将处理队列中等待布局传递和最终渲染的元素,并对这些元素递归调用 MeasureOverride
和 ArrangeOverride
( 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);
}
我有以下代码:
<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
事件处理程序,这意味着 Window
和 Canvas
都已加载(但未呈现)。
现在开始向 Canvas
.
UIElement
元素
Canvas.Children.Add
应该调用 InvalidateMeasure
方法。因为 InvalidateMeasure
触发异步布局传递,所以 Canvas
和当前 child 元素将被排入布局队列并且上下文继续执行(添加更多矩形)。
由于新添加的元素已经存在挂起的布局传递,因此您应该避免手动调用 UIElement.Measure
(这是递归调用,考虑到性能时相当昂贵)。
上下文完成后,将处理队列中等待布局传递和最终渲染的元素,并对这些元素递归调用 MeasureOverride
和 ArrangeOverride
( 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);
}