线段式布局

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

实现

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

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

  1. iOS:UICollectionView流式布局及其在该布局上的扩展的线式布局

    UICollectionViewFlowLayout是苹果公司做好的一种单元格布局方式,它约束item的排列规则是:从左到右依次排列,如果右边不够放下,就换一行重复上面的方式排放,,,,,   常用的 ...

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

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

  3. 关于解决python线上问题的几种有效技术

    工作后好久没上博客园了,虽然不是很忙,但也没学生时代闲了.今天上博客园,发现好多的文章都是年终总结,想想是不是自己也应该总结下,不过现在还没想好,等想好了再写吧.今天写写自己在工作后用到的技术干货,争 ...

  4. WPF中Grid布局

    WPF中Grid布局XMAl与后台更改,最普通的登录界面为例. <Grid Width="200" Height="100" > <!--定义 ...

  5. CSS全屏布局的5种方式

    × 目录 [1]float [2]inline-block [3]table[4]absolute[5]flex[6]总结 前面的话 全屏布局在实际工作中是很常用的,比如管理系统.监控平台等.本文将介 ...

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

    × 目录 [1]float [2]inline-block [3]table[4]flex 前面的话 等分布局是指子元素平均分配父元素宽度的布局方式,本文将介绍实现等分布局的4种方式 思路一: flo ...

  7. css布局 - 两栏自适应布局的几种实现方法汇总

    这种两列布局的样式是我们在平时工作中非常常见的设计,同时也是面试中要求实现的高频题.很有必要掌握以备不时之需: 整理了几种实现方法,当然,风骚的代码不止这几种能实现,欢迎各位的补充. 方法汇总目录 简 ...

  8. CSS全屏布局的6种方式

    前面的话 全屏布局在实际工作中是很常用的,比如管理系统.监控平台等.本文将介绍关于全屏布局的6种思路 float [1]float + calc 通过calc()函数计算出.middle元素的高度,并 ...

  9. 实现CSS等分布局的5种方式

    前面的话 等分布局是指子元素平均分配父元素宽度的布局方式,本文将介绍实现等分布局的5种方式 float [思路一]float 缺点:结构和样式存在耦合性,IE7-浏览器下对宽度百分比取值存在四舍五入的 ...

随机推荐

  1. SpringBoot2 整合 ClickHouse数据库,实现高性能数据查询分析

    本文源码:GitHub·点这里 || GitEE·点这里 一.ClickHouse简介 1.基础简介 Yandex开源的数据分析的数据库,名字叫做ClickHouse,适合流式或批次入库的时序数据.C ...

  2. 获取windows操作系统所有用户

    一.知识点简单介绍 1. 利用WindowsApi获取 [DllImport("Netapi32.dll ")] extern static int NetUserEnum([Ma ...

  3. IMP-00009: abnormal end of export file解决方案

    一.概述 最近在测试环境的一个oracle数据库上面,使用exp将表导出没有问题,而将导出的文件使用imp导入时却出现了如下错误. IMP-00009: abnormal end of export ...

  4. Java学习 1.3——Java开发环境的搭建:安装JDK,配置环境变量

    了解了基本的Java知识后,就需要开始搭建开发环境了. 一,安装JDK JDK1.8下载地址 接受协议,选择选择自己的系统,我的是Windows64位: 点进去后会让你登录Oracle账号,没有就创建 ...

  5. Oracle merge into的优势

    简介 Oracle merge into命令,顾名思义就是“有则更新,无则插入”,这个也是merge into 命令的核心思想,在实际开发过程中,我们会经常遇到这种通过两表互相关联匹配更新其中一个表的 ...

  6. Implement Property Value Validation in the Application Model 在应用程序模型中实现属性值验证

    In this lesson, you will learn how to check whether or not a property value satisfies a particular r ...

  7. 简约清新日系你好五月通用PPT模板推荐

    模版来源:http://ppt.dede58.com/peixunyanjiang/26488.html

  8. choose Perseverance :)

    心里话 很久都没有更新博客了,我会陆陆续续的把云笔记中的一些有意思的文章放在博客中. 这10个月以来经历了很多,9月份参加了省赛获得了一个二等奖,和一等奖失之交臂的滋味很难受,到10月份开始维护自己的 ...

  9. 团队项目之Scrum1

    小组:BLACK PANDA 时间:2019.11.16 部分 得分项 分数 完成内容 第 1 篇 Scrum 冲刺博客 各个成员在 Alpha 阶段认领的任务 3 明日各个成员的任务安排 3 用户登 ...

  10. The 2019 Asia Nanchang First Round Online Programming Contest

    传送门 A. Enju With math problem 题意: 给出\(a_1,\cdots,a_{100}\),满足\(a_i\leq 1.5*10^8\). 现在问是否存在一个\(pos\), ...