經過數十天的忙碌,今天終于有時間寫博客,
前面一章通過介紹有關模板作業方式相關的內容,同時介紹了FrameWorkElement下所有控制元件的模板,接下來將介紹如何構建一個簡單的自定義按鈕,并在該程序中學習有關控制元件模板的一些細節,
通過上一章內容,基本Button控制元件使用ButtonChrome類繪制其特殊的背景和邊框,Button類使用ButtonChrome類而不使用WPF繪圖圖元的一個原因是,標準按鈕的外觀依賴于幾個明顯的特征(是否被禁用、是否具有焦點以及是否正在被單擊)和其他一些更微妙的因素(如當前Windows主題),只使用觸發器實作這類邏輯是笨拙的,
然而,當構建自定義控制元件時,可以不用擔心標準化和主題集成(實際上,WPF不像以前的用戶界面技術那樣強呼叫戶界面標準化),反而能更需要關注如何創建富有吸引力的新穎控制元件,并將他們混合到用戶界面的其他部分,因此,可能不需要創建諸如ButtonChrome的類,而可使用以及學過的元素,設計自給自足的不使用代碼的控制元件模板,
一、簡單按鈕
為應用自定義控制元件模板,只需要設定控制元件的Template屬性,盡管可定義行內模板(通過在控制元件標簽內部嵌入控制元件模板標簽),但這種方法基本沒有意義,這是因為幾乎總是希望為同一控制元件的多個皮膚實體重用模板,為適應這種設計,需要將控制元件模板定義為資源,并使用StaticResource參考該資源,如下所示:
<Button Margin="10" Padding="5" Template="{StaticResource ButtonTemplate}" > A Simple Button with a Custom Template </Button>
通過這種方法,不僅可以較容易地創建許多自定義按鈕,在以后還可以很靈活地修改控制元件模板,而不會擾亂應用程式用戶界面的其余部分,
在這個特定示例中,ButtonTemplate資源放在包含視窗的Resource集合中,然而,在實際應用程式中,可能更喜歡使用應用程式資源,具體原因在下一章介紹的“組織模板資源”中進行討論,
下面是控制元件模板的基本框架:
<Window.Resources> <ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type Button}"> ... </ControlTemplate> </Window.Resources>
在上面的控制元件模板中設定了TargetType屬性,以明確指示該模板是為按鈕設計的,與樣式類似,這總是一個可以遵循的好約定,在內容控制元件(如按鈕)中也需要使用該約定,否則ContentPresenter元素就不能作業,
要為基本按鈕創建模板,需要自己繪制邊框和背景,然后在按鈕中放置內容,繪制邊框的兩種可能的候選方法是使用Rectangle類和Border類,下面的示例使用Border類,將具有圓角的桔色輪廓與引入注目的紅色背景和白色文本結合在一起:
<Window.Resources> <ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type Button}"> <Border BorderBrush="Orange" BorderThickness="3" CornerRadius="2" Background="Red" TextBlock.Foreground="White" Name="Border"> ... </Border> </ControlTemplate> </Window.Resources>
在此主要關注背景,但仍需要一種方法顯示按鈕內容,在以前的學習中,可能還記得Button類在其他控制元件模板中包含了一個ContentPresenter元素,所有內容控制元件都需要ContentPresenter元素——它是標示“在此插入內容”的標記器,告訴WPF在何處保存內容:
<ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type Button}"> <Border BorderBrush="Orange" BorderThickness="3" CornerRadius="2" Background="Red" TextBlock.Foreground="White" Name="Border"> <ContentPresenter RecognizesAccessKey="True"></ContentPresenter> </Border> </ControlTemplate>
在ContentPresenter元素將RecognizesAccessKey屬性設定為true,盡管這不是必需的,但可確保按鈕支持訪問鍵——具有下劃線的字母,可以使用該字母快速觸發按鈕,對于這種情況,如果按鈕具有文本Click_Me,那么當用戶按下Alt+M組合鍵時會觸發按鈕(在標準的Windows設定中,下劃線是隱藏的,并且只要按下Alt鍵,訪問鍵(在此是M鍵)就會具有下劃線),如果為將RecongnizesAccessKey屬性設定為true,就會忽略該細節,并且任何下劃線都將被視為普通的下劃線,并作為按鈕內容的一部分進行顯示,
二、模板系結
該例還存在一個小問題,現在為按鈕添加的標簽將Margin屬性的值指定為10,并將Padding屬性的值指定為5,StackPanel控制元件關注的是按鈕的Margin屬性,但忽略了Padding屬性,使按鈕的內容和側邊擠壓在一起,此處的問題是Padding屬性不起作用,除非在模板中特別注意它,換句話說,模板負責檢索內邊距值并使用該值在內容周圍插入額外的空白,
幸運的是,WPF專門針對該目的設計了一個工具:模板系結,頭能改過使用系結模板,模板可從應用模板的控制元件中提取一個值,在本例中,可使用模板系結檢索Padding屬性的值,并使用該屬性值在ContentPresenter元素周圍創建外邊距:
<ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type Button}"> <Border BorderBrush="Orange" BorderThickness="3" CornerRadius="2" Background="Red" TextBlock.Foreground="White" Name="Border"> <ContentPresenter RecognizesAccessKey="True" Margin="{TemplateBinding Padding}"></ContentPresenter> </Border> </ControlTemplate>
這樣就會得到所期望的效果,在邊框和內容之間添加了一些空白,如下圖顯示了新的簡單按鈕,

