【WPF学习】第十九章 控件类
WPF窗口充满了各种元素,但这些元素中只有一部分是控件。在WPF领域,控件通常被描述为与用户交互的元素——能接收焦点并接受键盘或鼠标输入的元素。明显的例子包括文本框和按钮。然而,这个区别有时有些模糊。将工具提示视为控件,因为它根据用户鼠标的移动显示或消失。将标签视为控件,因为它支持记忆码(mnemonics,将焦点转移到相关控件快捷键)。
所有控件都继承自System.Windows.Control类,该类添加了一小部分基本的基础结构:
- 设置控件内容对齐方式的能力
- 设置Tab键顺序的能力
- 支持绘制背景、前景和边框
- 支持格式化文本内容的尺寸和字体
一、背景画刷和前景画刷
所有控件都包含背景和前景概念。通常,背景是控件的表面(考虑一下按钮边框内部的白色或灰色区域),而前景是文本。在WPF中,分别使用Background和Foreground属性设置这两个区域(但非内容)的颜色。
自然会认为Background和Foreground属性使用颜色对象。然而,这些属性实际上使用的是更强大的对象:Brush对象。该对象为填充背景和前景内容提供了灵活性,可使用单一颜色(使用SolidColorBrush画刷)或更特殊的颜色(如使用LinearGradientBrush或TileBrush画刷)填充背景和前景。
1、用代码设置颜色
假设希望在名为cmd的按钮内部设置蓝色表面区域。下面是执行这一操作的代码:
cmd.Background=new SolidColorBrush(Colors.AliceBlue);
这行代码使用由简便类Colors的静态属性预定义的颜色,创建了一个新的SolidColorBrush画刷(属性的名称源自大多数Web浏览器支持的颜色名称)。然而将该画刷设置为按钮的背景画刷,从而使按钮的背景被绘制成带有轻微阴影的蓝色。
也可以根据用户的喜好从System.Windows.SystemColors枚举中获取系统颜色。下面是一个示例:
cmd.Background=new SolidColorBrush(SystemColors.ControlColor);
因为经常使用系统画刷,所以SystemControls类还提供了预定义的返回SolidColorBrush对象的属性。下面显示了如何使用这些属性:
cmd.Background=SystemColors.ControlBrush;
正如文档所记录的,这两个示例都存在一个小问题。如果系统颜色在运行这段代码后发生了变化,不会使用新的颜色更新按钮。本质上,代码获取的是当前颜色或画刷的快照。为确保程序能够根据配置的变化进行更新,需要使用动态资源。后面章节会进行详细介绍。
Colors和SystemColors类提供了便捷方法,但这并非设置颜色的唯一方法。也可通过提供R、G、B值(红、绿和蓝)创建Color对象。这三个值中的每一个都是0到255之间的数字:
int red=0; int green=255; int blue=0; cmd.Foreground=new SolidColorBrush(Color.FromRgb(red,green,blue));
也可通过提供Alpha值,并调用Color.FromArgb()方法来创建部分透明的颜色。Alpha值表示完全不透明,而0表示完全透明。
2、在XAML中设置颜色
在XAML中设置背景色和前景色时,可使用一种非常有用的快捷方式。不是定义Brush对象,而是提供颜色名或颜色值。WPF解析器将使用指定的颜色自动创建SolidColorBrush对象,并为前景或背景使用该画刷对象。下面是一个使用颜色名得示例:
<Button Background="Red">A Button</Button>
上面的标记和下面更繁琐的语法使等同的:
<Button>A Button <Button.Background> <SolidColorBrush Color="Red"/> </Button.Background> </Button>
如果想创建不同类型的画刷(如LinearGradientBrush画刷),并使用该画刷绘制背景,那么需要使用较长的格式。
如果希望使用颜色代码,需要使用稍难一点的语法,以十六进制形式设置R、G和B的值。可使用两种格式的任意一种——#rrggbb或#aarrggbb(它们之间的区别是后一种格式包含了alpha值)。因为使用的是十六进制方式,所以只需使用两位数字提供A、R、G和B值。下面的示例使用#aarrggbb方式创建与上面代码片段相同的颜色:
<Button Background="#FFFF0000">A Button</Button>
这里,alpha值是FF(255),红色值时FF(255),而绿色值和蓝色值是0;
使用画刷不仅可设置Background和Foreground属性,还可使用BorderBrush和BorderThickness属性在控件(以及其他元素,如Border元素)周围绘制一条边框。BorderBrush属性使用画刷,而BorderThickness属性使用设备无关单位的边框宽度值。在现实边框前需要设置这两个属性。
二、字体
Control类定义了一小部分与字体相关的属性,这些属性确定文本咋控件中的显示方式。下表列出了这些属性。
表 Control类中与字体有关的属性
名 称 | 说 明 |
FontFamily | 希望使用的字体名称 |
FontSize | 字体的设备无关单位尺寸(每单位表示1/96英寸)。为了支持新的WPF分辨率无关呈现模型,WPF中的字体尺寸和传统的字体尺寸有些不同。通常的Windows应用程序使用点数度量字体,在标准PC监视器上假定一点等于1/72英寸。如果想将WPF字体尺寸转换为自己更熟悉的点尺寸,可使用一个简便技巧——乘以3/4。例如,传统的38点字体等于WPF中48单位的尺寸 |
FontStyle | 由FontStyle对象表示的文本脚本。可从FontStyles类的静态属性中获取需要的预定义FontStyle对象,包括Normal、Italic或Obliqur字母(Oblique是一种在没有所需斜体的计算机上创建斜体字的“艺术”方式。字母来自正常字体,并使用变换进行倾斜。这样得到的结果通常并不理想 |
FontWeight | 由FontWeight对象表示的文本粗细,可从FontWeights类的静态属性中获取需要的预定义FontWeight对象。Bold是这些属性中最明显的一个,但有些字体还提供了其他变种,如Heavy、Light和ExtraBold等。 |
FontStretch | 字体的拉伸或压缩程序,由FontStretch对象表示。可从FontStretchs类的静态属性中获取需要的预定义FontStretch对象。例如,UltraCondensed将字体减至正常宽度的50%,而UltraExpanded将它们扩展到原来的200%。字体拉伸到一个OpenType特性,很多字体都不支持该特性(要测试该属性,可尝试使用确实支持它的Rockwell字体) |
1、字体家族
字体家族(font family)是相关字体的集合——例如,Arial Regular、Arial Bold、Arial Italic以及Arial Bold Italic字体都是Arial字体的家族的一部分。尽管每种字体分别定义排版规则和字符,但操作系统仍能识别出它们的相关的。因此,可使用Arial Regular字体配置元素,将FontWeight属性设置为Bold,但一定要使WPF将其转换为Arial Bold字体。
当选择字体时,必须提供完整的字体家族名称,如下所示:
<Button name="cmd" FontFamily="Times New Roman" FontSize="18">A Button</Button>
也可以使用代码:
cmd.FontFamily="Times New Roman"; cmd.FontSize="18";
当确定FontFamily时,不能使用缩写的字符串。这意味着不能使用Times或Times New代替全名Times New Roman。
还可以用字体的全名得到斜体或粗体,如下所示:
<Button name="cmd" FontFamily="Times New Ronman Bold">A Button</Button>
然而,仅使用字体家族名并设置其他属性(如FontStyle和FontWeight属性)得到所需的变体更清晰,也更灵活。例如,下面的标记将FontFamily属性设置为Times New Roman,并将FontWeight属性设置为FontWeights.Bold;
<Button name="cmd" FontFamily="Times New Roman" FontWeight="Bold">A Button</Button>
2、文本装饰和排版
有些元素还可以通过TextDecorations和Typography属性,支持更高级的文本控制。这些属性可以修饰文本。例如,可使用TextDecorations类中的静态属性设置TextDecorations属性。该类仅提供4中修饰,每种修饰都可以为文本添加几类线,包括BaseLine、OverLine、Strikethrough和Underline。Typography属性更高级,通过该属性可以访问只有某些字体才会提供的特殊字体变种。这方面的例子包括不同的数字对齐方式、连字(在相邻字母之间的连接)和小音标(caps)。
对于大多数情况,TextDecorations和Typography特征指用于流文档内容——用于创建丰富的可读文档。然而,这些属性可以用于TextBox类。此外,TextBlock元素也支持他们,TextBlock元素是Label控件的轻量级版本,对于现实少量可换行的文本内容,TextBlock元素是非常完美的。尽管你可能不喜欢对TextBox控件使用文本修饰或改变它的排版,但可能希望在TextBlock元素中使用下划线。如下所示:
<Button TextDecorations="Underline">Underlined Text</Button>
3、字体继承
当设置任何字体属性时,属性值都会流经嵌套的对象。例如,如果为顶级窗口设置FontFamily属性,窗口中的所有控件都会得到相同的FontFamily属性值(除非为控件明确设置了不同的字体)。这种做法之所以可行,是因为字体属性是依赖项属性,并且依赖项属性能够提供的特性之一就是属性值继承——这是在嵌套的控件中传递字体设置的魔力所在。
有必要指出,属性值继承能够流经那些根本就不支持相应属性的元素。例如,设想创建包含StackPanel面板的窗口,在StackPanel面板中有三个Label控件。可为窗口设置FontSize属性,因为Windows类继承自Control类。但不能为StackPanel面板设置FontSize属性,因为它不是控件。但如果设置了窗口的FontSize属性,属性值仍然会“经过”StackPanel面板,到达其内部的标签,并改变标签的字体尺寸。
与字体设置一样,其他几个基本属性也是用属性值继承。在Control类中,Foreground属性使用继承。Background属性不使用(然而,默认背景是空引用,大多数控件将其呈现为透明背景。这意味着仍会显示父元素的背景)。在UIElement类中,AllowDrop、IsEnabled以及IsVisible属性都使用属性继承。在FrameworkElement中,CultureInfo和FlowDirection属性也使用属性值继承。
4、字体替换
设置字体时务必谨慎,确保选择的字体在用户计算机上已经存在。然而,WPF没有通过字体回调系统提供一点灵活性。可将FontFamily属性设置为由逗号分隔的字体选项列表。WPF将按顺序遍历该列表,尝试查找在列表中指定的一种字体。
下面列举一个示例,该例试图使用Technical Italic字体,但如果该字体不存在,就使用ComicSans MS或Arial字体:
<Button FontFamily="Technical Italic,ComicSan MS,Arial">A Button</Button>
如果某个字体家族的名称中确实包含一个逗号,那么需要通过在一行中将其包含两次来转义该逗号。
顺便提一下,使用System.Windows.Media.Fonts类的静态SystemFontFamilies集合,可获得在当前计算机上已安装的所有字体的列表。
foreach(FontFamily fontFamily in Fonts.SystemFontamilies) { lstFonts.Items.Add(fontFamily.Source); }
FontFamily对象还允许检查其他细节,如行间距和关联的字体。
5、字体嵌入
处理不常见字体的另一种选择是在应用成功需中嵌入字体。通过嵌入字体,应用程序就永远不会出现找不到所需字体这一问题。
嵌入过程非常简单。首先向应用程序添加字体文件(通常是具有.ttf扩展名得文件),并将Build Action选项设置为Resource(为设置该属性,可在Visual Studio的Solution Explorer中选择字体文件,并在Properties窗口中改变它的Build Action属性)。
接下来,当使用字体时,需要在字体家族名称之前添加字符序列”./#”,如下所示:
<Label Name="tst" FontSize="20" FontFamily="./#Bayern" >This is an embedded font</Label>
WPF将”./”字符解释为“当前文件夹”。为理解该字符串序列的含义,需要进一步了解与XAML打包系统相关的内容。
可以在“./”字符序列之后提供文件名称,但通常添加数字记号(#)和字体的实际家族名。在上面的示例中,嵌入的字体名为Bayern。
6、文本格式化模式
WPF中的文本渲染和旧式的基于GDI的应用程序的文本渲染有很大区别。很大一部分区别是由于WPF的设备无关显示系统造成的,但WPF中的文本渲染也得到了显著增强,能更清晰地显示文本,在LCD监视器上尤其如此。
然而,WPF文本渲染具有一个众所周知的缺点。当使用较小的文本尺寸时,文本会变得模糊,并会显示一些令人讨厌的问题(例如边缘周围的颜色干扰)。使用GDI文本显示时不会发生这些问题,原因是GDI使用很多技巧来优化小文本的清晰度。例如,GDI能够修改小字母的形状,调整他们的位置,并在像素边界对齐所有内容。浙西步骤导致字体失去了其特殊的性质,当当处理极小的文本时,可在屏幕上得到更好的阅读模式。
那么如何修复WPF的小文本显示问题呢?最好增大文本(在96dpi的监视器上,使用大约15设备无关单位的文本尺寸,这个问题就会消失),或使用具有足够的分辨率,从而能够清晰显示任何尺寸文本的高dpi显示器。但是因为这些选择往往逃离了实际,所以WPF还具有选择使用与GDI类型的文本渲染能力。
为了使用GDI风格的文本渲染,为显示文本的元素(例如TextBlock或Label)增加了TextOptions.TextFormattingMode附加属性,并将其设置为Display(而不是标准值Ideal)。下面是一个例子:
<Window x:Class="Controls.GdiTextRendering" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="GdiTextRendering" Height="300" Width="300"> <StackPanel Margin="10"> <TextBlock FontSize="12" Margin="5"> This is a Test. Ideal text is blurry at small sizes. </TextBlock> <TextBlock FontSize="12" Margin="5" TextOptions.TextFormattingMode="Display"> This is a Test. Display text is crisp at small sizes. </TextBlock> </StackPanel> </Window>
TextFormattingMode属性仅仅是针对小尺寸文本的解决方案,记住这一点很重要。如果为更大的文本(超过15点的文本)使用该属性,文本将不会同样清晰,间隔将不会同样均衡,并且字体将不会被同样准确呈现。而且如果结合旋转、缩放和改变外观的变换使用文本,应当总是使用WPF的标准文本显示模式。因为针对显示文本的GDI风格的优化是在所有变换之前应用的。一旦应用变换,结果将不再对齐到像素边界,文本的显示将变得模糊不清。
三、鼠标光标
对于任何应用程序而言,一个常见任务是调整鼠标光标以指示当应用程序正处于繁忙状态或指示不同控件的工作方式。可为任何元素使用Cursor属性以设备鼠标指针,该属性继承自FrameworkElement类。
可以通过System.Windwos.Input.Cursor对象来表示每个光标。获取Cursor对象的最简易方法是使用Cursors类的静态属性,它们包含了所有标准的Windows鼠标光标,如沙漏光标、手庄光标、调整尺寸的箭头光标等。下面的示例将当前窗口的鼠标光标设置为沙漏光标:
this.Cursor=Cursors.Wait;
现在,将鼠标移到当前窗口上时,鼠标指针会变成大家属性的沙漏图标。
如果使用XAML设置鼠标光标,就不需要直接使用Cursors类。这是因为Cursor属性的类型转换器能识别属性名称,并从Cursors类中检索对应的鼠标光标。这意味着当鼠标位于某个按钮上时,为了显示“帮助”光标(箭头和问号的组合),可按如下方式编写标记:
<Button Cursor="Help">Help</Button>
有时可能设置相互重叠的光标。对于这种情况,会使用最特殊的光标。例如,可为一个按钮额包含按钮的窗口设置不同的光标。当鼠标移到按钮上时,将显示为按钮设置的光标,而对于窗口中的其他区域则显示为窗口设置的光标。
但有一个例外,通过使用ForceCursor属性,父元素可覆盖子元素的光标设置。将该属性设置为true时,会忽略子元素的Cursor属性,父元素的光标会被应用到内部的所有内容。
如果希望为应用程序每个窗口中的每个元素应用光标设置,使用FrameworkElement.Cursor属性将不起作用。相反,需要使用静态的Mouse.OverrideCursor属性,该属性覆盖每个元素的Cursor属性:
Mouse.OverrideCursor=Cursors.Wait;
为了移除应用程序范围的光标覆盖设置,需要将Mouse.OverrideCursor属性设置为null。
最后,WPF完全支持自定义光标。可使用普遍的.cur光标文件,也可使用.ant动画光标文件。要使用自定义的光标,需要为Cursor对象的构造函数传递光标文件的文件名或包含光标数据的流:
Cursor customCursor=new Cursor(Path.Combine(applicationdir,"stopwatch.ani"); this.Cursor=customCursor;
Cursor对象不直接支持URI资源语法,通过该语法,其他WPF元素(如Image对象)可使用保存在编译过的额程序集中的文件,然而,可方便地为应用程序添加光标文件作为资源,然后作为可用于构造Cursor对象的数据流检索该资源。诀窍是使用Application.GetResourceStream()方法:
StreamResourceInfo sri=Application.GetResourceStream(new Uri("stopwatch.ani",UriKind.Relative)); Cursor customCursor=new Cursor(sri.Stream); this.Cursor-customCursor;