傳統的windows程式簡直都有同樣標準的排版。一個程式的選單就幾乎都在主畫面的上方!該選單可以說是dock在上面區域,假如程式有工具列,也會在上方,也會相當擠!而若還有狀態列,就會在下方!
類似檔案總管的程式會在左邊的控制項中顯示樹狀目錄,但是選單和工作列以及狀態列就會各字有自己的優先權,因為他們大多繼承視窗的寬度!
WPF 包含了一個DockPanel類別來完成你想要dock的東西,你可以如下建立:
DockPanel dock=new DockPanel();
假如你在window物件中建立一個dockpanel,那你可以這樣寫:
Content =dock;
這樣是很常見的,且當然可以在dockpanel中再加上其他panel! 可以如下加上一個control:
dock.children.Add(ctrl);
但是現在若你要指定ctrl要在視窗中的哪裡dock就會有點不一樣,若要放在右邊:
DockPanel.SetDock(ctrl,Dock.Right);
不要對這句話錯誤解讀:這並不是說與你命名為dock的物件有關,這是DockPanel的靜態方法,這兩個參數是你想要dock的物件,以及Dock的形式,可以是 Dock.Left,Dock.Top,Dock.Right,Dock.Buttom
這和順序無關,你可以在還沒有建立DockPanel物件時就可以先指定dock的形式。
SetDock 的呼叫還會有個附屬屬性(attached property)的使用!這會在第八章提出!現在你可以先想辦法瞭解為何以下的程式相當於呼叫setDock
ctrl.SetValue(DockPanel.DockProperty, Dock.Right);
SetValue是由DependencyObject所定義,且DockPanel.DockProperty是一個唯讀的欄位,這就是一種attached 屬性,且這個屬性和他的設定(Dock.Right)會被控制項存放著!當執行版面時,Dockpanel物件可以藉由呼叫下面而取得control的Dock設定
Dock dck=DockPanel.GetDock(ctrl);
也相當於
Dock dck =(Dock)ctrl.getValue(DockPanel.DockProperty);
以下的程式會建立一個DockPanel和17個按鈕。
//---------------------------------------------------
// DockAroundTheBlock.cs (c) 2006 by Charles Petzold
//---------------------------------------------------
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace Petzold.DockAroundTheBlock
{
class DockAroundTheBlock : Window
{
[STAThread]
public static void Main()
{
Application app = new Application();
app.Run(new DockAroundTheBlock());
}
public DockAroundTheBlock()
{
Title = "Dock Around the Block";
DockPanel dock = new DockPanel();
Content = dock;
for (int i = 0; i < 17; i++)
{
Button btn = new Button();
btn.Content = "Button No. " + (i + 1);
dock.Children.Add(btn);
btn.SetValue(DockPanel.DockProperty, (Dock)(i % 4));
}
}
}
}

在這個程式中,btn.SetValue(DockPanel.DockProperty, (Dock)(i % 4)); 巧妙的設計把按鈕動態排版,並且按照順序左上右下!你可以看到那是如何容易地放上去!而第17個按鈕並沒有dock,且佔滿了剩下個空間!而這個功效是來自於設定DockPanel的LashChildFill 屬性為true所致且是default!而雖然程式有為最後一個按鈕設定排版方式但是會被忽略!
可以嘗試設定
dock.LastChildFill = false;

就可以看出這不同!
所以你知道DockPanel會由外往內排列!
DockPanel的Children 一般都會延伸寬度或是高度和panel一樣,因為這些children的HorizontalAlignment和VerticalAlignment都會預設是stretch!
改成如下會有啥改變:
btn.HorizontalAlignment = HorizontalAlignment.Center;

可以看到在上面的按鈕就會縮小到自己內容的大小,很明顯這並非一般user可以接受,而設定一個dock的控制項的margin屬性也是一樣!
下面程式會建立一個比較傳統的,會建立menu,toolnar,statusbar,listbox和textbox控制項,但是因為這些控制項會在之後的一些章節討論,所以就大概看一下就好,因為textbox是最後一個,所以他會填滿其他控制項用不到的地方!
//-----------------------------------------------
// MeetTheDockers.cs (c) 2006 by Charles Petzold
//-----------------------------------------------
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
namespace Petzold.MeetTheDockers
{
public class MeetTheDockers : Window
{
[STAThread]
public static void Main()
{
Application app = new Application();
app.Run(new MeetTheDockers());
}
public MeetTheDockers()
{
Title = "Meet the Dockers";
DockPanel dock = new DockPanel();
Content = dock;
// Create menu.
Menu menu = new Menu();
MenuItem item = new MenuItem();
item.Header = "Menu";
menu.Items.Add(item);
// Dock menu at top of panel.
DockPanel.SetDock(menu, Dock.Top);
dock.Children.Add(menu);
// Create tool bar.
ToolBar tool = new ToolBar();
tool.Header = "Toolbar";
// Dock tool bar at top of panel.
DockPanel.SetDock(tool, Dock.Top);
dock.Children.Add(tool);
// Create status bar.
StatusBar status = new StatusBar();
StatusBarItem statitem = new StatusBarItem();
statitem.Content = "Status";
status.Items.Add(statitem);
// Dock status bar at bottom of panel.
DockPanel.SetDock(status, Dock.Bottom);
dock.Children.Add(status);
// Create list box.
ListBox lstbox = new ListBox();
lstbox.Items.Add("List Box Item");
// Dock list box at left of panel.
DockPanel.SetDock(lstbox, Dock.Left);
dock.Children.Add(lstbox);
// Create text box.
TextBox txtbox = new TextBox();
txtbox.AcceptsReturn = true;
// Add text box to panel & give it input focus.
dock.Children.Add(txtbox);
txtbox.Focus();
}
}
}

