欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

WPF线段式布局的一种实现

程序员文章站 2022-03-20 12:18:50
线段式布局 有时候需要实现下面类型的布局方案,不知道有没有约定俗成的称呼,我个人强名为线段式布局。因为元素恰好放置在线段的端点上。 实现 WPF所有布局控件都直接或间接的继承自System.Windows.Controls. Panel,常用的布局控件有Canvas、DockPanel、Grid、S ......

线段式布局

有时候需要实现下面类型的布局方案,不知道有没有约定俗成的称呼,我个人强名为线段式布局。因为元素恰好放置在线段的端点上。

实现

wpf所有布局控件都直接或间接的继承自system.windows.controls. panel,常用的布局控件有canvas、dockpanel、grid、stackpanel、wrappanel,都不能直接满足这种使用场景。因此,我们不妨自己实现一个布局控件。

不难看出,该布局的特点是:最左侧朝右布局,最右侧朝左布局,中间点居中布局。因此,我们要做的就是在measureoverride和arrangeoverride做好这件事。另外,为了功能丰富,添加了一个朝向属性。代码如下:

using system;
using system.linq;
using system.windows;
using system.windows.controls;

namespace segmentdemo
{
    /// <summary>
    /// 类似线段的布局面板,即在最左侧朝右布局,最右侧朝左布局,中间点居中布局
    /// </summary>
    public class segmentspanel : panel
    {
        /// <summary>
        /// 可见子元素个数
        /// </summary>
        private int _visiblechildcount;

        /// <summary>
        /// 朝向的依赖属性
        /// </summary>
        public static readonly dependencyproperty orientationproperty = dependencyproperty.register(
            "orientation", typeof(orientation), typeof(segmentspanel),
            new frameworkpropertymetadata(orientation.horizontal, frameworkpropertymetadataoptions.affectsmeasure));

        /// <summary>
        /// 朝向
        /// </summary>
        public orientation orientation
        {
            get { return (orientation)getvalue(orientationproperty); }
            set { setvalue(orientationproperty, value); }
        }

        protected override size measureoverride(size constraint)
        {
            _visiblechildcount = this.countvisiblechild();

            if (_visiblechildcount == 0)
            {
                return new size(0, 0);
            }

            double width = 0;
            double height = 0;

            size availablesize = new size(constraint.width / _visiblechildcount, constraint.height);

            if (orientation == orientation.vertical)
            {
                availablesize = new size(constraint.width, constraint.height / _visiblechildcount);
            }

            foreach (uielement child in children)
            {
                child.measure(availablesize);
                size desiredsize = child.desiredsize;

                if (orientation == orientation.horizontal)
                {
                    width += desiredsize.width;
                    height = math.max(height, desiredsize.height);
                }
                else
                {
                    width = math.max(width, desiredsize.width);
                    height += desiredsize.height;
                }
            }

            return new size(width, height);
        }

        protected override size arrangeoverride(size arrangesize)
        {
            if (_visiblechildcount == 0)
            {
                return arrangesize;
            }

            int firstvisible = 0;
            while (internalchildren[firstvisible].visibility == visibility.collapsed)
            {
                firstvisible++;
            }

            uielement firstchild = this.internalchildren[firstvisible];
            if (orientation == orientation.horizontal)
            {
                this.arrangechildhorizontal(firstchild, arrangesize.height, 0);
            }
            else
            {
                this.arrangechildvertical(firstchild, arrangesize.width, 0);
            }

            int lastvisible = _visiblechildcount - 1;
            while (internalchildren[lastvisible].visibility == visibility.collapsed)
            {
                lastvisible--;
            }

            if (lastvisible <= firstvisible)
            {
                return arrangesize;
            }

            uielement lastchild = this.internalchildren[lastvisible];
            if (orientation == orientation.horizontal)
            {
                this.arrangechildhorizontal(lastchild, arrangesize.height, arrangesize.width - lastchild.desiredsize.width);
            }
            else
            {
                this.arrangechildvertical(lastchild, arrangesize.width, arrangesize.height - lastchild.desiredsize.height);
            }

            int ordinarychildcount = _visiblechildcount - 2;
            if (ordinarychildcount > 0)
            {
                double uniformwidth = (arrangesize.width  - firstchild.desiredsize.width / 2.0 - lastchild.desiredsize.width / 2.0) / (ordinarychildcount + 1);
                double uniformheight = (arrangesize.height - firstchild.desiredsize.height / 2.0 - lastchild.desiredsize.height / 2.0) / (ordinarychildcount + 1);

                int visible = 0;
                for (int i = firstvisible + 1; i < lastvisible; i++)
                {
                    uielement child = this.internalchildren[i];
                    if (child.visibility == visibility.collapsed)
                    {
                        continue;
                    }

                    visible++;

                    if (orientation == orientation.horizontal)
                    {
                        double x = firstchild.desiredsize.width / 2.0 + uniformwidth * visible - child.desiredsize.width / 2.0;
                        this.arrangechildhorizontal(child, arrangesize.height, x);
                    }
                    else
                    {
                        double y = firstchild.desiredsize.height / 2.0 + uniformheight * visible - child.desiredsize.height / 2.0;
                        this.arrangechildvertical(child, arrangesize.width, y);
                    }
                }
            }

            return arrangesize;
        }

        /// <summary>
        /// 统计可见的子元素数
        /// </summary>
        /// <returns>可见子元素数</returns>
        private int countvisiblechild()
        {
            return this.internalchildren.cast<uielement>().count(element => element.visibility != visibility.collapsed);
        }

        /// <summary>
        /// 在水平方向安排子元素
        /// </summary>
        /// <param name="child">子元素</param>
        /// <param name="height">可用的高度</param>
        /// <param name="x">水平方向起始坐标</param>
        private void arrangechildhorizontal(uielement child, double height, double x)
        {
            child.arrange(new rect(new point(x, 0), new size(child.desiredsize.width, height)));
        }

        /// <summary>
        /// 在竖直方向安排子元素
        /// </summary>
        /// <param name="child">子元素</param>
        /// <param name="width">可用的宽度</param>
        /// <param name="y">竖直方向起始坐标</param>
        private void arrangechildvertical(uielement child, double width, double y)
        {
            child.arrange(new rect(new point(0, y), new size(width, child.desiredsize.height)));
        }
    }
}

连线功能

端点有了,有时为了美观,需要在端点之间添加连线功能,如下:

该连线功能是集成在布局控件里面还是单独,我个人倾向于单独使用。因为本质上这是一种装饰功能,而非布局核心功能。

装饰功能需要添加很多属性来控制连线,比如控制连线位置的属性。但是因为我懒,所以我破坏了继承自decorator的原则。又正因为如此,我也否决了继承自border的想法,因为我想使用padding属性来控制连线位置,但是除非显式改写,否则border会保留padding的空间。最后,我选择了contentcontrol作为基类,只添加了连线大小一个属性。连线位置是通过verticalcontentalignment(horizontalcontentalignment)和padding来控制,连线颜色和粗细参考border,但是没有圆角功能(又是因为我懒,你来打我啊)。

连线是通过在onrender中画线来实现的。考虑到布局控件可能用于itemscontrol,并不是要求独子是布局控件,只要n代码单传是布局控件就行。代码就不贴了,放在代码部分:

代码

博客园:segmentdemo