2 卡片式容器
卡片容器(TabControl)是这样一种容器:它本身包含多个容器(类似于Panel类型的面板容器),但同时只能显示其中的一个,并隐藏其余的容器。
卡片容器的用途很广泛,目前流行的浏览器都使用卡片式方式在一个浏览器内可以同时打开多个页面,例如:
图1 卡片式浏览的Firefox浏览器
卡片式容器解决了一个这样的问题:当我们需要在同一个窗体内放置为数众多的控件时,如果不能让使用者有选择性的看到一些控件并隐藏其它控件,那么使用者将会觉得很烦——一个界面上如果有超过10个以上的控件时,用户将变得无所适从。
.net的卡片容器由TabPage(卡片页)组成,卡片页具有一个卡片标签,它类似一个按钮,显示卡片页的标题。点击某个卡片标签,将在卡片容器内显示对应的卡片页。每个卡片页像一个面板,可以容纳其它的控件。
整个卡片容器就像我们在文具店随处可见的带标签的记事本。可以通过点击某个卡片页的标签,显示所需要的一部分控件,隐藏的其它控件。
卡片页的Text属性用来访问卡片页的标签文本,例如:
[c-sharp] view plain copy
- // 实例化一个卡片页容器对象
- TabPage page = new TabPage();
- // 设置这个卡片页容器的标签文本
- page.Text = "动画播放";
通过TabControl容器的TabPages属性,我们可以访问到卡片容器内所有的TabPage容器,TabPages属性类型是一个TabPage的集合,所以可以有如下代码:
[c-sharp] view plain copy
- // 访问卡片容器内第一个卡片页
- TabPage page = this.tabControl.TabPages[0];
- // 访问卡片容器内第二个卡片页
- page = this.tabControl.TabPages[1];
- // 遍历所有的卡片页
- foreach (TabPage tp in this.tabControl.TabPages) {
- // 遍历所有的卡片页对象
- }
上例中介绍了如何访问卡片容器中的卡片页。这里假设我们的类里有一个TabControl类型的字段tabControl。
通过如下代码,可以为卡片容器增加一个新的卡片页:
[c-sharp] view plain copy
- // 实例化一个标签文本为"动画播放"的卡片页对象,这里同时设置了卡片页标签文本
- TabPage page = new TabPage("动画播放");
- // 将卡片页对象加入到卡片容器中
- this.tabControl.TabPages.Add(page);
上述代码为卡片容器增加了一个标签上显示“动画播放”的卡片页。
可以使用简化的方式,即TabPages属性Add方法的重载:
[c-sharp] view plain copy
- this.tabControl.TabPages.Add("动画播放");
上述代码为卡片容器增加了一个标签为“动画播放”的卡片页,其后可以使用TabPages属性来访问它。即在Add方法内部实例化了TabPage对象并设置了其Text属性。
[c-sharp] view plain copy
- this.tabControl.TabPages.Add("play", "动画播放");
上述代码同样是在Add方法内部实例化了TabPage对象并设置了其Text属性,并且还设置了TabPage对象的Name属性。通过Name属性,可以更方便的查找到这个TabPage对象。例如:
[c-sharp] view plain copy
- // 通过索引器根据TabPage对象的名称查找
- if (this.tabControl.TabPages.ContainsKey("play")) {
- TabPage page = this.tabControl.TabPages["play"];
- // 通过卡片页的名称删除卡片页
- this.tabControl.TabPages.RemoveByKey("play");
- }
- // 通过TabPage在卡片容器内的编号删除卡片页
- this.tabControl.TabPages.RemoveAt(0);
也可以通过如下代码批量增加卡片页:
[c-sharp] view plain copy
- // 实例化一个卡片页对象数组
- TabPage[] pages = {
- new TabPage("动画播放"),
- new TabPage("选项"),
- };
- // 将卡片页对象的数组加入卡片容器内
- this.tabControl.TabPages.AddRange(pages);
卡片页就可以看做一个普通的容器对象。卡片页完全充满卡片容器除了卡片标签外的剩余空间,并且不具备布局功能,可以像操作一个Panel容器一样来操作卡片页,例如:
[c-sharp] view plain copy
- // 为卡片容器内第1个卡片页的Resize事件增加委托方法
- this.tabControl.TabPages[0].Resize += new EventHandler(TabControlPage0Resize);
- // 为卡片容器内第2个卡片页设置容器与内容之间的空白
- this.tabControl.TabPages[1].Padding = new Padding(50);
- // 将表格布局管理容器加入到卡片容器的第2个卡片页中
- this.tabControl.TabPages[1].Controls.Add(this.tablePaneInPage1);
上述代码为卡片容器中的第1个卡片页设置了尺寸变化时的事件委托方法,并为第2个卡片页设置了容器和其内容直接的空白,最后将一个表格布局管理面板容器加入到了第2个卡片页中(假设表格布局面板为tablePaneInPage1字段)。这些事件、属性和方法都是一个容器对象所特有的标志。
可以看到,卡片容器的卡片页即可以使用TabPages属性的索引来访问,也可以通过一个字符串来访问。
卡片容器的Alignment属性可以设置卡片标签的位置,属性值是一个TabAlignment枚举的枚举项,可以取值Top(标签位于卡片容器顶端,默认值)、Bottom(标签位于卡片容器底部)、Left(标签位于卡片容器左侧)和Right(标签位于卡片容器右侧)。例如:
[c-sharp] view plain copy
- // 设置卡片容器的卡片标签位于容器顶端
- this.tabControl.Alignment = TabAlignment.Top;
上述代码设置卡片标签在卡片容器的顶端。
当我们通过卡片标签选择新的卡片页时,卡片容器会得到一个事件通知,可以使用一个委托方法处理这个事件,例如:
[c-sharp] view plain copy
- // 为选中新的卡片标签增加事件的委托方法
- this.tabControl.SelectedIndexChanged += new EventHandler(TabControlTabIndexChanged);
则事件处理方法为:
[c-sharp] view plain copy
- private void TabControlTabIndexChanged(object sender, EventArgs e) {
- // 获取被选中的卡片标签标题文本, 将文本显示在文本标签控件中
- this.textLabel.Text = string.Format("卡片/"{0}/"被选中",
- this.tabControl.SelectedTab.Text);
- }
这里,通过SelectedTab属性获取到了被选中的卡片标签。另外,我们也可以使用SelectedIndex属性获取被选中卡片页在卡片容器内的编号。
好了,基本就这么多了,TabControl的使用非常简单,这里我们做一个稍微复杂的练习,来综合使用前面学过的一部分容器和控件:
图2 卡片页“动画播放”示意图
图3 卡片页“选项”示意图
这次我们做一个简单的动画播放器,用到卡片容器:第一个卡片页显示动画播放,第二个卡片页显示控制动画的选项。
这次还是用到了Timer类(定时器),用于播放动画和控制动画播放的速度。
这次还使用到了“资源”。资源后面课程会有详细介绍,这里先熟悉一下基本用法。
整个窗体上有一个居中锚定的表格布局面板(TableLayoutPanel),分为2行1列。第一行内是一个居中锚定的文本标签控件(Label),第二行内是一个卡片容器(TabControl)。
卡片容器分为2页,第1页中包含一个图片显示框控件(PictrueBox),用于显示动画;第2页中包含一个居中锚定的表格布局面板,分为5行2列(注:图中标为4行2列是一个错误,懒的改图了,这里说明一下),第1列中全部为文本标签,显示提示信息,第2列中放置了一些控制动画播放的控件。
用鼠标右键打开项目菜单,选择“属性”菜单项:
图4 选择项目属性
再打开的界面中选择“资源”:
图5 项目属性-资源
选择“添加资源”->“添加现有文件”:
图6 添加资源文件
再“打开文件”对话框中,在左边选择正确的路径及文件夹,在右边选择要添加的图片文件,点击“打开”按钮:
图7 添加图片文件
选择“资源”菜单->“图像”,确认资源已添加正确:
图8 查看已添加资源
注:以上小狗图片均来自Sun公司Java Swing教程,特此说明。
至此,所有图片资源都已经添加完毕,我们可以在程序中访问这些资源。
代码如下:
Program.cs
[c-sharp] view plain copy
- using System;
- using System.Collections.Generic;
- using System.Drawing;
- using System.Resources;
- using System.Windows.Forms;
- // 通过该命名空间引入Resources类, 该类代码由Visual Studio自动生成
- using Edu.Study.Graphics.TabControlContainer.Properties;
- namespace Edu.Study.Graphics.TabControlContainer {
- /// <summary>
- /// 主窗体类
- /// </summary>
- class MyForm : Form {
- // 主窗体上的表格布局面板, 2行1列
- private TableLayoutPanel mainTablePane;
- // mainTablePane面板第1行文本标签控件
- private Label textLabel;
- // mainTablePane面板第2行卡片容器
- private TabControl tabControl;
- // 卡片第1页中的图片框控件
- private PictureBox pictrueBox;
- // 图片框中图片数组, Image类表示一个图片
- private Image[] pictrues;
- // 用于切换图片的定时器对象
- private Timer timer;
- // 表示当前显示的图片在pictrues数组中的索引
- private int pictrueIndex = 0;
- // 第2个卡片中的表格布局面板, 5行2列
- private TableLayoutPanel tablePaneInPage1;
- // 用于控制动画速度的数字滑块
- private TrackBar speedTrackBar;
- // 用于控制播放顺序的复选框
- private CheckBox playBackCheckBox;
- // 用于暂停动画的复选框
- private CheckBox pauseCheckBox;
- // 用于排列单选按钮控件的流式面板
- private FlowLayoutPanel purFlowPane;
- // 流式面板上的单选按钮控件组, 用于显示播放进度
- private RadioButton[] purRadioButton;
- /// <summary>
- /// 构造器
- /// </summary>
- public MyForm() {
- this.Text = "卡片容器测试";
- /***** 初始化最外层表格面板 *****/
- this.mainTablePane = new TableLayoutPanel();
- // *锚定
- this.mainTablePane.Dock = DockStyle.Fill;
- // 2行1列
- this.mainTablePane.RowCount = 2;
- this.mainTablePane.ColumnCount = 1;
- // 设置第1行的样式, 绝对高度, 70单位
- this.mainTablePane.RowStyles.Add(new RowStyle(SizeType.Absolute, 70.0F));
- // 设置第2行样式, 自动调整高度
- this.mainTablePane.RowStyles.Add(new RowStyle(SizeType.AutoSize));
- // 设置列样式, 自动调整宽度
- this.mainTablePane.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
- // 加入到窗体
- this.Controls.Add(this.mainTablePane);
- /***** 初始化mainTablePane第1行文本标签控件 *****/
- this.textLabel = new Label();
- this.textLabel.AutoSize = true;
- this.textLabel.Dock = DockStyle.Fill;
- // 显示一个实线边框
- this.textLabel.BorderStyle = BorderStyle.FixedSingle;
- // 文本对齐方式: 上下居中, 左右居中
- this.textLabel.TextAlign = ContentAlignment.MiddleCenter;
- // 文本颜色为蓝色
- this.textLabel.ForeColor = Color.Blue;
- // 字体设置为23号字
- this.textLabel.Font = new Font(this.Font.FontFamily, 23.0F);
- // 加入mainTablePane第1行
- this.mainTablePane.Controls.Add(this.textLabel, 0, 0);
- /***** 初始化mainTablePane第2行卡片容器 *****/
- this.tabControl = new TabControl();
- this.tabControl.Dock = DockStyle.Fill;
- // 实例化一个卡片页对象数组
- TabPage[] pages = {
- new TabPage("动画播放"),
- new TabPage("选项"),
- };
- // 将卡片页对象的数组加入卡片容器内
- this.tabControl.TabPages.AddRange(pages);
- // 为选中新的卡片标签增加事件的委托方法
- this.tabControl.SelectedIndexChanged += new EventHandler(TabControlTabIndexChanged);
- // 为卡片容器内卡片第1页的Resize事件增加委托方法
- this.tabControl.TabPages[0].Resize += new EventHandler(TabControlPage0Resize);
- // 为卡片容器内卡片第2页设置容器与内容之间的空白
- this.tabControl.TabPages[1].Padding = new Padding(50);
- // 设置卡片容器的卡片标签位于容器顶端
- this.tabControl.Alignment = TabAlignment.Top;
- // 加入mainTablePane第2行
- this.mainTablePane.Controls.Add(this.tabControl, 0, 1);
- /***** 初始化tabControl第1页图片框控件 *****/
- this.pictrueBox = new PictureBox();
- // 设置图片框尺寸和要显示图片尺寸一致.
- // Resources静态属性T0为名称为T0的图片资源, 为Image类型对象
- this.pictrueBox.Size = Resources.T0.Size;
- // 设置边框为3D效果
- this.pictrueBox.BorderStyle = BorderStyle.Fixed3D;
- // 加入卡片容器第1页
- this.tabControl.TabPages[0].Controls.Add(this.pictrueBox);
- /***** 从资源中加载图片 *****/
- // 实例化一个图片向量集合
- List<Image> imagesList = new List<Image>();
- // 获取资源管理器对象引用
- ResourceManager resMgr = Resources.ResourceManager;
- int index = 1;
- Image img = null;
- do {
- // 使用资源管理器获取名为Tn(n为从0开始数字)的资源
- img = (Image)resMgr.GetObject("T" + index++);
- if (img != null) {
- // 如果获取资源成功, 保存该图片对象
- imagesList.Add(img);
- }
- } while (img != null); // 如果获取资源失败, 停止循环
- // 初始化图片对象数组
- this.pictrues = new Image[imagesList.Count];
- // 将集合内元素复制到数组中
- imagesList.CopyTo(this.pictrues);
- /***** 初始化定时器 *****/
- this.timer = new Timer();
- // 激发时间间隔50毫秒
- this.timer.Interval = 50;
- // 定时器激发事件委托方法
- this.timer.Tick += new EventHandler(TimerTick);
- // 第2个卡片中文本标签文本数组
- string[] labelText = {
- "速度:",
- "倒放:",
- "暂停:",
- "帧列表:"
- };
- /***** 第2个卡片中表格布局面板 *****/
- this.tablePaneInPage1 = new TableLayoutPanel();
- this.tablePaneInPage1.Dock = DockStyle.Fill;
- // 5行2列. 第5行不放置控件, 仅仅为了填满容器剩余的空间, 将其它4行保持在足够的高度上
- this.tablePaneInPage1.ColumnCount = 2;
- this.tablePaneInPage1.RowCount = labelText.Length + 1;
- // 设置第1列样式, 绝对宽度, 110单位
- this.tablePaneInPage1.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 110.0F));
- // 设置第2列样式, 自动调整
- this.tablePaneInPage1.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
- // 将表格布局管理容器加入到卡片容器的第2个卡片页中
- this.tabControl.TabPages[1].Controls.Add(this.tablePaneInPage1);
- /***** 设置tablePaneInPage1容器第1列 *****/
- // 表示行数的整数
- int rows = 0;
- // 遍历labelText数组
- foreach (string s in labelText) {
- // 实例化文本标签控件
- Label lb = new Label();
- lb.Dock = DockStyle.Fill;
- // 设定文本靠顶部居右对齐
- lb.TextAlign = ContentAlignment.TopRight;
- // 设定四周空白, 上边预留8个单位空白
- lb.Margin = new Padding(0, 8, 0, 0);
- // 设定显示文本
- lb.Text = s;
- // 控件置于tablePaneInPage1第row行第1列
- this.tablePaneInPage1.Controls.Add(lb, 0, rows++);
- // 设定该行样式, 绝对高度, 高度为文本标签高度2倍
- this.tablePaneInPage1.RowStyles.Add(
- new RowStyle(SizeType.Absolute, (lb.Height + lb.Margin.Vertical) * 2)
- );
- }
- // 设定最末行样式, 自动高度
- this.tablePaneInPage1.RowStyles.Add(new RowStyle(SizeType.AutoSize));
- /***** 设置数字滑块控件 *****/
- this.speedTrackBar = new TrackBar();
- this.speedTrackBar.Dock = DockStyle.Fill;
- // 定义控件四周空白, 右边空白100单位
- this.speedTrackBar.Margin = new Padding(3, 5, 100, 3);
- // 定义数值范围, 最小值和最大值
- this.speedTrackBar.Minimum = 20;
- this.speedTrackBar.Maximum = 500;
- // 定义当前值, 和定时器激发时间一致
- this.speedTrackBar.Value = this.timer.Interval;
- // 定义数值改变时事件委托方法
- this.speedTrackBar.ValueChanged += new EventHandler(SpeedTrackBarValueChanged);
- // 加入tablePaneInPage1第1行第2列
- this.tablePaneInPage1.Controls.Add(this.speedTrackBar, 1, 0);
- /***** 设置复选框控件 *****/
- // 播放顺序复选框
- this.playBackCheckBox = new CheckBox();
- // 默认不选中
- this.playBackCheckBox.Checked = false;
- // 控件加入tablePaneInPage1第2行第2列
- this.tablePaneInPage1.Controls.Add(this.playBackCheckBox, 1, 1);
- // 播放暂停复选框
- this.pauseCheckBox = new CheckBox();
- // 默认不选中
- this.pauseCheckBox.Checked = false;
- // 设置选中状态改变事件委托方法
- this.pauseCheckBox.CheckedChanged += new EventHandler(PauseCheckBoxCheckedChanged);
- // 控件加入tablePaneInPage1第3行第2列
- this.tablePaneInPage1.Controls.Add(this.pauseCheckBox, 1, 2);
- /***** 设置流式面板 *****/
- this.purFlowPane = new FlowLayoutPanel();
- this.purFlowPane.Dock = DockStyle.Fill;
- // 设置布局方向
- this.purFlowPane.FlowDirection = FlowDirection.LeftToRight;
- // 设置内容可以换行
- this.purFlowPane.WrapContents = true;
- // 禁止自动出现滚动条
- this.purFlowPane.AutoScroll = false;
- // 控件加入tablePaneInPage1第4行第2列
- this.tablePaneInPage1.Controls.Add(this.purFlowPane, 1, 3);
- /***** 设置单选按钮数组 *****/
- // 数组长度和图片数组长度一致
- this.purRadioButton = new RadioButton[this.pictrues.Length];
- for (int i = 0; i < this.purRadioButton.Length; i++) {
- // 初始单选按钮
- RadioButton rb = new RadioButton();
- // 设置显示文本
- rb.Text = string.Format("第{0}帧", i);
- // 设置自动调整尺寸
- rb.AutoSize = true;
- // 控件加入purFlowPane容器内
- this.purFlowPane.Controls.Add(rb);
- // 控件对象存入数组
- this.purRadioButton[i] = rb;
- }
- }
- /// <summary>
- /// 窗体加载事件委托方法
- /// </summary>
- protected override void OnLoad(EventArgs e) {
- base.OnLoad(e);
- // 调用TabControlTabIndexChanged方法, 设置文本标签控件文本
- // 注意, 调用事件委托方法, 第一个参数要传递和事件相关的控件对象引用,
- // 第二个参数如无特殊要求, 传递EventArgs.Empty, 表示没有特殊事件参数
- TabControlTabIndexChanged(this.tabControl, EventArgs.Empty);
- // 启动定时器
- this.timer.Start();
- }
- /// <summary>
- /// 选中卡片标签事件处理委托方法
- /// </summary>
- private void TabControlTabIndexChanged(object sender, EventArgs e) {
- // 获取被选中的卡片标签标题文本, 将文本显示在文本标签控件中
- this.textLabel.Text = string.Format("卡片/"{0}/"被选中", this.tabControl.SelectedTab.Text);
- }
- /// <summary>
- /// 卡片第1页尺寸改变事件委托方法
- /// </summary>
- private void TabControlPage0Resize(object sender, EventArgs e) {
- // 获取到卡片第1页尺寸
- Size tabPageSize = this.tabControl.TabPages[0].Size;
- // 计算图片框位置: 保持在其容器*
- this.pictrueBox.Location = new Point(
- (int)(((float)tabPageSize.Width - (float)this.pictrueBox.Size.Width) / 2.0F),
- (int)(((float)tabPageSize.Height - (float)this.pictrueBox.Size.Height) / 2.0F)
- );
- }
- /// <summary>
- /// 定时器激发事件委托方法
- /// </summary>
- private void TimerTick(object sender, EventArgs e) {
- // 为图片框设置图片数组第pictrueIndex项图片对象引用
- this.pictrueBox.Image = this.pictrues[this.pictrueIndex];
- // 选中相应的单选按钮数组中的单选按钮
- this.purRadioButton[this.pictrueIndex].Checked = true;
- // 计算下一次显示图片的pictrueIndex值
- // 根据playBackCheckBox选中状态, 决定pictrueIndex加1或减1
- if (this.playBackCheckBox.Checked) {
- --this.pictrueIndex;
- } else {
- ++this.pictrueIndex;
- }
- // 如果pictrueIndex数字超出图片数组上限或下限, 修正数字
- if (this.pictrueIndex < 0) {
- this.pictrueIndex = this.pictrues.Length - 1;
- }
- if (this.pictrueIndex == this.pictrues.Length) {
- this.pictrueIndex = 0;
- }
- }
- /// <summary>
- /// 数字滑块改变事件委托方法
- /// </summary>
- private void SpeedTrackBarValueChanged(object sender, EventArgs e) {
- // 设置定时器激发时间间隔和滑块当前数值一致
- this.timer.Interval = this.speedTrackBar.Value;
- }
- /// <summary>
- /// 暂停复选框选中状态改变事件委托方法
- /// </summary>
- private void PauseCheckBoxCheckedChanged(object sender, EventArgs e) {
- // 查看复选框状态
- if (this.pauseCheckBox.Checked) {
- // 复选框选中, 停止定时器
- this.timer.Stop();
- } else {
- // 复选框未选中, 启动定时器
- this.timer.Start();
- }
- }
- }
- /// <summary>
- /// 包括主方法的类
- /// </summary>
- static class Program {
- /// <summary>
- /// 应用程序的主入口点。
- /// </summary>
- static void Main() {
- Application.EnableVisualStyles();
- Application.SetCompatibleTextRenderingDefault(false);
- Application.Run(new MyForm());
- }
- }
- }
本节代码下载
本节代码使用图片下载
本节练习使用的控件较多,布局也较复杂。但通过前面的学习,应该不难做到这样的布局。几个新的控件如PictrueBox用法都比较简单,添加资源按照步骤就可以正确完成。
重点关注动画的实现(297-319行),它展示了定时器的使用方法;各类控件对定时器的控制(324-341行)以及复杂的布局技巧(173-255行),展示了如何通过控件和控件相对位置来计算控件的尺寸。