很遺憾這些程式並沒有完成,但是是因為這些控制項也沒做啥,但是你應該也知道皆下來該怎麼做,而你看到listbox和textbox之間應該要有個分隔的東西!
一個間格器(splitter)可以用Grid panel來實做,但是這並非是使用Grid的理由!Grid可以行列的方式來規劃children的集合,他很像前面排30個按鈕的範例,然而通常當你在行列中排列控制項時你會想要他們排得很整齊!之前的範例是排不大整齊的!
可以用下建立一個grid!
Grid grid =new Grid();
你也可以顯示那些格線:
Grid.ShowGridLines=true;
加上去之後會在行列間看到格線,這並沒有方法來改變他的style或是顏色寬度!假如你想要更多控制項在行列間可以被適當大小的列印,最好是使用Table而不是Grid,System.Windows.Documents.Table很像你在HTML中的表格。Grid對於排版比較嚴謹!
你必須要告訴Grid有多少行列,並且這些行列的大小,每列的高度可以用下面的表示:
- 固定的與裝置無關的單位
- 根據每列最高
- 根據剩下的空間(也許是在其他列的分配後所得到的剩餘空間)
每一行的寬度也是很類似,可以有這三種選項,這需三種選項都有對應到三種的GridUnitType集合!Pixel / Auto / Sar ,star這個名詞是來自於HTML在表格中所代表分配所剩下的空間語法會給個*號!
你可以用GridLength來指定每列的高度和每行的寬度,兩個建構子可以輸入,你可以指定:new GridLength(100);
或是 new GridLength(100,GridUnitType.Pixel)
或是 new GridLength(100,GridUnitType.Star) -->只剩下空間,前面數字代表是要和其他也有設定star 的做結合!
你也可以指定Auto
new GridLength(0,GridUnitType.Auto)
然而在這種情形,數字會被忽略,所以你也可以用他的靜態欄位來設定:
GridLength.Auto 也是會回傳GridLength物件!
Grid有兩個屬性叫做RowDefinitions和ColumnDefinitions 這些屬性是RowDefinitionCollection和ColumnDefinitionsCollection集合的型態
你可以為每個列建立RowDefinition,最重要的是Height屬性,也就是你設定的GridLength物件!他也有最大和最小高度的屬性設定,這是選擇性的!而ColumnDefinition比較重要的屬性當然是Width,也有最大最小值,也都是你設定的GridLength物件!
但是不幸地~這些設定RowDefinitions和ColumnDefinitions 這些屬性是相當冗長的!這些程式如下:
RowDefinition ROWDEF = new RowDefinition();
ROWDEF.Height = GridLength.Auto;
grid.RowDefinitions.Add(ROWDEF);
ROWDEF = new RowDefinition();
ROWDEF.Height = new GridLength(33, GridUnitType.Star);
grid.RowDefinitions.Add(ROWDEF);
ROWDEF = new RowDefinition();
ROWDEF.Height = new GridLength(150);
grid.RowDefinitions.Add(ROWDEF);
ROWDEF = new RowDefinition();
ROWDEF.Height = new GridLength(67, GridUnitType.Star);
grid.RowDefinitions.Add(ROWDEF);
這上面分別設定了四列!
依據Grid本身的設定,還是有可能會有空白處,這些值並不需要指定成百分比,Grid會把所有*的加起來然後平均計算!
Column也是類似的設定,除了需要改成指定Width之外!若幸運的話也可放入for-loop中設定這些寬!
GridunitType.star的height和width屬性預設值都是1,所以當你想要平均的切割在行列間的空白時,你可以使用預設值就好,但是你仍然需要加入RowDefinition/ColumnDefinitions 物件到每個行列的集合中!程式可以簡化如下:
Grid.RowDefinitions.Add(new RowDefinition());
假如你只需要一行的Grid,你不用去定義ColumnDefinition,也不需要為了一列的Grid去定義RowDefinition,甚至若是只有一個格子的grid也不需要設定,
若你要加入其他元件,可以和其他panel一樣,grid.children.add(ctrl);
你也可以像下面定義一個grid:
Grid.SetRow(ctrl,2);
Grid.SetColumn(ctrl,5);
這是三個列和六個欄的表格!這也是會呼叫使用到attached 屬性,預設值都是0,大多數的物件都可以在一般cell裡面存放,但你應該不會想這樣,假如你沒有看到你丟入grid中的任何東西,可能是因為他和其他一些東西放在同一個格子中。
元件的HorizontalAlignment和VeriticalAlignment屬性都影響他在格子中的位置!一般而言,假如該格子中並沒有和元件一樣大小的話,該元件會改變大小和該格子一樣!
下面會建立一個grid,視窗會把大小配合Grid,且也沒有改變大小的按鈕,有三列兩行,都用GridLength.Auto,這程式會計算兩個日期的差距!
而一開始我寫了一個win32的程式,讓有需要的朋友使用,我可以使用DATETIMEPICK_CLASS,但是不幸地,在WPF的一開始版本並沒有相同的控制項,所以這個程式就只能用DateTime.TryParse方法來轉換字串成為日期!

