目前為止,我們已經看到樣式,作為一個Setter元素的集合。當應用一個樣 式時,在Setter元素中描述的設置不會無條件地應用(除非復寫每一個設置的實 例)。另一方面,觸發器是一種在條件中包裝了一個或更多Setter元素的方式, 如果條件為真,相應地Setter元素會被執行,而條件為false的時候,屬性值返 回預先觸發的值。
WPF伴隨著3種你可以在一個觸發器條件中檢查的事情一起發生,依賴屬 性,.NET屬性,.NET事件。頭兩個直接改變基於條件的值,如我所描述的;而最 後一個,一個事件觸發器,被激活於一個事件發生並開始一個引起屬性變化的動 畫。
5.6.1屬性觸發器
觸發器最簡單的形式是屬性觸發器,它將監視一個依賴屬性得到一個確定的 值。例如,如果我們想要使一個按鈕當用戶移動鼠標到這個按鈕上的時候變為黃 色,我們可以這麼做通過注釋IsMouseOver屬性是否有一個true值,正如示例5- 24所示。
示例5-24
<Style TargetType="{x:Type Button}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True" >
<Setter Property="Background" Value="Yellow" />
</Trigger>
</Style.Triggers>
</Style>
一些觸發器在Style.Triggers元素下被分組。在這種情形中,我們添加了一 個Trigger元素到button的樣式中。當isMouseOver屬性為true時,這個按鈕的 Background值會被設為黃色,正如圖5-8所示。
在圖5-8中,你會注意到,只有鼠標當前所在位置的按鈕會被設置黃色背景, 即使其他的按鈕曾經在鼠標下也沒有變色。當觸發器不再為真時,沒有必要著急 於使這個屬性回到原先,例如,監視IsMouseOver為false。WPF依賴屬性系統監 視到屬性觸發器變為無效的時,就會回復到原先設置的值。
圖5-8
可以設置屬性觸發器來監視控件上的任何一個依賴屬性,到樣式的定位,以 及設置控件上的任何依賴屬性到當條件為true的時候。實際上,你可以使用一個 單獨的觸發器來設置你喜歡的多重屬性,如示例5-25。
示例5-25
<Style TargetType="{x:Type Button}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True" >
<Setter Property="Background" Value="Yellow" />
<Setter Property="FontStyle" Value="Italic" />
</Trigger>
</Style.Triggers>
</Style>
在示例5-25中,我們設置背景為黃色和字體樣式為italic,當鼠標在按鈕上 的時候。
在觸發器中不允許設置Style屬性自身。如果你嘗試這麼做,會的到下面的錯 誤:
A Style object is not allowed to affect the Style property of the object to which it applies.
這樣做是有意義的。由於是Style在設置屬性,而且當觸發器不再為true時, 還有參與將這個屬性改為“未設置”,
這看上去有點像轉換你的幽默故事在你 在你著陸之前在開始奮力一跳之 後。
5.6.2多重觸發器
當你可以設置任意多的屬性在你的屬性觸發器的時候,在一個樣式中可能存 在多於一個的觸發器。當對Style.Trigger下的元素進行分組的時候,多重觸發 器表現為每個觸發器各自獨立。
例如,我們可以更新代碼為了鼠標移動到我們的一個按鈕之上,這個按鈕將 被設為黃色,而且一旦該按鈕獲得焦點(tab或箭頭移動到焦點范圍內),這個 按鈕會被設為綠色,如示例5-26所示。圖5-9顯示了這樣的結果:一個單元格得 到了焦點,另一個則有鼠標盤旋。
示例5-26
<Style TargetType="{x:Type Button}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True" >
<Setter Property="Background" Value="Yellow" />
</Trigger>
<Trigger Property="IsFocused" Value="True" >
<Setter Property="Background" Value="LightGreen" />
</Trigger>
</Style.Triggers>
</Style>
圖5-9
入如果多重觸發器設置了相同的屬性,會采取最後的一個。例如,在圖5-9中 ,如果一個按鈕得到焦點,同時鼠標盤旋其上,它的背景會變為綠色,因為 IsFocused觸發器是觸發器列表的最後一個。
5.6.3多重條件的屬性觸發器
如果你想要檢查多於一個的屬性在一個觸發器條件激活前,例如,鼠標盤旋 在一個按鈕上,按鈕內容為空。你可以聯合這些條件在一個多重條件屬性觸發器 中,如示例5-27所示。
示例5-27
<Style TargetType="{x:Type Button}">
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True" />
<Condition Property="Content" Value="{x:Null}" />
</MultiTrigger.Conditions>
<Setter Property="Background" Value="Yellow" />
</MultiTrigger>
</Style.Triggers>
</Style>
多重條件屬性觸發器檢查所有屬性的值是否被明確指定,而不僅是其中的一 個。這裡,我們監視按鈕盤旋以及內容不為空*,反映了游戲的邏輯:只有點擊 在一個空的單元格才認為是有效的一次移動。
*null值通過xaml擴展標記設置,你可以從附錄A中獲取更多信息。
圖5-10顯示了當鼠標盤旋在一個空單元格上,黃色高亮顯示,;圖5-11顯示 了當鼠標盤旋在一個非空單元格上,不具備黃色高亮顯示。
圖5-10
圖5-11
當一個用戶與顯示你的程序狀態的控件進行交互時,屬性觸發器時非常值得 關注的。然而,我們也相要關注程序的狀態何時改變,正如一個特定的玩家走了 一步,和相應地更新樣式的設置。為了這點,我們有了數據觸發器。
5.6.4數據觸發器
不同於屬性觸發器——只檢查WPF依賴屬性,數據觸發器可以檢查任何任何舊 有地.NET對象屬性。當屬性觸發器普遍用於檢查WPF可視化元素地屬性時,數據 觸發器通常用於檢查用於內容的非可視化對象的屬性,正如示例5-28的 PlayerMove對象。
示例5-28
<Window.Resources>
<Style TargetType="{x:Type Button}">
</Style>
<Style x:Key="CellTextStyle" TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=PlayerName}" Value="X">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=PlayerName}" Value="O">
<Setter Property="Foreground" Value="Green" />
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="MoveNumberStyle" TargetType="{x:Type TextBlock}">
</Style>
<DataTemplate DataType="{x:Type l:PlayerMove}">
<Grid>
<TextBlock
TextContent="{Binding Path=PlayerName}"
Style="{StaticResource CellTextStyle}" />
<TextBlock
TextContent="{Binding Path=MoveNumber}"
Style="{StaticResource MoveNumberStyle}" />
</Grid>
</DataTemplate>
</Window.Resources>
DataTrigger元素使用在Style.Trigger元素中,正如屬性觸發器一樣,這裡 可以有多於一個是活動的在任何時刻。一個屬性觸發器在顯示內容的可視化元素 的屬性上操作,而數據觸發器在其自身進行操作。這我們這種情形下,每一個單 元格的內容都是一個PlayerMove對象。在我們的兩個數據觸發器中,我們綁定了 PlayerName屬性。如果值為X,我們就設置前景色為紅色;如果為O,就設置為綠 色。
當你使用數據觸發器的時候要小心。在我們的示例中,我們獲得Button類型 的樣式和名為CellTextStyle的樣式作為潛在的選擇。這一章我已經寫過兩次了 ,每次開始我們都把數據觸發器放在按鈕樣式而不是數據模板的樣式中。數據觸 發器是基於內容的,因此確定你把它們放入了你的內容樣式中,而不是你的控件 樣式。
既然我們已經轉移到數據模板——在圖5-5中用編程方式設置了樣式,我們還 沒有為每個設置顏色,但是數據觸發器給我們帶來了之前的那個特征,伴隨著我 們創建的所有其他樣式,正如圖5-12所示。
不同於屬性觸發器——以來於依賴屬性通知的改變,數據觸發器以來於標准 的數據改變通知模式的實現。這些內嵌到.NET的模式將在第4張討論,例如 InotifyPropertyChanged。由於每一個PlayerMove對象都是常量,我們不必實現 這個模式,但是你還要使用數據觸發器,偶而你需要在你的自定義內容類中實現 它。
數據觸發器另一種便利的特征是,不必顯示地檢查null的內容。如果內容為 空,這個觸發器條件會自動為false,這是為什麼應用程序試著從一個空的 PlayerMove中獲取PalyerName屬性,卻沒有崩潰的原因。
圖5-12
5.6.5多重條件數據觸發器
正如屬性觸發器使用MultiTrigger元素編譯到“and”條件中,數據觸發器也 可以這麼編譯,使用MultiDataTrigger元素。例如,如果我們想要監視游戲的第 10次移動,確保是玩家O,以及做一些特殊的事情,正如示例5-29。
示例5-29
<?Mapping XmlNamespace="sys" ClrNamespace="System" Assembly="mscorlib" ?>
<Window xmlns:sys="sys">
<Style x:Key="MoveNumberStyle" TargetType="{x:Type TextBlock}">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=PlayerName}" Value="O" />
<Condition Binding="{Binding Path=MoveNumber}">
<Condition.Value>
<sys:Int32>10</sys:Int32>
</Condition.Value>
</Condition>
</MultiDataTrigger.Conditions>
<Setter Property="Background" Value="Yellow" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Window>
示例5-29唯一看上去有點奇怪的是,使用了映射語法從.NET編譯集中引進了 System命名空間。我們這麼做是為了可以把10作為一個整型而不是字符型;否則 ,多重條件數據觸發器不會正確匹配我們的MoveNumber屬性。示例5-29的多重條 件數據觸發器將MoveNumber的背景色設置為黃色,意味著一個根據——祝賀這次 特殊的移動,而正常的TTT並沒有,但是你可以使用多重條件數據觸發器為你自 己的種類。
5.6.6事件觸發器
屬性觸發器監視依賴屬性的值,數據觸發器監視CLR屬性的值,而事件觸發器 監視事件。當一個事件發生,例如Click事件,事件觸發器響應激發一個相關的 動畫行為。動畫相當具有挑戰性,所以要在第8章介紹。示例5-30闡明了一個簡 單的動畫,當一個空單元格被點擊時,這個格子在5秒內從深黃色轉換到白色。
示例5-30
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="White" />
<Style.Storyboards>
<ParallelTimeline Name="CellClickedTimeline" BeginTime="{x:Null}">
<SetterTimeline Path="(Button.Background). (SolidColorBrush.Color)">
<ColorAnimation From="Yellow" To="White" Duration="0:0:5" />
</SetterTimeline>
</ParallelTimeline>
</Style.Storyboards>
<Style.Triggers>
<EventTrigger RoutedEvent="Click">
<EventTrigger.Actions>
<BeginAction TargetName="CellClickedTimeline" />
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
</Style>
添加一個動畫到一個樣式需要兩件事情。首先是一個StoryBoard,帶有命名 的時間線來描述你想要發生的。在我們的情形中,我們的動畫是,按鈕的背景筆 刷顏色在5秒內從黃色轉換到白色。
對於任何帶有嵌入式路徑的動畫的屬性,需要有一個顯示的屬性設置,創建 頂級的嵌套。示例5-30中,這意味著我們需要一個Setter元素為Background屬性 。如果沒有創建頂級的嵌套,在運行期就不會有任何動畫效果。
第二件需要做的是事件觸發器開始了時間線。在我們這種情形,當用戶點擊 應用了CellButtonStyle樣式的按鈕,如Button的Background,我們開始了這個 動作——在StoryBoard中由命名的時間線描述。
寫到這裡,如果你有一個事件觸發器和一個多條件屬性觸發器,具有相同的 原型動畫,如Button的Background,確保你把多條件觸發器放在了xaml文件中的 事件觸發器前面;否則,你會得到一個荒謬的運行期錯誤。
這個動畫的結果是,顯示了黃色的各種陰影,經過點擊的效果如圖5-13所示 。
圖5-13
屬性觸發器和數據觸發器使你設置屬性——當屬性改變的時候。事件觸發器 使你觸發事件——當事件發生的時候。這兩套觸發器是相當不同的,例如,你不 能使用事件觸發器設置一個屬性;或者使用屬性觸發器和數據觸發器激活一個事 件。所有這些觸發器使你增加了一定程度的交互性到應用程序——以一種極好的 聲明式的方式,具有極少代碼或沒有代碼。