C#语言 第四部分 图形界面编程(五) 布局容器类(3)
2015-03-16 21:32
429 查看
原文: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
[c-sharp]
view plaincopyprint?
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行),算法的原理和代码都很简单,但这些代码并没有经过严格的测试,需要同学在看懂的基础上自行摸索。
运行程序,拖动鼠标改变窗体尺寸,看看控件的大小和位置,是否也随着容器的尺寸变化而变化?这就是相对布局的特色。
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
[c-sharp]
view plaincopyprint?
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行),算法的原理和代码都很简单,但这些代码并没有经过严格的测试,需要同学在看懂的基础上自行摸索。
运行程序,拖动鼠标改变窗体尺寸,看看控件的大小和位置,是否也随着容器的尺寸变化而变化?这就是相对布局的特色。
相关文章推荐
- C#语言 第四部分 图形界面编程(五) 布局容器类(4)
- C#语言 第四部分 图形界面编程(五) 布局容器类(1)
- C#语言 第四部分 图形界面编程(五) 布局容器类(3)
- C#语言 第四部分 图形界面编程(六) 分组容器和卡片容器(2)
- C#语言 第四部分 图形界面编程(六) 分组容器和卡片容器(1)
- C#语言 第四部分 图形界面编程(六) 分组容器和卡片容器(2)
- C#语言 第四部分 图形界面编程(五) 布局容器类(2)
- C#语言 第四部分 图形界面编程(三) 子窗体
- C#语言 第四部分 图形界面编程(四) 尺寸、坐标、边界
- 图形界面编程(五) 布局容器类(1)
- C#语言 图形界面编程(四) 尺寸、坐标、边界
- 图形界面编程(五) 布局容器类(3)
- 图形界面编程(五) 布局容器类(4)
- JavaSwing图形界面编程之布局管理器(一)
- C# 编程指南(1):语言部分:Hello World
- C语言控制台窗口图形界面编程(二):窗口缓冲区的设置
- C# 编程指南(3):语言部分:数据类型
- C# 编程指南(2):语言部分:程序的通用结构
- C# 编程指南(4):语言部分:数组
- C语言控制台窗口图形界面编程(六):光标操作