//--------------------------------------------------
// CalculateYourLife.cs (c) 2006 by Charles Petzold
//--------------------------------------------------
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace Petzold.CalculateYourLife
{
class CalculateYourLife : Window
{
TextBox txtboxBegin, txtboxEnd;
Label lblLifeYears;
[STAThread]
public static void Main()
{
Application app = new Application();
app.Run(new CalculateYourLife());
}
public CalculateYourLife()
{
Title = "Calculate Your Life";
SizeToContent = SizeToContent.WidthAndHeight;
ResizeMode = ResizeMode.CanMinimize;
// Create the grid.
Grid grid = new Grid();
Content = grid;
// Row and column definitions.
for (int i = 0; i < 3; i++)
{
RowDefinition rowdef = new RowDefinition();
rowdef.Height = GridLength.Auto;
grid.RowDefinitions.Add(rowdef);
}
for (int i = 0; i < 2; i++)
{
ColumnDefinition coldef = new ColumnDefinition();
coldef.Width = GridLength.Auto;
grid.ColumnDefinitions.Add(coldef);
}
// First label.
Label lbl = new Label();
lbl.Content = "Begin Date: ";
grid.Children.Add(lbl);
Grid.SetRow(lbl, 0);
Grid.SetColumn(lbl, 0);
// First TextBox.
txtboxBegin = new TextBox();
txtboxBegin.Text = new DateTime(1980, 1, 1).ToShortDateString();
txtboxBegin.TextChanged += TextBoxOnTextChanged;
grid.Children.Add(txtboxBegin);
Grid.SetRow(txtboxBegin, 0);
Grid.SetColumn(txtboxBegin, 1);
// Second label.
lbl = new Label();
lbl.Content = "End Date: ";
grid.Children.Add(lbl);
Grid.SetRow(lbl, 1);
Grid.SetColumn(lbl, 0);
// Second TextBox.
txtboxEnd = new TextBox();
txtboxEnd.TextChanged += TextBoxOnTextChanged;
grid.Children.Add(txtboxEnd);
Grid.SetRow(txtboxEnd, 1);
Grid.SetColumn(txtboxEnd, 1);
// Third label.
lbl = new Label();
lbl.Content = "Life Years: ";
grid.Children.Add(lbl);
Grid.SetRow(lbl, 2);
Grid.SetColumn(lbl, 0);
// Label for calculated result.
lblLifeYears = new Label();
grid.Children.Add(lblLifeYears);
Grid.SetRow(lblLifeYears, 2);
Grid.SetColumn(lblLifeYears, 1);
// Set margin for everybody.
Thickness thick = new Thickness(5); // ~1/20 inch.
grid.Margin = thick;
foreach (Control ctrl in grid.Children)
ctrl.Margin = thick;
// Set focus and trigger the event handler.
txtboxBegin.Focus();
txtboxEnd.Text = DateTime.Now.ToShortDateString();
}
void TextBoxOnTextChanged(object sender, TextChangedEventArgs args)
{
DateTime dtBeg, dtEnd;
if (DateTime.TryParse(txtboxBegin.Text, out dtBeg) &&
DateTime.TryParse(txtboxEnd.Text, out dtEnd))
{
int iYears = dtEnd.Year - dtBeg.Year;
int iMonths = dtEnd.Month - dtBeg.Month;
int iDays = dtEnd.Day - dtBeg.Day;
if (iDays < 0)
{
iDays += DateTime.DaysInMonth(dtEnd.Year,
1 + (dtEnd.Month + 10) % 12);
iMonths -= 1;
}
if (iMonths < 0)
{
iMonths += 12;
iYears -= 1;
}
lblLifeYears.Content =
String.Format("{0} year{1}, {2} month{3}, {4} day{5}",
iYears, iYears == 1 ? "" : "s",
iMonths, iMonths == 1 ? "" : "s",
iDays, iDays == 1 ? "" : "s");
}
else
{
lblLifeYears.Content = "";
}
}
}
}
這個程式有為兩個textbox控制項註冊了textchange事件,會在文字有改變的時後計算,轉換成日期後會計算兩者差距,正常的話假如你兩個日期相減會得到一個TimeSpan的物件,但是那並非相當合適!...(以下是計算日期的寫法,忽略之)
讓我們來看一下另外一種使用Grid的範例,這是一種可能在輸入表單時看到的格式:

