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 }
View Code
复制代码

 

posted @   ggtc  阅读(61)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
历史上的今天:
2020-04-24 通过XML标记生成word
//右下角目录
点击右上角即可分享
微信分享提示