按照上一節所講,我已經對佈局系統又所瞭解。接下來我就實現一個佈局控制元件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))); }
對MeasureOverride和ArrangeOverride稍作修改
protected override Size MeasureOverride(Size constraint) { //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; } 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; }
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
然後就可以在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 ColumnHeight { get { return (int)GetValue(ColumnHeightProperty); } set { SetValue(ColumnHeightProperty, value); } } // Using a DependencyProperty as the backing store for ColumnHeight. This enables animation, styling, binding, etc... public static readonly DependencyProperty ColumnHeightProperty = DependencyProperty.Register("ColumnHeight", typeof(int), typeof(FixedColumnGrid), new FrameworkPropertyMetadata(50,FrameworkPropertyMetadataOptions.AffectsMeasure));
同時載將測量和排列過載中的50換成高度
child.Measure(new Size(constraint.Width / 2, this.ColumnHeight)); ... child.Arrange(new Rect(new Point(0, i / 2 * this.ColumnHeight), new Size(arrangeBounds.Width / 2, this.ColumnHeight))); ... child.Arrange(new Rect(new Point(arrangeBounds.Width / 2, Math.Floor(i / 2d) * this.ColumnHeight), new Size(arrangeBounds.Width / 2, this.ColumnHeight))); ... child.Arrange(new Rect(new Point(offsetColumn * columnWidth, Math.Floor((double)i / this.Columns) * this.ColumnHeight), new Size(columnWidth, this.ColumnHeight)));
現在就可以在xaml中使用自定義高度了
<Border Background="Blue"> <local:FixedColumnGrid Columns="2" ColumnHeight="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 for (int i = 0; i < this.VisualChildrenCount; i++) 12 { 13 UIElement child = (UIElement)this.GetVisualChild(i); 14 if (child!=null) 15 { 16 child.Measure(new Size(constraint.Width / 2, this.ColumnHeight)); 17 } 18 } 19 return constraint; 20 } 21 22 protected override Size ArrangeOverride(Size arrangeBounds) 23 { 24 //base.ArrangeOverride(arrangeBounds); 25 for (int i = 0; i < this.VisualChildrenCount; i++) 26 { 27 UIElement child = (UIElement)this.GetVisualChild(i); 28 if (child!=null) 29 { 30 if (this.Columns == default(int)) 31 { 32 if (i % 2 == 0) 33 { 34 child.Arrange(new Rect(new Point(0, i / 2 * this.ColumnHeight), new Size(arrangeBounds.Width / 2, this.ColumnHeight))); 35 } 36 else 37 { 38 child.Arrange(new Rect(new Point(arrangeBounds.Width / 2, Math.Floor(i / 2d) * this.ColumnHeight), new Size(arrangeBounds.Width / 2, this.ColumnHeight))); 39 } 40 } 41 else 42 { 43 double columnWidth = arrangeBounds.Width / this.Columns;//列寬 44 int offsetColumn = i % this.Columns;//當前單元格處於哪一列 45 child.Arrange(new Rect(new Point(offsetColumn * columnWidth, Math.Floor((double)i / this.Columns) * this.ColumnHeight), new Size(columnWidth, this.ColumnHeight))); 46 } 47 } 48 } 49 return arrangeBounds; 50 } 51 52 53 54 public int Columns 55 { 56 get { return (int)GetValue(ColumnsProperty); } 57 set { SetValue(ColumnsProperty, value); } 58 } 59 60 public static readonly DependencyProperty ColumnsProperty = 61 DependencyProperty.Register("Columns", typeof(int), typeof(FixedColumnGrid), new FrameworkPropertyMetadata(default(int),FrameworkPropertyMetadataOptions.AffectsArrange)); 62 63 64 65 public int ColumnHeight 66 { 67 get { return (int)GetValue(ColumnHeightProperty); } 68 set { SetValue(ColumnHeightProperty, value); } 69 } 70 71 // Using a DependencyProperty as the backing store for ColumnHeight. This enables animation, styling, binding, etc... 72 public static readonly DependencyProperty ColumnHeightProperty = 73 DependencyProperty.Register("ColumnHeight", typeof(int), typeof(FixedColumnGrid), new FrameworkPropertyMetadata(50,FrameworkPropertyMetadataOptions.AffectsMeasure)); 74 75 76 }