模板系結和普通的資料系結類似,但它們的量級更輕,因為它們是專門針對在控制元件模板中使用而設計的,它們只支持單向資料banding(換句話說,它們是從控制元件向模板傳遞資訊,但不能從模板向控制元件傳遞資訊),并且不能用于從Freezable類的派生類的屬性中提取資訊,如果遇到模板系結不生效的情形,可改用具有完整功能的資料系結,
預計需要哪些模板系結的唯一方法是檢查默認控制元件模板,如果查看Button類的控制元件模板,就會發現在模板系結的使用方法上與自定義模板是完全相同的——獲取為按鈕指定的內邊距,并將它轉換成ContentPresenter元素周圍的外邊距,還會發現標準按鈕模板包含另外幾個模板系結,如HorizontalAlignment、VerticalAlignment以及Background,這個簡單的自定義模板中沒有使用這些模板系結,這意味著如果為控制元件設定了這些屬性,對于這個簡單的自定義模板來說,這些設定是沒有效果,
在許多情況下,可不考慮模板系結,實際上,如果不準備使用屬性或者不希望修改模板,就不必系結屬性,例如,當前得簡單按鈕將用于文本的Foreground屬性設定為白色并忽略為Background屬性設定的任何值是合理的,因為前景色和背景色是該該按鈕可視化外觀的固有部分,
可能選擇避免模板系結的另一個原因是——控制元件不能橫好地支持它們,例如,如果為按鈕設定了Background屬性,可能注意到當按鈕被按下時不會連貫地處理該背景色(實際上,這時該背景色消失了,并且被按下的默認外觀替換了),該例中的自定義模板與此類似,盡管還沒有任何滑鼠懸停和滑鼠單擊行為,但一旦添加這些細節,就會希望完全控制按鈕的顏色以及在不同狀態下它們的變化,
三、改變屬性的觸發器
如果測驗上一節創建的按鈕,就會發現它令人十分失望,本質上,它不過是一個紅色的圓角矩形——當在它上面移動滑鼠或單擊滑鼠時,其外觀沒有任何反應,按鈕只是無動于衷,呆在那兒不動,
可通過為控制元件模板添加觸發器來方便地解決這個問題,當一個屬性發生變化時,可使用觸發器改變另一個或多個屬性,在按鈕中至少希望回應IsMouseOver和IsPressed屬性,下面的標記是控制元件模板的修改版本,當這些屬性發生變化時,會改變控制元件的顏色:
<Window.Resources> <ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type Button}"> <Border BorderBrush="Orange" BorderThickness="3" CornerRadius="2" Background="Red" TextBlock.Foreground="White" Name="Border"> <ContentPresenter RecognizesAccessKey="True" Margin="{TemplateBinding Padding}"></ContentPresenter> </Border> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter TargetName="Border" Property="Background" Value="DarkRed"/> </Trigger> <Trigger Property="IsPressed" Value="True"> <Setter TargetName="Border" Property="Background" Value="IndianRed"/> <Setter TargetName="Border" Property="BorderBrush" Value="DarkKhaki"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Window.Resources>
為使該模板能夠作業,還要進行另一項修改,已為Border元素指定一個名稱,并且該名稱被用于設定每個設定其的TargetName屬性,通過這種方法,設定器能更新在模板中指定的Border元素的Background和BorderBrush屬性,使用名稱是確保更新模板特定部分的最容易方法,可創建一條元素型別規則來影響所有Border元素(原因是已經知道在按鈕模板中只有一個邊框),但如果在以后改變模板,這種方法更清晰,也跟更靈活,
在所有按鈕(以及其他大部分控制元件)中還需要另一個元素——焦點指示器,雖然無法改變現有的邊框以天津焦點效果,但是可以很容易地天津另一個元素以顯示是否具有焦點,并且可以簡單地使用觸發器根據Button.IsKeyboardFocused屬性顯示或隱藏該元素,盡管可使用許多方法創建焦點效果,但下面的示例值只天津了一個具有虛線邊框的透明的Rectangle元素,Rectangle元素不能包含子內容,從而需要確保Rectangle元素和其余內容相互重疊,完成該操作最容易得方法是,使用只有一個單元格的Grid空哦關鍵來封裝Rectangle元素和ContentPresenter元素,這兩個元素位于同一個單元格中,
下面是修改后的支持焦點的的模板:
<Window.Resources> <ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type Button}"> <Border BorderBrush="Orange" BorderThickness="3" CornerRadius="2" Background="Red" TextBlock.Foreground="White" Name="Border"> <Grid> <Rectangle Name="FocusCue" Visibility="Hidden" Stroke="Black" StrokeThickness="1" StrokeDashArray="1 2" SnapsToDevicePixels="True"></Rectangle> <ContentPresenter RecognizesAccessKey="True" Margin="{TemplateBinding Padding}"></ContentPresenter> </Grid> </Border> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter TargetName="Border" Property="Background" Value="DarkRed"/> </Trigger> <Trigger Property="IsPressed" Value="True"> <Setter TargetName="Border" Property="Background" Value="IndianRed"/> <Setter TargetName="Border" Property="BorderBrush" Value="DarkKhaki"/> </Trigger> <Trigger Property="IsKeyboardFocused" Value="True"> <Setter TargetName="FocusCue" Property="Visibility" Value="Visible"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Window.Resources>
設定器在此使用TargetName屬性茶盅需要改變的元素,
下圖顯示了使用修改版模板的三個按鈕,第二個按鈕當前具有焦點(通過虛線矩形表示),而滑鼠正好懸停在第三個按鈕上,

