前言
我在學習WPF的早期,對依賴屬性理解一直都非常的不到位,其惡果就是,我每次在寫依賴屬性的時候,需要翻過去的程式碼來複制黏貼。
相信很多朋友有著和我相同的經歷,所以這篇文章希望能幫助到那些剛剛開始學依賴屬性的朋友。
那些[討厭]的依賴屬性的講解文章
初學者肯定會面臨一件事,就是百度,谷歌,或者MSDN來檢視依賴屬性的定義和使用,而這些文章雖然都寫的很好,但,那是相對於已經學會使用依賴屬性的朋友而言。
而對於初學者而言,說是誤導都不過分。
比如,官網的這篇文章https://docs.microsoft.com/zh-cn/dotnet/framework/wpf/advanced/dependency-properties-overview
介紹依賴屬性是這樣。
public static readonly DependencyProperty IsSpinningProperty = DependencyProperty.Register( "IsSpinning", typeof(Boolean), typeof(MyCode) ); public bool IsSpinning { get { return (bool)GetValue(IsSpinningProperty); } set { SetValue(IsSpinningProperty, value); } }
他做了一個定義,然後告訴你,依賴屬性的定義格式如此。
如果你是個初學者,你想不疑惑都很難。因為沒人能把這種定義給背下來。
其結果就是,你要和我當初一樣,每次定義依賴屬性,都要去複製黏貼。但這並不是最大的惡果,最大的惡果是,因為太過複雜的定義,讓你放棄了對他理解,就記住了依賴屬性要複製黏貼,從而導致了,你喪失了對依賴屬性靈活運用的能力。
正確的理解依賴屬性
如何正確的理解依賴屬性呢?
很簡單,拆分一下就可以理解了。
現在我們來拆分依賴屬性,首先拆分他的定義,將依賴和屬性拆分。
我們先看屬性,如下,我們定義了一個屬性。
private bool _IsSpinning; public bool IsSpinning { get { return _IsSpinning; } set { _IsSpinning = value; } }
然後我們使用DependencyProperty類定義一個物件,這個物件將作為IsSpinning屬性的依賴,如下:
public static readonly DependencyProperty IsSpinningProperty
然後,我們在將這個依賴物件,註冊到屬性IsSpinning的所在類上,如下:
DependencyProperty.Register( "IsSpinning", typeof(bool), typeof(你的屬性所在的類的名稱));
從註冊程式碼中,我們可以看到,他註冊了三個資訊:
1,當前DependencyProperty類定義的物件IsSpinningProperty,依賴於屬性IsSpinning。
2,物件IsSpinningProperty的依賴型別與屬性IsSpinning的型別一樣都是bool。
3,物件IsSpinningProperty註冊的類是宣告屬性IsSpinning的類,即,在其他類裡,將看不到該依賴物件。
現在,我們做最後的操作,修改屬性,將依賴物件IsSpinningProperty與屬性IsSpinning繫結。
如何繫結呢?很簡單,將我們屬性定義裡的【private bool _IsSpinning】替換為我們剛剛定義的依賴【IsSpinningProperty】即可。
public bool IsSpinning { get { return (bool)GetValue(IsSpinningProperty); } set { SetValue(IsSpinningProperty, value); } }
這裡我們看到了,在給屬性賦值和取值時,用到了GetValue和SetValue,他們倆是哪來的呢?
使用F12,我們跟蹤進去,發現它們是類DependencyProperty裡定義的方法,那麼為什麼我們在窗體裡也可以用呢?
很簡單,我們跟進一下Window的父類,發現最後的父類Visual繼承了DependencyProperty,所以我們可以直接使用GetValue和SetValue來賦值和獲取依賴物件的值。也就是隻要是繼承了類DependencyProperty的子類,都可以使用依賴屬性。
完整版依賴屬性定義程式碼:
public static readonly DependencyProperty IsSpinningProperty = DependencyProperty.Register("IsSpinning", typeof(bool), typeof(DependecyUserControl)); public bool IsSpinning { get { return (bool)GetValue(IsSpinningProperty); } set { SetValue(IsSpinningProperty, value); } }
到這裡,依賴屬性的拆分就完事了,現在,大家應該很清楚依賴屬性到底是什麼了吧。
PS:有沒有人曾經告訴你,依賴屬性的命名必須是 屬性名+Property,然後你還信以為真了。哈哈。
依賴屬性的簡單應用
現在讓我們來自定義一個帶依賴屬性的系統控制元件來加深記憶。
public class KButton : Button { public static readonly DependencyProperty ForeImageProperty; public static readonly DependencyProperty BackImageProperty; public static readonly DependencyProperty MouseOverBackColorProperty; public static readonly DependencyProperty StretchProperty; static KButton() { ForeImageProperty = DependencyProperty.Register("ForeImage", typeof(string), typeof(KButton),null); ForeImageProperty = DependencyProperty.Register("BackImage", typeof(string), typeof(KButton),null); MouseOverBackColorProperty = DependencyProperty.Register("MouseOverBackColor", typeof(Brush), typeof(KButton), null); StretchProperty = DependencyProperty.Register("Stretch", typeof(Stretch), typeof(KButton), null); DefaultStyleKeyProperty.OverrideMetadata(typeof(KButton), new FrameworkPropertyMetadata(typeof(KButton)));//使KButton去讀取KButton型別的樣式,而不是去讀取Button的樣式 } public string ForeImage { get { return (string)GetValue(ForeImageProperty); } set { SetValue(ForeImageProperty, value); } } public string BackImage { get { return (string)GetValue(BackImageProperty); } set { SetValue(BackImageProperty, value); } } public Brush MouseOverBackColor { get { return (Brush)GetValue(MouseOverBackColorProperty); } set { SetValue(MouseOverBackColorProperty, value); } } public Stretch Stretch { get { return (Stretch)GetValue(StretchProperty); } set { SetValue(StretchProperty, value); } } }
如上述程式碼所示,我們定義了一個繼承至Button的類KButton。
在KButtion中,我們定義了四個依賴屬性:
ForeImageProperty:按鈕的前景圖片。
BackImageProperty:按鈕的背景圖片。
MouseOverBackColorProperty:按鈕在滑鼠經過時的顏色。
StretchProperty:按鈕圖片的拉伸模式。
程式碼非常簡潔,除了四個依賴屬性之外,什麼也沒有;現在我們去定義Kbutton型別的樣式。
為了演示方便,我直接將樣式定義在了App.xaml檔案內。
<Style TargetType="{x:Type local:KButton}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate> <DockPanel Name="dpCon" Width="{Binding Width, RelativeSource={x:Static RelativeSource.TemplatedParent}}" Height="{Binding Height, RelativeSource={x:Static RelativeSource.TemplatedParent}}" Background="{Binding Background, RelativeSource={x:Static RelativeSource.TemplatedParent}}" ToolTip="{Binding ToolTip, RelativeSource={x:Static RelativeSource.TemplatedParent}}" > <DockPanel DockPanel.Dock="Top" Name="dpBtn"> <DockPanel.Background> <ImageBrush ImageSource="{Binding ForeImage, RelativeSource={x:Static RelativeSource.TemplatedParent}}" Stretch="{Binding Stretch,RelativeSource={x:Static RelativeSource.TemplatedParent}}"/> </DockPanel.Background> <TextBlock FontSize="15" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="#f9fcff" Text="{Binding Content, RelativeSource={x:Static RelativeSource.TemplatedParent}}"></TextBlock> </DockPanel> </DockPanel> <ControlTemplate.Triggers> <DataTrigger Binding="{Binding IsMouseOver,RelativeSource={x:Static RelativeSource.Self}}" Value="True"> <Setter Property="Background" TargetName="dpBtn"> <Setter.Value> <ImageBrush ImageSource="{Binding BackImage, RelativeSource={x:Static RelativeSource.TemplatedParent}}" Stretch="{Binding Stretch,RelativeSource={x:Static RelativeSource.TemplatedParent}}"/> </Setter.Value> </Setter> <Setter Property="Background" TargetName="dpCon" Value="{Binding MouseOverBackColor, RelativeSource={x:Static RelativeSource.TemplatedParent}}"></Setter> </DataTrigger> <DataTrigger Binding="{Binding BackImage,RelativeSource={x:Static RelativeSource.Self},Mode=TwoWay}" Value="{x:Null}"> <Setter Property="Background" TargetName="dpBtn"> <Setter.Value> <ImageBrush ImageSource="{Binding ForeImage, RelativeSource={x:Static RelativeSource.TemplatedParent}}" Stretch="{Binding Stretch,RelativeSource={x:Static RelativeSource.TemplatedParent}}"/> </Setter.Value> </Setter> </DataTrigger> <Trigger Property="IsEnabled" Value="true"/> <Trigger Property="IsEnabled" Value="false"> <Setter Property="Foreground" Value="Gray"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style>
樣式程式碼如上所示,也非常簡單,就是定義了一個模板,然後在模板裡擺放好按鈕背景圖和按鈕文字的位置。然後將我們之前定義好的依賴屬性繫結到對應的值上。
其中需要注意的是,在模板中繫結自定義依賴屬性,是使用RelativeSource.TemplatedParent的,如{Binding ForeImage, RelativeSource={x:Static RelativeSource.TemplatedParent}}。
而在模板的資料事件DataTrigger中,繫結依賴屬性的模式卻是分兩種的。
第一種,繫結資料事件DataTrigger的條件時,使用RelativeSource.Self,如{Binding IsMouseOver,RelativeSource={x:Static RelativeSource.Self}}。
第二種,條件成立,觸發模板變化時,使用RelativeSource.TemplatedParent,如{Binding BackImage, RelativeSource={x:Static RelativeSource.TemplatedParent}}。
----------------------------------------------------------------------------------------------------
現在我們使用下我們製作好的自定義控制元件,程式碼如下所示:
<DockPanel> <StackPanel> <local:KButton Height="50" Width="50" Stretch="None" ForeImage="/Image/關閉.png" BackImage="/Image/關閉退出.png" Background="Gray" MouseOverBackColor="Brown"/> <local:KButton Height="50" Width="50" Margin="0,10,0,0" Stretch="None" ForeImage="/Image/關閉.png" Background="Gray" MouseOverBackColor="Brown"/> <local:KButton Height="100" Width="100" Margin="0,10,0,0" Content="籃子" Stretch="Fill" ForeImage="/Image/籃子.png" Background="Gray" MouseOverBackColor="Brown"/> </StackPanel> </DockPanel>
介面效果如下:
自定義使用者控制元件中使用依賴屬性
首先我們新增新項,然後選擇使用者控制元件。
然後,我們新增一個依賴屬性HeaderTitle,同時設定當前控制元件的DataContext為自身—this.DataContext = this。
public string HeaderTitle { get { return (string)GetValue(HeaderTitleProperty); } set { SetValue(HeaderTitleProperty, value); } } public static readonly DependencyProperty HeaderTitleProperty = DependencyProperty.Register("HeaderTitle", typeof(string), typeof(DependecyUserControl), null); public DependecyUserControl() { this.DataContext = this; InitializeComponent(); }
現在,我們在使用者控制元件的Xaml頁面新增一個TextBlock,並繫結他的Text為我們剛剛定義的HeaderTitle,程式碼如下所示。
<Grid> <TextBlock Text = "{Binding HeaderTitle}" TextAlignment="Center"></TextBlock> </Grid>
接著我們回到主窗體,引用這個使用者控制元件,程式碼如下所示:
<local:DependecyUserControl Height = "30" HeaderTitle="我是Header" DockPanel.Dock="Top"></local:DependecyUserControl>
執行結果:
可以看到,我們成功在主頁面設定了使用者控制元件的依賴屬性,並讓他成功的繫結到了使用者控制元件中的TextBlock的Text屬性。也就是說,我們簡單的實現了Header的Title動態設定。
結語
WPF擁有非常強大的自定義能力,而,正確的學會了依賴屬性是體會到它強大的第一步。
----------------------------------------------------------------------------------------------------
到此WPF依賴屬性的正確學習方法就已經講解完成了。
程式碼已經傳到Github上了,歡迎大家下載。
Github地址:https://github.com/kiba518/WpfDependency
----------------------------------------------------------------------------------------------------
注:此文章為原創,歡迎轉載,請在文章頁面明顯位置給出此文連結!
若您覺得這篇文章還不錯,請點選下方的【推薦】,非常感謝!