從ContainerControl類繼承的子類作爲容器窗體,可以容納除Form類對象外的其餘窗體對象。
在所有容器窗體內,最基本的就是頂級容器Form類以及面板容器Panel類。這兩者的主要區別爲:前者具有Windows標準框架(標題欄,最大化、最小化和關閉按鈕,窗體邊框,可調整尺寸),並且可以獨立存在;後者只是一塊區域,並且必須依附在某個容器窗體上,無法獨立存在。除了它們的區別外,他們都具有:Controls屬性,可以在上面放置控件;控件放置的位置需要通過控件的Top和Left屬性或者控制。
1 絕對佈局
對於容器類型的控件(包括Form類型),出了Size,Bounds屬性外,還有ClientSize和ClientRectangle屬性,前者表示客戶區尺寸,Size類型;後者表示客戶區的矩形,是一個Rectangle類型。所謂客戶區,就是容器實際可以使用的空間,對於Form類型來說,客戶區就是除過標題欄,四周的邊框外剩餘的部分。
一般來說,直接在Form上或Panel上放置控件,控件的位置不會自動調整,完全依賴控件的Left, Top和Location等屬性控制,控件的大小也不會自動改變,完全依靠控件的Width, Height和Size等屬性控制。這種方式稱爲絕對位置佈局。
我們再來熟悉一下這些用於定位一個控件的屬性們,它們可以用於獲取或設置控件的位置和尺寸:
- Left屬性:控件距離其容器左邊界的距離,int類型;
- Top屬性:控件距離其容器上邊界的距離,int類型;
- Location屬性:控件左上角座標距離其容器的距離,Point類型。一般而言,可以把容器的左上角認爲是座標軸原點,則Location屬性表示控件相對於其容器的座標。所以其X屬性等於控件的Left屬性,Y屬性等於控件的Top屬性;
- Width屬性:控件的寬度,int類型;
- Height屬性:控件的高度,int類型;
- Size屬性:控件的尺寸,包括寬度和高度,Size類型;
除了以上六個屬性外,還可以使用Bounds屬性,這是一個Rectangle類型的屬性,表示一個相對於容器左上角爲座標原點的矩形,即控件的位置和尺寸。利用SetBounds方法還可以使用X, Y, Width, Height四個分量設置Bounds屬性。
2 錨定相對佈局
如果進一步設置控件的Dock屬性,則可以設定控件再容器內的相對位置,Dock屬性可以設置控件按照其所在容器的“左右上下中”這五個方位來放置控件,此時控件只能設置Width、Height和Size屬性,而無法設置Left、Top和Location屬性,即控件只能調整大小,無法自由設置位置。這種控件依照容器的相對位置放置控件的方式稱爲“相對位置佈局”。
Dock的屬性值是一個DockType枚舉,其稱謂爲“錨定”,就是將控件和其容器的左右上下中這五個位置綁定,無論容器移動到什麼地方,也無論容器的尺寸如何變化,控件始終確定在這五個位置之一的地方。這個枚舉其定義如下:
/// <summary> |
/// 定義錨定方位的枚舉 |
/// </summary> |
public enum DockStyle { |
/// <summary> |
/// 取消錨定 |
/// </summary> |
None = 0, |
/// <summary> |
/// 錨定在頂部 |
/// </summary> |
Top = 1, |
/// <summary> |
/// 錨定在底部 |
/// </summary> |
Bottom = 2, |
/// <summary> |
/// 錨定在左邊 |
/// </summary> |
Left = 3, |
/// <summary> |
/// 錨定在右邊 |
/// </summary> |
Right = 4, |
/// <summary> |
/// 錨定在中央並充滿容器 |
/// </summary> |
Fill = 5, |
} |
下面我們用一段代碼來演示一下:
1 | using System; |
2 | using System.Drawing; |
3 | using System.Windows.Forms; |
4 | |
5 | namespace Edu.Study.Graphics.DockLayout { |
6 | |
7 | /// <summary> |
8 | /// 繼承Form類, 定義窗體類 |
9 | /// </summary> |
10 | class MyForm : Form { |
11 | |
12 | // 以不變模式設定WIN_TITLE, 表示窗體標題 |
13 | private const string WIN_TITLE = "面板演示"; |
14 | |
15 | /// <summary> |
16 | /// 面板類 |
17 | /// </summary> |
18 | private Panel panel; |
19 | |
20 | /// <summary> |
21 | /// 按鈕 |
22 | /// </summary> |
23 | private Button button; |
24 | |
25 | /// <summary> |
26 | /// 單選按鈕數組 |
27 | /// </summary> |
28 | private RadioButton[] radioButtons; |
29 | |
30 | /// <summary> |
31 | /// 構造器 |
32 | /// </summary> |
33 | public MyForm() { |
34 | // 設定窗體標題 |
35 | this.Text = WIN_TITLE; |
36 | // 設定窗體最大化 |
37 | this.WindowState = FormWindowState.Maximized; |
38 | |
39 | // 實例化面板類對象 |
40 | this.panel = new Panel(); |
41 | // 設置面板的邊框爲三維效果 |
42 | this.panel.BorderStyle = BorderStyle.Fixed3D; |
43 | // 設定面板的錨定方式爲無 |
44 | this.panel.Dock = DockStyle.None; |
45 | // 設定面板的背景色爲白色 |
46 | this.panel.BackColor = Color.White; |
47 | // 爲面板更改尺寸事件綁定委託方法 |
48 | this.panel.Resize += new EventHandler(OnPanelResized); |
49 | |
50 | // 實例化按鈕對象 |
51 | this.button = new Button(); |
52 | // 因爲button是panel的子窗體, 所以背景色默認也會變爲白色 |
53 | // 這裏爲按鈕重新指定顏色, SystemColors.Control是操作系統默認的控件背景色 |
54 | this.button.BackColor = SystemColors.Control; |
55 | // 設置按鈕文本 |
56 | button.Text = "點下我!"; |
57 | // 爲按鈕點擊事件綁定委託方法 |
58 | button.Click += new EventHandler(OnButtonClick); |
59 | |
60 | // 將按鈕加入面板 |
61 | this.panel.Controls.Add(this.button); |
62 | |
63 | // 實例化radioButtons數組, 數組長度和錨定樣式數量相同 |
64 | this.radioButtons = new RadioButton[(int)DockStyle.Fill + 1]; |
65 | for (int i = 0; i < this.radioButtons.Length; i++) { |
66 | RadioButton rb = new RadioButton(); |
67 | // 設定RadioButton的顯示文本 |
68 | rb.Text = ((DockStyle)i).ToString(); |
69 | |
70 | // 將RadioButton對象加入面板 |
71 | this.panel.Controls.Add(rb); |
72 | // 爲數組元素賦值RadioButton對象引用 |
73 | this.radioButtons[i] = rb; |
74 | } |
75 | |
76 | // 將面板加入窗體 |
77 | this.Controls.Add(this.panel); |
78 | } |
79 | |
80 | /// <summary> |
81 | /// 處理按鈕點擊事件的委託方法 |
82 | /// </summary> |
83 | private void OnButtonClick(object sender, EventArgs e) { |
84 | |
85 | // 設置面板的寬度和高度爲父窗體的1/3 |
86 | this.panel.Width = this.ClientSize.Width / 3; |
87 | this.panel.Height = this.ClientSize.Height / 3; |
88 | |
89 | // 每點擊一次按鈕, 更改面板的錨定方式 |
90 | if (this.panel.Dock == DockStyle.Fill) { |
91 | this.panel.Dock = DockStyle.None; |
92 | } else { |
93 | this.panel.Dock = (DockStyle)this.panel.Dock + 1; |
94 | } |
95 | |
96 | // 更改主窗體標題 |
97 | this.Text = String.Format("{0} 面板的錨定方式目前爲:{1}", WIN_TITLE, this.panel.Dock); |
98 | this.radioButtons[(int)this.panel.Dock].Checked = true; |
99 | } |
100 | |
101 | /// <summary> |
102 | /// 面板尺寸改變事件委託方法 |
103 | /// </summary> |
104 | private void OnPanelResized(object sender, EventArgs e) { |
105 | // 在面板改變尺寸後, 設置按鈕位於面板中央 |
106 | this.button.Left = (this.panel.ClientSize.Width - this.button.Width) / 2; |
107 | this.button.Top = (this.panel.ClientSize.Height - this.button.Height) / 2; |
108 | |
109 | // 間隔距離 |
110 | int marginPart = 0; |
111 | // 起始位置 |
112 | int start = 0; |
113 | // 遍歷RadioButton數組, 設置每一個RadioButton的位置 |
114 | foreach (RadioButton rb in this.radioButtons) { |
115 | // 根據面板的錨定方式選擇計算位置的分支 |
116 | switch (this.panel.Dock) { |
117 | case DockStyle.Fill: |
118 | case DockStyle.Top: |
119 | case DockStyle.Bottom: |
120 | // 對於填充, 置頂, 置底三種錨定方式, 採用橫向擺放RadioButton控件 |
121 | |
122 | // 設定每個RadioButton的上邊距爲面板高度的四分之一 |
123 | rb.Top = this.panel.ClientSize.Height / 4; |
124 | |
125 | // 計算每個RadioButton控件的間距 |
126 | if (marginPart == 0) { |
127 | marginPart = this.ClientSize.Width / this.radioButtons.Length; |
128 | start = marginPart; |
129 | } |
130 | |
131 | // 按照RadioButton的間距等距設定其左邊距 |
132 | rb.Left = start - rb.Width; |
133 | break; |
134 | case DockStyle.Left: |
135 | case DockStyle.Right: |
136 | // 對於靠左、靠右兩種種錨定方式, 採用縱向擺放RadioButton控件 |
137 | |
138 | // 設定RadioButton的左邊距爲面板寬度的四分之一 |
139 | rb.Left = this.panel.ClientSize.Width / 4; |
140 | if (this.panel.Dock == DockStyle.Right) { |
141 | // 如果面板靠右錨定, 設置RadioButton的左邊距爲面板的四分之三 |
142 | rb.Left = this.panel.ClientSize.Width - rb.Width; |
143 | } |
144 | |
145 | // 計算每個RadioButton控件的間距 |
146 | if (marginPart == 0) { |
147 | marginPart = this.ClientSize.Height / this.radioButtons.Length; |
148 | } |
149 | // 按照RadioButton的高度垂直等間距設置上邊距 |
150 | rb.Top = start + rb.Height; |
151 | break; |
152 | } |
153 | start += marginPart; |
154 | } |
155 | } |
156 | } |
157 | |
158 | |
159 | static class Program { |
160 | static void Main() { |
161 | Application.EnableVisualStyles(); |
162 | Application.SetCompatibleTextRenderingDefault(false); |
163 | Application.Run(new MyForm()); |
164 | } |
165 | } |
166 | } |
本章代碼下載
可以看到,在主窗體上,我們放置了一個面板(Panel),面板上有一個按鈕(Button)和五個單項按鈕(RadioButton),第40-77行代碼是Form類構造器,實例化並創建了上述控件,並將它們都加入各自的容器(這裏將Panel的容器設置爲From)。對於面板,我們綁定了它的Resize事件,當面板尺寸發生改變時引發,執行OnPanelResized方法(第104-156行);對於按鈕,我們綁定了它的Click事件,當按鈕點擊時引發,執行OnButtonClick方法(第83-99行)。
在OnButtonClick方法中,重新定義了面板的尺寸,然後將面板的Dock屬性切換爲另一個值。所以我們可以看到,一旦點擊按鈕,面板的位置就會發生變化,分別會位於Form容器的上(值爲1)、下(值爲2)、左(值爲3)、右(值爲4)、中(值爲5)幾個位置,而面板的Dock屬性爲None(值爲0)時,面板絕對定位位置,不在按照錨定位置佈局。如下圖:
圖3 錨定方位示意圖
可以看到,當Dock屬性爲DockStyle.Top或DocStyle.Bottom時,只能設置控件的Height屬性,其它位置和尺寸屬性均無效;當設置爲DockType.Left或DocType.Right時,只能設置控件的Width屬性,其它屬性無效。當控件的Dock屬性爲DockType.None時,則控件的位置按照其Top,Left和Location屬性定位,控件的尺寸按照其Width,Height和Size屬性來設定。
在OnPanelResized方法中,由於按鈕和單選按鈕都沒有設定其Dock屬性(即Dock屬性爲DockType.None),所以我們採用絕對定位,通過一個簡單的算法,根據面板當前的的錨定方式,計算按鈕和五個單選按鈕的位置。如果是面板按照DockType.Top, DockType.Buttom, DockType.Fill佈局,則單選按鈕按照橫向平均間距佈局,否則按照縱向平均間距佈局,對於DockType.None不作處理。
通過上面的比較可以看出,絕對佈局存在的問題是:除非不允許容器改變大小,否則容器一旦改變尺寸,將會破壞佈局結構,而相對佈局則不存在這個問題。