Is it possible to style the contents of a DockPanel to fill the last *visible* child?

好久不见. 提交于 2019-12-01 18:43:11

I created a solution in the form of a DockPanel subclass, but I'm not marking this as the accepted answer because I'm hoping to still find a way to do this via styles or attached behaviors so it can be used with any DockPanel (or other subclass), not just this.

Still, for others, this may be helpful so I'm posting it here.

The code for the full class is below. The meat of the work is in the ArrangeOverride method, which was based on the original logic from DockPanel as extracted from Reflector.

The way the existing logic worked was inside ArrangeOverride, if LastChildFill was set it stored the index of the last child (i.e. the index of the item to be filled) in a variable. If LastChildFill wasn't set, it instead stored 'count' in that variable.

Then when looping through the children performing the actual arrange, if the element being arranged had an index less than the previously stored index, it performed the 'docking' logic, otherwise it performed 'fill' logic.

That meant when LastChildFill was false, every element ran the 'docking' logic since they all had an index below that stored index (which again equals 'count', or highest index + 1). However, when LastChildFill was true, the last element didn't have an index less than the stored index (it was actually equal to it), so that one element ran the 'fill' logic while everything else ran the 'docking' logic.

The change I made was if LastChildFill is set, as above, the stored index starts out pointing to the last child, but I then check the visibility of that element and if it's invisible, I lower the index by one and check again, continuing until I either find a visible element, or I run out of children to check (i.e. if they were all invisible.) That's also why I named the variable 'firstFilledIndex' since technically that, and all elements afterwards use the 'Fill' logic, even though all the elements after it are invisible.

I finally added a new LastVisibleChildFill property to enable or disable my new behavior. As a help to consumers, if you set that to true, it implicitly also set LastChildFill to true for you.

Here's the full code.

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;
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!