WPF入門教程系列(二) 深入剖析WPF Binding的使用方法


WPF入門教程系列(二) 深入剖析WPF Binding的使用方法

同一個對象(特指System.Windows.DependencyObject的子類)的同一種屬性(特指DependencyProperty)只能擁有一個binding

這一點可以通過設置binding對象的方法名得知:

public static BindingExpressionBase SetBinding(

      DependencyObject target,

      DependencyProperty dp,

      BindingBase binding

)

 

方法名是SetBinding而不是AddBinding。如果想要驗證一下,也可以在listView1_SelectionChanged事件方法中增加斷點,可以監視到。

 

當多次在同一個對象上設置Binding時,其實並不會增加多余的binding,而是將原來的binding替換掉了,這里以textBox_ContactID為例如下圖:

 

 

 

另一個相對的概念:同一個Binding可以同時與多個對象的多個屬性(或同一對象的多個屬性)形成Binding這一點就不做實踐驗證了,在MSDN上《Data Binding Overview》中有一段“Binding and BindingExpression”就是講這個道理的。

也許很多讀者已經知道或潛意識里有了這種概念,這里只是做一些強調。

改善優化WPF Binding

下面的內容在修正錯誤的同時,還將對原有的Binding做一些小小的改進。

 

在上一篇Blog中,我曾經提到過這樣一個問題,在將數據庫中的DataTable對象的DefaultView通過XAML語言Binding到listView1對象的ItemsSource上時總是失敗。

 

這里順便公布一下答案,那就是使用DataContext,不過在動手之前先來梳理一下Binding的一些基本概念及使用方法。

在此之前先來點基礎知識,都是MSDN上的內容(不過是E文的),感覺不錯所以寫下來,供大家閱讀:

 

l         典型的Binding具有四個重要組成部分:Binding目標對象(binding target object)、目標對象屬性(target property)、Binding數據源(binding source)、Path(用於指明要從數據源中取得的值,就是我們通常寫的屬性名稱)。

 

   例如:想要把一個員工(Employee)的姓名(EmpName)Binding顯示到一個TextBox的Text屬性上。

 

   分析一下其中的4個組成部分:Binding目標對象是TextBox(因為它要最終使用這些數據);目標對象屬性是TextProperty(這里要注意,不是Text屬性,而是與Text屬性相對應的Dependency Property,通常命名是屬性名+Property);

 

   數據源是員工(Employee);Path是EmpName。我的經驗是:剛開始使用Binding的時候由於還不熟悉,建議在做之前先在心理把這4個部分對號入座一下,等日后就可以熟悉成自然了。

 

l         目標對象屬性一定要是Dependency Property。由於只有DependencyObject類及其子類能夠定義Dependency Property,因此,Binding目標對象就必須是DependencyObject類及其子類了。另一方面反過來說,UIElement(DependencyObject的子類)的大多數屬性都有與其對應的Dependency Property,因此,大多數的UIElement屬性我們都可以利用其Dependency Property作為目標對象屬性從而與數據源建立Binding。

 

l        再有一點,建立Binding的數據源不一定要是一個CLR對象,也可以是XML數據、ADO.NET對象及DependencyObject。

 

關於為什么binding的對象一定要是DependencyObject,為什么binding對象的屬性一定要是dependency property,這個我就不知道了。

在我第一次看到在XAML中設置Binding的語法時,簡直是滿頭霧水。這東西到底怎么回事,簡直就沒有章法可循,一會兒ElementName,一會兒Source,還有時什么都沒有,就一個{Binding },再有就是Mode,UpdateSourceTrigger等,簡直沒有頭緒。

 

不過只要打開MSDN,看一下System.Windows.Data.Binding對象,似乎開始有點感覺了,查看Binding對象的屬性欄(Properties),你會發現,原來我們在XAML中設置Binding時使用的東西和Binding對象的屬性好像都是一一對應的。

 

確實如此,在XAML中設置Binding就是在這只System.Windows.Data.Binding對象,這里先提醒一下大家。

 

WPF可不是就這么一個Binding噢,實時上,從BindingBase繼承過來的有三個兄弟,Binding只是最常用而已。