//---------------------------------------------
// EnterTheGrid.cs (c) 2006 by Charles Petzold
//---------------------------------------------
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace Petzold.EnterTheGrid
{
public class EnterTheGrid : Window
{
[STAThread]
public static void Main()
{
Application app = new Application();
app.Run(new EnterTheGrid());
}
public EnterTheGrid()
{
Title = "Enter the Grid";
MinWidth = 300;
SizeToContent = SizeToContent.WidthAndHeight;
// Create StackPanel for window content.
StackPanel stack = new StackPanel();
Content = stack;
// Create Grid and add to StackPanel.
Grid grid1 = new Grid();
grid1.Margin = new Thickness(5);
stack.Children.Add(grid1);
// Set row definitions.
for (int i = 0; i < 5; i++)
{
RowDefinition rowdef = new RowDefinition();
rowdef.Height = GridLength.Auto;
grid1.RowDefinitions.Add(rowdef);
}
// Set column definitions.
ColumnDefinition coldef = new ColumnDefinition();
coldef.Width = GridLength.Auto;
grid1.ColumnDefinitions.Add(coldef);
coldef = new ColumnDefinition();
coldef.Width = new GridLength(100, GridUnitType.Star);
grid1.ColumnDefinitions.Add(coldef);
// Create labels and text boxes.
string[] strLabels = { "_First name:", "_Last name:",
"_Social security number:",
"_Credit card number:",
"_Other personal stuff:" };
for(int i = 0; i < strLabels.Length; i++)
{
Label lbl = new Label();
lbl.Content = strLabels[i];
lbl.VerticalContentAlignment = VerticalAlignment.Center;
grid1.Children.Add(lbl);
Grid.SetRow(lbl, i);
Grid.SetColumn(lbl, 0);
TextBox txtbox = new TextBox();
txtbox.Margin = new Thickness(5);
grid1.Children.Add(txtbox);
Grid.SetRow(txtbox, i);
Grid.SetColumn(txtbox, 1);
}
// Create second Grid and add to StackPanel.
Grid grid2 = new Grid();
grid2.Margin = new Thickness(10);
stack.Children.Add(grid2);
// No row definitions needed for single row.
// Default column definitions are "star".
grid2.ColumnDefinitions.Add(new ColumnDefinition());
grid2.ColumnDefinitions.Add(new ColumnDefinition());
// Create buttons.
Button btn = new Button();
btn.Content = "Submit";
btn.HorizontalAlignment = HorizontalAlignment.Center;
btn.IsDefault = true;
btn.Click += delegate { Close(); };
grid2.Children.Add(btn); // Row & column are 0.
btn = new Button();
btn.Content = "Cancel";
btn.HorizontalAlignment = HorizontalAlignment.Center;
btn.IsCancel = true;
btn.Click += delegate { Close(); };
grid2.Children.Add(btn);
Grid.SetColumn(btn, 1); // Row is 0.
// Set focus to first text box.
(stack.Children[0] as Panel).Children[1].Focus();
}
}
}
這程式是依賴元件的預設行為來填滿視窗,StackPanel 佔滿了視窗版面,就和兩個grid一樣,在上面的grid panel第一個欄(label)使用GridLength.Auto,而第二欄(textbox)使用 GridLength.Star,這也就是說當你把視窗變大,textbox也會跟著變大,當你輸入的字便多也會變寬,而前五個列的高度都是看textbox的控制項,而為了要看起來有對齊,所以都是設定按鈕和標籤的HorizontalAlignment = HorizontalAlignment.Center;
而在Grid中也可以讓某元件跨欄,可以使用靜態方法:Grid.SetRowSpan和Grid.SetColumnSpan!下面是一些範例:
maingrid.Children.add(ctrl);
Grid.SetRow(ctrl,3);
Grid.SetRowSpan(ctrl,2);
Grid.SetColumn(ctrl,5);
Grid.SetColumnSpan(ctrl,3);
這樣的話,這個ctrl會佔據了六個cell,會從第3.4列,而欄數是5,6,7
下面的程式也大概的介紹使用grid來排版,有6列四欄!前五列包含了label和textbox,最後一列包含了按鈕,第一欄用來顯示label,靠右邊的兩欄顯示按鈕,textbox佔據了第2.3.4欄!
//---------------------------------------------
// SpanTheCells.cs (c) 2006 by Charles Petzold
//---------------------------------------------
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace Petzold.SpanTheCells
{
public class SpanTheCells : Window
{
[STAThread]
public static void Main()
{
Application app = new Application();
app.Run(new SpanTheCells());
}
public SpanTheCells()
{
Title = "Span the Cells";
SizeToContent = SizeToContent.WidthAndHeight;
// Create Grid.
Grid grid = new Grid();
grid.Margin = new Thickness(5);
Content = grid;
// Set row definitions.
for (int i = 0; i < 6; i++)
{
RowDefinition rowdef = new RowDefinition();
rowdef.Height = GridLength.Auto;
grid.RowDefinitions.Add(rowdef);
}
// Set column definitions.
for (int i = 0; i < 4; i++)
{
ColumnDefinition coldef = new ColumnDefinition();
if (i == 1)
coldef.Width = new GridLength(100, GridUnitType.Star);
else
coldef.Width = GridLength.Auto;
grid.ColumnDefinitions.Add(coldef);
}
// Create labels and text boxes.
string[] astrLabel = { "_First name:", "_Last name:",
"_Social security number:",
"_Credit card number:",
"_Other personal stuff:" };
for(int i = 0; i < astrLabel.Length; i++)
{
Label lbl = new Label();
lbl.Content = astrLabel[i];
lbl.VerticalContentAlignment = VerticalAlignment.Center;
grid.Children.Add(lbl);
Grid.SetRow(lbl, i);
Grid.SetColumn(lbl, 0);
TextBox txtbox = new TextBox();
txtbox.Margin = new Thickness(5);
grid.Children.Add(txtbox);
Grid.SetRow(txtbox, i);
Grid.SetColumn(txtbox, 1);
Grid.SetColumnSpan(txtbox, 3);
}
// Create buttons.
Button btn = new Button();
btn.Content = "Submit";
btn.Margin = new Thickness(5);
btn.IsDefault = true;
btn.Click += delegate { Close(); };
grid.Children.Add(btn);
Grid.SetRow(btn, 5);
Grid.SetColumn(btn, 2);
btn = new Button();
btn.Content = "Cancel";
btn.Margin = new Thickness(5);
btn.IsCancel = true;
btn.Click += delegate { Close(); };
grid.Children.Add(btn);
Grid.SetRow(btn, 5);
Grid.SetColumn(btn, 3);
// Set focus to first text box.
grid.Children[1].Focus();
}
}
}

注意到這個程式設定六列的高為GridLength.Auto,有四欄除了第二欄外其寬度都是Auto,而第二欄是設定為 GridUnitType.Star
當你把視窗變大,textbox也會變寬,其他的不會,因為只有他設定為 GridUnitType.Star

這兩個按鈕會保持他的位置在右邊,且這視窗也在使用者輸入文字過長時自動變大。
Grid panel也有讓你可以有垂直或是水平的splitter,也就是會有一條bar讓使用者可以移動調整兩個區域的空間。
GridSplitter 類別經由Thumb類別而繼承自Control!他必須是Grid panel的一個children,程式會指定GridSplitter行列的位置。
但是有一個奇特的地方:GridSplitter可以在一個cell裡面和其他元件共享,也有可能遮蓋到該cell上的元件或是該cell上的元件會覆蓋到GridSplitter,若你要避免這種問題,就需要設定元件的margin,或是把GridSplitter加到該cell的元件之後,讓他可以出現在前景中!
預設值,GridSplitter 會有HorizontalAlignment為Right和VeriticalAlignment為Stretch,會讓垂直的splitter出現在cell的右邊! 預設的寬度是0,所以可以指定其一個寬度,你可以移動該splitter!在下面這個範例中你可以看到在第二列的右邊有一個splitter,
可以移動,來改變排版!

