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

  • 相关阅读:
    Swift-自定义类的构造函数
    Swift-存储属性,计算属性,类属性
    iOS-UICollectionViewController协议及回调
    Swift-数组
    Swift-switch使用注意点
    Swift-setValuesForKeysWithDictionary
    解决IDEA Struts2 web.xml问题
    枚举类
    增强for循环 -- foreach循环
    静态导入
  • 原文地址:https://www.cnblogs.com/yiyan127/p/WP-SegmentsPanel.html
Copyright © 2011-2022 走看看