1

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

我已经阅读了一堆文章并探索了 wpf 源代码。

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

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

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

我想我对这两种方法的机制有一些基本的误解。

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

4

1 回答 1

4

如何正确实施MeasureOverrideArrangeOverride

由于我认为这是您要问的实际问题,因此我将尽力为您提供有关此主题的信息。

在我们开始之前,您可能想先阅读MS Docs 上的《测量和安排儿童》。它让您大致了解布局过程是如何工作的,尽管它并没有真正提供有关您应该如何实际实现MeasureOverrideArrangeOverride.

注意:为简洁起见,从这里开始,每当我说“控制”时,我的真正意思是“任何类派生自FrameworkElement”。

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

重要的是要知道有许多参数会影响控件的大小和排列:

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

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

事实上,如果你检查FrameworkElementAPI,你会注意到测量过程被分成MeasureCoreMeasureOverride,前者负责所有需要的更正,事实上你从不直接在子控件上调用它们——你调用Measure(Size)哪个魔法。ArrangeCore和 也是如此ArrangeOverride

2.如何实施MeasureOverride

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

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

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

输入

来自文档:

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

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

如前所述,该参数已经预先校正。例如:

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

输出

来自文档:

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

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

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

同样,返回的值随后会进行调整,因此在确定该值时,我们不应该关注子控件以外的任何内容。

DesiredSize财产价值的关系

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

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

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

概括

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

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

3.如何实施ArrangeOverride

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

输入

来自文档:

父元素中用于排列自身及其子元素的最后一个区域。

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

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

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

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

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

输出

来自文档:

使用的实际尺寸。

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

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

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

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

4. 结语

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


免责声明

提供的信息只是我对 WPF 布局过程的理解,不保证正确。它结合了多年来积累的经验,一些人围绕WPF .NET Core 源代码进行探索,并以一种古老的“把意大利面扔到墙上,看看有什么好”的方式来玩弄代码。

于 2021-04-01T19:13:49.927 回答