Можно ли стилизовать содержимое DockPanel, чтобы заполнить последний * видимый * дочерний элемент?

Рассмотрим этот фрагмент 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» теперь должен был заполнять область, как здесь...

введите здесь описание изображения

Единственный известный нам способ сделать это — физически удалить «FillButton» из дочерних элементов «TestDockPanel», но для этого требуется код программной части, чего мы пытаемся избежать.

Обновлять

Ниже в ответе я представил решение, основанное на подклассе. Тем не менее, я оставляю это открытым, так как мне нужно что-то, что можно использовать с любой DockPanel (или другим подклассом) и предпочтительно применять через стиль или прикрепленное поведение. Кроме того, чтобы внести ясность, требование решения состоит в том, что оно должно быть основано на DockPanel, а не на сетке или другой панели.


person Mark A. Donohoe    schedule 12.09.2013    source источник
comment
Я хотел бы знать, почему меня здесь проголосовали. Я предполагаю, что это был человек, опубликовавший ответ, который был неправильным, потому что он не только не работал, но и не касался фактического вопроса, и им не понравилось, что я призвал их к этому. Я подозреваю их, потому что их ответ был удален в то же самое время, когда я получил отрицательный голос. Не круто, дружище!   -  person Mark A. Donohoe    schedule 13.09.2013


Ответы (1)


Я создал решение в виде подкласса DockPanel, но я не отмечаю его как принятый ответ, потому что я все еще надеюсь найти способ сделать это с помощью стилей или прикрепленных поведений, чтобы его можно было использовать с любой DockPanel (или другого подкласса), а не только этот.

Тем не менее, для других это может быть полезно, поэтому я размещаю его здесь.

Код для полного класса приведен ниже. Суть работы заключается в методе ArrangeOverride, который был основан на исходной логике DockPanel, извлеченной из Reflector.

Существующая логика работала внутри ArrangeOverride, если LastChildFill был установлен, он сохранял индекс последнего дочернего элемента (то есть индекс заполняемого элемента) в переменной. Если LastChildFill не был установлен, вместо этого он сохранял «счетчик» в этой переменной.

Затем при циклическом просмотре дочерних элементов, выполняющих фактическое размещение, если у размещаемого элемента был индекс меньше, чем ранее сохраненный индекс, он выполнял логику «стыковки», в противном случае он выполнял логику «заполнения».

Это означало, что когда LastChildFill имел значение false, каждый элемент выполнял логику «стыковки», поскольку все они имели индекс ниже сохраненного индекса (который снова равен «счетчику» или самому высокому индексу + 1). Однако, когда значение LastChildFill равно true, последний элемент не имеет индекса меньше сохраненного индекса (он фактически равен ему), так что один элемент выполняет логику «заполнения», а все остальные — логику «стыковки».

Изменение, которое я сделал, заключалось в том, что если LastChildFill установлен, как указано выше, сохраненный индекс начинается с указания на последнего дочернего элемента, но затем я проверяю видимость этого элемента, и если он невидим, я уменьшаю индекс на единицу и проверяю снова, продолжая до тех пор, пока Я либо нахожу видимый элемент, либо у меня заканчиваются дочерние элементы для проверки (т.е. все ли они невидимы). Именно поэтому я назвал переменную 'firstFilledIndex', поскольку технически это так, и все элементы после этого используют логику 'Fill', даже хотя все элементы после него невидимы.

Наконец-то я добавил новое свойство LastVisibleChildFill, чтобы включить или отключить мое новое поведение. В качестве помощи потребителям, если вы установите для него значение true, оно неявно также установит для вас значение true для LastChildFill.

Вот полный код.

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;
    }
}
person Mark A. Donohoe    schedule 12.09.2013