参考链接
正文
获取布局信息
API
| GetLayoutClip(FrameworkElement)) | 返回表示元素的可见区域的 Geometry。 |
|---|---|
| GetLayoutExceptionElement(Dispatcher)) | 返回一个 UIElement,布局引擎将在发生未经处理的异常时对其进行处理。 |
| GetLayoutSlot(FrameworkElement)) | 返回表示为子元素保留的布局分区的 Rect。 |
官方示例
下图显示了一个简单的布局。
可以通过使用以下 XAML 实现此布局。
<Grid Name="myGrid" Background="LightSteelBlue" Height="150"><Grid.ColumnDefinitions><ColumnDefinition Width="250"/></Grid.ColumnDefinitions><Grid.RowDefinitions><RowDefinition /><RowDefinition /><RowDefinition /></Grid.RowDefinitions><TextBlock Name="txt1" Margin="5" FontSize="16" FontFamily="Verdana" Grid.Column="0" Grid.Row="0">Hello World!</TextBlock><Button Click="getLayoutSlot1" Width="125" Height="25" Grid.Column="0" Grid.Row="1">Show Bounding Box</Button><TextBlock Name="txt2" Grid.Column="1" Grid.Row="2"/></Grid>
单个 TextBlock 元素托管在 Grid 中。 虽然文本仅填充第一列的左上角,但 TextBlock 的分配空间实际上要大得多。 可以使用 GetLayoutSlot 方法检索任何 FrameworkElement 的边界框。
private void getLayoutSlot1(object sender, System.Windows.RoutedEventArgs e){RectangleGeometry myRectangleGeometry = new RectangleGeometry();myRectangleGeometry.Rect = LayoutInformation.GetLayoutSlot(txt1);Path myPath = new Path();myPath.Data = myRectangleGeometry;myPath.Stroke = Brushes.LightGoldenrodYellow;myPath.StrokeThickness = 5;Grid.SetColumn(myPath, 0);Grid.SetRow(myPath, 0);myGrid.Children.Add(myPath);txt2.Text = "LayoutSlot is equal to " + LayoutInformation.GetLayoutSlot(txt1).ToString();}
下图显示 TextBlock 元素的边界框。如黄色矩形所示,TextBlock 元素的分配空间实际上远大于所显示的空间。 由于还有其他元素添加到 Grid 中,这种分配可能会收缩或扩展,具体取决于所添加元素的类型和大小。
布局系统
简单地说,布局是一个递归系统,实现对元素进行大小调整、定位和绘制。 更具体地说,布局描述测量和排列 Panel 元素的 Children 集合的成员的过程。 布局是一个密集的过程。 Children 集合越大,必须进行的计算次数就越多。 根据拥有该集合的 Panel 元素所定义的布局行为,还可能会增加复杂性。 相对简单的 Panel(如 Canvas)可以比更复杂的 Panel(如 Grid)具有更好的性能。
每当子 UIElement 改变其位置时,布局系统都可能触发一个新的传递。 因此,了解哪些事件会调用布局系统就很重要,因为不必要的改变布局的行为可能导致应用程序性能变差。 下面描述调用布局系统时发生的过程。
- 子 UIElement 通过首先测量其核心属性来开始布局过程。
- 计算 FrameworkElement 上定义的大小调整属性,例如 Width、Height 和 Margin。
- 应用 Panel 特定的逻辑,例如 Dock 方向或堆叠 Orientation。
- 测量所有子级后排列内容。
- 在屏幕上绘制 Children 集合。
- 如果向集合添加了其他 Children,应用了 LayoutTransform,或者调用了 UpdateLayout 方法,则会再次调用该过程。
布局性能注意事项
布局是一个递归过程。 Children 集合中的每个子元素都会在每次调用布局系统期间得到处理。 因此,应避免在不必要时触发布局系统。 以下注意事项有助于实现更好的性能。
- 应注意哪些属性值更改会强制执行布局系统的递归更新。如果依赖属性的值可能导致布局系统被初始化,则会使用公共标志对该依赖属性进行标记。 AffectsMeasure 和 AffectsArrange 提供了有用的线索,说明哪些属性值更改会强制执行布局系统的递归更新。 一般来说,任何可能影响元素边界框大小的属性都应将 AffectsMeasure 标志设置为 True。 有关详细信息,请参阅依赖项属性概述。
- 如果可能,请使用 RenderTransform 而不是 LayoutTransform。LayoutTransform 是一种影响用户界面 (UI) 内容的非常有用的方式。 但是,如果转换效果不需要影响其他元素的位置,则最好改用 RenderTransform,因为 RenderTransform 不会调用布局系统。 LayoutTransform 会应用其转换,并强制执行递归布局更新以获得受影响元素的新位置。
- 避免对 UpdateLayout 进行不必要的调用。UpdateLayout 方法会强制执行递归布局更新,通常是不必要的。 除非你确定需要进行完整更新,否则请依赖布局系统为你调用此方法。
- 在处理大型 Children 集合时,请考虑使用 VirtualizingStackPanel 而不是常规的 StackPanel。通过虚拟化子集合,VirtualizingStackPanel 仅在内存中保留当前位于父级视区内的对象。 因此,在大多数情况下,性能得到显著提高。
-
自定义布局面板
对于需要使用任何预定义的 Panel 元素都无法实现的布局的应用程序,可以通过继承 Panel 并重写 MeasureOverride 和 ArrangeOverride 方法来实现自定义布局行为。
尽管 WPF 提供了一系列灵活的布局控件,但通过重写 ArrangeOverride 和 MeasureOverride 方法也可以实现自定义布局行为。 可以通过在这些重写方法内定义新的位置行为来实现自定义大小调整和位置。
同样,可以通过重写其 ArrangeOverride 和 MeasureOverride 方法定义基于派生类(如 Canvas 或 Grid)的自定义布局行为。
以下标记演示如何创建自定义 Panel 元素。 这一定义为 PlotPanel 的新 Panel 支持通过使用硬编码的 x 和 y 坐标来定位子元素。 在此示例中,Rectangle 元素(未显示)定位在 50 (x) 和 50 (y) 的绘图点。public class PlotPanel : Panel{// Default public constructorpublic PlotPanel(): base(){}// Override the default Measure method of Panelprotected override Size MeasureOverride(Size availableSize){Size panelDesiredSize = new Size();// In our example, we just have one child.// Report that our panel requires just the size of its only child.foreach (UIElement child in InternalChildren){child.Measure(availableSize);panelDesiredSize = child.DesiredSize;}return panelDesiredSize ;}protected override Size ArrangeOverride(Size finalSize){foreach (UIElement child in InternalChildren){double x = 50;double y = 50;child.Arrange(new Rect(new Point(x, y), child.DesiredSize));}return finalSize; // Returns the final Arranged size}}
若要查看更复杂的自定义面板实现,请参阅创建自定义内容换行面板示例。
子像素渲染和布局舍入
WPF 图形系统使用与设备无关的单元来使分辨率和设备独立。 每个与设备无关的像素都会随着系统的每英寸点数 (dpi) 设置自动进行缩放。 这为 WPF 应用程序提供了不同 dpi 设置的适当缩放,并使应用程序自动感知 dpi。
但是,这种 dpi 无关性可能由于抗锯齿而呈现出不规则的边缘。 这些伪影通常被视为模糊或半透明边缘,当边缘的位置落在设备像素的中间而不是设备像素之间时,就可能出现。 布局系统提供了一种通过布局倒圆对此进行调整的方法。 布局舍入是布局系统在布局传递中舍入任何非整数像素值的情况。
默认情况下禁用布局舍入。 若要启用布局舍入,请在任何 FrameworkElement 上将 UseLayoutRounding 属性设置为 true。 因为它是一个依赖属性,所以该值将传播到可视化树中的所有子级。 若要为整个 UI 启用布局舍入,请在根容器上将 UseLayoutRounding 设置为 true。 有关示例,请参见 UseLayoutRounding。官方示例
<StackPanel Width="150" Margin="7" Orientation="Horizontal"><!-- Single pixel line with layout rounding turned OFF.--><Rectangle UseLayoutRounding="False"Width="45.5" Margin="10" Height="1" Fill="Red"/><!-- Single pixel line with layout rounding turned ON.--><Rectangle UseLayoutRounding="True"Width="45.5" Margin="10" Height="1" Fill="Red"/></StackPanel>
UseLayoutRounding当元素的属性为true时,在传递Arrange过程中Measure计算的所有非整型像素值都会舍入到整个像素值。
此属性由子元素继承。
在像素边界上绘制对象时,当边缘落在设备像素中间时,消除由抗锯齿生成的半透明边缘。 下图显示了位于设备像素中间的单像素宽度线的输出。 左侧的线条不使用布局舍入,反锯齿。 右侧的线条使用布局舍入。
使用布局舍入和 Star 调整大小时,布局系统会在列或行度量中创建较小的变体,以避免子像素呈现。 例如,如果网格的总宽度为 100,每个列各 Star有 3 列,而不是创建宽度等于 33.3 的三列,则布局系统将创建宽度为 33 且宽度为 34 的 2 列。实际应用
布局舍入与游戏开发中常见的反锯齿有关系,WPF默认为关闭布局舍入的,这样会产生一种边缘柔滑的效果,锯齿不是很明显,如果开启布局舍入,则像素都会取整,边缘会有明显的锯齿
Panel类
官方文档定义
为所有 Panel 元素提供基类。 使用 Panel 元素放置和排列 Windows Presentation Foundation (WPF) 应用程序中的子对象。
字段
| BackgroundProperty | 标识 Background 依赖项属性。 | | —- | —- | | IsItemsHostProperty | 标识 IsItemsHost 依赖项属性。 | | ZIndexProperty | 标识 ZIndex 附加属性。 |
官方文档中的注解
Panel的所有子元素都存储在 Children属性中,Children属性的数据类型是 UIElementCollection,存储所有子元素的一个列表。
WPF 提供了一套全面的派生 Panel 实现,支持许多复杂的布局。 如果要实现新的布局容器,请继承Panel类,重写 MeasureOverride 和 ArrangeOverride 方法。 有关如何使用这些方法的演示,请参阅 “创建自定义内容包装面板示例”。
Panel 如果未给 Background 赋值,则无法接收鼠标或触笔事件。 如果需要处理鼠标或触笔事件,但不希望你的 Panel有背景色,请将背景色设置为 Transparent。
Alignment、Margin 、 Padding
HorizontalAlignment 属性声明适用于子元素的水平对齐特征。 下表列出了 HorizontalAlignment 属性的每个可能值。
| 成员 | 说明 |
|---|---|
| Left | 子元素与父元素的已分配布局空间的左端对齐。 |
| Center | 子元素与父元素的已分配布局空间的中心对齐。 |
| Right | 子元素与父元素的已分配布局空间的右端对齐。 |
| Stretch (默认值) |
拉伸子元素以填充父元素的已分配布局空间。 显式 Width 值和 Height 值优先。 |
本地化、全球化支持
本地化、全球化需要解决的问题是因为各个国家语言不同,导致文本的宽度和行数不同,间接导致原先的布局出现问题的问题。
所有面板元素本身支持 FlowDirection 属性,该属性可用于根据用户的区域设置或语言设置动态地重排内容。 有关详细信息,请参阅 FlowDirection。
SizeToContent 属性提供使应用程序开发者可以预测本地化 UI 需求的机制。 使用此属性的 WidthAndHeight 值,父 Window 始终可以动态调整大小以适应内容,并且不受人为的高度或宽度限制的约束。
DockPanelGrid 和 StackPanel 都是适用于可本地化 UI 的好选择。 但是,Canvas 不是很好的选择,因为它以绝对方式定位内容,使其难以本地化。
有关创建带有可本地化用户界面 (UI) 的 WPF 应用程序的其他信息,请参阅使用自动布局概述。
自适应布局解决方案
滚动条
ViewBox
官方推荐解决方案
Width Height
FrameworkElement 类公开了描述元素宽度特征的四个属性。 这四个属性可能会产生冲突;冲突发生时,优先的值按如下方式确定:MinWidth 值优先于 MaxWidth 值,而后者又优先于 Width 值。 第四个属性 ActualWidth 为只读,并报告在布局的交互过程中确定的实际宽度。
FrameworkElement 类公开描述元素高度特征的四个属性。 这四个属性可能会产生冲突;冲突发生时,优先的值按如下方式确定:MinHeight 值优先于 MaxHeight 值,而后者又优先于 Height 值。 第四个属性 ActualHeight 为只读,并报告在布局的交互过程中确定的实际高度。
