WPF自定义FixedColumnGrid布局控件
按照上一节所讲,我已经对布局系统又所了解。接下来我就实现一个布局控件FixedColumnGrid。
1.基础版
布局控件机制如下,FixedColumnGrid将子控件按照水平排列,每行满两列后换行。每个控件大小相同,高度固定为50。
第一步,先重载测量和排列方法
protected override Size MeasureOverride(Size constraint) { //base.MeasureOverride(constraint); return constraint; } protected override Size ArrangeOverride(Size arrangeBounds) { //base.ArrangeOverride(arrangeBounds); return arrangeBounds; }
根据机制,我们需要自己决定子控件尺寸,也就是需要自己测量和排列子控件。所以我们就不需要祖先的递归了,将由我们自己手动递归,所有注释掉base调用。
第二步,测量子控件
虽然我们可以直接把constraint传过去,但我们根据布局机制,尽可能的少传递可用空间给子控件。所以我们接下来在MeasureOverride添加如下测量代码。
//base.MeasureOverride(constraint); for (int i = 0; i < this.VisualChildrenCount; i++) { UIElement child = (UIElement)this.GetVisualChild(i); if (child!=null) { child.Measure(new Size(constraint.Width / 2, 50)); } } return constraint;
第三步,测量子控件后,根据其期望尺寸,排列子控件,因此,接下来在ArrangeOverride中添加排列代码。
//base.ArrangeOverride(arrangeBounds); for (int i = 0; i < this.VisualChildrenCount; i++) { UIElement child = (UIElement)this.GetVisualChild(i); if (child!=null) { if (i % 2 == 0) { child.Arrange(new Rect(new Point(0, Math.Floor(i / 2d) * 50), child.DesiredSize)); } else { child.Arrange(new Rect(new Point(arrangeBounds.Width / 2, i / 2 * 50), child.DesiredSize)); } } } return arrangeBounds;
现在,我们已经可以试着看看效果了。先生成一下项目,再到mainWindow.xaml中添加一个FixedColumnGrid控件
<Border Background="Blue"> <local:FixedColumnGrid> <Button Content="btn" Width="500" Height="100" HorizontalAlignment="Center" VerticalAlignment="Center"/> <Button Content="btn" Width="500" Height="100"/> <Button Content="btn" Width="500" Height="100"/> <Button Content="btn"/> </local:FixedColumnGrid> </Border>
可以看到一些问题。第一个button是手动居中的,第二个就没有居中了。这时因为第二个button期望的控件大于FixedColumnGrid理应给他的控件,所以尽管他的期望尺寸被限制在了FixedColumnGrid所给的尺寸(400,50),但其真是大小却要大些,所以看起来第二个button的内容就没有居中了。
第4个button没有设置尺寸,又太小了。这时因为我们排列button时,使用的时其期望大小,而button没有手动指定width和height时,其期望大小是根据内容定的。
因此我改进了下排列,不根据子控件期望尺寸排列,而是根据我们根据布局机制规定的尺寸排列,现在得到了想要的效果。
if (i % 2 == 0) { child.Arrange(new Rect(new Point(0, i/2 * 50), new Size(arrangeBounds.Width / 2, 50))); } else { child.Arrange(new Rect(new Point(arrangeBounds.Width / 2, Math.Floor(i / 2d) * 50), new Size(arrangeBounds.Width / 2, 50))); }
2.可扩展列数版
既然我们可以排两列,哪能不能排1列,2列,3列呢。我决定继续进行增强。
首先,我们要能定义列数,于是我增加了一个依赖属性Columns。
public int Columns { get { return (int)GetValue(ColumnsProperty); } set { SetValue(ColumnsProperty, value); } } public static readonly DependencyProperty ColumnsProperty = DependencyProperty.Register("Columns", typeof(int), typeof(FixedColumnGrid), new FrameworkPropertyMetadata(default(int),FrameworkPropertyMetadataOptions.AffectsArrange));
为了在更改列数时能重新排列,所以还增加了AffectsArrange
对MeasureOverride和ArrangeOverride稍作修改
protected override Size MeasureOverride(Size constraint) { //base.MeasureOverride(constraint);
double columnWidth = constraint.Width / this.Columns;//列宽
for (int i = 0; i < this.VisualChildrenCount; i++) { UIElement child = (UIElement)this.GetVisualChild(i); if (child!=null) { child.Measure(new Size(constraint.Width / columnWidth, 50)); } } return constraint; } protected override Size ArrangeOverride(Size arrangeBounds) { //base.ArrangeOverride(arrangeBounds); for (int i = 0; i < this.VisualChildrenCount; i++) { UIElement child = (UIElement)this.GetVisualChild(i); if (child!=null) { if (this.Columns == default(int)) { if (i % 2 == 0) { child.Arrange(new Rect(new Point(0, i / 2 * 50), new Size(arrangeBounds.Width / 2, 50))); } else { child.Arrange(new Rect(new Point(arrangeBounds.Width / 2, Math.Floor(i / 2d) * 50), new Size(arrangeBounds.Width / 2, 50))); } } else { double columnWidth = arrangeBounds.Width / this.Columns;//列宽 int offsetColumn = i % this.Columns;//当前单元格处于哪一列 child.Arrange(new Rect(new Point(offsetColumn * columnWidth, Math.Floor((double)i / this.Columns) * 50), new Size(columnWidth, 50))); } } } return arrangeBounds; }
然后就可以在xaml中定义列数
<Border Background="Blue"> <local:FixedColumnGrid Columns="1"> <Button Content="btn" Width="500" Height="100" HorizontalAlignment="Center" VerticalAlignment="Center"/> <Button Content="btn" Width="500" Height="100"/> <Button Content="btn" Width="500" Height="100" HorizontalAlignment="Left"/> <Button Content="btn"/> </local:FixedColumnGrid> </Border>
这里没有居中的原因button自身HorizontalAlignment属性会影响到排列
3.可自定义高度版
列数都能调整了,哪每行高度也能否调整呢,这倒是简单,只是增加一个RowHeight依赖属性罢了。
public int RowHeight { get { return (int)GetValue(RowHeightProperty); } set { SetValue(RowHeightProperty, value); } } // Using a DependencyProperty as the backing store for ColumnHeight. This enables animation, styling, binding, etc... public static readonly DependencyProperty RowHeightProperty = DependencyProperty.Register("RowHeight", typeof(int), typeof(FixedColumnGrid), new FrameworkPropertyMetadata(50,FrameworkPropertyMetadataOptions.AffectsMeasure));
同时载将测量和排列重载中的50换成高度
child.Measure(new Size(constraint.Width / 2, this.RowHeight)); ... child.Arrange(new Rect(new Point(0, i / 2 * this.RowHeight), new Size(arrangeBounds.Width / 2, this.RowHeight))); ... child.Arrange(new Rect(new Point(arrangeBounds.Width / 2, Math.Floor(i / 2d) * this.RowHeight), new Size(arrangeBounds.Width / 2, this.RowHeight))); ... child.Arrange(new Rect(new Point(offsetColumn * columnWidth, Math.Floor((double)i / this.Columns) * this.RowHeight), new Size(columnWidth, this.RowHeight)));
现在就可以在xaml中使用自定义高度了
<Border Background="Blue"> <local:FixedColumnGrid Columns="2" RowHeight="120"> <Button Content="btn" Width="500" Height="100" HorizontalAlignment="Center" VerticalAlignment="Center"/> <Button Content="btn" Width="500" Height="100"/> <Button Content="btn"/> <Button Content="btn"/> <Button Content="btn"/> <Button Content="btn"/> </local:FixedColumnGrid> </Border>
FixedColumnGrid完整代码

1 public partial class FixedColumnGrid : Panel 2 { 3 public FixedColumnGrid() 4 { 5 InitializeComponent(); 6 } 7 8 protected override Size MeasureOverride(Size constraint) 9 { 10 //base.MeasureOverride(constraint); 11 double columnWidth = constraint.Width / this.Columns;//列宽 12 for (int i = 0; i < this.VisualChildrenCount; i++) 13 { 14 UIElement child = (UIElement)this.GetVisualChild(i); 15 if (child!=null) 16 { 17 child.Measure(new Size(constraint.Width / columnWidth, this.RowHeight)); 18 } 19 } 20 return constraint; 21 } 22 23 protected override Size ArrangeOverride(Size arrangeBounds) 24 { 25 //base.ArrangeOverride(arrangeBounds); 26 for (int i = 0; i < this.VisualChildrenCount; i++) 27 { 28 UIElement child = (UIElement)this.GetVisualChild(i); 29 if (child!=null) 30 { 31 if (this.Columns == default(int)) 32 { 33 if (i % 2 == 0) 34 { 35 child.Arrange(new Rect(new Point(0, i / 2 * this.RowHeight), new Size(arrangeBounds.Width / 2, this.RowHeight))); 36 } 37 else 38 { 39 child.Arrange(new Rect(new Point(arrangeBounds.Width / 2, Math.Floor(i / 2d) * this.RowHeight), new Size(arrangeBounds.Width / 2, this.RowHeight))); 40 } 41 } 42 else 43 { 44 double columnWidth = arrangeBounds.Width / this.Columns;//列宽 45 int offsetColumn = i % this.Columns;//当前单元格处于哪一列 46 child.Arrange(new Rect(new Point(offsetColumn * columnWidth, Math.Floor((double)i / this.Columns) * this.RowHeight), new Size(columnWidth, this.RowHeight))); 47 } 48 } 49 } 50 return arrangeBounds; 51 } 52 53 54 55 public int Columns 56 { 57 get { return (int)GetValue(ColumnsProperty); } 58 set { SetValue(ColumnsProperty, value); } 59 } 60 61 public static readonly DependencyProperty ColumnsProperty = 62 DependencyProperty.Register("Columns", typeof(int), typeof(FixedColumnGrid), new FrameworkPropertyMetadata(default(int),FrameworkPropertyMetadataOptions.AffectsArrange)); 63 64 65 66 public int RowHeight 67 { 68 get { return (int)GetValue(RowHeightProperty); } 69 set { SetValue(RowHeightProperty, value); } 70 } 71 72 // Using a DependencyProperty as the backing store for ColumnHeight. This enables animation, styling, binding, etc... 73 public static readonly DependencyProperty RowHeightProperty = 74 DependencyProperty.Register("RowHeight", typeof(int), typeof(FixedColumnGrid), new FrameworkPropertyMetadata(50,FrameworkPropertyMetadataOptions.AffectsMeasure)); 75 76 77 }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
2020-04-24 通过XML标记生成word