WPF基础到企业应用系列7——深入剖析依赖属性(三)
八. 只讀依賴屬性
  我們以前在對簡單屬性的封裝中,經常會對那些希望暴露給外界只讀操作的字段封裝成只讀屬性,同樣在WPF中也提供了只讀屬性的概念,如一些 WPF控件的依賴屬性是只讀的,它們經常用于報告控件的狀態和信息,像IsMouseOver等屬性, 那么在這個時候對它賦值就沒有意義了。 或許你也會有這樣的疑問:為什么不使用一般的.Net屬性提供出來呢?一般的屬性也可以綁定到元素上呀?這個是由于有些地方必須要用到只讀依賴屬性,比如 Trigger等,同時也因為內部可能有多個提供者修改其值,所以用.Net屬性就不能完成天之大任了。 
 那么一個只讀依賴屬性怎么創建呢?其實創建一個只讀的依賴屬性和創建一個一般的依賴屬性大同小異(研究源碼你會發現,其內部都是調用的同 一個Register方法)。僅僅是用DependencyProperty.RegisterReadonly替換了 DependencyProperty.DependencyProperty而已。和前面的普通依賴屬性一樣,它將返回一個 DependencyPropertyKey。該鍵值在類的內部暴露一個賦值的入口,同時只提供一個GetValue給外部,這樣便可以像一般屬性一樣使 用了,只是不能在外部設置它的值罷了。