找到了binding的老家,相信大家對{Binding ElementName=ElemName, Path=PropertyName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, …}這樣的語法應該清楚了吧。

那么{Binding } 及{Binding XXXX}這樣的語法呢?其實可以就是等於new Binding()及new Binding(XXXX),其實可以考慮成是分別使用了Binding的兩種構造方法,而XXXX的作用自然也就清晰了。

其實和{Binding Path=XXXX}是等價的,都是設置Path屬性的。至於這里的Path屬性在我們平常使用時其實是用來設置屬性名的地方,為什么不直接叫做是PropertyName之類的名字呢?這個又要留着以后思考了。

當一個數據源與一個UIElement形成Binding后,數據源的改變,如果傳遞給UIElement的指定屬性?

同樣,當UIElement中指定屬性值改變了,如何才能夠通知到數據源?要討論這個問題,需要按數據源來分類進行討論。這個分類並不是互斥的之前存在一些交集。

因此,同一種Binding場景可能適用於多種情況。下面的內容是來自MSDN中《Binding Sources Overview》中的內容,由於怕翻譯的不好而誤導大家,所以分類的名稱還是使用英文吧。

Using a CLR Class as the Binding Source Object

在典型的Binding情景中,一個CLR 對象作為數據源,將其中一個屬性值Binding到某個UIElement的屬性上(以上面顯示員工姓名的情景為例。

 

用XAML實現<TextBox Text=”{Binding Source=Employee, Path=EmpName}” />)。當數據源屬性值變化了,相應讓UIElement中對應屬性變化。

 

需要實現一個中通知機制,最簡單的方法是通過實現INotifyPropertyChanged接口完成此操作。具體實現,可以參考MSDN上的文章《How to: Implement Property Change Notification》。

 

其實就是實現了”public event PropertyChangedEventHandler PropertyChanged;”事件,通常的做法是在上面加封裝一個”protected void OnPropertyChanged(string name)”函數。在屬性的set方法結尾處,調用此函數實現通知的作用。

 

如果不實行此接口,則必須自己實現一套通知系統。為你的類中的每一個想要實現通知機制的屬性創建一個PropertyChanged模板。

 

即為你的每一個屬性創建一個名為”PropertyNameChanged”事件,其中PropertyName是與這個事件對應的屬性名稱。然后在屬性發生變化是觸發這個事件。(聽起來就麻煩,我也懶得去實踐驗證了)

 

如果以上兩種方法都無法實現的話,那么就只好在你認為需要的時候顯示的調用UpdateTarget方法來手動更新了。

 

(這種方法沒用過,感覺已經失去了Binding的意義了)

Entire Objects Used as a Binding Source

可以理解為將整個對象作為數據源,與上一個題目的區別很小。

 

基本上可以認為是原來Binding實現的是數據源屬性與UIElement屬性之間的Binding,而這次是直接將一個Object對象與一個UIElement的屬性進行Binding了。

 

這種情況可以使用Binding的Source屬性指明數據源(假設Employee對象與一個ListBox實現banding,用XAML實現<ListBox ItemsSource=”{Binding Employee}” />),或者還可以將DataContext屬性設置為Employee(DataContext屬性只有UIElement的一個子類System.Windows.FrameworkElement及其子類才會有)。

 

這樣,在設置ItemsSource時就可以直接使用”{Binding }”這樣的語法了。當然,DataContext屬性設置成Employee還是需要使用DataContext=”{Binding Employee}”語句的。

 

有人在這里會嘲笑這一舉動實在是啰嗦,事實上,DataContext屬性是非常有用的。之所以Binding對象失敗而最終不得不使用C#語句進行Binding就是因為沒有設置DataContext。

 

因為,當我們使用{Binding Source= Employee, Path=”EmpName”}設置屬性后,WPF查找Source對象是根據Element Tree逐級向上自己的Parent Element查找的,查找每一Element上的DataContext所指定的內容是否包含這一Source內容,並以第一個匹配到的結果作為最終對象。

 

也就是說,DataContext是按照Element Tree向下繼承的,並且決定這WPF在運行時能否找到我們所制定的Source對象。即使我們使用{Binding Path=”EmpName”}的語句也是如此。

 

