线段式布局

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

实现

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. RabbitMQ几个常用面试题

    以下观点,仅为个人理解的总结,如有错漏,欢迎指正! -------------------------------------------------------------------------- ...

  2. Centos 7 下安装 Jenkins

    Jenkins介绍 Jenkins是一个开源的支持自动化构建.部署等任务的平台.基本上可以说是持续集成(CI).持续发布(CD)不可或缺的工具. 安装Java环境 CentOS 7 安装 JAVA环境 ...

  3. C#以对象为成员的例子

    using System; using System.Collections.Generic; using System.Text; namespace test { class Program { ...

  4. C# OpenCVSharp使用SetMouseCallback设置回调方法,报错"向非托管代码传递委托时,托管应用程序必须让这些委托保持活动状态,直到确信不会再次调用它们。"

    一.描述问题 当非托管代码调用托管应用程序的委托的时候,经常会报错“托管调试助手 "CallbackOnCollectedDelegate"   Message=托管调试助手 &q ...

  5. extjs 动态加载列表,优化思路

    功能截图 之前做法,先查询每一行的前4个字段,然后动态拼接出其他的字段,效率极低,以下是优化后的代码,供参考,只提供一个优化思路,授人以鱼不如授人以渔 后台Sql语句优化(语法仅支持Oracle) S ...

  6. 网络协议 15 - P2P 协议

    大家说起种子,应该都知道是用来下载资源的.那么资源下载都有哪些方式?种子下载又有什么优势呢? 下载电影的两种方式     第一种是通过 HTTP 进行下载.这种方式,有过经历的人应该体会到,当下载文件 ...

  7. Mac版Sourcetree的安装使用

    本人也在亲测,感觉很有效,和大家分享,参考链接: https://www.jianshu.com/p/b8d0547a8449

  8. Spring Boot 2 构建可部署的war包

    默认情况下Spring Boot使用了内嵌的Tomcat服务器,项目最终被打成jar包运行,每个jar包可以被看作一个独立的Web服务器.传统的Web开发,一般会将Web应用打成一个war包,然后将其 ...

  9. MATLAB最大均值差异(Maximum Mean Discrepancy)

    MATLAB最大均值差异(Maximum Mean Discrepancy) 作者:凯鲁嘎吉 - 博客园 http://www.cnblogs.com/kailugaji/ 更多内容,请看标签:MAT ...

  10. optix之纹理使用

    1.在OpenGL中生成纹理texture optix中的纹理直接使用OpenGL纹理的id,不能跨越OpenGL纹理,所以我们先在OpenGL环境下生成一张纹理. 这个过程就很熟悉了: void W ...