C#語言 第四部分 圖形界面編程(五) 佈局容器類(1)

從ContainerControl類繼承的子類作爲容器窗體,可以容納除Form類對象外的其餘窗體對象。

在所有容器窗體內,最基本的就是頂級容器Form類以及面板容器Panel類。這兩者的主要區別爲:前者具有Windows標準框架(標題欄,最大化、最小化和關閉按鈕,窗體邊框,可調整尺寸),並且可以獨立存在;後者只是一塊區域,並且必須依附在某個容器窗體上,無法獨立存在。除了它們的區別外,他們都具有:Controls屬性,可以在上面放置控件;控件放置的位置需要通過控件的Top和Left屬性或者控制。

1 絕對佈局

對於容器類型的控件(包括Form類型),出了Size,Bounds屬性外,還有ClientSizeClientRectangle屬性,前者表示客戶區尺寸,Size類型;後者表示客戶區的矩形,是一個Rectangle類型。所謂客戶區,就是容器實際可以使用的空間,對於Form類型來說,客戶區就是除過標題欄,四周的邊框外剩餘的部分。

客戶區示意圖 圖1 客戶區示意圖

一般來說,直接在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,
    }

錨定佈局方位圖圖2 錨定佈局方位圖

下面我們用一段代碼來演示一下:

using System;
using System.Drawing;
using System.Windows.Forms;
 
namespace Edu.Study.Graphics.DockLayout {
 
    /// <summary>
    /// 繼承Form類, 定義窗體類
    /// </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)時,面板絕對定位位置,不在按照錨定位置佈局。如下圖:

Dock佈局運行效果圖 Dock佈局運行效果圖 Dock佈局運行效果圖
Dock佈局運行效果圖 Dock佈局運行效果圖 Dock佈局運行效果圖

圖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不作處理。

通過上面的比較可以看出,絕對佈局存在的問題是:除非不允許容器改變大小,否則容器一旦改變尺寸,將會破壞佈局結構,而相對佈局則不存在這個問題。

發佈了51 篇原創文章 · 獲贊 7 · 訪問量 40萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章