比較常見的做法是,當我們有大量的Element需要與一個數據源中的眾多屬性實現Binding時,我們可以直接在一個公共Parent Element的DataContext上設置這一對象(甚至可以是整個window或page上),這將會極大的加少我們重復的代碼量,同時也會是程序更加可讀。

當我們需要使用的數據源是一個集合時,如果想要實現改變通知的機制。

 

同樣需要實現一個接口INotifyCollectionChanged用來在集合發生改變時發起,用法單個對象是實現INotifyPropertyChanged接口類似。

 

首先是將上以前做的不是很好的那一段代碼改成XAML語言實現。

 

將TextBox標簽中的Text屬性與上面listView1中被選定的記錄(listView1.SelectedItem)中對應屬性(例如:ContactID)做Binding。

 

實現后如圖所示:

<TextBox Text="{Binding ElementName=listView1, Path=SelectedItem.ContactID}" MinWidth="100" />

 

看以來代碼有點長,而且,下面的幾個TextBox也都存在大量的重復內容,這里就可以考慮使用上面所說的DataContext了。

將listView1中被選定的記錄(listView1.SelectedItem)作為DataContext放在公共Parent Element上,做法如下:

 

<WrapPanel Grid.Row="1" Orientation="Horizontal" DataContext="{Binding ElementName=listView1, Path=SelectedItem}">

然后剩下的代碼就可以簡化很多了:

<StackPanel Orientation="Horizontal" Margin="5,2,5,2">

    <TextBlock Text="ContactID:" TextAlignment="Center" />

    <TextBox Text="{Binding ContactID}" MinWidth="100" />

</StackPanel>

<StackPanel Orientation="Horizontal" Margin="5,2,5,2">

    <TextBlock Text="FirstName:" TextAlignment="Center" />

    <TextBox Text="{Binding FirstName}" MinWidth="100" />

</StackPanel>

<StackPanel Orientation="Horizontal" Margin="5,2,5,2">

    <TextBlock Text="LastName:" TextAlignment="Center" />

    <TextBox Text="{Binding EmailAddress}" MinWidth="100" />

</StackPanel>

<StackPanel Orientation="Horizontal" Margin="5,2,5,2">

    <TextBlock Text="EmailAddress:" TextAlignment="Center" />

    <TextBox Text="{Binding LastName}" MinWidth="100" />

</StackPanel>

 

原來的SelectionChanged事件處理方法現在已經沒有用了,將多余的代碼去掉:

 

<ListView Name="listView1" SelectionChanged="listView1_SelectionChanged" MinWidth="280" >

 

后台代碼中的對應方法也可以刪掉了private void listView1_SelectionChanged(object sender, SelectionChangedEventArgs e)

 

F5 Debug運行一下,結果和原來是完全一樣的。

在上一文中無得以選擇使用C#實現Binding的部分代碼,現在使用XAML語言實現,有幾種方案可以選擇,通過如下幾步實現:

1)      將原先建立給listView1建立Binding那一大串代碼刪掉,替換成如下代碼:

listView1.DataContext = dt.DefaultView;

執行結果如下圖所示:

 

 

2)      將其與listView1的ItemsSource屬性建立Binding,設置每一列的header及DisplayMemberBinding值。

這里由於DataContext的原因,ItemsSource可以寫成"{Binding }"的形式而其Children Element也都可以簡寫成"{Binding ContactID}"這樣的形式。

<ListView Name="listView1" MinWidth="280" ItemsSource="{Binding }">

    <ListView.View>

        <GridView x:Name="gridView1">

            <GridView.Columns>

                <GridViewColumn DisplayMemberBinding="{Binding ContactID}" Header="ContactID"></GridViewColumn>

                <GridViewColumn DisplayMemberBinding="{Binding FirstName}" Header="FirstName"></GridViewColumn>

                <GridViewColumn DisplayMemberBinding="{Binding LastName}" Header="LastName"></GridViewColumn>

            </GridView.Columns>

        </GridView>

    </ListView.View>

</ListView>

改造完后,運行結果會發現,效果和原來是一樣。效率提高了,代碼更加簡化。

WPF入門系列教程(二) 深入剖析WPF Binding的使用方法(全文完


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM