1
第 4 章 WPF 控件
本章内容
·内容控件
·Items 控件
·Range 控件
·文本和墨水控件
任何一个完整的现代图形呈现框架都应该为开发人员提供标准控件集,便于他们快速地构建传统用户
界面。WPF 包含许多这种控件,你已经在前几章中看到了一些,本章将详细介绍主要的内建控件,并突出
说明每一种控件的不同之处。
本书所有图例中展示的 WPF 控件都是基于 Windows Vista 默认的 Aero 桌面主题的。大部分 WPF 控件
包含的外观截然不同,这是因为 WPF 所装配的主题 DLL 都包含了基于不同 Windows 主题下的控件模板,
相关的主题如下:
·Aero(默认的 Windows Vista 主题)
·Luna(默认的 Windows XP 主题)
·Royale(Windows XP Media Center 2005 版以及 Windows XP Tablet PC 版主题)
·Classic(Windows 2000 以及以后版本都包含了这个主题)
如图 4-1 所示,标准 WPF 按钮控件在不同 Windows 支持的主题下有不同的外观。如果 WPF 遇到了一
些未知的新主题,比如微软在 2006 年发布的 Zune 主题,WPF 会自动把它当作 Classic 主题来对待。
图 4-1 WPF 在不同主题下的外观(另见彩插)
在大部分情形下,不同外观之间的差别是非常细微的。当然,可以使用自定义控件模板把控件设计成
完全不同的外表。在第 10 章中会讨论这部分内容。
本章中包含的控件可以根据它们所对应的继承关系(见封三)分成 4 个不同的种类:
·内容控件
·Items 控件
·Range 控件
2
·文本和墨水控件
4.1 内容控件
内容控件是只允许包含单一项(item)的简单控件。内容控件都继承自 System.Windows.
Controls.ContentControl,它们拥有只含有一项的 Object 类型的 Content 属性(参看第 2 章中演示的 Button
控件。)
因为内容控件的单一项可能是任意对象,所以它可能包含一个很大的对象树。它只能有一个直接子节
点。除了 Content 以外,ContentControl 类另外一个有趣的成员是 HasContent 布尔型属性。如果 Content 为
空,它会返回 false,反之则返回 true。
为什么内容控件要定义 HasContent 属性?直接检查 Content == null 和检查 HasContent == flase 一样方便。
欢迎进入 WPF API 的世界,WPF API 与一般的.NET API 还是有所不同的。
从 C#语言角度来看,HasContent 属性是冗余的;但是从 XAML 语言角度来看,它却是非常有用的。
比如说,当 HasContent 属性为 true 时,使用属性触发器去设置不同的属性值会变得非常容易。
Content 和任意对象
既然一个内容控件的 Content 可以为任意的托管对象,那么如果你把它的内容指定为一个没有 UI 的对
象,比如 Hashtable 或者 RegistryKey 的实例,你会自然而然地想了解接下来将会发生什么。这种形式的内
容控件的工作原理是非常简单的。如果你指定给它的内容派生自 WPF 的 UIElement 类,它会调用 UIElement
的 OnRender 方法来绘制自己;否则,如果在控件中使用了一个数据模板(参见第 9 章),这个模板会提供
一个相应的渲染自己的行为;如果前两个条件都不满足,内容的 ToString 方法将会被调用并且返回一串在
TextBlock 里面渲染过的文本。
WPF 自带的内容控件有 3 大种类:
·按钮
·简单容器
·带头(header)的容器
Window 类也是一种内容控件,对它的详细研究请参见第 7 章。
4.1.1 按钮
按钮控件可能是最熟悉且最主要的用户界面元件。图 4-1 所示的 WPF Button 控件在本书中已经被包装
成许多不同的外观。
尽管每个人都明白什么是按钮控件,但是对它在 WPF 里的精确定义可能还不是很清楚。基本的按钮控
件是一个可以被单击但不能被双击的内容控件。这种行为其实被一个叫作 ButtonBase 的抽象类所捕获,其
他许多不同的控件也都从 ButtonBase 抽象类继承。
3
ButtonBase 类包含了 Click 事件和用来定义单击含义的逻辑。好比一个标准的 Windows 按钮控件,一
个 Click 事件可以是由鼠标左键被按下再释放后所产生的,也可以是从键盘(按钮选中后按回车或者空格键)
获得。
可能你想在按钮按下时做些什么(当鼠标左键或者空格键被按下,但还没有被释放时),为此 ButtonBase
类定义了布尔类型的 IsPressed 属性。
ButtonBase 最有意思的特点是 ClickMode 属性。通过使用 ClickMode 枚举值来修改该属性值,可以准
确控制何时触发 Click 事件。这个枚举类型的值为:Release(默认)、Press 和 Hover。尽管设置标准按钮的
ClickMode 属性会让用户很困惑,但是这个属性对于那些拥有自定义样式的、看起来完全不像标准按钮的按
钮却是一种福音。在这种情况下,按下一个对象和点击一个对象会产生相同的结果。
有些控件完全继承自 ButtonBase 类,这些控件是:
·Button
·RepeatButton
·ToggleButton
·CheckBox
·RadioButton
以下几节将分别对每个控件做详细讲解。
1.Button
WPF 的按钮类只在 ButtonBase 现有基础上加了个简单的概念:是一个取消按钮还是一个默认按钮。这
种机制对于对话框来说是一种捷径。如果对话框上一个按钮的 Button.IsCancel 被设置成了 True,你点击了
那个按钮,这个对话框会自动关闭。如果 Button.IsDefault 被设置成了 true,除非焦点不在这个按钮上,否
则按回车就会触发这个按钮的 Click 事件。
按钮控件的 IsDefault 和 IsDefaulted 属性有什么差别?
IsDefault 属性是一种可读写属性,可以决定按钮是否应该是默认的。IsDefaulted 名字取得比较烂,它是
只读属性,表示按钮的某一状态,比如按回车键会使它处于点击状态。换句话说,当 IsDefault 是 true 的时
候,IsDefaulted 只能是 true,并且不管是默认按钮还是 TextBox(同时 AcceptsReturn 被设为 false)获得了
焦点。这个特性使得你可以通过按回车键来触发默认按钮的点击事件,即使这时焦点在 TextBox 上。
如何才能以编程方式点击一个按钮?
按钮和其他 WPF 控件一样,有一个属于 System.Windows.Automation.Peers 命名空间的 peer 类来支持
UI Automation:ButtonAutomationPeer,可以这样使用它:
4
UI Automation 类有许多对于自动测试相当有用的成员。
2.RepeatButton
RepeatButton 的行为基本和 Button 一样,除了它会在按钮一直被按着的情况下触发点击事件(它没有
Button 的取消和默认这两种行为,因为它直接继承自 ButtonBase)。产生点击事件的频率主要由 RepeatButton
的 Delay 以及 Interval 这两个属性的值决定;这两个属性的默认值分别是 SystemParameters.KeyboardDelay
以及 SystemParameters.KeyboardSpeed。默认情况下,Repeat- Button 看起来和 Button 一样(如图 4-1 所示)。
RepeatButton 的行为乍听起来很奇怪,其实它对于计算一段时间内某个按钮被按次数的增减统计是非常
有用的。例如,当用鼠标点在上面,保持长时间不释放时,滚动条底下的那个按钮就可以展示被重复按下
的行为,或者你在做一个数字的“上下”控件(WPF 没有内建这种控件),你会使用两个 RepeatButton 去控
制数字的大小。RepeatButton 属于 System.Windows.Controls.- Primitives 命名空间,你应该会把它和其他成
熟的控件一起使用,而不是单独使用它。
3.ToggleButton
ToggleButton 是一种在点击时可以保留其状态的“粘性”按钮(它也没有 Button 控件的取消行为以及默
认行为)。第 1 次点击它的时候,IsChecked 属性会被设为 true;再点击一次,就被设为了 false。ToggleButton
的默认外观和 Button 以及 RepeatButton 完全一样。
ToggleButton 还有一个 IsThreeState 属性,如果把它设为 true 的话,IsChecked 就会有 3 种值:true、false
或者 null。事实上,IsChecked 是 Nullable<Boolean>类型的。第 1 次点击 ToggleButton 会把 IsChecked 设为
true,第 2 次则把它设为 null,第 3 次把它设为 false,依此类推。
除了 IsChecked 属性以外,ToggleButton 分别为每一个 IsChecked 的值定义了不同的事件:true 对应
Checked 事件,false 对应 Unchecked 事件,null 对应 Indeterminate 事件。ToggleButton 没有单独的
IsCheckedChanged 事件似乎很奇怪,但是 3 种不同的事件便于声明。
和 RepeatButton 一样,ToggleButton 也属于 System.Windows.Controls.Primitives 命名空间,这意味着
WPF 的设计器并不期望用户在没有额外定制的情况下直接使用 ToggleButton。这个设想很自然,但在后面
的章节里你会看到,如何在一个 ToolBar 里直接使用 ToggleButton。
4.CheckBox
CheckBox 是一种常见的控件,如图 4-2 所示。但是请等一下……这一节难道不是在讨论按钮控件吗?
没错,不过请先想一下 WPF CheckBox 的特性:
·它拥有由外部提供的内容(所以不算标准的复选框)。
5
·它可以区分是被鼠标点击还是被键盘点击。
·它可以在被点击时记录选中(checked)或者未选中(unchecked)状态。
·它支持 3 种状态模式,分别为选中(checked)、不确定(indeterminate)、未选中(unche- cked)。
听起来觉得似曾相识吗?应该很熟悉吧,因为 CheckBox 和 ToggleButton 只有外观上的差别!CheckBox
除了在继承 ToggleButton 时重写了控件默认样式以及视觉外观以外,其他都与 ToggleButton 完全一样,如
图 4-2 所示。
图 4-2 WPF CheckBox
5.RadioButton
RadioButton是另一种从 ToggleButton 继承过来的控件,但它的特殊在于支持互斥性。当多个 RadioButton
被放在一个组里,一次只有一个可以被选中,把一个 RadioButton 选中就会自动把组中其他所有的
RadioButton 设为不选中。事实上,用户不能直接通过选中 RadioButton 来取消对它本身的选中,这一动作
只能通过编程方式来完成。因此,RadioButton 是为多项选择所设计的。RadioButton 的默认外观如图 4-3 所
示。
图 4-3 WPF RadioButton
把几个 WPF RadioButton 放在同一组里是非常直观的。默认情况下,任何 RadioButton 被自动分成一个
组,共享同一个逻辑父元素。比如,以下几个 RadioButton 一次只有一个可以被选中:
如果需要用自定义的方法对 RadioButton 作分组,那么可以用它的 GroupName 属性,这个属性是字符
串类型的,任何拥有相同 GroupName 的 RadioButton 会被分在同个组里(只要它们在逻辑上属于同一个源)。
因此可以把属于不同父元素的 RadioButton 放在一个组中: