一、什麼是觸發器?
觸發器(Trigger)就是當某種條件滿足後即完成相應邏輯功能的一部分程式組成。在目前的WPF中,Trigger一共有三種類型,它們分别是:
(1)屬性觸發器:其對應的類是Trigger。它在特定關聯屬性發生變化時被觸發。
(2)資料觸發器:其對應的類是DataTrigger。它在特定的CLR類型所記錄的值發生變化時被觸發。
(3)事件觸發器:其對應的類是EventTrigger。它将在特定的路由事件發生時被觸發。
在WPF中,每一個可以使用觸發器的類中都會有一個Triggers屬性。擁有這個屬性的類有:FrameworkElement,Style,DataTemplate和ControlTemplate。
注意:FrameworkElement類隻支援EventTrigger。這是因為微軟還沒有完成它對其他兩類觸發器的支援。
如果程式中需要使用屬性觸發器或資料觸發器的功能,軟體開發人員就需要使用設定樣式觸發器的方法對觸發器進行一次包裝,再将該樣式應用在FrameworkElement類的執行個體上。是以就現在來說,Trigger和EventTrigger僅可以用在控件模闆或樣式中,而DataTrigger則隻能用在資料模闆之中。同時,為了支援對複雜觸發條件的表示,WPF還引入了MultiTrigger和MultiDataTrigger完成對與邏輯的支援。如果想用觸發器表示邏輯,軟體開發人員可以通過将多個觸發器同時放置到Triggers屬性中完成。
二、觸發器使用準則
不論是上面的哪種觸發器,都不能脫離WPF對使用者界面進行定義的三個準則。而這三個準則不僅導緻觸發器成為了WPF的一部分,更重要的是,還完成了對觸發器使用規範的定義。
(1)元素合成
常用的,指定targetType.比如将觸發源定義為Button類執行個體的好處是:軟體在處理Button類執行個體的滑鼠左鍵消息的同時也就處理了Image類執行個體的滑鼠左鍵消息。元素合成對觸發器使用的影響不僅如此。實際上WPF中的各個控件都是由其他界面元素組成的,比如組成按鈕控件的Border,ButtonChrome等。那麼在使用XAML定義一個控件的外觀,也就是該控件的ControlTemplate的時候,軟體開發人員就需要考慮觸發器消息源的位置。
(2)界面與行為分離
一個界面上的功能,而與背景程式的業務邏輯完全沒有關系。是以軟體開發人員需要在XAML中使用一種方法完成該功能。這個方法就是使用觸發器。
(3)選擇合适的觸發條件
在WPF中,使用者可以發現許多貌似重複的事件以及函數。比如IInputElement接口已經實作了表示滑鼠左鍵點選的MouseLeftButtonDown事件,而在ButtonBase類中WPF又為相同行為添加了Click事件。該事件不僅表示點選滑鼠左鍵導緻的按鈕被按下這一行為,也表示預設按鈕在使用者按下Enter鍵時被按下或目前具有輸入焦點的按鈕在使用者按下空格鍵時被按下這一行為。另外一個例子是TextBox類不僅有GotFocus這一事件,更有GotKeyboardFocus,GotStylusCapture和GotMouseCapture等事件。也就是說,Click事件以及GotFocus事件都是具有更強大功能的事件,而且可以預計的是,各種WPF控件中還有許多這樣的類似情況。是以在XAML中定義觸發器的時候,軟體開發人員一定要考慮清楚觸發器的實際觸發條件。
三、觸發器類的繼承結構
TriggerBase類是一個虛基類。該類直接派生自DependencyObject類,并隻引入了兩個新的屬性:EnterActions和ExitActions。這兩個屬性分别表示所偵聽的屬性觸發目前觸發器時以及離開觸發狀态時所要執行的動作。但是,由于EventTrigger表示發生事件的一個時間點,而并不是保持在某一種狀态的一段時間,是以EventTrigger并不支援對該屬性的使用。為了賦予EventTrigger相同的功能,WPF為它添加了Actions屬性。
示例:
<Style x:Key="ButonStyle" TargetType="Button">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="0.25" Duration="0:0:1"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:1"/>
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</Style.Triggers>
</Style>
上面這段樣式表示,當滑鼠移上的時候,Button變得不透明,離開後又逐漸恢複原樣。
四、Setter類的使用
Setter類是非常容易使用的。它具有三個常用的屬性:TargetName,Property和Value。這三個屬性分别表示需要設定屬性所在執行個體的名稱、需要進行設定的屬性和需要将該屬性設定的值。但需要注意的是,被設定的屬性必須是關聯屬性。當Trigger或Style中設定了TargetType的時候,XAML可以直接指定需要設定的屬性而省略對象的類型。但在沒有指定TargetType的情況下,Setter中對TargetType類型的Property屬性的設定就必須使用TargetType.Property的形式。例如,當需要使用Setter元素設定按鈕控件的背景顔色為藍色時,軟體開發人員就可以使用下面的XAML語句:
<Setter Property="Button.Background" Value="Blue"/>
從MSDN對Setter類的基類SetterBase的介紹中可以看到,Setter類的基類SetterBase不隻有一個派生類。除了Setter類之外,SetterBase類的派生類還有一個EventSetter類。EventSetter類用來完成對事件處理函數的定義。例如,若想讓一個Button類執行個體在滑鼠移動到其上時運作OnMouseEnter函數,軟體開發人員就可以使用下面的代碼:
<EventSetter Event="Button.MouseEnter" Handler="OnMouseEnter"/>
EventSetter無疑是一個不太友好的設計。而且在不同地方使用不同的EventSetter的情況下,軟體開發人員并沒有一個好的辦法判斷各個事件處理函數被執行的先後次序。而在一個以事件作為驅動的程式中,無法對事件響應函數的執行順序進行控制無疑是一件最危險的事情。
五、各種觸發器的使用
1.屬性觸發器
首先來看看屬性觸發器。屬性觸發器在指定的屬性具有指定的值時,執行它所包含的一系列Setter完成對其他屬性的設定。而當該屬性不再是該指定值時,所有的屬性設定将被恢複到前一狀态。
樣式:
<Style x:Key="TextBoxStyle1" TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="Text" Value="text">
<Setter Property="Background" Value="Aqua"/>
</Trigger>
</Style.Triggers>
</Style>
應用:
<TextBlock Width="50" Style="{StaticResource TextBoxStyle1}" Text="text">
</TextBlock>
在包含上例代碼的程式中,如果使用者在文本輸入框中輸入"text",輸入框的背景顔色将變成綠色。完成這種控制邏輯的就是在Style中定義的屬性觸發器Trigger。在Trigger的聲明中,對Trigger各屬性的設定聲明了Trigger被觸發的條件:當Text屬性為字元串"text"的時候,執行Setter中對屬性的設定,即将背景顔色變成綠色。
4.資料觸發器
除了Trigger類可以用來偵聽屬性的變化外,軟體開發人員還可以使用DataTrigger完成對任意類型的CLR資料變化的偵聽。是以,DataTrigger類不僅可以完成Trigger類的所有功能,更可以運作非關聯屬性的更改觸發邏輯。DataTrigger類一共引入了三個參數:Binding,Value和Setters。當需要設定資料觸發器偵聽的資料源時,軟體開發人員應該以通過綁定對Binding屬性指派的方式來完成。即如果需要使用DataTrigger完成上面對TextBox背景顔色進行更改的功能。
<Style x:Key="TextBoxStyle2" TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Text}" Value="123">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
需要注意的是:雖然軟體開發人員可以使用DataTrigger完成對任意CLR類型資料變化的偵聽,但Setter隻能對關聯屬性進行設定。并且XAML不能在Setter中對Style屬性進行更改。其原因是:觸發器可以在樣式中進行定義。當一個在樣式中定義的觸發器更改了其所在執行個體的樣式時,WPF怎麼繼續處理觸發器中剩餘的設定?為了避免這個問題,WPF禁止在觸發器中對樣式進行設定。
5.事件觸發器
WPF中還提供另一種觸發器。該觸發器的觸發條件就是一個事件的發生。該觸發器所對應的類為EventTrigger,即事件觸發器。該類從TriggerBase類派生後隻添加了三個屬性:Actions,RoutedEvent和SourceName。軟體開發人員可以通過SourceName屬性指定激活該觸發器的元素名稱。而RoutedEvent屬性則記錄激活該觸發器的事件。Actions是一個隻讀屬性,表示觸發器被觸發時需要執行的動作。
<EventTrigger RoutedEvent="Mouse.MouseEnter">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Duration="0:0:0.2"
Storyboard.TargetProperty="Width"
To="65" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
6.或邏輯觸發器
當需要表示或邏輯關系時,軟體開發人員可以簡單地将多個觸發器并列。當某一個觸發器所辨別的條件滿足時,該觸發器所包含的行為将執行,導緻使用這個觸發器的使用者界面元素執行個體的屬性改變。如果在前面的例子中,軟體設計師不僅希望TextBox 的背景顔色在使用者輸入為"text"時為綠,也希望背景在使用者輸入為"text."時為綠,示例如下:
<TextBox TextWrapping="Wrap" Margin="5">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Text}"Value="text">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Text}" Value="text.">
<Setter Property="Background" Value="Blue"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
在或邏輯關系中,觸發器的各屬性比對可能在同一時間被滿足。在這種情況下,觸發器對狀态的設定同時生效。在各個觸發器對屬性的設定發生沖突時,WPF将按照後聲明的觸發器所制定的規則對屬性進行設定。如下:
<Button Content="Press Me!">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Style.Triggers>
<Trigger Property="Button.IsMouseOver" Value="True">
<Setter Property="Button.Foreground" Value="Blue"/>
</Trigger>
<Trigger Property="Button.IsPressed" Value="True">
<Setter Property="Button.Foreground" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
當使用者将滑鼠移動到按鈕上面并使用左鍵對按鈕進行點選的時候,按鈕的顔色将是紅色,而不是藍色。因為在按鈕被按下之前,IsMouseOver屬性的值為True,而在按鈕被按下時IsPressed屬性的值也變為True,是以按照後聲明優先的決定方式,WPF将設定該按鈕的背景顔色為紅色。
7.與邏輯觸發器
如果要表示與邏輯關系,軟體開發人員就需要使用MutiTrigger或MutiDataTrigger。在使用這兩種觸發器時,軟體開發人員需要向它們的Conditions集合中添加觸發條件。假設軟體需要下面一種功能:當TextBox中所記錄的字元串是"text"并且滑鼠在TextBox之上時,TextBox的背景顔色将變成綠色。示例如下:
<TextBox TextWrapping="Wrap" Margin="5">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Text" Value="text"/>
<Condition Property="IsMouseOver" Value="True"/>
</MultiTrigger.Conditions>
<Setter Property="Background" Value="Aqua"/>
</MultiTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>