//--------------------------------------------------
// SplitNine.cs (c) 2006 by Charles Petzold
//--------------------------------------------------
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace Petzold.SplitNine
{
public class SplitNine : Window
{
[STAThread]
public static void Main()
{
Application app = new Application();
app.Run(new SplitNine());
}
public SplitNine()
{
Title = "Split Nine";
Grid grid = new Grid();
Content = grid;
// Set row and column definitions.
for (int i = 0; i < 3; i++)
{
grid.ColumnDefinitions.Add(new ColumnDefinition());
grid.RowDefinitions.Add(new RowDefinition());
}
// Create 9 buttons.
for (int x = 0; x < 3; x++)
for (int y = 0; y < 3; y++)
{
Button btn = new Button();
btn.Content = "Row " + y + " and Column " + x;
grid.Children.Add(btn);
Grid.SetRow(btn, y);
Grid.SetColumn(btn, x);
}
// Create splitter.
GridSplitter split = new GridSplitter();
split.Width = 6;
grid.Children.Add(split);
Grid.SetRow(split, 1);
Grid.SetColumn(split, 1);
}
}
}
若是改成以下~你會更覺得有感覺:
Grid.SetRow(split, 0);
Grid.SetColumn(split, 1);
Grid.SetRowSpan(split, 3);

圖中的splitter變長了~可以移動控制寬度!
我們改回控制一個cell,GridSplitter其最重要的屬性就是HorizontalAlignment和VerticalAlignment,這些屬性會控制splitter出現在cell中是水平或是垂直!且會有哪些行列被影響!在預設值時,GridSplitter會出現在cell的右邊,移動splitter會改變splitter左右欄寬度的分配!
你可以改變如下:
split.HorizontalAlignment = HorizontalAlignment.Center;


可以看到splitter就會變到中間,且當你改變splitter的位置時,就會直接改變鄰近cell的,不會改變本cell的寬度!這種用法應該是比較少見的,但確實他也是GridSplitter的功能之一,因為若是中間沒有按鈕,寬度應該就會正常,看起來就不會這麼怪!
你已經知道splitter如何改變他噁寬度,你可以使用GridSplitter的ResizeBehavior屬性來覆寫他的行為!~你可以設定選擇GridResizeBehavior集合!這些成員會參考到哪些欄位被splitter影響,這些成員有CurrentAndNext(通常用在splitter是靠右),PreviousAndCurrent(通常用在splitter是靠左),以及PreviousAndBext (通常用在splitter是在置中)還有預設值的BasedOnAlignment(沒有任何對於splitter的靠的位置有關去做覆寫)
GridSplitter split = new GridSplitter();
split.HorizontalAlignment = HorizontalAlignment.Stretch;
split.VerticalAlignment = VerticalAlignment.Top;
split.Height = 6;
grid.Children.Add(split);
//Grid.SetRow(split, 1);
Grid.SetRow(split, 0);
Grid.SetColumn(split, 0);
Grid.SetColumnSpan(split, 3);

所以假如VeriticalAlignment =Stretch 則splitter為 垂直,影響寬度
若是HorizonalAlignment=Stretch則splitter為水平!影響高度!
你也可以用ResizeDirection屬性來改變splitter的外觀,你可以設定GridResizeDirection的成員:Column,Rows,Auto!
例如:split.ResizeDirection = GridResizeDirection.Columns;
可以看到雖然splitter是水平的,但是卻無法往上下拉~你只能改變他的寬度!

GridSplitter是相當的多才多藝!你可以設定HorizontalAlignment和VeriticalAlignment成為Stretch來讓sokitter填滿整個cell。
split.HorizontalAlignment = HorizontalAlignment.Stretch;
split.VerticalAlignment = VerticalAlignment.Stretch;

或是你也不要使用HorizontalAlignment / VerticalAlignment =stretch,讓splitter依照width或是height來便成一個盒子!
我的建議相當簡單:不要把GidSplitter放到一個cell中且和一個元件共用,並且切開一個Grid專門給切割的功能,假如你想要有一個垂直的splitter,可以建立一個Grid有三個欄為,把中間的欄位的寬度使用GridLength.Auto,並且把splitter放到這個欄位中,假如你想要有一個水平的splitter,可以建立一個三個列的Grid,讓中間列的高度=GridLength.Auto,並且把splitter放到這個欄位中。這些案例中,你都可以把你想要的放在另外兩個欄位中,包含其他的Grid panel!
//-----------------------------------------------
// SplitTheClient.cs (c) 2006 by Charles Petzold
//-----------------------------------------------
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
class SplitTheClient : Window
{
[STAThread]
public static void Main()
{
Application app = new Application();
app.Run(new SplitTheClient());
}
public SplitTheClient()
{
Title = "Split the Client";
// Grid with vertical splitter.
Grid grid1 = new Grid();
grid1.ColumnDefinitions.Add(new ColumnDefinition());
grid1.ColumnDefinitions.Add(new ColumnDefinition());
grid1.ColumnDefinitions.Add(new ColumnDefinition());
grid1.ColumnDefinitions[1].Width = GridLength.Auto;
Content = grid1;
// Button at left of vertical splitter.
Button btn = new Button();
btn.Content = "Button No. 1";
grid1.Children.Add(btn);
Grid.SetRow(btn, 0);
Grid.SetColumn(btn, 0);
// Vertical splitter.
GridSplitter split = new GridSplitter();
split.ShowsPreview = true;
split.HorizontalAlignment = HorizontalAlignment.Center;
split.VerticalAlignment = VerticalAlignment.Stretch;
split.Width = 6;
grid1.Children.Add(split);
Grid.SetRow(split, 0);
Grid.SetColumn(split, 1);
// Grid with horizontal splitter.
Grid grid2 = new Grid();
grid2.RowDefinitions.Add(new RowDefinition());
grid2.RowDefinitions.Add(new RowDefinition());
grid2.RowDefinitions.Add(new RowDefinition());
grid2.RowDefinitions[1].Height = GridLength.Auto;
grid1.Children.Add(grid2);
Grid.SetRow(grid2, 0);
Grid.SetColumn(grid2, 2);
// Button at top of horizontal splitter.
btn = new Button();
btn.Content = "Button No. 2";
grid2.Children.Add(btn);
Grid.SetRow(btn, 0);
Grid.SetColumn(btn, 0);
// Horizontal splitter.
split = new GridSplitter();
split.ShowsPreview = true;
split.HorizontalAlignment = HorizontalAlignment.Stretch;
split.VerticalAlignment = VerticalAlignment.Center;
split.Height = 6;
grid2.Children.Add(split);
Grid.SetRow(split, 1);
Grid.SetColumn(split, 0);
// Bottom at bottom of horizontal splitter.
btn = new Button();
btn.Content = "Button No. 3";
grid2.Children.Add(btn);
Grid.SetRow(btn, 2);
Grid.SetColumn(btn, 0);
}
}



