WPF线段式布局的一种实现

线段式布局

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

实现

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

原文地址:https://www.cnblogs.com/yiyan127/p/WP-SegmentsPanel.html

时间: 2024-10-15 15:49:37

WPF线段式布局的一种实现的相关文章

WPF布局的6种面板

WPF用于布局的面板主要有6个,StackPanel(栈面板).WrapPanel(环绕面板).DockPanel(停靠面板).Canvas(画布).Grid(网格面板)和 UniformGrid(均布网格).一下详细介绍几种面板各自的特点: 1.StackPanel 栈面板,可以将元素排列成一行或者一列.其特点是:每个元素各占一行或者一列.Orientation属性指定排列方式:Vertical(垂直)[默认].Horizontal(水平).默认情况下,水平排列时,每个元素都与面板一样高:垂直

移动端 三段式布局 (flex方式)

分享一种平时用的三段式布局(flex) 主要思路是  上中下    header&footer 给高度  main 占其余部分 html 部分 <div class='wrap'> <div class='header'></div> <div class='main'></div> <div class='footer'></div> </div> css 部分 .wrap{ display: fle

转载:CSS实现三栏布局的四种方法示例

转载网址:http://www.jb51.net/css/529846.html 前言 其实不管是三栏布局还是两栏布局都是我们在平时项目里经常使用的,也许你不知道什么事三栏布局什么是两栏布局但实际已经在用,或许你知道三栏布局的一种或两种方法,但实际操作中也只会依赖那某一种方法,本文具体的介绍了三栏布局的四种方法,并介绍了它的使用场景. 所谓三栏布局就是指页面分为左中右三部分然后对中间一部分做自适应的一种布局方式. 1.绝对定位法 HTML代码如下: <div class="left&quo

页面布局的几种方式

基本布局的几种方式: (1)流体布局: 流布局与固定宽度布局基本不同点就在于对网站尺寸的测量单位不同.固定宽度布局使用的是像素,但是流布局使用的是百分比,看到百分比,你应该想到,这将提供了很强的可塑性和流动性.换句话说,通过设置了百分比,你将不需要考虑设备尺寸或者屏幕宽度大小了,结论就是,你可以为每种情形找到一种可行的方案,因为你的设计尺寸将适应所有的设备尺寸.流布局与媒体查询和优化样式技术的关系密切. (2)固定布局 在固定布局中,网页的宽度是必须指定为一个像素值,一般设为960像素.在过去,

CSS全屏布局的5种方式

× 目录 [1]float [2]inline-block [3]table[4]absolute[5]flex[6]总结 前面的话 全屏布局在实际工作中是很常用的,比如管理系统.监控平台等.本文将介绍关于全屏布局的5种思路 思路一: float [1]float + calc 通过calc()函数计算出.middle元素的高度,并设置子元素高度为100% <style> body,p{margin: 0;} body,html,.parent{height: 100%;} .middle{

中间定宽,两边自适应布局的三种实现方法

中间定宽,两边自适应布局的三种实现方法 1. 浮动加定位 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>中间定宽,两边自适应</title> <style type="text/css"> html,body,div{ height: 100%; } .parent{ p

实现CSS等分布局的4种方式

× 目录 [1]float [2]inline-block [3]table[4]flex 前面的话 等分布局是指子元素平均分配父元素宽度的布局方式,本文将介绍实现等分布局的4种方式 思路一: float 缺点:结构和样式存在耦合性,IE7-浏览器下对宽度百分比取值存在四舍五入的误差 [1]float + padding + background-clip 使用padding来实现子元素之间的间距,使用background-clip使子元素padding部分不显示背景 <style> body

android中填充界面布局的三种方式

改变原来界面布局的三种方式: 1.第一种方式: LayoutInflater li = LayoutInflater.from(this); 具体代码: public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activ

WPF中Grid布局

WPF中Grid布局XMAl与后台更改,最普通的登录界面为例. <Grid Width="200" Height="100" > <!--定义了两列--> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="100*"/> </Grid.Column