我以 DockPanel 子类的形式创建了一个解决方案,但我没有将其标记为已接受的答案,因为我仍然希望通过样式或附加行为找到一种方法,以便它可以与任何DockPanel 一起使用(或其他子类),而不仅仅是这个。
不过,对于其他人来说,这可能会有所帮助,所以我将其发布在这里。
完整课程的代码如下。工作的核心在于 ArrangeOverride 方法,该方法基于从 Reflector 中提取的 DockPanel 的原始逻辑。
现有逻辑的工作方式是在 ArrangeOverride 内部,如果设置了 LastChildFill,它会将最后一个子项的索引(即要填充的项的索引)存储在一个变量中。如果 LastChildFill 未设置,则将“count”存储在该变量中。
然后当遍历执行实际排列的子元素时,如果正在排列的元素的索引小于先前存储的索引,则执行“停靠”逻辑,否则执行“填充”逻辑。
这意味着当 LastChildFill 为假时,每个元素都运行“对接”逻辑,因为它们都有一个低于该存储索引的索引(再次等于“计数”,或最高索引 + 1)。然而,当 LastChildFill 为真时,最后一个元素的索引不小于存储的索引(实际上等于它),因此一个元素运行“填充”逻辑,而其他所有元素运行“对接”逻辑。
我所做的更改是如果设置了 LastChildFill,如上所述,存储的索引开始指向最后一个子元素,但我随后检查该元素的可见性,如果它不可见,我将索引降低 1 并再次检查,继续直到我要么找到一个可见元素,要么我用完了要检查的子元素(即它们是否都是不可见的。)这也是我将变量命名为“firstFilledIndex”的原因,因为从技术上讲,然后所有元素都使用“填充”逻辑,甚至尽管它之后的所有元素都是不可见的。
我终于添加了一个新的 LastVisibleChildFill 属性来启用或禁用我的新行为。作为对消费者的帮助,如果您将其设置为 true,它还会为您隐式地将 LastChildFill 设置为 true。
这是完整的代码。
public class DockPanelEx : DockPanel
{
public static readonly DependencyProperty LastVisibleChildFillProperty = DependencyProperty.Register(
"LastVisibleChildFill",
typeof(bool),
typeof(DockPanelEx),
new UIPropertyMetadata(true, (s,e) => {
var dockPanelEx = (DockPanelEx)s;
var newValue = (bool)e.NewValue;
if(newValue)
dockPanelEx.LastChildFill = true; // Implicitly enable LastChildFill
// Note: For completeness, we may consider putting in code to set
// LastVisibileChildFill to false if LastChildFill is set to false
}));
/// <summary>
/// Indicates that LastChildFill should fill the last visible child
/// Note: When set to true, automatically also sets LastChildFill to true as well.
/// </summary>
public bool LastVisibleChildFill
{
get { return (bool)GetValue(LastVisibleChildFillProperty); }
set { SetValue(LastVisibleChildFillProperty, value); }
}
protected override Size ArrangeOverride(Size totalAvailableSize)
{
UIElementCollection internalChildren = base.InternalChildren;
int count = internalChildren.Count;
int firstFilledIndex = count;
if(LastChildFill)
{
for(firstFilledIndex = count - 1; firstFilledIndex >= 0; firstFilledIndex--)
{
if(!LastVisibleChildFill || internalChildren[firstFilledIndex].IsVisible)
break;
}
}
double usedLeftEdge = 0.0;
double usedTopEdge = 0.0;
double usedRightEdge = 0.0;
double usedBottomEdge = 0.0;
for (int i = 0; i < count; i++)
{
UIElement element = internalChildren[i];
if (element != null)
{
Size desiredSize = element.DesiredSize;
var finalRect = new Rect(
usedLeftEdge,
usedTopEdge,
Math.Max(0.0, (totalAvailableSize.Width - (usedLeftEdge + usedRightEdge))),
Math.Max(0.0, (totalAvailableSize.Height - (usedTopEdge + usedBottomEdge))));
if (i < firstFilledIndex)
{
switch (GetDock(element))
{
case Dock.Left:
usedLeftEdge += desiredSize.Width;
finalRect.Width = desiredSize.Width;
break;
case Dock.Top:
usedTopEdge += desiredSize.Height;
finalRect.Height = desiredSize.Height;
break;
case Dock.Right:
usedRightEdge += desiredSize.Width;
finalRect.X = Math.Max((double) 0.0, (double) (totalAvailableSize.Width - usedRightEdge));
finalRect.Width = desiredSize.Width;
break;
case Dock.Bottom:
usedBottomEdge += desiredSize.Height;
finalRect.Y = Math.Max((double) 0.0, (double) (totalAvailableSize.Height - usedBottomEdge));
finalRect.Height = desiredSize.Height;
break;
}
}
element.Arrange(finalRect);
}
}
return totalAvailableSize;
}
}