為了潤色該按鈕,還需要另一個觸發器,當按鈕的IsEnable屬性變為false是,該觸發器改變按鈕的背景色(也可改變文本的前景色):
<Window.Resources> <ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type Button}"> <Border BorderBrush="Orange" BorderThickness="3" CornerRadius="2" Background="Red" TextBlock.Foreground="White" Name="Border"> <Grid> <Rectangle Name="FocusCue" Visibility="Hidden" Stroke="Black" StrokeThickness="1" StrokeDashArray="1 2" SnapsToDevicePixels="True"></Rectangle> <ContentPresenter RecognizesAccessKey="True" Margin="{TemplateBinding Padding}"></ContentPresenter> </Grid> </Border> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter TargetName="Border" Property="Background" Value="DarkRed"/> </Trigger> <Trigger Property="IsPressed" Value="True"> <Setter TargetName="Border" Property="Background" Value="IndianRed"/> <Setter TargetName="Border" Property="BorderBrush" Value="DarkKhaki"/> </Trigger> <Trigger Property="IsKeyboardFocused" Value="True"> <Setter TargetName="FocusCue" Property="Visibility" Value="Visible"/> </Trigger> <Trigger Property="IsEnabled" Value="False"> <Setter TargetName="Border" Property="TextBlock.Foreground" Value="Gray" /> <Setter TargetName="Border" Property="Background" Value="MistyRose" /> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Window.Resources>
為確保該規則優先與其他相沖突的觸發器設定,應當在觸發器串列的末尾定義它,這樣,不管IsMouseOver屬性是否為true,IsEnabled屬性觸發器都具有優先權,并且按鈕保持未激活狀態的外觀,
下圖設定按鈕不可用時所示的圖片:

