1. 程式人生 > >【WPF學習】第十九章 控制元件類

【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類中與字型有關的屬性

 

   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;

&n