WPF視窗充滿了各種元素,但這些元素中只有一部分是控制項。在WPF領域,控制項通常被描述為與用戶交互的元素——能接收焦點並接受鍵盤或滑鼠輸入的元素。明顯的例子包括文本框和按鈕。然而,這個區別有時有些模糊。將工具提示視為控制項,因為它根據用戶滑鼠的移動顯示或消失。將標簽視為控制項,因為它支持記憶碼(mnemo ...
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類中與字體有關的屬性
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;