MeasureOverride 和 ArrangeOverride。什么是真正的 AvailableSize/DesiredSize?
MeasureOverride and ArrangeOverride. What is really AvailableSize/DesiredSize?
为了理解WPF的布局原理卡了两天
我已经阅读了一堆文章并探索了 wpf 源代码。
我知道 measure/measureoverride 方法接受可用大小,然后设置 DesiredSize 属性。
这是有道理的。它递归调用 children 并要求他们设置各自所需的大小。
有两件事(至少)我不明白。让我们考虑一个 WrapPanel。
- 查看 WPF 源代码,MeasureOverride() 方法接受可用大小,然后将其传递给所有 children。它然后 returns 最大宽度 和 最大高度 在 children 中得到的期望大小属性。不应该把可用的space分给child人吗?我认为它会遍历 children 然后测量第一个,然后从总可用大小中减去生成的期望大小,以便下一个 child 占用更少的 space。当我阅读 WPF 时,WrapPanel.MeasureOverride 似乎没有设置适合所有 children 所需的所需大小。它只是给出了 children 中的任何一个都适合的 DesiredSize。
- 由于包装面板的性质,我预计对于垂直方向的堆叠面板,高度限制会导致更宽的 DesiredSize(以容纳更多列)。由于高度限制会影响包裹面板的所需大小,因此此逻辑不属于 MeasureOverride 方法吗?为什么堆叠然后只反映在 ArrangeOverride 方法中?
我想我对这两种方法的机制有一些根本性的误解。
任何人都可以给我口头描述 DesiredSize and/or AvailableSize 使这个实现有意义吗?
如何正确实施MeasureOverride
和ArrangeOverride
?
因为我认为这是您要问的实际问题,所以我会尽可能多地告诉您有关该主题的信息。
在我们开始之前,您可能想先阅读 Measuring and Arranging Children MS Docs。它让您大致了解布局过程的工作原理,尽管它并没有真正提供有关您应该如何实际实现 MeasureOverride
和 ArrangeOverride
.
的任何信息
注意:为了简洁起见,从现在开始,每当我说“控制”时,我的意思都是“从FrameworkElement
派生的任何class”。
1。影响控件布局的组件有哪些?
请务必注意,有许多参数会影响控件的大小和排列:
- 内容(即子控件)
- 显式宽度和高度
- 边距
- 水平和垂直对齐方式
- 布局变换
- 布局舍入
- 我可能忽略的其他内容
幸运的是,在实现自定义布局时我们唯一需要担心的组件是子控件。这是因为其他组件对所有控件都是通用的,并且完全由 MeasureOverride
和 ArrangeOverride
之外的框架处理。我所说的完全外部是指输入和输出都进行了调整以考虑到这些组件。
事实上,如果你检查FrameworkElement
API,你会发现测量程序分为MeasureCore
和MeasureOverride
,前者负责所有必需的更正,事实上你永远不会直接在子控件上调用它们 - 你调用 Measure(Size)
来完成所有的魔法。 ArrangeCore
和 ArrangeOverride
.
也是如此
2。如何实现MeasureOverride
?
layout pass 中测量阶段的目的是向父控件提供关于我们的控件想要的大小的反馈。你可能会认为这是一个假设性的问题:
Given that much available space, what is the minimal space you need to accommodate all your contents?
不用说,这(通常)是确定父控件大小所必需的 - 毕竟,我们(通常)测量我们的子控件以确定我们控件的大小,不是吗?
输入
来自文档:
The available size that this element can give to child elements. Infinity can
be specified as a value to indicate that the element will size to whatever content
is available.
availableSize
参数告诉我们有多少 space 可供我们支配。请注意,这可能是一个任意值(包括无限宽度 and/or 高度),并且您不应期望在排列阶段获得完全相同的 space 数量。毕竟父控件可能会在我们的控件上多次调用Measure(Size)
,无论参数是什么,然后在排列阶段就完全忽略了
如前所述,此参数已预先更正。例如:
- 如果父控件调用
Measure(100x100)
,并且我们的控件将边距设置为 20
(在每一侧),availableSize
的值将是 60x60
。
- 如果父控件调用
Measure(100x100)
,并且我们的控件将宽度设置为200
,availableSize
的值将是200x100
(希望它会变得清楚为什么当你继续阅读时)。
输出
来自文档:
The size that this element determines it needs during layout, based on its calculations
of child element sizes.
生成的所需大小应该是容纳所有内容所需的最小 大小。此值 必须具有 有限的宽度和高度。它通常但不要求在任一维度上都小于 availableSize
。
该值影响DesiredSize
属性的值,并影响后续ArrangeOverride
调用的finalSize
参数的值。
同样,returned 值随后进行了调整,因此在确定此值时我们不应关注除子控件之外的任何内容。
与 DesiredSize
属性 值的关系
大小 return 由 MeasureOverride
影响,但不一定成为 DesiredSize
的值。这里的关键是 属性 并不是真正打算由控件本身使用,而是一种将所需大小传达给父控件的方式。请注意 Measure
不会 return 任何值 - 父控件需要访问 DesiredSize
才能知道调用的结果。正因为如此,它的值实际上是为由父控件查看而定制的。特别是,保证不超过作为Measure
参数传递的原始大小,无论子MeasureOverride
.
的结果如何
您可能会问 “为什么我们需要这个 属性?我们不能简单地制作 Measure
return 大小吗?” .我认为这样做是出于优化原因:
- 通常我们需要在
ArrangeOverride
中访问子控件所需的大小,因此再次调用 Measure(Size)
会触发子控件(及其后代)的冗余测量传递。
- 可以在不使测量无效的情况下使排列无效,这会触发布局传递跳过测量阶段并直接进入排列阶段。例如,如果我们对
StackPanel
中的控件重新排序,子控件的总大小不会改变,只会改变它们的排列方式。
总结
从我们的控件的角度来看,这是测量阶段的样子:
- 父控件在控件上调用
Measure(Size)
。
MeasureCore
预先更正提供的尺寸以考虑边距等。
MeasureOverride
调用调整后 availableSize
.
- 我们执行自定义逻辑来确定所需的控件大小。
- 结果所需的大小被缓存。后面用来调整
ArrangeOverride
的finalSize
参数。稍后会详细介绍。
- returned 所需大小被裁剪为不超过
availableSize
。
- 裁剪后的所需尺寸已 post 更正以考虑边距等(步骤 2. 已还原)。
- 第 7 步的值设置为
DesiredSize
的值。
可能会再次裁剪此值,使其不超过作为 Measure(Size)
参数传递的原始大小,但我认为应该已经在第 6 步中得到保证。
3。如何实现ArrangeOverride
?
布局过程中排列阶段的目的是根据控件本身定位所有子控件。
输入
来自文档:
The final area within the parent that this element should use to arrange itself
and its children.
finalSize
参数告诉我们有多少 space 需要安排子控件。我们应该将其视为最终约束(因此得名),并且不要违反它。
它的值受父控件作为参数传递给 Arrange(Rect)
的矩形大小的影响,而且如前所述,还受 return 从 MeasureOverride
编辑的所需大小的影响.具体来说,它是两个维度中的最大值,规则是这个尺寸 保证 不小于所需尺寸(让我再次强调这与值 return来自 MeasureOverride
而不是 DesiredSize
的值)。请参阅 this comment 以供参考。
鉴于此,如果我们使用与测量相同的逻辑,我们不需要任何额外的预防措施来确保我们不会违反约束。
您可能想知道为什么 DesiredSize
和 finalSize
之间存在这种差异。好吧,这就是裁剪机制的好处。考虑一下 - 如果禁用裁剪(例如 Canvas
),除非正确安排,否则框架将如何呈现“溢出”的内容?
老实说,我不确定如果违反约束会发生什么。就个人而言,如果您报告了所需的尺寸但无法放入其中,我会认为这是一个错误。
输出
来自文档:
The actual size used.
这是我无知的边界,知识的尽头和猜测的开始。
我不太确定这个值如何影响整个布局(和渲染)过程。我知道这会影响 RenderSize
属性 的值 - 它成为初始值,稍后会修改以考虑剪裁、舍入等。但我不知道它可能有什么实际意义。
我个人对此的看法是,我们有机会在 MeasureOverride
中挑剔;现在是时候将我们的话付诸行动了。如果我们被告知在给定大小内排列内容,那正是我们应该做的 - 在 finalSize
内排列子控件,不多也不少。我们不必用子控件紧紧覆盖整个区域,可能会有间隙,但这些间隙已被考虑在内,并且是我们控制的一部分。
话虽如此,我的建议是简单地 return finalSize
,就好像在说 “这就是你告诉我的,所以这就是我”。 =219=] 到父控件。这种方法似乎在常用 WPF 控件中广为使用,例如:
4。结语
我想这就是我对这个主题的全部了解,或者至少是我能想到的全部。我敢打赌甜甜圈还有更多,但我相信这应该足以让你继续前进并使你能够创建一些重要的布局逻辑。
免责声明
所提供的信息仅是我对 WPF 布局过程的理解,并不保证是正确的。它结合了多年来积累的经验,一些人在 WPF .NET Core source code 周围摸索,并以一种古老的“将意大利面条扔到墙上,看看有什么粘住”的方式玩弄代码。
@grx70 的回答很棒而且非常详细。然而,关于 WPF 布局系统还有很多东西需要了解,我在 CodeProject 上写了一篇关于它的整篇文章:Deep Dive into WPF Layouting and Rendering
这里概述了 MeasureOverride()
、ArrangeOverride()
和 OnRender()
的属性和覆盖如何协同工作以生成和使用 DesiredSize
和 RenderSize
(其中顺便说一下,与 ActualHeight
和 ActuelWidth
).
完全相同的值
详细说明见文章。
为了理解WPF的布局原理卡了两天
我已经阅读了一堆文章并探索了 wpf 源代码。
我知道 measure/measureoverride 方法接受可用大小,然后设置 DesiredSize 属性。 这是有道理的。它递归调用 children 并要求他们设置各自所需的大小。
有两件事(至少)我不明白。让我们考虑一个 WrapPanel。
- 查看 WPF 源代码,MeasureOverride() 方法接受可用大小,然后将其传递给所有 children。它然后 returns 最大宽度 和 最大高度 在 children 中得到的期望大小属性。不应该把可用的space分给child人吗?我认为它会遍历 children 然后测量第一个,然后从总可用大小中减去生成的期望大小,以便下一个 child 占用更少的 space。当我阅读 WPF 时,WrapPanel.MeasureOverride 似乎没有设置适合所有 children 所需的所需大小。它只是给出了 children 中的任何一个都适合的 DesiredSize。
- 由于包装面板的性质,我预计对于垂直方向的堆叠面板,高度限制会导致更宽的 DesiredSize(以容纳更多列)。由于高度限制会影响包裹面板的所需大小,因此此逻辑不属于 MeasureOverride 方法吗?为什么堆叠然后只反映在 ArrangeOverride 方法中?
我想我对这两种方法的机制有一些根本性的误解。
任何人都可以给我口头描述 DesiredSize and/or AvailableSize 使这个实现有意义吗?
如何正确实施MeasureOverride
和ArrangeOverride
?
因为我认为这是您要问的实际问题,所以我会尽可能多地告诉您有关该主题的信息。
在我们开始之前,您可能想先阅读 Measuring and Arranging Children MS Docs。它让您大致了解布局过程的工作原理,尽管它并没有真正提供有关您应该如何实际实现 MeasureOverride
和 ArrangeOverride
.
注意:为了简洁起见,从现在开始,每当我说“控制”时,我的意思都是“从FrameworkElement
派生的任何class”。
1。影响控件布局的组件有哪些?
请务必注意,有许多参数会影响控件的大小和排列:
- 内容(即子控件)
- 显式宽度和高度
- 边距
- 水平和垂直对齐方式
- 布局变换
- 布局舍入
- 我可能忽略的其他内容
幸运的是,在实现自定义布局时我们唯一需要担心的组件是子控件。这是因为其他组件对所有控件都是通用的,并且完全由 MeasureOverride
和 ArrangeOverride
之外的框架处理。我所说的完全外部是指输入和输出都进行了调整以考虑到这些组件。
事实上,如果你检查FrameworkElement
API,你会发现测量程序分为MeasureCore
和MeasureOverride
,前者负责所有必需的更正,事实上你永远不会直接在子控件上调用它们 - 你调用 Measure(Size)
来完成所有的魔法。 ArrangeCore
和 ArrangeOverride
.
2。如何实现MeasureOverride
?
layout pass 中测量阶段的目的是向父控件提供关于我们的控件想要的大小的反馈。你可能会认为这是一个假设性的问题:
Given that much available space, what is the minimal space you need to accommodate all your contents?
不用说,这(通常)是确定父控件大小所必需的 - 毕竟,我们(通常)测量我们的子控件以确定我们控件的大小,不是吗?
输入
来自文档:
The available size that this element can give to child elements. Infinity can be specified as a value to indicate that the element will size to whatever content is available.
availableSize
参数告诉我们有多少 space 可供我们支配。请注意,这可能是一个任意值(包括无限宽度 and/or 高度),并且您不应期望在排列阶段获得完全相同的 space 数量。毕竟父控件可能会在我们的控件上多次调用Measure(Size)
,无论参数是什么,然后在排列阶段就完全忽略了
如前所述,此参数已预先更正。例如:
- 如果父控件调用
Measure(100x100)
,并且我们的控件将边距设置为20
(在每一侧),availableSize
的值将是60x60
。 - 如果父控件调用
Measure(100x100)
,并且我们的控件将宽度设置为200
,availableSize
的值将是200x100
(希望它会变得清楚为什么当你继续阅读时)。
输出
来自文档:
The size that this element determines it needs during layout, based on its calculations of child element sizes.
生成的所需大小应该是容纳所有内容所需的最小 大小。此值 必须具有 有限的宽度和高度。它通常但不要求在任一维度上都小于 availableSize
。
该值影响DesiredSize
属性的值,并影响后续ArrangeOverride
调用的finalSize
参数的值。
同样,returned 值随后进行了调整,因此在确定此值时我们不应关注除子控件之外的任何内容。
与 DesiredSize
属性 值的关系
大小 return 由 MeasureOverride
影响,但不一定成为 DesiredSize
的值。这里的关键是 属性 并不是真正打算由控件本身使用,而是一种将所需大小传达给父控件的方式。请注意 Measure
不会 return 任何值 - 父控件需要访问 DesiredSize
才能知道调用的结果。正因为如此,它的值实际上是为由父控件查看而定制的。特别是,保证不超过作为Measure
参数传递的原始大小,无论子MeasureOverride
.
您可能会问 “为什么我们需要这个 属性?我们不能简单地制作 Measure
return 大小吗?” .我认为这样做是出于优化原因:
- 通常我们需要在
ArrangeOverride
中访问子控件所需的大小,因此再次调用Measure(Size)
会触发子控件(及其后代)的冗余测量传递。 - 可以在不使测量无效的情况下使排列无效,这会触发布局传递跳过测量阶段并直接进入排列阶段。例如,如果我们对
StackPanel
中的控件重新排序,子控件的总大小不会改变,只会改变它们的排列方式。
总结
从我们的控件的角度来看,这是测量阶段的样子:
- 父控件在控件上调用
Measure(Size)
。 MeasureCore
预先更正提供的尺寸以考虑边距等。MeasureOverride
调用调整后availableSize
.- 我们执行自定义逻辑来确定所需的控件大小。
- 结果所需的大小被缓存。后面用来调整
ArrangeOverride
的finalSize
参数。稍后会详细介绍。 - returned 所需大小被裁剪为不超过
availableSize
。 - 裁剪后的所需尺寸已 post 更正以考虑边距等(步骤 2. 已还原)。
- 第 7 步的值设置为
DesiredSize
的值。 可能会再次裁剪此值,使其不超过作为Measure(Size)
参数传递的原始大小,但我认为应该已经在第 6 步中得到保证。
3。如何实现ArrangeOverride
?
布局过程中排列阶段的目的是根据控件本身定位所有子控件。
输入
来自文档:
The final area within the parent that this element should use to arrange itself and its children.
finalSize
参数告诉我们有多少 space 需要安排子控件。我们应该将其视为最终约束(因此得名),并且不要违反它。
它的值受父控件作为参数传递给 Arrange(Rect)
的矩形大小的影响,而且如前所述,还受 return 从 MeasureOverride
编辑的所需大小的影响.具体来说,它是两个维度中的最大值,规则是这个尺寸 保证 不小于所需尺寸(让我再次强调这与值 return来自 MeasureOverride
而不是 DesiredSize
的值)。请参阅 this comment 以供参考。
鉴于此,如果我们使用与测量相同的逻辑,我们不需要任何额外的预防措施来确保我们不会违反约束。
您可能想知道为什么 DesiredSize
和 finalSize
之间存在这种差异。好吧,这就是裁剪机制的好处。考虑一下 - 如果禁用裁剪(例如 Canvas
),除非正确安排,否则框架将如何呈现“溢出”的内容?
老实说,我不确定如果违反约束会发生什么。就个人而言,如果您报告了所需的尺寸但无法放入其中,我会认为这是一个错误。
输出
来自文档:
The actual size used.
这是我无知的边界,知识的尽头和猜测的开始。
我不太确定这个值如何影响整个布局(和渲染)过程。我知道这会影响 RenderSize
属性 的值 - 它成为初始值,稍后会修改以考虑剪裁、舍入等。但我不知道它可能有什么实际意义。
我个人对此的看法是,我们有机会在 MeasureOverride
中挑剔;现在是时候将我们的话付诸行动了。如果我们被告知在给定大小内排列内容,那正是我们应该做的 - 在 finalSize
内排列子控件,不多也不少。我们不必用子控件紧紧覆盖整个区域,可能会有间隙,但这些间隙已被考虑在内,并且是我们控制的一部分。
话虽如此,我的建议是简单地 return finalSize
,就好像在说 “这就是你告诉我的,所以这就是我”。 =219=] 到父控件。这种方法似乎在常用 WPF 控件中广为使用,例如:
4。结语
我想这就是我对这个主题的全部了解,或者至少是我能想到的全部。我敢打赌甜甜圈还有更多,但我相信这应该足以让你继续前进并使你能够创建一些重要的布局逻辑。
免责声明
所提供的信息仅是我对 WPF 布局过程的理解,并不保证是正确的。它结合了多年来积累的经验,一些人在 WPF .NET Core source code 周围摸索,并以一种古老的“将意大利面条扔到墙上,看看有什么粘住”的方式玩弄代码。
@grx70 的回答很棒而且非常详细。然而,关于 WPF 布局系统还有很多东西需要了解,我在 CodeProject 上写了一篇关于它的整篇文章:Deep Dive into WPF Layouting and Rendering
这里概述了 MeasureOverride()
、ArrangeOverride()
和 OnRender()
的属性和覆盖如何协同工作以生成和使用 DesiredSize
和 RenderSize
(其中顺便说一下,与 ActualHeight
和 ActuelWidth
).
详细说明见文章。