這個程式會設定GridSplitter的ShowsPreview屬性為true,當你移到splitter,該cell不會改變尺寸,直到你放開滑鼠,注意你可以使用tab鍵來讓不同的splitter得到focus,並且可以用上下左右鍵來移動!若是這些反應不如預期,你可以檢查一下 KeyboradIncrement 屬性!
你可能已經注意到,當你改變這些視窗的大小,兩邊的內容都會一起改變大小,但有時候你想要再改變視窗大小時,內容可以不要更著改大小,類似檔案總管!
下面程式介紹這些技術和一些其他的!我用windows 1.0寫了第一版,大概是在Microsoft Systems Journal 1987 5月提出!在印象中,這程式是在自動化排版的首創研究,原始程式有六個label和3個scrollbar,讓你可以選擇紅藍綠的程度,當你把視窗變大或變小,程式就會改變,且重新定位label和scrollbar!會用很多計算,也長的很醜!
WPF Scrollbar經由RangeBase繼承Control(RangeBase是一個抽象類別,也是ProgressBar和Slider的父類別)。Scrollbar可以依照Orientation屬性設定垂直或是水平放置,而Value屬性可以在Minimum屬性以及Maximum屬性範圍間跳動,點選scrollbar的箭頭可以改變Value屬性,且可由SmallChange的值來設定最小跳動值!若要設定一次跳動的最大值,可以設定LargoChange!這些都是double的值!
Scrollbar類別會從Scrollbar繼承ValueChanged事件,並且會定義一個Scroll事件!Scroll事件
會傳送ScrollEventType列舉中的成員中的一些資訊,讓你可以知道使用者正在操縱scrollbar的哪一個部分!舉例來說,若你的程式並不能知曉大移動,他就有可能會忽略所有ScrollEventType型態的事件,ThumbTrack就會跳到最後面,得到ScrollbarEventType.EndScroll
下面的程式會先建立一個Grid panel,只為了要去做一個垂直的splitter,第一個cell會包含一個grid,包含了六個標籤和三個scrollbar,中間的cell包含了GridSplitter,最後一個cell包含一個StackPanel,用來顯示背景色!
//---------------------------------------------------
// ScrollCustomColors.cs (c) 2006 by Charles Petzold
//---------------------------------------------------
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
class ColorScroll : Window
{
ScrollBar[] scrolls = new ScrollBar[3];
TextBlock[] txtValue = new TextBlock[3];
Panel pnlColor;
[STAThread]
public static void Main()
{
Application app = new Application();
app.Run(new ColorScroll());
}
public ColorScroll()
{
Title = "Color Scroll";
Width = 500;
Height = 300;
// GridMain contains a vertical splitter.
Grid gridMain = new Grid();
Content = gridMain;
// GridMain column definitions.
ColumnDefinition coldef = new ColumnDefinition();
coldef.Width = new GridLength(200, GridUnitType.Pixel);
gridMain.ColumnDefinitions.Add(coldef);
coldef = new ColumnDefinition();
coldef.Width = GridLength.Auto;
gridMain.ColumnDefinitions.Add(coldef);
coldef = new ColumnDefinition();
coldef.Width = new GridLength(100, GridUnitType.Star);
gridMain.ColumnDefinitions.Add(coldef);
// Vertical splitter.
GridSplitter split = new GridSplitter();
split.HorizontalAlignment = HorizontalAlignment.Center;
split.VerticalAlignment = VerticalAlignment.Stretch;
split.Width = 6;
gridMain.Children.Add(split);
Grid.SetRow(split, 0);
Grid.SetColumn(split, 1);
// Panel on right side of splitter to display color
pnlColor = new StackPanel();
pnlColor.Background = new SolidColorBrush(SystemColors.WindowColor);
gridMain.Children.Add(pnlColor);
Grid.SetRow(pnlColor, 0);
Grid.SetColumn(pnlColor, 2);
// Secondary grid at left of splitter
Grid grid = new Grid();
gridMain.Children.Add(grid);
Grid.SetRow(grid, 0);
Grid.SetColumn(grid, 0);
// Three rows for label, scroll, and label.
RowDefinition rowdef = new RowDefinition();
rowdef.Height = GridLength.Auto;
grid.RowDefinitions.Add(rowdef);
rowdef = new RowDefinition();
rowdef.Height = new GridLength(100, GridUnitType.Star);
grid.RowDefinitions.Add(rowdef);
rowdef = new RowDefinition();
rowdef.Height = GridLength.Auto;
grid.RowDefinitions.Add(rowdef);
// Three columns for Red, Green, and Blue.
for (int i = 0; i < 3; i++)
{
coldef = new ColumnDefinition();
coldef.Width = new GridLength(33, GridUnitType.Star);
grid.ColumnDefinitions.Add(coldef);
}
for (int i = 0; i < 3; i++)
{
Label lbl = new Label();
lbl.Content = new string[] { "Red", "Green", "Blue" }[i];
lbl.HorizontalAlignment = HorizontalAlignment.Center;
grid.Children.Add(lbl);
Grid.SetRow(lbl, 0);
Grid.SetColumn(lbl, i);
scrolls[i] = new ScrollBar();
scrolls[i].Focusable = true;
scrolls[i].Orientation = Orientation.Vertical;
scrolls[i].Minimum = 0;
scrolls[i].Maximum = 255;
scrolls[i].SmallChange = 1;
scrolls[i].LargeChange = 16;
scrolls[i].ValueChanged += ScrollOnValueChanged;
grid.Children.Add(scrolls[i]);
Grid.SetRow(scrolls[i], 1);
Grid.SetColumn(scrolls[i], i);
txtValue[i] = new TextBlock();
txtValue[i].TextAlignment = TextAlignment.Center;
txtValue[i].HorizontalAlignment = HorizontalAlignment.Center;
txtValue[i].Margin = new Thickness(5);
grid.Children.Add(txtValue[i]);
Grid.SetRow(txtValue[i], 2);
Grid.SetColumn(txtValue[i], i);
}
// Initialize scroll bars.
Color clr = (pnlColor.Background as SolidColorBrush).Color;
scrolls[0].Value = clr.R;
scrolls[1].Value = clr.G;
scrolls[2].Value = clr.B;
// Set initial focus.
scrolls[0].Focus();
}
void ScrollOnValueChanged(object sender, RoutedEventArgs args)
{
ScrollBar scroll = sender as ScrollBar;
Panel pnl = scroll.Parent as Panel;
TextBlock txt = pnl.Children[1 +
pnl.Children.IndexOf(scroll)] as TextBlock;
txt.Text = String.Format("{0}n0x{0:X2}", (int)scroll.Value);
pnlColor.Background =
new SolidColorBrush(
Color.FromRgb((byte) scrolls[0].Value,
(byte) scrolls[1].Value,(byte) scrolls[2].Value));
}
}

ValueChanged event handler會隨著Scrollbar改變值而更新TextBlock,且會重新計算新的Color!
和Grid很類似的是UniformGrid,其列欄的高度都一樣,且也沒有RowDefinition和ColumnDefinition物件,但你可以設定其Rows/Columns屬性來設定列數和欄數,且他也沒有attached屬性,當你加入一個成員,他會順序性地佔據第一列的cell,接下來是第二列…
Rows或是Columns都可以是0,且UniformGrid可以針對children的數量給予比較適當的值!
你可在下一張看到我在實做著名的 14-15 puzzle 時看到Uniform的範例,還有Jeu de Tacquin!

