1. 程式人生 > >WPF觸發器(非資料庫中的觸發器)

WPF觸發器(非資料庫中的觸發器)

原文: WPF觸發器(非資料庫中的觸發器)

一、什麼是觸發器?
觸發器(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事件。該事件不僅表示點選滑鼠左鍵導致的按鈕被按下這一行為,也表示預設按鈕在使用者按下回車鍵時被按下或當前具有輸入焦點的按鈕在使用者按下空格鍵時被按下這一行為。另外一個例子是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>