2 卡片式容器
卡片容器(TabControl)是这样一种容器:它本身包含多个容器(类似于Panel类型的面板容器),但同时只能显示其中的一个,并隐藏其余的容器。
卡片容器的用途很广泛,目前流行的浏览器都使用卡片式方式在一个浏览器内可以同时打开多个页面,例如:
图1 卡片式浏览的Firefox浏览器
卡片式容器解决了一个这样的问题:当我们需要在同一个窗体内放置为数众多的控件时,如果不能让使用者有选择性的看到一些控件并隐藏其它控件,那么使用者将会觉得很烦——一个界面上如果有超过10个以上的控件时,用户将变得无所适从。
.net的卡片容器由TabPage(卡片页)组成,卡片页具有一个卡片标签,它类似一个按钮,显示卡片页的标题。点击某个卡片标签,将在卡片容器内显示对应的卡片页。每个卡片页像一个面板,可以容纳其它的控件。
整个卡片容器就像我们在文具店随处可见的带标签的记事本。可以通过点击某个卡片页的标签,显示所需要的一部分控件,隐藏的其它控件。
卡片页的Text属性用来访问卡片页的标签文本,例如:
-
- TabPage page = new TabPage();
-
- page.Text = "动画播放";
通过TabControl容器的TabPages属性,我们可以访问到卡片容器内所有的TabPage容器,TabPages属性类型是一个TabPage的集合,所以可以有如下代码:
-
- TabPage page = this.tabControl.TabPages[0];
-
- page = this.tabControl.TabPages[1];
-
- foreach (TabPage tp in this.tabControl.TabPages) {
-
- }
上例中介绍了如何访问卡片容器中的卡片页。这里假设我们的类里有一个TabControl类型的字段tabControl。
通过如下代码,可以为卡片容器增加一个新的卡片页:
-
- TabPage page = new TabPage("动画播放");
-
- this.tabControl.TabPages.Add(page);
上述代码为卡片容器增加了一个标签上显示“动画播放”的卡片页。
可以使用简化的方式,即TabPages属性Add方法的重载:
- this.tabControl.TabPages.Add("动画播放");
上述代码为卡片容器增加了一个标签为“动画播放”的卡片页,其后可以使用TabPages属性来访问它。即在Add方法内部实例化了TabPage对象并设置了其Text属性。
- this.tabControl.TabPages.Add("play", "动画播放");
上述代码同样是在Add方法内部实例化了TabPage对象并设置了其Text属性,并且还设置了TabPage对象的Name属性。通过Name属性,可以更方便的查找到这个TabPage对象。例如:
-
- if (this.tabControl.TabPages.ContainsKey("play")) {
- TabPage page = this.tabControl.TabPages["play"];
-
- this.tabControl.TabPages.RemoveByKey("play");
- }
-
- this.tabControl.TabPages.RemoveAt(0);
也可以通过如下代码批量增加卡片页:
-
- TabPage[] pages = {
- new TabPage("动画播放"),
- new TabPage("选项"),
- };
-
-
- this.tabControl.TabPages.AddRange(pages);
卡片页就可以看做一个普通的容器对象。卡片页完全充满卡片容器除了卡片标签外的剩余空间,并且不具备布局功能,可以像操作一个Panel容器一样来操作卡片页,例如:
-
- this.tabControl.TabPages[0].Resize += new EventHandler(TabControlPage0Resize);
-
-
- this.tabControl.TabPages[1].Padding = new Padding(50);
-
-
- this.tabControl.TabPages[1].Controls.Add(this.tablePaneInPage1);
上述代码为卡片容器中的第1个卡片页设置了尺寸变化时的事件委托方法,并为第2个卡片页设置了容器和其内容直接的空白,最后将一个表格布局管理面板容器加入到了第2个卡片页中(假设表格布局面板为tablePaneInPage1字段)。这些事件、属性和方法都是一个容器对象所特有的标志。
可以看到,卡片容器的卡片页即可以使用TabPages属性的索引来访问,也可以通过一个字符串来访问。
卡片容器的Alignment属性可以设置卡片标签的位置,属性值是一个TabAlignment枚举的枚举项,可以取值Top(标签位于卡片容器顶端,默认值)、Bottom(标签位于卡片容器底部)、Left(标签位于卡片容器左侧)和Right(标签位于卡片容器右侧)。例如:
-
- this.tabControl.Alignment = TabAlignment.Top;
上述代码设置卡片标签在卡片容器的顶端。
当我们通过卡片标签选择新的卡片页时,卡片容器会得到一个事件通知,可以使用一个委托方法处理这个事件,例如:
-
- this.tabControl.SelectedIndexChanged += new EventHandler(TabControlTabIndexChanged);
则事件处理方法为:
- 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
- using System;
- using System.Collections.Generic;
- using System.Drawing;
- using System.Resources;
- using System.Windows.Forms;
-
-
- using Edu.Study.Graphics.TabControlContainer.Properties;
-
-
- namespace Edu.Study.Graphics.TabControlContainer {
-
-
-
-
- class MyForm : Form {
-
-
- private TableLayoutPanel mainTablePane;
-
-
- private Label textLabel;
-
-
- private TabControl tabControl;
-
-
- private PictureBox pictrueBox;
-
-
- private Image[] pictrues;
-
-
- private Timer timer;
-
-
- private int pictrueIndex = 0;
-
-
- private TableLayoutPanel tablePaneInPage1;
-
-
- private TrackBar speedTrackBar;
-
-
- private CheckBox playBackCheckBox;
-
-
- private CheckBox pauseCheckBox;
-
-
- private FlowLayoutPanel purFlowPane;
-
-
- private RadioButton[] purRadioButton;
-
-
-
-
- public MyForm() {
- this.Text = "卡片容器测试";
-
-
- this.mainTablePane = new TableLayoutPanel();
-
- this.mainTablePane.Dock = DockStyle.Fill;
-
- this.mainTablePane.RowCount = 2;
- this.mainTablePane.ColumnCount = 1;
-
- this.mainTablePane.RowStyles.Add(new RowStyle(SizeType.Absolute, 70.0F));
-
- this.mainTablePane.RowStyles.Add(new RowStyle(SizeType.AutoSize));
-
- this.mainTablePane.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
-
- this.Controls.Add(this.mainTablePane);
-
-
- 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;
-
- this.textLabel.Font = new Font(this.Font.FontFamily, 23.0F);
-
- this.mainTablePane.Controls.Add(this.textLabel, 0, 0);
-
-
- 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);
-
- this.tabControl.TabPages[0].Resize += new EventHandler(TabControlPage0Resize);
-
- this.tabControl.TabPages[1].Padding = new Padding(50);
-
- this.tabControl.Alignment = TabAlignment.Top;
-
- this.mainTablePane.Controls.Add(this.tabControl, 0, 1);
-
-
- this.pictrueBox = new PictureBox();
-
-
- this.pictrueBox.Size = Resources.T0.Size;
-
- this.pictrueBox.BorderStyle = BorderStyle.Fixed3D;
-
- 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 {
-
- 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();
-
- this.timer.Interval = 50;
-
- this.timer.Tick += new EventHandler(TimerTick);
-
-
- string[] labelText = {
- "速度:",
- "倒放:",
- "暂停:",
- "帧列表:"
- };
-
-
- this.tablePaneInPage1 = new TableLayoutPanel();
- this.tablePaneInPage1.Dock = DockStyle.Fill;
-
- this.tablePaneInPage1.ColumnCount = 2;
- this.tablePaneInPage1.RowCount = labelText.Length + 1;
-
- this.tablePaneInPage1.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 110.0F));
-
- this.tablePaneInPage1.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
-
- this.tabControl.TabPages[1].Controls.Add(this.tablePaneInPage1);
-
-
-
- int rows = 0;
-
- foreach (string s in labelText) {
-
- Label lb = new Label();
- lb.Dock = DockStyle.Fill;
-
- lb.TextAlign = ContentAlignment.TopRight;
-
- lb.Margin = new Padding(0, 8, 0, 0);
-
- lb.Text = s;
-
- this.tablePaneInPage1.Controls.Add(lb, 0, rows++);
-
- 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;
-
- 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);
-
- this.tablePaneInPage1.Controls.Add(this.speedTrackBar, 1, 0);
-
-
-
- this.playBackCheckBox = new CheckBox();
-
- this.playBackCheckBox.Checked = false;
-
- this.tablePaneInPage1.Controls.Add(this.playBackCheckBox, 1, 1);
-
-
- this.pauseCheckBox = new CheckBox();
-
- this.pauseCheckBox.Checked = false;
-
- this.pauseCheckBox.CheckedChanged += new EventHandler(PauseCheckBoxCheckedChanged);
-
- 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;
-
- 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;
-
- this.purFlowPane.Controls.Add(rb);
-
- this.purRadioButton[i] = rb;
- }
- }
-
-
-
-
- protected override void OnLoad(EventArgs e) {
- base.OnLoad(e);
-
-
-
-
- TabControlTabIndexChanged(this.tabControl, EventArgs.Empty);
-
-
- this.timer.Start();
- }
-
-
-
-
- private void TabControlTabIndexChanged(object sender, EventArgs e) {
-
- this.textLabel.Text = string.Format("卡片/"{0}/"被选中", this.tabControl.SelectedTab.Text);
- }
-
-
-
-
- private void TabControlPage0Resize(object sender, EventArgs e) {
-
- 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)
- );
- }
-
-
-
-
- private void TimerTick(object sender, EventArgs e) {
-
- this.pictrueBox.Image = this.pictrues[this.pictrueIndex];
-
-
- this.purRadioButton[this.pictrueIndex].Checked = true;
-
-
-
- if (this.playBackCheckBox.Checked) {
- --this.pictrueIndex;
- } else {
- ++this.pictrueIndex;
- }
-
-
- if (this.pictrueIndex < 0) {
- this.pictrueIndex = this.pictrues.Length - 1;
- }
- if (this.pictrueIndex == this.pictrues.Length) {
- this.pictrueIndex = 0;
- }
- }
-
-
-
-
- private void SpeedTrackBarValueChanged(object sender, EventArgs e) {
-
- this.timer.Interval = this.speedTrackBar.Value;
- }
-
-
-
-
- private void PauseCheckBoxCheckedChanged(object sender, EventArgs e) {
-
- if (this.pauseCheckBox.Checked) {
-
- this.timer.Stop();
- } else {
-
- this.timer.Start();
- }
- }
- }
-
-
-
-
- static class Program {
-
-
-
- static void Main() {
- Application.EnableVisualStyles();
- Application.SetCompatibleTextRenderingDefault(false);
- Application.Run(new MyForm());
- }
- }
- }
本节代码下载
本节代码使用图片下载
本节练习使用的控件较多,布局也较复杂。但通过前面的学习,应该不难做到这样的布局。几个新的控件如PictrueBox用法都比较简单,添加资源按照步骤就可以正确完成。
重点关注动画的实现(297-319行),它展示了定时器的使用方法;各类控件对定时器的控制(324-341行)以及复杂的布局技巧(173-255行),展示了如何通过控件和控件相对位置来计算控件的尺寸。