下面我們就用一個簡單的例子來概括一下:
public partial class Window1 : Window{public Window1(){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,這里你也可以設置一個private的SetValue進行限制
public int Counter{get { return (int)GetValue(counterKey.DependencyProperty); }}//用RegisterReadOnly來代替Register來注冊一個只讀的依賴屬性
private static readonly DependencyPropertyKey counterKey =DependencyProperty.RegisterReadOnly("Counter",typeof(int),typeof(Window1),new PropertyMetadata(0));}
?? 
 XAML中代碼:
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Read-Only Dependency Property" Height="300" Width="300">
<Grid>
<Viewbox>
<TextBlock Text="{Binding ElementName=winThis, Path=Counter}" />
</Viewbox>
</Grid>
</Window>
效果如下圖所示:?
九. 附加屬性
前面我們講了依賴屬性。現在我們再繼續探討另外一種特殊的Dependency屬性——附加屬性。附加屬性是一種特殊的依賴屬性。他允許給一個對象添加一個值,而該對象可能對此值一無所知。
最好的例子就是布局面板。每一個布局面板都需要自己特有的方式來組織它的子元素。如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來注冊一個依賴屬性,同時依賴屬性本身也對外提供了 DependencyProperty.RegisterAttached方法來注冊附加屬性。這個RegisterAttached的參數和 Register是完全一致的,那么Attached(附加)這個概念又從何而來呢?
其實我們使用依賴屬性,一直在Attached(附加)。我們注冊(構造)一個依賴屬性,然后在DependencyObject中通過 GetValue和SetValue來操作這個依賴屬性,也就是把這個依賴屬性通過這樣的方法關聯到了這個DependencyObject上,只不過是 通過封裝CLR屬性來達到的。那么RegisterAttached又是怎樣的呢?
下面我們來看一個最簡單的應用:首先我們注冊(構造)一個附加屬性
public class AttachedPropertyChildAdder{//通過使用RegisterAttached來注冊一個附加屬性
public static readonly DependencyProperty IsAttachedProperty =DependencyProperty.RegisterAttached("IsAttached", typeof(bool), typeof(AttachedPropertyChildAdder),new FrameworkPropertyMetadata((bool)false));//通過靜態方法的形式暴露讀的操作
public static bool GetIsAttached(DependencyObject dpo){return (bool)dpo.GetValue(IsAttachedProperty);}//通過靜態方法的形式暴露寫的操作
public static void SetIsAttached(DependencyObject dpo, bool value){dpo.SetValue(IsAttachedProperty, value);} }
在XAML中就可以使用剛才注冊(構造)的附加屬性了:
?在上面的例子中,AttachedPropertyChildAdder 中 并沒有對IsAttached采用CLR屬性形式進行封裝,而是使用了靜態SetIsAttached方法和GetIsAttached方法來存取 IsAttached值,當然如果你了解它內部原理,你就會看到實際上還是調用的SetValue與GetValue來進行操作(只不過擁有者不同而 已)。這里我們不繼續深入下去,詳細在后面的內容會揭開謎底。
十. 清除本地值
在很多時候,由于我們的業務邏輯和UI操作比較復雜,所以一個龐大的頁面會進行很多諸如動畫、3D、多模板及樣式的操作,這個時候頁面的值已經 都被改變了,如果我們想讓它返回默認值,可以用ClearValue 來清除本地值,但是遺憾的是,很多時候由于WPF依賴屬性本身的設計,它往往會不盡如人意(詳細就是依賴屬性的優先級以及依賴屬性EffectiveValueEntry 的 影響)。ClearValue 方法為在元素上設置的依賴項屬性中清除任何本地應用的值提供了一個接口。但是,調用 ClearValue 并不能保證注冊屬性時在元數據中指定的默認值就是新的有效值。值優先級中的所有其他參與者仍然有效。只有在本地設置的值才會從優先級序列中移除。例如,如 果您對同時也由主題樣式設置的屬性調用 ClearValue,主題值將作為新值而不是基于元數據的默認值進行應用。如果您希望取消過程中的所有屬性值,而將值設置為注冊的元數據默認值,則可以 通過查詢依賴項屬性的元數據來最終獲得默認值,然后使用該默認值在本地設置屬性并調用 SetValue來實現,這里我們得感得PropertyMetadata類為我們提供了諸如DefaultValue這樣的外部可訪問的屬性。
上面講了這么多,現在我們就簡單用一個例子來說明上面的原理(例子很直觀,相信大家能很容易看懂)
XAML中代碼如下:
<Window x:Class="WpfApplication1.DPClearValue"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="400" Width="400">
<StackPanel Name="root">
<StackPanel.Resources>
<Style TargetType="Button">
<Setter Property="Height" Value="20"/>
<Setter Property="Width" Value="250"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
</Style>
<Style TargetType="Ellipse">
<Setter Property="Height" Value="50"/>
<Setter Property="Width" Value="50"/>
<Setter Property="Fill" Value="LightBlue"/>
</Style>
<Style TargetType="Rectangle">
<Setter Property="Height" Value="50"/>
<Setter Property="Width" Value="50"/>
<Setter Property="Fill" Value="MediumBlue"/>
</Style>
<Style x:Key="ShapeStyle" TargetType="Shape">
<Setter Property="Fill" Value="Azure"/>
</Style>
</StackPanel.Resources>
<DockPanel Name="myDockPanel">
<Ellipse Height="100" Width="100" Style="{StaticResource ShapeStyle}"/>
<Rectangle Height="100" Width="100" Style="{StaticResource ShapeStyle}" />
</DockPanel>
<Button Name="RedButton" Click="MakeEverythingAzure" Height="39" Width="193">改變所有的值</Button>
<Button Name="ClearButton" Click="RestoreDefaultProperties" Height="34" Width="192"> 清除本地值</Button>
</StackPanel>
</Window>
后臺代碼:
public partial class DPClearValue{//清除本地值,還原到默認值
void RestoreDefaultProperties(object sender, RoutedEventArgs e){UIElementCollection uic = myDockPanel.Children;foreach (Shape uie in uic){LocalValueEnumerator locallySetProperties = uie.GetLocalValueEnumerator();while (locallySetProperties.MoveNext()){DependencyProperty propertyToClear = (DependencyProperty)locallySetProperties.Current.Property;if (!propertyToClear.ReadOnly) { uie.ClearValue(propertyToClear); }}}}//修改本地值
void MakeEverythingAzure(object sender, RoutedEventArgs e){UIElementCollection uic = myDockPanel.Children;foreach (Shape uie in uic) { uie.Fill = new SolidColorBrush(Colors.Azure); }}}
?
當按下”改變所有的值“按鈕的時候,就會把之前的值都進行修改了,這個時候按下”清除本地值“就會使原來的所有默認值生效。
轉載于:https://blog.51cto.com/knightswarrior/383935
總結
以上是生活随笔為你收集整理的WPF基础到企业应用系列7——深入剖析依赖属性(三)的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: python学习笔记1
 - 下一篇: 开源游戏引擎体验