MeasureOverride 和ArrangeOverride什么是可用大小/期望大小? 输入输出与 DesiredSize 属性值的关系总结输入输出

问题描述

我已经困了两天试图理解 WPF 的布局原则。

我阅读了大量文章并探索了 wpf 源代码

我知道 measure/measureoverride 方法接受可用大小,然后设置 DesiredSize 属性。 这是有道理的。它递归地调用孩子并要求他们设置各自所需的大小。

有两件事(至少)我不明白。让我们考虑一个 WrapPanel。

  • 查看 WPF 源代码,MeasureOverride() 方法接受一个可用大小,然后将其传递给所有子级。然后它返回子节点中得到的所需大小属性最大宽度最大高度。它不应该划分孩子之间的可用空间吗?我认为它会遍历孩子,然后测量第一个,然后从总可用大小中减去得到的期望大小,以便下一个孩子占据更少的空间。当我阅读 WPF 时,WrapPanel.MeasureOverride 似乎没有设置适合所有孩子的所需大小。它只是给出了任何一个孩子都适合的 DesiredSize。
  • 由于包装面板的性质,我预计对于垂直方向的堆栈面板,高度限制会导致更宽的 DesiredSize(以适合更多列)。由于高度限制会影响包装面板的所需尺寸,因此该逻辑不属于 MeasureOverride 方法吗?为什么堆叠然后只反映在ArrangeOverride 方法中?

我想我对这两种方法的机制存在一些根本性的误解。

谁能给我一个 DesiredSize 和/或 AvailableSize 的口头描述,使这个实现有意义?

解决方法

如何正确实现MeasureOverrideArrangeOverride

因为我认为这就是您要问的实际问题,所以我会尽量向您提供我对这个主题的了解。

在我们开始之前,您可能需要先阅读 MS Docs 上的 Measuring and Arranging Children。它让您大致了解布局过程的工作原理,尽管它并没有真正提供关于您应该如何实际实现 MeasureOverrideArrangeOverride 的任何信息。

注意:为简洁起见,从现在开始,每当我说“控制”时,我的意思都是“从 FrameworkElement 派生的任何类”。

1.影响控件布局的组件有哪些?

需要注意的是,有许多参数会影响控件的大小和排列:

  • 内容(即子控件)
  • 明确的宽度和高度
  • 边距
  • 水平和垂直对齐
  • 布局变换
  • 布局四舍五入
  • 我可能忽略的其他内容

幸运的是,我们在实现自定义布局时唯一需要担心的组件是子控件。这是因为其他组件对于所有控件都是通用的,并且完全由 MeasureOverrideArrangeOverride 之外的框架处理。完全外部我的意思是调整输入和输出以考虑这些组件。

事实上,如果您检查 FrameworkElement API,您会注意到测量过程分为 MeasureCoreMeasureOverride,前者负责所有需要的修正,而事实上,你从来没有直接在子控件上调用它们 - 你调用 Measure(Size) 来完成所有的魔法。 ArrangeCoreArrangeOverride 也是如此。

2.如何实现MeasureOverride

布局过程中测量阶段的目的是向父控件提供关于我们的控件想要的大小的反馈。您可以将其视为一个假设性问题:

考虑到这么多可用空间,容纳所有内容所需的最小空间是多少?

不用说,这(通常)是确定父控件大小所必需的——毕竟,我们(通常)测量子控件来确定控件的大小,不是吗?

输入

来自文档:

此元素可以赋予子元素的可用大小。无限可以 被指定为一个值,以指示元素将调整为任何内容 可用。

availableSize 参数告诉我们有多少空间可供我们使用。请注意,这可能是一个任意值(包括无限的宽度和/或高度),并且您不应期望在排列阶段获得完全相同的空间量。毕竟,父控件可能会使用任何参数多次调用我们的控件上的 Measure(Size),然后在排列阶段完全忽略它。

如前所述,这个参数已经预先修正了。例如:

  • 如果父控件调用 Measure(100x100),并且我们的控件将边距设置为 20(在每一侧),则 availableSize 的值将为 60x60
  • 如果父控件调用 Measure(100x100),并且我们的控件的宽度设置为 200,则 availableSize 的值将是 200x100(希望随着您继续阅读)。

输出

来自文档:

此元素根据其计算确定其在布局期间所需的大小 子元素大小。

生成的所需大小应该是容纳所有内容所需的最小大小。该值必须具有有限的宽度和高度。它通常(但不要求)在任一维度上小于 availableSize

此值影响 DesiredSize 属性的值,并影响后续 finalSize 调用的 ArrangeOverride 参数值。

同样,返回值是后续调整的,所以在确定这个值的时候,除了子控件之外我们不应该关注任何东西。

