【问题标题】:MeasureOverride and ArrangeOverride. What is really AvailableSize/DesiredSize?MeasureOverride 和 ArrangeOverride。什么是真正的 AvailableSize/DesiredSize?
【发布时间】:2021-06-23 08:45:42
【问题描述】:

我在试图理解 WPF 的布局原则时被困了两天。

看了一堆文章,探索了wpf源码。

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

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

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

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

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

【问题讨论】:

  • wrappanel 不会尝试均衡子项的大小。你可以连续有两个不同宽度的东西。它需要一个,看看它是否适合。好的。把它放在行中。下一个。这适合剩余的空间还是我需要一个新的行?
  • 就是这样。它不考虑“剩余空间”。在将可用空间传递给其子代之前,它根本不会改变可用空间。
  • 很难以目前的形式回答您的问题(请参阅接近投票)。一方面,您要求解释测量/排列在 WPF 中的工作方式(标题)。另一方面,您要求解释为什么WrapPanel 有效(正文中的实际问题,除了沿途的一堆额外问题)。我认为改写您的帖子会很好,以便清楚您的确切要求。我认为将问题放在标题中(以便于搜索)并在正文中提供任何必要的细节是很常见的。
  • 感谢您的意见。我的问题是了解通用方法。 wrappanel 是“我自己试图理解的内容”,因为它是少数几个受 availablesize 影响的控件之一,这是问题的症结所在。如果我将其改写为“以使 wrappppanel 的当前实现有意义的方式解释可用大小/所需大小”会更好吗?
  • 我已经编译并发布了我所知道的关于 WPF 布局过程的所有内容。关于您对 WrapPanel 实现的疑虑 - 我查看了源代码,老实说,对我来说,它看起来就像您所说的那样工作,所以也许我们中的一个人误解了一些东西 :) 如果你想要我们可以开始聊天,仔细看看那里到底发生了什么。另外,我鼓励您复制粘贴 WrapPanel 源代码并使用调试器逐步完成这些方法。

标签: wpf measureoverride arrangeoverride


【解决方案1】:

如何正确实现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属性的值,并影响后续ArrangeOverride调用的finalSize参数值。

同样,返回的值随后会被调整,所以我们在确定这个值的时候,除了子控件外,不要关注任何东西。

DesiredSize 属性值的关系

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

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

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

总结

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

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

3。如何实现ArrangeOverride

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

输入

来自文档:

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

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

它的值受父控件作为参数传递给Arrange(Rect) 的矩形大小的影响,而且如前所述,还受MeasureOverride 返回的所需大小的影响。具体来说,它是两个维度中的最大值,规则是该大小保证不小于所需大小(让我再次强调这与从 @987654384 返回的值有关@ 而不是 DesiredSize 的值)。参考this comment

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

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

说实话,我不确定如果你违反了约束会发生什么。就我个人而言,如果您报告了所需的尺寸但无法适应它,我会认为这是一个错误。

输出

来自文档:

实际使用的尺寸。

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

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

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

话虽如此,我的建议是简单地返回finalSize,就好像对父控件说“这就是你指示我成为的人,所以我就是这样”。这种方法在股票 WPF 控件中似乎是臭名昭著的实践,例如:

4。结语

我想这就是我所知道的关于这个主题的全部内容,或者至少是我能想到的全部内容。我向你打赌甜甜圈还有更多,但我相信这应该足以让你继续前进,并使你能够创建一些非平凡的布局逻辑。


免责声明

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

【讨论】:

  • 哇。出色的解释和要点示例是纯金。但我对为什么 ArrangeOverride 传递一个大小而不是一个矩形感到困惑。当 Arrange 需要一个矩形时,ArrangeOverride 将如何对其子级调用 Arrange,但 ArrangeOverride 只传递了一个大小?它是否暗示了一个基于 (0,0) 的矩形?
  • "[...] 定位所有子控件相对于控件本身。" 换句话说,在ArrangeOverride 你'在您自己的世界中,其中 (0,0) 是控件的左上角,并且所有子控件都相对于该点定位。对于您所关心的一切,“外部世界”并不存在,因此没有您控制位置的概念 - 它整个世界。我想这有点类似于我们不问“宇宙的位置是什么?”,而只问“宇宙有多大?”和“宇宙中的东西在哪里?”。
  • 这很有帮助(也很有趣)。我知道了。这为我创造了另一个任务。我需要一个递归模型来确定对象的绝对位置。你看,我正在进行的任务是在控制系统中自动生成操作员图像。这些图像今天是手动绘制的,我打算实现 WPF 布局原则,以便通过带有 WPF 布局控件的简化版本的代码来完成排列。我可能会实现在开始时递归设置的获取属性 AbsoluteX、AbsoluteY,但以后可能会更改。像 DesiredSize。再次感谢您。
猜你喜欢
  • 1970-01-01
  • 2014-08-23
  • 2013-11-09
  • 2012-02-01
  • 2021-10-01
  • 2011-01-25
  • 2011-02-04
  • 1970-01-01
  • 2016-05-09
相关资源
最近更新 更多