4、 只讀依賴屬性ide
在之前在對於非WPF的功能來講,對於類的屬性的封裝中,常常會對那些但願暴露給外界只讀操做的字段封裝成只讀屬性,一樣在WPF中也提供了只讀屬性的概念,如一些 WPF控件的依賴屬性是隻讀的,它們常常用於報告控件的狀態和信息,像IsMouseOver等屬性, 那麼在這個時候對它賦值就沒有意義了。 或許你也會有這樣的疑問:爲何不使用通常的.Net屬性提供出來呢?通常的屬性也能夠綁定到元素上呀?這個是因爲有些地方必需要用到只讀依賴屬性,好比 Trigger等,同時也由於內部可能有多個提供者修改其值,因此用.Net屬性就不能完整天之大任了。
那麼一個只讀依賴屬性怎麼建立呢?其實建立一個只讀的依賴屬性和建立一個通常的依賴屬性大同小異。不一樣的地方就是DependencyProperty.Register變成了DependencyProperty.RegisterReadOnly。和前面的普通依賴屬性同樣,它將返回一個 DependencyPropertyKey。並且只提供一個GetValue給外部,這樣即可以像通常屬性同樣使用了,只是不能在外部設置它的值罷了。佈局
下面咱們就用一個簡單的例子來歸納一下:編碼
public partial class WindowReadOnly : Window { public WindowReadOnly () { InitializeComponent(); //用SetValue的方法來設置值 DispatcherTimer timer = new DispatcherTimer(TimeSpan.FromSeconds(1), DispatcherPriority.Normal, (object sender, EventArgs e)=> { int newValue = Counter == int.MaxValue ? 0 : Counter + 1; SetValue(counterKey, newValue); }, Dispatcher); } //屬性包裝器,只提供GetValue public int Counter { get { return (int)GetValue(counterKey.DependencyProperty); } } //用RegisterReadOnly來代替Register來註冊一個只讀的依賴屬性 private static readonly DependencyPropertyKey counterKey = DependencyProperty.RegisterReadOnly("Counter", typeof(int), typeof(WindowReadOnly), new PropertyMetadata(0)); }
XAML代碼: spa
<Window x:Name="winReadOnly" x:Class="WpfApp1.WindowReadOnly" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="WindowDepend" Height="300" Width="300"> <Grid> <Viewbox> <TextBlock Text="{Binding ElementName=winReadOnly, Path=Counter}" /> </Viewbox> </Grid> </Window>
效果以下圖所示: 設計
5、 附加屬性code
如今咱們再繼續探討另一種特殊的依賴屬性——附加屬性。附加屬性是一種特殊的依賴屬性。這是WPF的特性之一,通俗的理解起來就是,別人有的屬性,因爲你跟他產生了關係因此你也有了這個屬於他的屬性。orm
附加屬性是說一個屬性原本不屬於某個對象,但因爲某種需求而被後來附加上,也就是把對象放入一個特定環境後對象才具備的屬性就稱爲附加屬性,附加屬性的做 用就是將屬性與數據類型解耦,讓數據類型的設計更加靈活,舉例,一個TextBox被放在不一樣的佈局容器中時就會有不一樣的佈局屬性,這些屬性就是由佈局容 器爲TextBox附加上的,附加屬性的本質就是依賴屬性,兩者僅僅在註冊和包裝器上有一點區別。
xml
附加屬性是依賴屬性的一種特殊形式,它可讓用戶在一個元素中設置其餘元素的屬性。通常來講,附加屬性是用於一個父元素定位其餘元素佈局 的。就像Grid和DockPanel元素就包含附加屬性。Grid使用附加屬性來指定包含子元素的特定行和列,而DockPanel使用附加屬性是來指 定子元素應該停靠在面板中的何處位置。
對象
附加屬性就是本身沒有這個屬性,在某些上下文中須要就被附加上去。好比StackPanel的Grid.Row屬性,若是咱們定義StackPanel類時定義一個Row屬性是沒有意義的,由於咱們並不知道必定會放在Grid裏,這樣就形成了浪費。blog
例如,下面轉場控件的定義使用了Grid的Row屬性來將自身定位到特定的行中。
<Grid> <Grid.RowDefinitions> <RowDefinition Height="101*"/> <RowDefinition Height="80"/> <RowDefinition Height="80"/> </Grid.RowDefinitions> <StackPanel Grid.Row="0" >
儘管對於一個普通的WPF開發人員來講,理解依賴和附加屬性並不必定是必須的,可是掌握好WPF系統的整個運行機制對於提高WPF應用技術是很是重要的。
使用附加屬性,能夠避開可能會防止一個關係中的不一樣對象在運行時相互傳遞信息的編碼約定。必定能夠針對常見的基類設置屬性,以便每一個對象只需獲取和 設置該屬性便可。可是,你可能但願在不少狀況下這樣作,這會使你的基類最終充斥着大量可共享的屬性。它甚至可能會引入如下狀況:在數百個後代中,只有兩個 後代嘗試使用一個屬性。這樣的類設計很糟糕。爲了解決此問題,咱們使用附加屬性概念來容許對象爲不是由它本身的類結構定義的屬性賦值。在建立對象樹中的各 個相關對象以後,在運行時從子對象讀取此值。
最好的例子就是佈局面板。每個佈局面板都須要本身特有的方式來組織它的子元素。如Canvas須要Top和left來布 局,DockPanel須要Dock來佈局。固然你也能夠寫本身的佈局面板(在上一篇文章中咱們對佈局進行了比較細緻的探討,若是有不清楚的朋友也能夠再 回顧一下)。
下面代碼中的Button 就是用了Canvas的Canvas.Top和Canvas.Left="20" 來進行佈局定位,那麼這兩個就是傳說中的附加屬性。
<Canvas> <Button Canvas.Top="20" Canvas.Left="20" Content="Knights Warrior!"/> </Canvas>
定義附加屬性的方法與定義依賴屬性的方法一致,前面咱們是使用DependencyProperty.Register來註冊一個依賴屬性,只是在註冊屬性時使用的是RegisterAttach()方法。這個RegisterAttached的參數和 Register是徹底一致的,那麼Attached(附加)這個概念又從何而來呢?
其實咱們使用依賴屬性,一直在Attached(附加)。咱們註冊(構造)一個依賴屬性,而後在DependencyObject中經過 GetValue和SetValue來操做這個依賴屬性,也就是把這個依賴屬性經過這樣的方法關聯到了這個DependencyObject上,只不過是 經過封裝CLR屬性來達到的。那麼RegisterAttached又是怎樣的呢?
下面咱們來看一個最簡單的應用:首先咱們註冊(構造)一個附加屬性
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Media; namespace WpfApp1.Services { public class TurnoverManager : DependencyObject { //經過靜態方法的形式暴露讀的操做 public static double GetAngle(DependencyObject obj) { return (double)obj.GetValue(AngleProperty); } //經過靜態方法的形式暴露寫的操做 public static void SetAngle(DependencyObject obj, double value) { obj.SetValue(AngleProperty, value); } //經過使用RegisterAttached來註冊一個附加屬性 public static readonly DependencyProperty AngleProperty = DependencyProperty.RegisterAttached("Angle", typeof(double), typeof(TurnoverManager), new PropertyMetadata(0.0, OnAngleChanged)); //根據附加屬性中的值,當值改變的時候,旋轉相應的角度。 private static void OnAngleChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) { var element = obj as UIElement; if (element != null) { element.RenderTransformOrigin = new Point(0.5, 0.5); element.RenderTransform = new RotateTransform((double)e.NewValue); } } } }
而後,咱們在程序中使用這個咱們本身定義的附加屬性
<Window x:Class="WpfApp1.WindowTurnover" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfApp1.Services" Title="WindowTurnover" Height="400" Width="500" Loaded="Window_Loaded"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="313*"/> <RowDefinition Height="57*"/> </Grid.RowDefinitions> <Canvas Grid.Row="0"> <Ellipse Name="ellipseRed" Fill="Red" Width="100" Height="60" Canvas.Left="56" Canvas.Top="98" local:TurnoverManager.Angle="{Binding ElementName=sliderAngle, Path=Value}"/> <Rectangle Name="ellipseBlue" Fill="Blue" Width="80" Height="80" Canvas.Left="285" Canvas.Top="171" local:TurnoverManager.Angle="45" /> <Button Name="btnWelcome" Content="歡迎光臨" Canvas.Left="265" Canvas.Top="48" FontSize="20" local:TurnoverManager.Angle="60"/> </Canvas> <WrapPanel Grid.Row="1"> <Label Content="角度大小" /> <Slider x:Name="sliderAngle" Minimum="0" Maximum="240" Width="300" /> </WrapPanel> </Grid> </Window>
在XAML中就可使用剛纔註冊(構造)的附加屬性了:以下圖。
經過調整角度值,顯示不一樣的效果以下兩圖。圖1,圖2。
圖1
圖2