上篇文章WPF实现高仿统计标题卡中,实现了依据自己喜欢的统计卡片组件样式,实现过程中,发现,现有的WPF
默认自带面板Grid
、UniformGrid
、StackPanel
、DockPanel
、WrapPanel
以及Canvas
等暂时没有满足条件的面板容器,接下来就是一个简单的容器面板我称之为BootstrapPanel
,参考容器面板UniformGrid
和WrapPanel
两个容器的功能,项目仍然为上篇文章使用的项目为例
基础面板分析
提供功能
该容器面板能够依据内部的可视子级数量,在未设置Rows
、Columns
、FirstColumn
等属性的条件下,进行自动填充均分容器面板的各区域
1 2 3 4 5 6 7
| <ItemsControl ItemsSource="{Binding Path=Data}" FocusVisualStyle="{x:Null}"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <UniformGrid></UniformGrid> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> </ItemsControl>
|
问题
当随着容器面板大小缩放时,容器内的布局并不能够根据容器大小做出相应调整(换行减列)
WrapPanel
提供功能
能够根据容器的大小,合理为容器内的各个可见元素做自适应处理(加列减行或减列加行)
1 2 3 4 5 6 7
| <ItemsControl ItemsSource="{Binding Path=Data}" FocusVisualStyle="{x:Null}"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <WrapPanel></WrapPanel> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> </ItemsControl>
|
问题
虽然能够自动处理容器中元素的位置,但过于容器内的尺寸过于依赖已定尺寸,变换过程中,显示过程中,元素的尺寸无法自动填充容器的富余空间
思路分析
实际使用的过程中,我需要使用的容器需求归纳是如下几点:
- 能够实现容器内元素均分(
UniformGrid
具备)
- 能够在容器变化时,自动处理容器中的元素位置变化(
WrapPanel
具备)
- 能够自动填充富余空间(
UniformGrid
具备)
编辑与实现
于是,开启源码的浏览过程,自定义项目,结构如下:
测量容器、元素尺寸
此处忽略,菜鸟博主详细的探索细节,基本方向就是,通过看源码,重新构建上述两个容器的源码,通过源码最终决定以UniformGrid
作为基础参照类,将动态设置元素布局的ArrangeOverride
函数保留,能够保留元素动态适应容器布局,ps,可以直接挪用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| protected override Size ArrangeOverride(Size arrangeSize) { Rect finalRect = new Rect(0.0, 0.0, arrangeSize.Width / (double)_columns, arrangeSize.Height / (double)_rows); double width = finalRect.Width; double num = arrangeSize.Width - 1.0; foreach (UIElement internalChild in base.InternalChildren) { internalChild.Arrange(finalRect); if (internalChild.Visibility != Visibility.Collapsed) { finalRect.X += width; if (finalRect.X >= num) { finalRect.Y += finalRect.Height; finalRect.X = 0.0; } } } return arrangeSize; }
|
处理容器行列
主要需要考虑的是在测量也就是MeasureOverride
函数对容器尺寸变更时,对行列的动态计算,我采用的方式,对列作处理,在不同条件下,显示不同的数据列,改变原有UniformGrid
的固定行列方式,于是改动其中的UpdateComputedValues
函数,将固定均分改为自定义实现逻辑,同时为了能够控制均分后,统一控制每个元素中的外边距,添加了一个依赖属性Gutter
Gutter
依赖属性
1 2 3 4 5 6 7 8 9 10
| /// <summary> /// 元素之间的间距 /// </summary> public Thickness Gutter { get { return (Thickness)GetValue(GutterProperty); } set { SetValue(GutterProperty, value); } } public static readonly DependencyProperty GutterProperty = DependencyProperty.Register("Gutter", typeof(Thickness), typeof(BootstrapPanel), new PropertyMetadata(new Thickness(0)));
|
UpdateComputedValues
函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
| protected override Size MeasureOverride(Size constraint) { //自动获取行列数量 UpdateComputedValues(constraint);
//设置容器测量尺寸 Size availableSize = new Size(constraint.Width / (double)_columns, constraint.Height / (double)_rows); double num = 0.0; double num2 = 0.0; int i = 0; for (int count = base.InternalChildren.Count; i < count; i++) { UIElement uIElement = base.InternalChildren[i]; //Margin uIElement.SetValue(MarginProperty, Gutter); uIElement.Measure(availableSize); Size desiredSize = uIElement.DesiredSize; if (num < desiredSize.Width) { num = desiredSize.Width; } if (num2 < desiredSize.Height) { num2 = desiredSize.Height; } } return new Size(num * (double)_columns, num2 * (double)_rows); }
/// <summary> /// 自动设置行列规则 /// </summary> private void UpdateComputedValues(Size constraint) { //通过当前容器宽度以及默认最小宽度 //获取到实际默认容器的列数 if (DoubleUtil.IsNaN(ItemMinWidth)) { throw new ArgumentNullException("请设置ItemMinWidth的目标值"); } #region 统计容器中可见元素数量 //统计未隐藏不占位的子级元素数量 int childrenCount = 0; int i = 0; for (int count = base.InternalChildren.Count; i < count; i++) { UIElement uIElement = base.InternalChildren[i]; if (uIElement.Visibility != Visibility.Collapsed) { childrenCount++; } } if (childrenCount == 0) { childrenCount = 1; } #endregion
//同行列数 int colCount = (int)(constraint.Width / (ItemMinWidth + Gutter.Left + Gutter.Right)); if (colCount >= childrenCount)//同行最大列数 { _columns = childrenCount; } else if (colCount == 0)//容器宽度不能满足最小宽度 { _columns = 1; } else { _columns = colCount; } if (_columns > 0) { _rows = (childrenCount + (_columns - 1)) / _columns; return; } //通过元素数量获取数据行数 _rows = (int)Math.Sqrt(childrenCount); if (_rows * _rows < childrenCount) { _rows++; } if (_columns == 0) { _columns = (childrenCount + (_rows - 1)) / _rows; } }
|
容器代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
| /// <summary> /// 实现简单动态均分Panel容器 /// </summary> public class BootstrapPanel : Panel {
#region 依赖属性 /// <summary> /// 子级最小宽度 /// </summary> public double ItemMinWidth { get { return (double)GetValue(ItemMinWidthProperty); } set { SetValue(ItemMinWidthProperty, value); } }
public static readonly DependencyProperty ItemMinWidthProperty = DependencyProperty.Register("ItemMinWidth", typeof(double), typeof(BootstrapPanel), new FrameworkPropertyMetadata(double.NaN, FrameworkPropertyMetadataOptions.AffectsMeasure), IsWidthHeightValid); private static bool IsWidthHeightValid(object value) { double num = (double)value; if (!DoubleUtil.IsNaN(num)) { if (num >= 0.0) { return !double.IsPositiveInfinity(num); } return false; } return true; }
/// <summary> /// 元素之间的间距 /// </summary>
public Thickness Gutter { get { return (Thickness)GetValue(GutterProperty); } set { SetValue(GutterProperty, value); } } public static readonly DependencyProperty GutterProperty = DependencyProperty.Register("Gutter", typeof(Thickness), typeof(BootstrapPanel), new PropertyMetadata(new Thickness(0)));
#endregion /// <summary> /// 实际行数 /// </summary> private int _rows; /// <summary> /// 实际列数 /// </summary> private int _columns;
protected override Size MeasureOverride(Size constraint) { //自动获取行列数量 UpdateComputedValues(constraint);
//设置容器测量尺寸 Size availableSize = new Size(constraint.Width / (double)_columns, constraint.Height / (double)_rows); double num = 0.0; double num2 = 0.0; int i = 0; for (int count = base.InternalChildren.Count; i < count; i++) { UIElement uIElement = base.InternalChildren[i]; //Margin uIElement.SetValue(MarginProperty, Gutter); uIElement.Measure(availableSize); Size desiredSize = uIElement.DesiredSize; if (num < desiredSize.Width) { num = desiredSize.Width; } if (num2 < desiredSize.Height) { num2 = desiredSize.Height; } } return new Size(num * (double)_columns, num2 * (double)_rows); }
/// <summary> /// 自动设置行列规则 /// </summary> private void UpdateComputedValues(Size constraint) { //通过当前容器宽度以及默认最小宽度 //获取到实际默认容器的列数 if (DoubleUtil.IsNaN(ItemMinWidth)) { throw new ArgumentNullException("请设置ItemMinWidth的目标值"); } #region 统计容器中可见元素数量 //统计未隐藏不占位的子级元素数量 int childrenCount = 0; int i = 0; for (int count = base.InternalChildren.Count; i < count; i++) { UIElement uIElement = base.InternalChildren[i]; if (uIElement.Visibility != Visibility.Collapsed) { childrenCount++; } } if (childrenCount == 0) { childrenCount = 1; } #endregion
//同行列数 int colCount = (int)(constraint.Width / (ItemMinWidth + Gutter.Left + Gutter.Right)); if (colCount >= childrenCount)//同行最大列数 { _columns = childrenCount; } else if (colCount == 0)//容器宽度不能满足最小宽度 { _columns = 1; } else { _columns = colCount; } if (_columns > 0) { _rows = (childrenCount + (_columns - 1)) / _columns; return; } //通过元素数量获取数据行数 _rows = (int)Math.Sqrt(childrenCount); if (_rows * _rows < childrenCount) { _rows++; } if (_columns == 0) { _columns = (childrenCount + (_rows - 1)) / _rows; } }
protected override Size ArrangeOverride(Size arrangeSize) { Rect finalRect = new Rect(0.0, 0.0, arrangeSize.Width / (double)_columns, arrangeSize.Height / (double)_rows); double width = finalRect.Width; double num = arrangeSize.Width - 1.0; foreach (UIElement internalChild in base.InternalChildren) { internalChild.Arrange(finalRect); if (internalChild.Visibility != Visibility.Collapsed) { finalRect.X += width; if (finalRect.X >= num) { finalRect.Y += finalRect.Height; finalRect.X = 0.0; } } } return arrangeSize; } }
|
容器使用
构造好的容器所在项目Bolg.AntPanel
,被Blog.TotalCard
项目引用,在页面中使用
命名空间引用
统计标题卡片对应的xaml
中引入对应命名空间
1
| xmlns:ant="clr-namespace:Blog.AntPanel.Controls;assembly=Blog.AntPanel"
|
组件中引用,替换掉之前的ItemControl
对应的ItemsPanel
,其中ItemMinWidth
设置最小子级元素的宽度,Gutter
设置子级元素间的外边距,主要作用到MeasureOverride
中元素的属性设定
1 2 3 4 5 6 7 8 9 10 11 12
| protected override Size MeasureOverride(Size constraint) { //省略部分内容 for (int count = base.InternalChildren.Count; i < count; i++) { //省略部分内容 //Margin-设置元素外边距 uIElement.SetValue(MarginProperty, Gutter); //省略部分内容 } //省略部分内容 }
|
xaml
中使用
统计标题卡片对应的xaml
中ItemsControl
模板的使用
1 2 3 4 5
| <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <ant:BootstrapPanel ItemMinWidth="240" Gutter="10"></ant:BootstrapPanel> </ItemsPanelTemplate> </ItemsControl.ItemsPanel>
|
运行效果
实现功能
- 能够根据容器中元素数量设定行列
- 通过容器的尺寸变化变更元素排列
- 元素均分的区域块元素自动填充
Q&A
如果需要项目源码,可以关注一下公众号,回复【BootstrapPanel
】获取源码资源