zoukankan      html  css  js  c++  java
  • WPF线段式布局的一种实现

    线段式布局

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

    segment

    实现

    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)));
            }
        }
    }

    连线功能

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

    segment_line

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

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

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

    代码

    博客园:SegmentDemo

  • 相关阅读:
    centos8 将SSSD配置为使用LDAP并要求TLS身份验证
    Centos8 搭建 kafka2.8 .net5 简单使用kafka
    .net core 3.1 ActionFilter 拦截器 偶然 OnActionExecuting 中HttpContext.Session.Id 为空字符串 的问题
    Springboot根据不同环境加载对应的配置
    VMware Workstation12 安装 Centos8.3
    .net core json配置文件小结
    springboot mybatisplus createtime和updatetime自动填充
    .net core autofac依赖注入简洁版
    .Net Core 使用 redis 存储 session
    .Net Core 接入 RocketMQ
  • 原文地址:https://www.cnblogs.com/yiyan127/p/WP-SegmentsPanel.html
Copyright © 2011-2022 走看看