【发布时间】:2011-03-01 11:12:33
【问题描述】:
我正在尝试通过覆盖 MeasureOverride 和 ArrangeOverride 为 WPF 编写自定义 Panel 类,但是,虽然它大部分在工作,但我遇到了一个奇怪的问题,我可以'不解释。
特别是,在我对 ArrangeOverride 中的子项目调用 Arrange 并确定它们的尺寸后,它们的尺寸不符合我给他们的尺寸,而且似乎是尺寸合适在MeasureOverride 中传递给他们的Measure 方法。
我是否错过了这个系统应该如何工作的东西?我的理解是,调用Measure 只会导致孩子根据提供的availableSize 评估其DesiredSize,并且不应影响其实际的最终大小。
这是我的完整代码(面板,顺便说一句,旨在以最节省空间的方式安排子项,为不需要它的行提供更少的空间,并将剩余空间平均分配给其余的行——它目前只支持垂直方向,但我计划在它正常工作后添加水平方向):
编辑:感谢您的回复。我稍后会更仔细地检查它们。不过,让我澄清一下我的预期算法是如何工作的,因为我没有解释。
首先,思考我在做什么的最好方法是想象一个每行设置为 * 的 Grid。这将空间均匀地划分。但是,在某些情况下,一行中的元素可能不需要所有空间;如果是这种情况,我想占用任何剩余空间并将其分配给那些可以使用该空间的行。如果没有行需要任何额外的空间,我只是尝试均匀地间隔(这就是extraSpace 正在做的事情,仅适用于这种情况)。
我分两次执行此操作。第一遍的最终目的是确定行的最终“正常大小”——即将被缩小的行的大小(给定一个小于其所需大小的大小)。为此,我将最小的项目逐步增加到最大,并在每一步调整计算出的正常大小,将每个小项目的剩余空间添加到每个后续较大的项目,直到没有更多项目“适合”,然后打破。
在下一轮中,我使用这个正常值来确定一个项目是否适合,只需将正常尺寸的Min 与项目所需的尺寸相结合。
(为了简单起见,我还将匿名方法更改为 lambda 函数。)
编辑 2: 我的算法似乎在确定孩子的适当大小方面效果很好。然而,孩子们只是不接受他们给定的尺寸。我通过传递 PositiveInfinity 并返回 Size(0,0) 尝试了 Goblin 建议的 MeasureOverride,但这会导致孩子们绘制自己,就好像根本没有空间限制一样。对此不明显的部分是它的发生是因为调用了Measure。微软关于这个主题的文档一点都不清楚,因为我已经多次阅读了每个类和属性描述。但是,现在很明显,调用 Measure 实际上确实会影响子级的渲染,因此我将尝试按照 BladeWise 的建议将逻辑拆分到两个函数中。
解决了!!我搞定了。正如我所怀疑的,我需要对每个孩子调用 Measure() 两次(一次用于评估 DesiredSize,第二次为每个孩子提供适当的高度)。在我看来,WPF 中的布局会以如此奇怪的方式设计,它被分成两个通道,但 Measure 通道实际上做了两件事:测量 和 大小的子级以及 Arrange 通道除了实际定位孩子之外,几乎什么都不做。很奇怪。
我将在底部发布工作代码。
一、原(坏掉的)代码:
protected override Size MeasureOverride( Size availableSize ) {
foreach ( UIElement child in Children )
child.Measure( availableSize );
return availableSize;
}
protected override System.Windows.Size ArrangeOverride( System.Windows.Size finalSize ) {
double extraSpace = 0.0;
var sortedChildren = Children.Cast<UIElement>().OrderBy<UIElement, double>( child=>child.DesiredSize.Height; );
double remainingSpace = finalSize.Height;
double normalSpace = 0.0;
int remainingChildren = Children.Count;
foreach ( UIElement child in sortedChildren ) {
normalSpace = remainingSpace / remainingChildren;
if ( child.DesiredSize.Height < normalSpace ) // if == there would be no point continuing as there would be no remaining space
remainingSpace -= child.DesiredSize.Height;
else {
remainingSpace = 0;
break;
}
remainingChildren--;
}
// this is only for cases where every child item fits (i.e. the above loop terminates normally):
extraSpace = remainingSpace / Children.Count;
double offset = 0.0;
foreach ( UIElement child in Children ) {
//child.Measure( new Size( finalSize.Width, normalSpace ) );
double value = Math.Min( child.DesiredSize.Height, normalSpace ) + extraSpace;
child.Arrange( new Rect( 0, offset, finalSize.Width, value ) );
offset += value;
}
return finalSize;
}
这是工作代码:
double _normalSpace = 0.0;
double _extraSpace = 0.0;
protected override Size MeasureOverride( Size availableSize ) {
// first pass to evaluate DesiredSize given available size:
foreach ( UIElement child in Children )
child.Measure( availableSize );
// now determine the "normal" size:
var sortedChildren = Children.Cast<UIElement>().OrderBy<UIElement, double>( child => child.DesiredSize.Height );
double remainingSpace = availableSize.Height;
int remainingChildren = Children.Count;
foreach ( UIElement child in sortedChildren ) {
_normalSpace = remainingSpace / remainingChildren;
if ( child.DesiredSize.Height < _normalSpace ) // if == there would be no point continuing as there would be no remaining space
remainingSpace -= child.DesiredSize.Height;
else {
remainingSpace = 0;
break;
}
remainingChildren--;
}
// there will be extra space if every child fits and the above loop terminates normally:
_extraSpace = remainingSpace / Children.Count; // divide the remaining space up evenly among all children
// second pass to give each child its proper available size:
foreach ( UIElement child in Children )
child.Measure( new Size( availableSize.Width, _normalSpace ) );
return availableSize;
}
protected override System.Windows.Size ArrangeOverride( System.Windows.Size finalSize ) {
double offset = 0.0;
foreach ( UIElement child in Children ) {
double value = Math.Min( child.DesiredSize.Height, _normalSpace ) + _extraSpace;
child.Arrange( new Rect( 0, offset, finalSize.Width, value ) );
offset += value;
}
return finalSize;
}
调用Measure 两次(并迭代Children 三次)可能不是超级高效,但它可以工作。对算法的任何优化将不胜感激。
【问题讨论】:
-
还有一件事:我不担心在
MeasureOverride中返回availableSize会出现异常,因为面板在ScrollViewer这样的无限空间内毫无意义。只有在空间有限时才有意义。 -
我也有这个问题。 ArrangeOverride 中的任何大小更改都只是剪辑。不是我对阅读文档或任何 WPF 书籍的期望。也曾两次调用Measure来解决这个问题。谢谢。
标签: c# wpf custom-controls panel