四、使用影片的觸發器
觸發器并非局限于設定屬性,當特定屬性發生變化時,還可以使用事件觸發器運行影片,
乍一看,這好像有些曲折,但除了最簡單的WPF控制元件外,觸發器實際上時其他所有WPF控制元件的關鍵要素,例如,考慮到目前位置研究過的按鈕,目前,當滑鼠移到按鈕上時,該按鈕立即從一種顏色切換到另一種顏色,然而,更時髦的按鈕可能使用一個非常短暫的影片從一種顏色昏倒到其他顏色,從而創建微妙但優雅的效果,類似地,按鈕可使用影片改變焦點提示矩形的透明度,當按鈕獲取焦點時將快速淡入到試圖中,而不是驟然顯示,換句話說,事件觸發器允許控制元件更通暢地一點點從一個狀態改變到另一個狀態,從而進一步潤色其外觀,
下面是重新設計的按鈕模板,當滑鼠懸停在按鈕上時,該模板使用觸發器實作按鈕顏色脈沖效果(在紅色和藍色之間不斷切換),當滑鼠離開時,使用一個單獨的持續1秒得影片,將按鈕背景回傳到其正常顏色:
<Window.Resources> <ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type Button}"> <Border BorderBrush="Orange" BorderThickness="3" CornerRadius="2" Background="Red" TextBlock.Foreground="White" Name="Border"> <Grid> <Rectangle Name="FocusCue" Visibility="Hidden" Stroke="Black" StrokeThickness="1" StrokeDashArray="1 2" SnapsToDevicePixels="True"></Rectangle> <ContentPresenter RecognizesAccessKey="True" Margin="{TemplateBinding Padding}"></ContentPresenter> </Grid> </Border> <ControlTemplate.Triggers> <EventTrigger RoutedEvent="MouseEnter"> <BeginStoryboard> <Storyboard> <ColorAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="Background.Color" To="Blue" AutoReverse="True" RepeatBehavior="Forever" Duration="0:0:1"></ColorAnimation> </Storyboard> </BeginStoryboard> </EventTrigger> <EventTrigger RoutedEvent="MouseLeave"> <BeginStoryboard> <Storyboard> <ColorAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="Background.Color" Duration="0:0:0.5"></ColorAnimation> </Storyboard> </BeginStoryboard> </EventTrigger> <Trigger Property="IsPressed" Value="True"> <Setter TargetName="Border" Property="Background" Value="IndianRed" /> <Setter TargetName="Border" Property="BorderBrush" Value="DarkKhaki" /> </Trigger> <Trigger Property="IsKeyboardFocused" Value="True"> <Setter TargetName="FocusCue" Property="Visibility" Value="Visible" /> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Window.Resources>
可使用兩種等價的方法添加滑鼠懸停影片——創建回應MouseEnter和MouseLeave事件的事件觸發器,或創建當IsMouseOver屬性發生變化時添加進入和退出動作的屬性觸發器,最終效果圖如下所示:

該例使用兩個ColorAnimation物件來改變按鈕,下面是可能希望使用EventTrigger驅動的影片只需的其他一些任務:
- 顯示或隱藏元素,為此,需要改變控制元件模板中元素的Opacity屬性,
- 改變形狀或位置,可使用TranslateTransform物件調整元素的位置(例如,稍偏移元素使按鈕具有已被按下的感覺),當用戶將滑鼠移到元素上時,可使用ScaleTransform或RotateTransform物件稍微旋轉元素的外觀,
- 改變光斬訓著色,為此,需使用改變繪制背景色畫刷的影片,可使用ColorAnimation影片改變SolidBrush畫刷中的顏色,也可動態顯示更復雜的畫刷以得到更高級的效果,例如,使用LinearGradientBrush畫刷中的一種顏色(這是默認按鈕控制元件模板執行的操作),也可改變RadialGradientBrush畫刷的中心點,
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/154.html
標籤:WPF
上一篇:[WPF 學習] 10.觸發器