DesiredSize 属性值的关系

MeasureOverride 返回的大小会影响,但不一定会变成 DesiredSize 的值。这里的关键是这个属性并不是真的要由控件本身使用,而是一种将所需大小传达给父控件的方式。请注意,Measure 不返回任何值 - 父控件需要访问 DesiredSize 才能知道调用的结果。正因为如此,它的值实际上是为供父控件查看而定制的。特别是,无论孩子的 Measure 的结果如何,都保证不超过作为 MeasureOverride 参数传递的原始大小。

您可能会问“为什么我们需要这个属性?我们不能简单地让 Measure 返回大小吗?”。我认为这样做是出于优化原因:

  1. 通常我们需要在 ArrangeOverride 中访问 c​​hild 所需的大小,因此再次调用 Measure(Size) 会触发 child control(及其后代)的冗余度量传递。
  2. 可以在不使度量无效的情况下使排列无效,这会触发布局传递跳过度量阶段并直接进入排列阶段。例如,如果我们对 StackPanel 中的控件重新排序,子控件的总大小不会改变,只会改变它们的排列。

总结

从我们控制的角度来看,测量阶段是这样的:

  1. 父控件在控件上调用 Measure(Size)
  2. MeasureCore 预先更正提供的尺寸以考虑利润等。
  3. MeasureOverride 使用调整后的 availableSize 调用。
  4. 我们使用自定义逻辑来确定所需的控件大小。
  5. 结果所需的大小被缓存。稍后用于调整 finalSizeArrangeOverride 参数。稍后会详细介绍。
  6. 将返回的所需大小裁剪为不超过 availableSize
  7. 裁剪后的所需尺寸会根据边距等因素进行后更正(还原第 2 步)。
  8. 第 7 步中的值设置为 DesiredSize 的值。 这个值可能会再次被裁剪,以不超过作为 Measure(Size) 参数传递的原始大小,但我认为这应该已经在第 6 步中得到保证。

3.如何实现ArrangeOverride

在布局过程中排列阶段的目的是将所有子控件相对于控件本身定位。

输入

来自文档:

该元素应该用来排列自己的父级中的最后一个区域 和它的孩子。

finalSize 参数告诉我们需要多少空间来安排子控件。我们应该将其视为最终约束(因此得名),并且不要违反它

它的值受父控件作为参数传递给 Arrange(Rect) 的矩形大小的影响,而且如前所述,还受 MeasureOverride 返回的所需大小的影响。具体来说,它是任一维度中两者的最大值,规则是保证此大小不小于所需大小(让我再次强调这与从 {{ 1}} 而不是 MeasureOverride 的值)。请参阅 this comment 以供参考。

有鉴于此,如果我们使用与测量相同的逻辑,我们不需要任何额外的预防措施来确保我们不会违反约束。

您可能想知道为什么 DesiredSizeDesiredSize 之间存在这种差异。嗯,这就是剪辑机制的好处。考虑一下 - 如果裁剪被禁用(例如 finalSize),除非它们被正确排列,否则框架将如何呈现“溢出”的内容?

老实说,我不确定如果您违反约束会发生什么。就我个人而言,如果您报告所需的尺寸,然后无法放入其中,我会认为这是一个错误。

输出

来自文档:

实际使用的尺寸。

这是我无知的边界,知识结束,猜测开始。

我不太确定这个值如何影响整个布局(和渲染)过程。我知道这会影响 Canvas 属性的值 - 它成为初始值,后来被修改以考虑裁剪、舍入等。但我不知道它可能有什么实际影响。

我个人对此的看法是,我们有机会在 RenderSize 中挑剔;现在是时候付诸行动了。如果我们被告知在给定的大小内排列内容,这正是我们应该做的 - 在 MeasureOverride 内排列子控件,不多也不少。我们不必用子控件紧紧覆盖整个区域,可能会存在差距,但这些差距已被考虑在内,并且是我们控制的一部分。

话虽如此,我的建议是简单地返回 finalSize,就像对父控件说“这就是你指示我成为的,所以我就是”。这种方法在股票 WPF 控件中似乎是众所周知的做法,例如:

4.结语

我想这就是我对这个主题的全部了解,或者至少我能想到的全部。我敢打赌,甜甜圈还有更多内容,但我相信这应该足以让您前进并让您能够创建一些重要的布局逻辑。


免责声明

提供的信息仅是我对 WPF 布局过程的理解,不保证正确。它结合了多年来积累的经验,一些人在 WPF .NET Core source code 周围闲逛,并以一种古老的“向墙上扔意大利面条,看看什么能坚持下来”的方式来玩代码。