考虑一下这个XAML片段......
<DockPanel x:Name="TestDockPanel">
<Button x:Name="RightButton" Content="Right" DockPanel.Dock="Right" />
<Button x:Name="FillButton" Content="Fill" />
</DockPanel>
如上所述,DockPanel会将'RightButton'布局在右边,然后用'FillButton'填充剩下的区域......
我们正试图找到一种方式来设置它,以便当'FillButton'的可见性变为'Collapsed'时,'RightButton'现在应该填充该区域,就像这样......
我们知道如何做到这一点的唯一方法是从'TestDockPanel'的子代中删除'FillButton',但这需要代码隐藏,我们正试图避免。
下面在答案中,我提出了一个基于子类的解决方案。但是,由于我想要一些可以与任何 DockPanel(或其他子类)一起使用并且最好通过样式或附加行为应用的东西,因此我将其保留为开放状态。此外,需要明确的是,解决方案的要求是它必须基于DockPanel,而不是网格或其他面板。
答案 0 :(得分:2)
我以DockPanel子类的形式创建了一个解决方案,但我并没有将此标记为已接受的答案,因为我希望仍然可以通过样式或附加行为找到一种方法来实现这一点,因此它可以用于任何 DockPanel(或其他子类),而不仅仅是这个。
不过,对于其他人来说,这可能会有所帮助,所以我在这里发帖。
完整课程的代码如下。这项工作的重点在于ArrangeOverride方法,该方法基于DockPanel中从Reflector中提取的原始逻辑。
现有逻辑的工作方式是在ArrangeOverride中,如果设置了LastChildFill,则它将变量中的最后一个子项的索引(即要填充的项的索引)存储起来。如果未设置LastChildFill,则会将“count”存储在该变量中。
然后当循环执行实际排列的子项时,如果排列的元素的索引小于先前存储的索引,则执行“对接”逻辑,否则执行“填充”逻辑。
这意味着当LastChildFill为false时,每个元素都运行'对接'逻辑,因为它们都有一个低于该存储索引的索引(再次等于'count'或最高索引+ 1)。但是,当LastChildFill为true时,最后一个元素的索引没有小于存储的索引(它实际上等于它),因此一个元素运行'fill'逻辑,而其他所有元素都运行'docking'逻辑。
我做的更改是如果设置了LastChildFill,如上所述,存储的索引开始指向最后一个子节点,但我检查该元素的可见性,如果它不可见,我将索引降低1并再次检查继续,直到我找到一个可见元素,或者我没有孩子去检查(即它们是否都是不可见的。)这也是我从技术上命名变量'firstFilledIndex'的原因,之后所有元素都使用'Fill'逻辑,即使它之后的所有元素都是不可见的。
我最后添加了一个新的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;
}
}