WPF提供了很重要的一個東西就是綁定Binding, 它幫助我們做了很多事情,這個我們在WPF學習之綁定這篇裡邊有講過。對於Binding我們可以設置其綁定對象,關系,並通過某種規則去驗證輸入,或者轉換值等等,這一切的背後是省去了很多我們需要自己去處理的代碼。而對於WPF最主要表現的東西—渲染UI,當然是我們必須去了解和把握的了。美工設計了很多效果,並把其設計成樣式展現(很大程度上我們應該認為Style也是一種資源),而作為程序員的我們不應該只是簡單的拿來這些拼湊的效果,根據程序的邏輯和用戶的操作來動態的展現效果才是我們能發揮它對界面渲染的更好途徑。Trigger就給我們提供了很好的途徑去結合這些元素。
觸發器,從某種意義上來說它也是一種Style,因為它包含有一個Setter集合,並根據一個或多個條件執行Setter中的屬性改變。因為復用的緣故,Styles是放置觸發器的最好位置。但對於每個FrameworkElement來說都有Triggers集合,你也可以放在Triggers集合裡。觸發器有三種類型:
· 屬性觸發器Property Trigger:當Dependency Property的值發生改變時觸發。
· 數據觸發器Data Trigger: 當普通.NET屬性的值發生改變時觸發。
· 事件觸發器Event Trigger: 當路由時間被觸發時調用。
1.屬性觸發器(Property Trigger)
屬性觸發器是WPF中最常用的觸發器類型,因為我們前邊說過依賴屬性具有垂直變更通知的功能,所以在使用屬性觸發器時會很方便,而且因為WPF中每個控件超過2/3的屬性都是依賴屬性,所以它用到的場合更多。屬性觸發器是在當某個依賴屬性的值發生變化時觸發執行一個Setter的集合,當屬性失去這個值時,這些被處罰執行的Setter集合會自動被撤銷。
例如,下邊的例子設置了當鼠標放置於按鈕之上懸停時,按鈕的外表會發生變化。注意,屬性觸發器是用Trigger標識的。
<Style x:Key="buttonMouseOver" TargetType="{x:Type Button}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="RenderTransform">
<Setter.Value>
<RotateTransform Angle="10"></RotateTransform>
</Setter.Value>
</Setter>
<Setter Property="RenderTransformOrigin" Value="0.5,0.5"></Setter>
<Setter Property="Background" Value="#FF0CC030" />
</Trigger>
</Style.Triggers>
</Style>
屬性觸發器還經常被用在做數據驗證時用來顯示驗證錯誤信息。在WPF學習之綁定裡的Validation部分我們附有用屬性觸發器來判斷是否有驗證錯誤並顯示相應驗證錯誤信息的示例。
<TextBox Style="{StaticResource validateTextBoxStyle}">
<TextBox.Text>
<Binding UpdateSourceTrigger="PropertyChanged" Path="Department">
<Binding.ValidationRules>
<local:JpgValidationRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
…..
<Style x:Key="validateTextBoxStyle" TargetType="{x:Type TextBox}">
<Setter Property="Width" Value="300" />
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="Background" Value="Red"/>
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
2.數據觸發器DataTrigger
數據觸發器和屬性觸發器除了面對的對象類型不一樣外完全相同。數據觸發器是來檢測非依賴屬性------也就是用戶自定義的.NET屬性-----的值發生變化時來觸發並調用符合條件的一系列Setter集合。
下邊的示例演示了在綁定的ListBox裡如果某個User對象符合某種特點(Role=Admin),則以突出方式顯示這個對象。這裡就用了DataTrigger,因為我們需要檢測的是User對象的屬性Role,這個對象是自定義的非可視化對象並且其屬性為普通.NET屬性。
<Page.Resources>
<clr:Users x:Key="myUsers" />
<DataTemplate DataType="{x:Type clr:User}">
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
...
</Page.Resources>
<StackPanel>
<ListBox Width="200"
ItemsSource="{Binding Source={StaticResource myUsers}}" />
</StackPanel>
主要的部分定義在了Style中,其針對的是每個ListBox的項,當其被綁定的數據的屬性Role為Admin時,突出顯示:
<Style TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Role}" Value="Admin">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
3.事件觸發器 Event Trigger
事件觸發器,顧名思義是在某個事件被觸發時來調用這個觸發器的相關操作。因為WPF提供了用XAML來標記對象,事件等,所以其提供了一些在普通.NET開發中看似沒用的屬性例如IsMouseOver, IsPressed等,這是為了XAML來用的,使其可以很方便的通過某個屬性來判斷狀態,也方便了Property Trigger的應用。而作為事件觸發器來說,它所做的事情和Property Trigger類似,不過是它的內部不能是簡單的Setter集合,而必須是TriggerAction的實例。
以下示例演示了如何應用Event Trigger當鼠標點擊按鈕時,讓按鈕的陰影效果發生變化。
<Button Margin="15" Width="200" Name="myButton">
Click Me to Animate Drop Shadow!
<Button.BitmapEffect>
<!-- This BitmapEffect is targeted by the animation.-->
<DropShadowBitmapEffect x:Name="myDropShadowBitmapEffect" Color="Black" ShadowDepth="0" />
</Button.BitmapEffect>
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<!-- Animate the movement of the button.-->
<ThicknessAnimation
Storyboard.TargetProperty="Margin" Duration="0:0:0.5"
From="50,50,50,50" To="0,0,50,50" AutoReverse="True" />
<!-- Animate shadow depth of the effect.-->
<DoubleAnimation
Storyboard.TargetName="myDropShadowBitmapEffect"
Storyboard.TargetProperty="ShadowDepth"
From="0" To="30" Duration="0:0:0.5"
AutoReverse="True" />
<!-- Animate shadow softness of the effect.As
the Button appears to get farther from the shadow,
the shadow gets softer.-->
<DoubleAnimation
Storyboard.TargetName="myDropShadowBitmapEffect"
Storyboard.TargetProperty="Softness"
From="0" To="1" Duration="0:0:0.5"
AutoReverse="True" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
4.MultiDataTrigger & MultiTrigger
截至目前我們討論的都是針對單個條件的觸發器,也就是說當某一個條件滿足時就會觸發。而現實中我們可能需要滿足很多個條件時才觸發一系列操作,這個時候就需要用到MultiDataTrigger或MultiTrigger。MutliDataTrigger和MultiTrigger都具有一個Conditions集合用來存放一些觸發條件,這裡的Condition之間是and的關系,當所有條件都滿足時,Setter集合才會被調用。根據名字就可以看清楚:MultiDataTrigger用來實現多個數據觸發器(只用於普通.NET屬性)滿足條件時調用;MultiTrigger用來實現多個屬性觸發器(用於依賴屬性)滿足條件時調用。
以下示例僅當按鈕的IsEenabled屬性為true,並且可見時(Visibility=Visible)會以醒目的方式顯示,否則當IsEnabled屬性為false時將以灰色顯示。
<Style TargetType="{x:Type Button}" x:Key="highlightStyle">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Background" Value="#EEEEEE" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Visibility " Value="Visible" />
<Condition Property="IsEnabled" Value="true" />
</MultiTrigger.Conditions>
<Setter Property="BorderBrush" Value="Red"/>
<Setter Property="FontSize" Value="14" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Foreground" Value="Red" />
</MultiTrigger>
</Style.Triggers>
</Style>
…
<Button Style="{StaticResource highlightStyle}" Content="Hight Value" x:Name="btnVisible" Click="Button_Click" />
給按鈕添加單擊事件用來改變IsEnabled屬性:
private void Button_Click(object sender, RoutedEventArgs e)
{
this.btnVisible.IsEnabled = !this.btnVisible.IsEnabled;
}
看看效果(左邊為不單擊後不滿足條件時的樣式):
同樣的,你也可以用MultiDataTrigger來對自定義的屬性進行多條件的與關系操作。
5.在觸發器中執行用戶代碼
DependencyProperty.RegisterAttached方法允許用戶給控件/窗體等定義自己的依賴屬性,其包含的CallBack參數可以允許執行某個特定方法。這允許我們在Trigger中去調用特定的事件處理。其實嚴格的說這和Trigger不太有關系,因為這相當於我們給某個對象添加了自定義屬性並執行某些事件。但trigger可以恰恰利用這個好處來簡介的執行業務邏輯:
public static readonly DependencyProperty SomethingHappenedProperty = DependencyProperty.RegisterAttached("SomethingHappened", typeof(bool), typeof(Window1), new PropertyMetadata(false, new PropertyChangedCallback(SomethingHappened)));
public bool GetSomethingHappened(DependencyObject d)
{
return (bool)d.GetValue(SomethingHappenedProperty);
}
public void SetSomethingHappened(DependencyObject d, bool value)
{
d.SetValue(SomethingHappenedProperty, value);
}
public static void SomethingHappened(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//do something here
}