依賴屬性(Dependency Property)
.NET Framework 3.0引入了一個新的屬性類型叫依賴屬性, WPF,WF都在使用依賴屬性用來實現樣式化,數據綁定等.我們更多的使用依賴屬性是為了讓父元素的屬性值在邏輯樹上慢慢的傳遞到其子元素中,從而可以在整個可是父元素的邏輯子元素中共享屬性值.WF就是依靠依賴屬性來在工作流中的各Activity間傳遞屬性值的. 所以,依賴屬性內建的傳遞變更通知的能力是其最大特征.
如果你想讓屬性在一個包含內容子控件樹的整個邏輯控件樹中都有效並共享值時,你僅僅只需要將這個屬性聲明為依賴屬性即可, WPF會通過內建的架構來支持屬性的共享. 而在工作流中我們經常需要用到依賴屬性,它保證了在一個工作流實例中,多個組件共享了同一個值. 幸運的是在WPF中大部分空間的屬性都是依賴屬性,這讓我們應用時非常方便,而你並不需要著後邊發生了什麼。
依賴屬性的實現
依賴屬性其實也是普通的.NET屬性,只是通過DependencyProperty.Register方法將普通的.NET屬性注冊為依賴屬性。在依賴屬性的聲明中,其實對應的普通.NET屬性並不是必需的,因為其內部的GetValue和SetValue方法是公開的,依賴屬性的使用者可以通過調用GetValue/SetValue而放棄對普通.NET屬性的依賴。但建立在普通.NET熟悉之上更符合我們通常的做法,而且這樣有利於在XAML中設置屬性。
下面的代碼展示了定義依賴屬性的通常做法:
public class Button:ButtonBase
{
//依賴屬性
public static readonly DependencyProperty IsDependencyProperty;
static Button()
{
//注冊依賴屬性
Button.IsDependencyProperty = DependencyProperty.Register("IsDefault",
typeof(bool), typeof(Button),
new FrameworkPropertyMetadata(false,
new PropertyChangedCallback(OnIsDefaultChanged)));
}
//.NET屬性(可選)
public bool IsDefault
{
get { return (bool)GetValue(Button.IsDefaultProperty); }
set { SetValue(Button.IsDefaultProperty, value); }
}
//屬性改變時的回調
private static void OnIsDefaultChanged(DependencyObject o,
DependencyPropertyChangedEventArgs e)
{...}
}
在上面的示例代碼中是標准的實現依賴屬性定義的方法,這裡需要注意的是依賴屬性始終是定義在普通.NET屬性之上的用Register靜態方法注冊的特殊屬性,即便這裡的.NET屬性(如IsDefault屬性)不是必需的。另一個需要注意的地方是,通過聲明依賴屬性,我們多了一個控制屬性改變時的回調方法(即便這同樣可以通過聲明委托和事件來定義,但這裡我們什麼都沒做,為什麼不用呢?),這樣的好處是我們可以在屬性改變的時候做些我們想做的事情,而我們卻省去了手動聲明事件的任務。其實回調函數的存在是為了讓我們保證在屬性包裝器中(IsDefault屬性)僅僅使用標准的GetValue/SetValue而不用任何其他邏輯,轉而將自定義邏輯寫入回調函數--這樣做是為了遵循WPF的統一設計原則,讓XAML 設置屬性與使用過程式代碼設置屬性保持一致。
觸發器
依賴屬性的實現讓我們可以在一個局部范圍內保持屬性值的共享,這樣的好處是對於內存的節約,因為GetValue/SetValue內部使用了高效的稀疏存儲系統. 前邊提到過,依賴屬性的一大特征是變更通知,意思就是當某些依賴屬性的只改變了,WPF就會更具屬性的元數據觸發一系列動作.
WPF中有三種方式來實現這樣的變更通知:
屬性觸發器: 在屬性發生改變時執行自定義動作,而不用改任何過程代碼。
數據觸發器: 當普通.NET屬性的值改變時調用自定義動作。
事件觸發器: 當路由事件被觸發時調用自定義動作。
屬性觸發器(Trigger)
當某個屬性有一個特定的值時,屬性觸發器會執行一個Setter的集合來設置相關對象的屬性,而當屬性失去這個值的時候,屬性觸發器會撤消該Setter集合. 這樣的好處是大大簡化了我們用聲明事件的辦法來處理滿足一定條件時的邏輯,我們在XAML中就可以完成相應的簡單邏輯(如果復雜還是需要過程式代碼)。
下邊的示例顯示了旋轉變換僅在鼠標移到按鈕上方時才會發生,並會設置相應屬性。
<Style x:key="buttonStyle" TargetType="{x:Type Button}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="RenderTransform">
<Setter.Value>
<RotageTransform Angle="10" />
</Setter.Value>
</Setter>
<Setter Property="Foreground" Value="Black" />
</Trigger>
</Style.Trigger>
<Setter Property="FontSize" Value="22" />
<Setter Property="Foreground" Value="White" />
<Setter Property="Background" value="Purple" />
<Setter Property="Height" Value="50" />
<Setter Property="Width" Value="50" />
<Setter Property="RenderTransformOrigin" Value="0.5,0.5" />
</Style>
對於屬性觸發器的應用我們更多的還可以通過自定義驗證規則來實現對樣式和Tooltip的自動設置。
數據觸發器(Data Trigger)
數據觸發器與屬性觸發器幾乎一樣,只是數據觸發器可以由任何.NET屬性觸發,而不僅僅是依賴屬性.為了使用數據觸發器,可向Triggers集合中添加DataTrigger對象然後指定屬性/值對.同時可以用Binding來指定相關屬性而不僅僅是屬性名.
以下示例通過binding指定當自己的值為disabled的時候將自己禁用. 注意:Text屬性不是依賴屬性.
<StackPanel Width="200">
<StaticPanel.Resources>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeResource=
{RelativeResource Self}, Path=Text}"
value = "disabled">
<Setter Property="IsEnabled" Value="false" />
/DataTrigger>
</Style.Triggers>
<Setter Property="Background" Value=
"{Binding RelativeResource={RelativeResource Self}, Path=Text}" />
</Style>
</StackPanel.Resources>
<TextBox Margin="3" />
</StackPanel>
事件觸發器(Event Trigger)
當一個已選擇的事件產生時事件觸發器會被激活.這個事件由觸發器的RouteEvent屬性指定,他在Action集合中包含一個或多個動作(從抽象類TriggerAction繼承來的對象).
下邊的示例展示了在Button的Click事件被觸發時執行DoubleAnimation動作.
<Button>OK
<Button.Triggers>
<EventTrigger RouteEvent="Button.Click">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard TargetProperty="width">
<DoubleAnimation From="50" To="100"
Duration="0:0:5" AutoReverse="True" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Button.Triggers>
</Button>
屬性值繼承
附加屬性(Attached Property)
附加屬性也屬於XAML為了擴展其能力的一種,它提供了讓用戶在父屬性裡設置自己沒有的子屬性值的能力。而在應用附加屬性時,通常它也可以體現WPF中屬性傳遞的優點:例如,當你給一個Panel中的某類元素設置了附加屬性,那麼在不顯式聲明相關屬性值(重寫)的情況下,此類元素共同從父元素得到這個值。
作為附加屬性,之所以說是擴展了XAML的應用是因為它提供了通過父元素來設置子元素並在其可視樹范圍內默認共享的能力。那麼我們可以通過給Panel來設置TextElement.FontSize屬性來讓所有在這個Panel下的擁有TextElement.FontSize相關屬性的控件自動擁有這個值,而不需要我們挨個對所有此類空間設置。例如:
<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel Orientation="Horizontal" TextElement.FontSize=”10”>
<Image Source="Fireworks.bmp"/>
<StackPanel Width="200">
<TextBlock Margin="5,0" VerticalAlignment="center">Fireworks</TextBlock>
<TextBlock Margin="5" VerticalAlignment="center" TextWrapping="Wrap">
Sleek, with a black sky containing fireworks. When you need to
celebrate PowerPoint-style, this design is for you!
</TextBlock>
<Button x:name=”btnSubmit”>Agree</Button>
<Button x:name=”btnCancel”>Disagree</Button>
</StackPanel>
</StackPanel>
</ UserControl >
在上述的例子中,由於在StackPanel中設置了TextElement.FontSize,所以在它的內部的可視控件自動有用這個屬性:TextBlock,Button都會以FontSize=10來顯示。