WPF提供了一系列内嵌控件。其中大多数符合标准的你已经熟悉的Windows控件类型。注意到没有一个是包装在旧的Win32控件外面的控件。虽然它们看上去就像是它们的副本,它们都是与生俱来的WPF控件。这意味着它们为WPF在本书中描述的功能提供了完全的支持,包括样式、独立的分辨率、数据绑定、合成、以及充分的集成支持WPF的图形化能力。
3.3.1按钮
按钮是用户可以点击的控件。点击的结果由应有程序的开发者胜任,但是共同的期望依赖于按钮的类型。例如,点击一个用来表示选择的CheckBox或RadioButton,并未正常拥有任何即时的效果来真实反映那个选择。与之对比,点击一个正常的按钮,通常会有即时的效果。
使用按钮是直接的。示例3-11显示了按钮元素的标记。
示例3-11
元素的内容(这种情形下是“
Button ”文字)用于按钮的标题。点击事件的句柄通过一个属性明确地指定。这表明了xaml 的后台代码必须包含一个在标签中明确指定名称的方法,正如示例3-12 所示(当然我们还可以附属事件句柄,通过给按钮一个x :Name ,以及使用正常的C# 事件句柄语法。)示例3-12
可选择性的,一个按钮常规的属性可以被设定,在这种情形中,当按钮被点击时,指定的命令将会被调用。示例
3-13 显示了一个按钮调用标准ApplicationCommands.Copy 命令。示例3-13
图
3-4 显示了3 种由WPF 提供的按钮类型。这些按钮都派生于一个共同的基类,ButtonBase ——这个类派生于ContentControl ,意味着它们全部支持内容模型:你不受限制于为一个按钮使用简单的文本作为一个标签。图3-4
如图3-5所示,你可以使用无论任何你喜欢的内容,虽然你仍能获取默认的按钮外观在你选择的内容周围或者旁边(如果你希望取代整个按钮的外观,而不是仅定义它的标题,你可以使用一个控件模板。参考第5章获取更多关于模板的信息。)
图3-5
虽然这些按钮派生于共同的ButtonBase基类,RadioButton和CheckBox通过ToggleButton类间接派生于这个基类。这个结基类定义了一个IsChecked属性,指出了用户是否检查了按钮。
Radio按钮正常使用于组中,其中每次只能选择一个按钮。使用RadioButtonList元素来指出一组radio按钮作为一个组,正如示例3-14所示。
示例3-14
WPF提供了允许从一定范围中选取一个值的控件。它们都提供了一个类似的外观和用法:显示了一个跟踪,指定了范围,以及一个可以拖动的“thumb”——用来调整值。有两个Slider控件,HorizontalSlider和VerticalSlider,如图3-6所示。有两个ScrollBar控件,HorizontalScrollBar和VerticalScrollBar,如图3-7所示。主要的不同是一个约定而不是功能,ScrollBar通常用于与某些滚动的可视化区域协力工作;而slider是用来调整值的。
图3-6
图3-7
Slider和ScrollBar在使用上是非常类似的。它们都派生于一个共同的基类RangeBase。这个类提供了Minimum和Maxmum属性——定义了一定范围的由控件表示的值;提供了Value属性保持当前选定的值;还定义了SmallChange和LargeiChange属性——由Value改变的多少来决定,当使用方向键调整的时候,或者是相应的PageUp和PageDown键。LargeChange值还用于当slider的一部分track在thumb被点击的任何一边。Slider控件有一个固定大小的thumb,而ScrollBar上的thumb可以在大小上改变。如果slider用于联合一个可滚动的视图,thumb的大小——相当于track,与可视化区域的大小——相对于全部可滚动区域,是成比例的。例如,thumb是大约scrollbar的长度和宽度的1/3,这就指定了1/3可滚动区域在当前视图中。
你可以通过ViewPortSize属性控制scrollbar的thumb大小。无论何处这可以是从0到Maximum属性值。如果ViewPortSize与Maximum相同,thumb将会填充track,并且不能移动。ViewPortSize越小,thumb也会越小。
如果你想提供一个可滚动的视图——一个更大的用户区域,你可以非常规的直接使用scrollbar控件。这通常更容易使用ScrollViewer控件。
一个ScrollViewer元素有一个单独的子元素。示例3-15使用了Ellipse元素,但是它可能是任何事情。如果你想放置多个元素在一个scrollable视图中,你可以嵌入它们在一个面板中(在第2章讨论)
示例3-15
如果
ScrollViewer 的内容大于可利用的空间,ScrollViewer 会提供滚动条允许用户滚动内容,如示例3-8 所示。默认的,ScrollViewer 提供了一个垂直滚动条,并不是一个水平滚动条。在示例3-15 中,HorizontalScrollBarVisiablity 属性设置为Auto ,指定了水平滚动条在需要的时候添加上去。图3-8
这个Auto可见性——我们为水平滚动条所选择的,不同于默认的垂直的行为。VerticalScrollBarVisiablity默认为Visible,意味着这个滚动条总是存在无论是否需要它。
有两种确保滚动条不显示的方式。你可以设置它的可见性为Disabled(默认的水平滚动条)或者Hidden。二者的区别是,Disabled约束了ScrollViewer内容的逻辑上的大小与可利用的空间一样。Hidden允许这个逻辑上的大小是不受约束的,即使用户无法在额外的空间滚动。这可以改变确定的外观样式的行为。
为了检查这些设置如是如何影响ScrollViewer的行为,我们看一下示例3-16发生了什么,当我们改变ScrollViewer属性时。
示例3-16
图3-9显示了示例3-16中的Grid是如何显示在一个ScrollViewer中,当这里有更多充分的空间时。显示了HorizontalScrollBarVisiablity所有的四种选项,在这四种情形中,按钮被伸展了来填充空间。
图3-9
图3-10
图3-10显示了相同的四个排列,但是带有不充分的水平空间。顶部的2个ScrollViewer元素支持水平滚动,带有各自的Visible和Auto属性。正如你希望的,ScrollViewer提供了充分的空间来容纳所有的内容和允许用户在视图中的裁剪部分内滚动。在左下位置,水平的滚动条被设置为Hidden,外观行为是一样的。它排列了元素,好像有充分的空间来容纳所有的内容。唯一的不同是它并没有显示一个滚动条。在右下位置,我们可以看到,Disabled是不同于行为结果的。这里,不仅不显示滚动条,而且水平滚动条完全被Dsiabled了。Grid因此被强制裁剪按钮来适合可利用的空间。
3.3.3文本控件
WPF提供了编辑和显示文本的控件。最简单的文本编辑控件是TextBox。默认的,它允许编辑文本的单独一行;但是通过设置AcceptReturn为true,它可以编辑多行。它提供了标准的基本文本编辑机制:支持选择,系统剪切板集成(剪切、粘贴等),以及支持多重级别的Undo。
示例3-17显示了2中TextBox元素,一个带有默认的设置;另一个是多行的模式。它还显示了类似的PasswordBox,设计用来输入密码。图3-11显示了这些结果。正如你看到的,PasswordBox中的文本被显示为一行星号。这是惯例。为了防止密码被任何人可以在屏幕上看到。PasswordBox还拒绝复制到剪切板内容的能力。
示例3-17
图3-11 TextBox和PasswordBox只支持普通的文本。它们不支持任意种类的内嵌内容——试图嵌入任何而不是普通的文本——会引起一个运行期错误。这使得它们易于在输入和编辑简单数据时使用。TextBox提供了一个Text属性,代表了一个控件的内容,作为一个字符串。
PasswordBox没有Text属性。取代的,它有一个Password属性。它返回一个SecureString类型,而不是返回一个String。它仍然只提供普通的文本值。然而,它还提供了两种机制来防止意外的泄漏密码数据。
首先,它以加密的形式储存字符串。这意味着包含字符串的内存应该被分发到系统分页文件中,它的内容是不可读取的。(.NET在运行时生成了一个随机的密钥,将它存储在一个内存位置中,这个位置是被锁定的,用来防止被修改到分页文件中)。如果使用一个正常的字符串,攻击者能够获取它的内容,通过制作一个你的系统交换文件的副本。各种各样的.NET安全API可以传递一个SecureString,意味着你的代码从不用处理解密的版本。
其次,你可以在Secure上调用Dispose,这个方法会复写密码数据。这意味着即使以其加密的形式,敏感性数据也会按需求被清除。带有一个正常的String,数据能长时间保存在内存中,直到你已经完成了使用,只会被销毁于垃圾收集器开始执行时。
意识到每当你读取Password数据的时候,都会返回一个新的SecureString,因此如果你打算利用按需清除的行为,你必须Dispose在你每次读取这个属性的时候。PasswordBox维护着它内部的字符串复制,当控件被销毁时,将字符串安排给你。
普通文本的简单的是很好的,如果你仅仅需要普通文本作为输入。然而,这样做有时是有用的——允许更加多样化的输入。WPF因此提供了RichTextBox。
RichTextBox非常有弹性的,可以包括几乎任意的内容。示例3-18显示了RichTextBox的标记,包含了文本、图形、控件的混合。结果在图3-12显示。
示例3-18
图3-12 对待非文本化的元素和对待文本的字符有同样的方式,你可以在它们的前后插入文本,或者使用剪切和粘贴来移动文本。Windows 没有为用户定义一个标准的方法,来使用键盘“输入”一个椭圆,因此虽然我们能在标签中预载带有这些元素的RichTextBox ,并没有为用户提供一个添加这类元素的方式。然而,写一个提供了如此便捷的程序,是相当容易的。
示例3-19显示了应用程序使用RichTextBox所用的标签。在屏幕的顶级,它提供了一个包括控件的面板,该控件允许用户添加椭圆和矩形。如图3-13所示。
图3-13 示例3-19
示例3-20
在前面的章节,示例3-19使用了Label控件。它典型地应用于为控件提供一个标题,该控件没有其自身的内嵌标题。最普通的示例是TextBox。Label看起来可能是多余的,由于达到了同样的可视化效果而不用一个完整的控件;你可以只使用底层的TextBlock元素。然而,Label有一个重要的处理焦点的职责。
设计良好的用户界面应该是易于键盘使用的。一种通常的达到目的的方式是提供访问键。访问键是一个关联到控件的字母,从而一旦你按住了Alt键,同时按住访问键,这个行为与你点击当前控件是一样的:当按下Alt键的时候,通过在控件的标题中给相应的字母加下划线,应用程序把它们的访问键告诉用户。(你也可以配置Windows来一直显示访问键的下划线,无论是否按下Alt键)。示例3-19中使用了内部的AccessKey元素,为我们管理这个下划线。
图3-14显示了示例3-9中用户界面一部分的特写,显示了访问键下划线,正如用户会看到的,如果当他按下Alt键的时候。只是一个Button,并不需要特殊的代码,就能使访问键工作。所有你需要做的是,使用AccessKey内嵌的修改器,在控件的标记之中。在这种情形中,使用Alt+R或Alt+E将自动的得到相同的效果,如同点击矩形的或椭圆形的按钮。
图3-14
TextBox面临着比较多的挑战。他们有一个标题,只有显示文本而且这些文本还是可以编辑的。这个标题由一个独立的元素提供在TextBox的左边——这里引进了一个Label。Label控件的意图是为了提供放置标题的地方,同时带有AccessKey元素。当访问键按下的时候,这个Label将会重定向输入到相关的控件,这里是一个TextBox。Label是如何知道重定向访问键到哪一个控件的呢?Label有一个Target属性,指出了访问键的已有目标。我们使用绑定表达式来连接label到它的目标。绑定表达式细节将在第4章讨论。这里的表达式简单地设置了Target属性到相关的命名元素。
3.3.5 Selectors
一些类型的控件运行用户从一组项目中进行选择。在某些情形中,如RadioButtonList和TabControl,经常只有一个选择项。ComboBox扩展了这个功能——没有当前的选项。ListBox 则更加进一步的,具有同时选择多个选项的能力。所有的这些控件从Selector基类继承了共同的列表和选择功能。
最简单的使用这些控件的方式是将内容添加到它们的Item属性。示例3-21显示了ComboBox标签,各种元素被添加到它的Item中。
示例3-21
同样的技术还可以使用在
ListBox 、TabControl 和RedioButtonList 中,正如你在图3-15 中看到的,每一个控件以它们自己的方式代表项。RadioButtonList 为每个项生成一个RadioButton ,使用这些项作为标题。TabControl 把每个元素放进它自己的TabControl 中,为了以其自己的tab 页面来表示。(图3-15 只显示了第一项,但是另外联合的三个贯穿3 个tab 头)。图3-15
所有的Selector控件包装了我们所有的项,为了以一种合适的方式表示它们。虽然这有可能是便利的,在某种情形中,你可能想要更多的控件。例如,显示在上面的TabControl控件,并不是特别的有用。它包装了我们的项,带着没有标题的tab。为了修复这个问题,我们简单地提供了我们自己的TabItem控件,取代以让TabControl元素为我们生这些项。我们可以接着设置Header属性从而控制tab页的标题。这些技术在示例3-22中说明。
示例3-22
图3-16
提供一组固定的元素贯穿Item属性,对tab页和RadioButton来说是有意义的,在你设计用户界面的时候,你想知道需要什么元素。但是这可能不适合ComboBox和列表。为了支持你在运行时决定要显示哪一个选项,所有Selector控件提供了一个折中的方法来导入列表:数据绑定。替代以使用Items,你可以提供一个带有ItemSource属性的数据源对象,以及使用数据样式来决定元素如何显示。这些技术将在第4章和第5章介绍。
不管你是否使用了一组项或一个绑带数据源,你总是可以查找出选中的元素什么时候有改动——通过处理SelectionChanged事件。你可以接着使用SelectedIndex或者SelectedItem属性来查找当前选中了哪一个元素。
3.3.6菜单
很多Windows应用程序通过层级菜单,提供了访问他们功能的方法。这些典型的表现为顶级窗口的主菜单和弹出式“上下文”菜单。WPF提供了两种菜单控件:Menu是永久性可见的菜单(如主菜单);ContextMenu是上下文菜单。
#如今Windows中的菜单被典型的视为不同于其他用户界面的元素。这是一个特殊的句柄类型。在Windows窗体中,大多数可见的元素派生于Control基类,但菜单不是。这意味着菜单趋向于有点非弹性的。为了避免它的缺点,一些用户界面工具选择不用这些Windows内嵌的简单菜单处理。在WPF中,菜单只是一个普通的控件,因此它们不需要特殊的样式和约束。
这两种类型的菜单都以同样的方式生成。它们的内容组成了层级的MenuItem元素。示例3-23创建了一个典型地菜单。结果如图3-17所示。
图3-17
示例3-23
示例3-24
图3-18
每个MenuItem都有一个Header属性。作为Menu的子一级,这个header决定了显示在菜单条上的label。为了一个MenuItem——内嵌在ContextMenu或另一个MenuItem中,Header包括了菜单线的内容。这个Header熟悉允许普通文本,如示例3-23,或者内嵌内容。示例3-25显示了一个菜单项目的修改后版本,暴露了添加结构的能力,从而标记了一个字母作为访问键。图3-19显示了结果。
示例3-25
图3-19
示例3-23的菜单并没有做任何有意义的事情,因为没有明确的事件句柄或命令。有两种方式使你可以钩住MenuItem到一些代码。你可以处理它的Click事件——就像处理一个按钮的Click事件。折中地,你可以在MenuItem上设置Command属性,使用相同的语法——如我们在示例3-8所见。
示例3-26显示了一个Edit子菜单修改后的版本,菜单项都联合到相关的标准命令上。只要焦点落在一个控件上,如TetBox或RichTextBox——理解这些标准命令;这些标准命令将会被处理而无需任何显示的代码。(如果焦点不在这样的控件上,这些命令会简单地向上冒泡。如果没有处理这些命令的事物,就会忽略它们。)
示例3-26
一旦一个菜单的Command有一个联合的键-表示的绑定,WPF会自动地在菜单中展示这个快捷键。由于示例33-26使用了标准的剪切板和undo/redo命令,其产生的菜单就有联合的快捷键,正如你在图3-20所看到的。
图3-20
一旦由于某些原因,你选择不使用WPF的基本命令系统,你仍然可以展示一个快捷键。MenuItem提供了一个InputGestureText属性,让你选择要出现的文本在正常的位置,作为这样一个快捷键。示例3-27显示了一个菜单项同时具有快捷键和访问键。
示例3-27
注意到,
Menu 和ContextMenu 都间接派生于ItemControl ,同一个基类作为所有的Selector 控件。这意味着你可以使用ItemDataSource 属性来导入使用了数据绑定的菜单,而不是使用固定的内容。这将是有用的——如果你想要制作自己的可配置菜单结构。参见第4 章获取更多如何使用数据绑定的细节。3.3.7 工具栏
大多数Windows应用程序在提供菜单的同时,还提供了工具栏。工具栏为频繁使用的操作提供了更快速的访问,因为用户不需要通过菜单系统进行导航,而工具栏在屏幕上一直是可见的,如示例3-21所示。
图3-21
WPF通过ToobarTray和ToolBar支持工具栏。ToobarTray提供了一个容器,你可以向其中添加多个TooBar元素。示例3-28显示了一个简单的例子,这为图3-21提供了标记。示例3-28
在这个例子里,我们使用了一些简单的向量图,用来绘制通常的图标如New和Open等等。这里使用的图形化元素在第7章中更详细的描述。实际上,你可以很少地放置像这样的内嵌的图形。通常你可以希望图画是简单引用到的资源,通过工具栏上的按钮。看第6章获取更多细节。
由于工具栏上的按钮只是正常的Button元素,带有指定的可视化,关于它们的行为,没有什么尤其特殊之处。工具栏很少只提供一个特定的方式排列和呈现控件。你也可以添加其他元素到工具栏,如TextBox或ComboBox。这些将连同按钮一起排列在工具栏上。