4

考虑这个 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,而不是网格或其他面板。

4

1 回答 1

2

我以 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;
    }
}
于 2013-09-12T22:28:46.287 回答