原文:http://blog.csdn.net/mousebaby808/article/details/5532190
5 表格佈局
無論使用錨定佈局還是流式佈局,都無法達到複雜佈局的效果,很多時候我們不得不使用絕對佈局,忍受絕對佈局帶來的麻煩(要麼容器尺寸一變化,界面就變得一團糟;要麼在容器的Resize事件中寫複雜的佈局代碼)。其實.net Framework中還具備一種很高級的佈局方式——表格佈局。
表格佈局顧名思義,就是將容器分爲n行m列的二維表,這樣一個二維元組就可以表示表格的一個單元格,例如(0,0)就表示表格的第一行第一列。通過這個表格,我們可以將控件妥善的安放在容器合適的位置。
通過控件的Margin屬性,我們還可以控制控件距離表格單元格邊框的空白;通過控件的Dock屬性,我們可以進一步控制控件錨定在單元格的哪個方位。
在我們熟悉的網頁中,DIV和Table(層和表)就可以佈局出來豐富多彩的頁面,那麼.net Framework的表格佈局,就具備層和表的特點,同樣也可以佈局出來五花八門的界面。
TableLayoutPanel是.net Framework中的表格佈局容器,這個容器可以分爲n行m列,如下圖:
圖1 表格佈局示意圖
從上圖可以形象的看到,TableLayoutPanel被分成了若干行和列,即容器被分爲了若干格子,每個格子內規定只能放置一個控件,格子也可以跨行或跨列(也可以同時既跨行又跨列),從而把一個控件方式放置在相鄰的多行、多列中。
如果要給格子裏放置多個控件呢?首先就得給格子裏放置一個容器類控件,例如放一個Panel或FlowlayoutPanel,然後再把多個控件放置在這些容器內,就可以解決問題了。
下面我們看一下表格佈局的步驟:
-
初始化TableLayoutPanel對象,然後要指定容器究竟分爲多少行和列,設置容器對象的RowCount(行)和ColumnCount(列)屬性;
-
爲所有的行和列設置樣式,樣式具體指:行的高度(自動調整、絕對、百分比),列的寬度(自動調整、絕對、百分比);
-
將控件增加到TableLayoutPanel容器對象內,調用容器的Controls屬性的Add方法,這個和前面講的容器相同;
-
定位控件:調用TableLayoutPanel容器對象的SetRow方法(指定行)和SetColumn方法(指定列);除此之外,TableLayoutPanel對象的Controls屬性具有一個重載的Add方法,可以在加入控件的同時指定其行列數;
-
設置跨行、跨列(可選):調用TableLayoutPanel容器對象的SetRowSpan方法(設置控件跨行)和SetColumnSpan方法(設置控件跨列)
好了,通過一個例子,我們仔細體會這種容器佈局的特點:
圖2 程序執行效果圖
可以看到,本次程序執行結果是一個簡易的計算器,是使用表格佈局的絕好素材。
詳細看一下界面佈局方式:
圖3 界面佈局分佈圖
可以看到,整個界面分爲兩大部分,上面的顯示區和下面的按鈕區,這是由一個2行1列的TableLayoutPanel容器構成的;在顯示區裏(綠框),使用一個Panel容器承載了一個TextBox,用於顯示計算結果;在按鈕區域(黃框),使用一個6行5列的TableLayoutPanel容器(紅框),存放所有按鈕。
對於最外面的TableLayoutPanel容器,其顯示區(綠框)具有一個固定尺寸的行樣式;按鈕區(黃框)是自動尺寸的行樣式,其列樣式爲自動尺寸,這樣,當界面尺寸改變後,顯示區的尺寸不會發生變化,按鈕區則隨着容器的尺寸變化而變化;
對於按鈕區內的TableLayoutPanel容器(紅框),則按容器尺寸百分比平均分爲6行5列,這樣,無論容器尺寸如何變化,其行列百分比是不會變化的,單元格尺寸會隨着容器尺寸自動調整。
看代碼:
Program.cs
- using System;
- using System.Drawing;
- using System.Runtime.InteropServices;
- using System.Windows.Forms;
- namespace Edu.Study.Graphics.TableLayout {
- /// <summary>
- /// 計算器主窗體
- /// </summary>
- class MyForm : Form {
- /// <summary>
- /// 計算器運算操作標誌枚舉
- /// </summary>
- private enum Operators {
- // 無操作
- None,
- // 加法操作
- Add,
- // 減法操作
- Sub,
- // 除法操作
- Div,
- // 乘法操作
- Mult
- }
- // 主容器面板, 充滿整個窗體
- private TableLayoutPanel mainPane;
- // 顯示結果的容器面板
- private Panel displayPane;
- // 顯示操作盤的容器面板
- private TableLayoutPanel optPane;
- // 顯示結果的文本框
- private TextBox resultTextbox;
- // 保存第一個數字
- private decimal firstNumber = 0;
- // 保存運算操作
- private Operators operators = Operators.None;
- // 是否需要清屏
- private bool needClear = true;
- // M功能鍵保存的值
- private decimal mValue = 0;
- // MR功能鍵按鈕
- private Button MRButton;
- /// <summary>
- /// 構造器, 初始化所有控件
- /// </summary>
- public MyForm() {
- // 設置窗體標題
- this.Text = "簡易計算器";
- /************ 初始化mainPane容器 ***********/
- this.mainPane = new TableLayoutPanel();
- // 容器分爲2行1列
- this.mainPane.RowCount = 2;
- this.mainPane.ColumnCount = 1;
- // 爲一列設置列樣式, 根據父容器(Form窗體)自動調整尺寸
- this.mainPane.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
- // 爲第一行設置行樣式, 固定高度, 50單位
- this.mainPane.RowStyles.Add(new RowStyle(SizeType.Absolute, 50));
- // 爲第二行設置行樣式, 自動調整尺寸
- this.mainPane.RowStyles.Add(new RowStyle(SizeType.AutoSize));
- // mainPane在父容器上的錨定方式爲中央充滿
- this.mainPane.Dock = DockStyle.Fill;
- /************ 初始化displayPane容器 ***********/
- this.displayPane = new Panel();
- // 設置displayPane容器在父容器上的錨定方式爲中央充滿
- this.displayPane.Dock = DockStyle.Fill;
- // 設置displayPane容器的Padding屬性爲5
- this.displayPane.Padding = new Padding(5);
- // 將displayPane容器加入到mainPane容器內
- this.mainPane.Controls.Add(this.displayPane);
- // 設置displayPane容器位於mainPane容器的第1行
- this.mainPane.SetRow(this.displayPane, 0);
- /************ 初始化resultTextbox容器 ***********/
- this.resultTextbox = new TextBox();
- // 設置resultTextbox控件顯示文本
- this.resultTextbox.Text = "0";
- // 設置resultTextbox文本對齊方式爲居右對齊
- this.resultTextbox.TextAlign = HorizontalAlignment.Right;
- // 設置resultTextbox只讀
- this.resultTextbox.ReadOnly = true;
- // 設置resultTextbox控件背景色爲淡藍色
- this.resultTextbox.BackColor = Color.LightBlue;
- // 設置resultTextbox控件字體
- this.resultTextbox.Font = new Font(new FontFamily("Times New Roman"), this.displayPane.Height / 2);
- // 設置resultTextbox控件在父容器上居中錨定
- this.resultTextbox.Dock = DockStyle.Fill;
- this.displayPane.Controls.Add(this.resultTextbox);
- /************ 初始化resultTextbox容器 ***********/
- this.optPane = new TableLayoutPanel();
- // 設置optPane具有6行
- this.optPane.RowCount = 6;
- // 設置optPane具有5列
- this.optPane.ColumnCount = 5;
- // 設置optPane在父容器內居中錨定
- this.optPane.Dock = DockStyle.Fill;
- // 爲optPane中的每一行設置樣式, 百分比高度, 每行高度爲總體的16.67%
- for (int i = 0; i < optPane.RowCount; i++) {
- this.optPane.RowStyles.Add(
- new RowStyle(SizeType.Percent, 100.0F / (float)optPane.RowCount));
- }
- // 爲optPane中的每一列設置樣式, 百分比寬度, 每列寬度爲總體的20%
- for (int i = 0; i < optPane.ColumnCount; i++) {
- this.optPane.ColumnStyles.Add(
- new ColumnStyle(SizeType.Percent, 100.0F / (float)optPane.ColumnCount));
- }
- // 設置optPane容器Padding屬性爲5
- this.optPane.Padding = new Padding(5);
- // 將optPane容器增加在mainPane容器中
- this.mainPane.Controls.Add(this.optPane);
- // 設置optPane容器位於mainPane容器的第2行
- this.optPane.SetRow(this.optPane, 1);
- // 保存計算器按鈕的字符串數組
- // 數組中null值項表示這裏沒有對應的按鈕
- string[] BUTTON_TEXT = {
- "MC", "MR", "MS", "M+", "M-",
- "BACK", "CE", "C", "+-", "SQRT",
- "7", "8", "9", "/", "%",
- "4", "5", "6", "*", "1/x",
- "1", "2", "3", "-", "=",
- "0", null, ".", "+", null
- };
- /************ 初始化Button控件 ***********/
- for (int i = 0; i < BUTTON_TEXT.Length; i++) {
- // 如果BUTTON_TEXT數組第i項不爲null,
- // 表示要爲該項生成一個對應按鈕
- if (BUTTON_TEXT[i] != null) {
- // 計算按鈕所處的行和列
- int row = i / this.optPane.ColumnCount;
- int col = i - row * 5;
- // 初始化按鈕
- Button btn = new Button();
- // 設置按鈕上呈現的文字(Text屬性)和按鈕的名字
- btn.Text = btn.Name = BUTTON_TEXT[i];
- // 設置按鈕錨定在父容器中央並充滿
- btn.Dock = DockStyle.Fill;
- // 設置按鈕四周空白爲2個單位
- btn.Margin = new Padding(2);
- // 設置按鈕點擊事件處理委託
- btn.Click += new EventHandler(ButtonClicked);
- // 將按鈕加入到optPane容器內
- this.optPane.Controls.Add(btn, col, row);
- // 將MR按鈕的引用存儲在字段中, 程序中要使用
- if (string.CompareOrdinal(btn.Text, "MR") == 0) {
- this.MRButton = btn;
- }
- }
- }
- // 找到名稱爲"="的按鈕
- Control[] ctrl = this.optPane.Controls.Find("=", false);
- // 設置控件向右跨兩行
- this.optPane.SetRowSpan(ctrl[0], 2);
- // 找到名爲"0"的按鈕
- ctrl = this.optPane.Controls.Find("0", false);
- // 設置控件向下跨兩列
- this.optPane.SetColumnSpan(ctrl[0], 2);
- // 將mainPane容器添加到窗體上
- this.Controls.Add(this.mainPane);
- }
- /// <summary>
- /// 處理按鈕點擊事件的方法
- /// </summary>
- private void ButtonClicked(object sender, EventArgs e) {
- // 得到本次點擊的按鈕實例引用(通過sender參數)
- Button btn = (Button)sender;
- // 根據按鈕上的文本, 進行分支處理
- switch (btn.Text) {
- case "1":
- case "2":
- case "3":
- case "4":
- case "5":
- case "6":
- case "7":
- case "8":
- case "9":
- case "0":
- /*** 對於數字按鈕的處理 ***/
- if (this.needClear) {
- // 如果needClear字段爲true, 則將按鈕
- // 的Text屬性賦予文本框Text屬性, 相當於
- // 清除掉文本框原有內容
- this.resultTextbox.Text = btn.Text;
- // 確保以後輸入不再清屏
- this.needClear = false;
- } else {
- // 對於needClear字段false的情況, 將
- // 按鈕對應的數字字符加到文本框字符串
- // 末尾即可
- this.resultTextbox.Text += btn.Text;
- }
- break;
- case ".":
- /*** 對於小數點按鈕的處理 ***/
- if (this.resultTextbox.Text.IndexOf('.') >= 0) {
- // 如果文本框內已經有一個小數點, 則發出警報聲
- MessageBeep(0xFFFFFFFF);
- } else {
- // 在文本框字符串末尾增加小數點字符
- this.resultTextbox.Text += btn.Text;
- }
- break;
- case "BACK":
- /*** 對於刪除按鈕的處理 ***/
- if (this.resultTextbox.Text.Length > 1) {
- // 如果文本框字符串長度大於1個字符, 則刪除最後一個字符
- this.resultTextbox.Text =
- this.resultTextbox.Text.Remove(this.resultTextbox.Text.Length - 1);
- } else {
- // 如果文本框字符串只剩1個字符, 則將文本框設置爲字符"0"
- this.resultTextbox.Text = "0";
- // 文本框需要清屏一次
- this.needClear = true;
- }
- break;
- case "+":
- /*** 對於加號按鈕的處理 ***/
- // 將文本框字符串轉爲數字類型保存在firstNumber字段中
- this.firstNumber = decimal.Parse(this.resultTextbox.Text);
- // 設置操作標誌爲Add
- this.operators = Operators.Add;
- // 文本框需要清屏一次
- this.needClear = true;
- break;
- case "-":
- /*** 對於減號按鈕的處理, 操作同上 ***/
- this.firstNumber = decimal.Parse(this.resultTextbox.Text);
- this.operators = Operators.Sub;
- this.needClear = true;
- break;
- case "*":
- /*** 對於乘號按鈕的處理, 操作同上 ***/
- this.firstNumber = decimal.Parse(this.resultTextbox.Text);
- this.operators = Operators.Mult;
- this.needClear = true;
- break;
- case "/":
- /*** 對於除號按鈕的處理, 操作同上 ***/
- this.firstNumber = decimal.Parse(this.resultTextbox.Text);
- this.operators = Operators.Div;
- this.needClear = true;
- break;
- case "=": {
- /*** 對於等號按鈕的處理 ***/
- // 將文本框字符串轉爲數字類型保存在secondNumber變量中
- decimal secondNumber = decimal.Parse(this.resultTextbox.Text);
- // 根據保存的運算操作類型進行分支, 進行加減乘除運算
- // 計算結果保存在firstNumber字段中
- switch (this.operators) {
- case Operators.Add:
- this.firstNumber += secondNumber;
- break;
- case Operators.Sub:
- this.firstNumber -= secondNumber;
- break;
- case Operators.Div:
- this.firstNumber /= secondNumber;
- break;
- case Operators.Mult:
- this.firstNumber *= secondNumber;
- break;
- }
- // 在文本框中顯示firstNumber字段的內容
- this.resultTextbox.Text = this.firstNumber.ToString();
- // 文本框需要清屏一次
- this.needClear = true;
- }
- break;
- case "CE":
- /*** 對於CE按鈕的處理 ***/
- // CE按鈕清除以前所有的操作和屏幕顯示
- this.firstNumber = 0;
- this.needClear = true;
- this.resultTextbox.Text = "0";
- this.operators = Operators.None;
- break;
- case "C":
- /*** 對於C按鈕的處理 ***/
- // C按鈕只清除屏幕顯示, 運算類型和上一次輸入數字不清除
- this.resultTextbox.Text = "0";
- this.needClear = true;
- break;
- case "+-":
- /*** 對於正負號按鈕的處理 ***/
- // 根據文本框字符串第一個字符是否爲-號字符, 對文本框字符串進行處理
- if (this.resultTextbox.Text[0] == '-') {
- // 刪除第一個字符
- this.resultTextbox.Text = this.resultTextbox.Text.Remove(0, 1);
- } else {
- // 在第一個字符處插入-號字符
- this.resultTextbox.Text = this.resultTextbox.Text.Insert(0, "-");
- }
- break;
- case "SQRT":
- /*** 對於開方號按鈕的處理 ***/
- // 得到文本框內字符串表示的數字, 保存在firstNumber字段中
- this.firstNumber = decimal.Parse(this.resultTextbox.Text);
- // 對firstNumber中保存的數字進行開方
- this.firstNumber = (decimal)Math.Sqrt((double)this.firstNumber);
- // 將結果存入文本框字符串中
- this.resultTextbox.Text = this.firstNumber.ToString();
- // 設置運算操作類型爲"無", 防止按等號後改變計算結果
- this.operators = Operators.None;
- // 文本框清屏一次
- this.needClear = true;
- break;
- case "1/x":
- /*** 對於倒數按鈕的處理 ***/
- // 得到文本框內字符串表示的數字, 保存在firstNumber字段中
- this.firstNumber = decimal.Parse(this.resultTextbox.Text);
- // 防止除數爲0的判斷
- if (this.firstNumber > 0) {
- // 對firstNumber中保存的數字求倒數
- this.firstNumber = 1 / this.firstNumber;
- // 將結果存入文本框字符串中
- this.resultTextbox.Text = this.firstNumber.ToString();
- }
- // 設置運算操作類型爲"無", 防止按等號後改變計算結果
- this.operators = Operators.None;
- // 文本框清屏一次
- this.needClear = true;
- break;
- case "M+":
- /*** 對於M+按鈕的處理 ***/
- // 得到文本框內字符串表示的數字, 保存在firstNumber字段中
- this.firstNumber = decimal.Parse(this.resultTextbox.Text);
- // 將firstNumber字段值和mValue字段值相加,
- // 結果保存在mValue字段中
- this.mValue += this.firstNumber;
- // 設置文本框清除一次
- this.needClear = true;
- // 如果mValue的值不爲0, 則MR按鈕顯示爲粉紅色, 表示有值已被存儲
- // 如果mValue的值爲0, 表示沒有存儲有效值, 按鈕恢復原本顏色
- this.MRButton.BackColor = this.mValue == 0 ? SystemColors.ButtonFace : Color.Pink;
- break;
- case "M-":
- /*** 對於M-按鈕的處理, 處理同上, 這一次是減法 ***/
- this.firstNumber = decimal.Parse(this.resultTextbox.Text);
- this.mValue -= this.firstNumber;
- this.needClear = true;
- this.MRButton.BackColor = this.mValue == 0 ? SystemColors.ButtonFace : Color.Pink;
- break;
- case "MS":
- /*** 對於MS按鈕的處理, 處理類似M+, 將文本框內表示的數字存儲起來 ***/
- this.firstNumber = decimal.Parse(this.resultTextbox.Text);
- this.mValue = this.firstNumber;
- this.needClear = true;
- this.MRButton.BackColor = this.mValue == 0 ? SystemColors.ButtonFace : Color.Pink;
- break;
- case "MR":
- /*** 對於MR按鈕的處理 ***/
- // 將存儲的mValue的值恢復到文本框和firstNumber字段中, 以備運算使用
- this.firstNumber = this.mValue;
- this.resultTextbox.Text = this.firstNumber.ToString();
- this.needClear = true;
- break;
- case "MC":
- /*** 對於MC按鈕的處理 ***/
- // 將存儲的mValue的值清除
- this.mValue = 0;
- this.MRButton.BackColor = SystemColors.ButtonFace;
- break;
- }
- }
- /// <summary>
- /// 從Win32 SDK中引入播放報警聲音的系統函數
- /// </summary>
- [DllImport("user32.dll")]
- public static extern int MessageBeep(uint n);
- }
- /// <summary>
- /// 包含主方法的類
- /// </summary>
- static class Program {
- /// <summary>
- /// 應用程序的主入口點。
- /// </summary>
- static void Main() {
- Application.EnableVisualStyles();
- Application.SetCompatibleTextRenderingDefault(false);
- Application.Run(new MyForm());
- }
- }
- }
本節代碼下載
注意事項:
- 代碼中設置容器行列數(67-68行,108-110行);針對行列樣式的設置(70-74行,114-122行);控件加入單元格的各種方法(85-87行,162行,前者加入控件後設置控件的佈局位置,後者再加入的同時指定佈局位置);設置控件跨行跨列(174行、179行);
- 本次代碼爲一系列控件指定了同一個事件處理委託方法,注意這種事件處理的方式(159行指定委託;190行,根據sender參數獲取發送事件控件對象的引用);
- 本地代碼使用了控件的Name屬性(153行,設置Name屬性),其作用是,可以根據Name屬性在容器中查找控件的對象引用(172行,177行);
- 本次代碼使用了一些技巧一次性實例化和佈局多個按鈕(132-179行),這種批量處理的技巧在製作一些控件重複性很高的界面中,非常有用;
- 本代碼主要是介紹表格佈局管理,附帶了一個簡易計算器的算法流程(188-409行),算法的原理和代碼都很簡單,但這些代碼並沒有經過嚴格的測試,需要同學在看懂的基礎上自行摸索。
運行程序,拖動鼠標改變窗體尺寸,看看控件的大小和位置,是否也隨着容器的尺寸變化而變化?這就是相對佈局的特色。