天天看點

WPF資料綁定詳解

轉載自:李小龍的部落格http://blog.163.com/bruce_lee04/blog/static/45755321201008102135814/

Windows Presentation Foundation (WPF) 資料綁定為應用程式提供了一種簡單而一緻的方法來顯示資料以及與資料互動。元素可以以公共語言運作庫 (CLR) 對象和 XML 的形式綁定到各種資料源的資料。ContentControl(如 Button)和 ItemsControl(如 ListBox 和 ListView)具有内置功能,使單個資料項或資料項集合可以進行靈活的樣式設定。可以在資料之上生成排序、篩選和分組視圖。

WPF 中的資料綁定功能與傳統模型相比具有一些優勢,包括本質上支援資料綁定的各種屬性、靈活的資料 UI 表示形式,以及業務邏輯與 UI 的完全分離。

本主題首先讨論 WPF 資料綁定的基本概念,然後詳細介紹 Binding 類以及資料綁定的其他功能的用法。

什麼是資料綁定?

資料綁定是在應用程式 UI 與業務邏輯之間建立連接配接的過程。如果綁定具有正确設定并且資料提供正确通知,則當資料更改其值時,綁定到資料的元素會自動反映更改。資料綁定可能還意味着如果元素中資料的外部表現形式發生更改,則基礎資料可以自動更新以反映更改。例如,如果使用者編輯 TextBox 元素中的值,則基礎資料值會自動更新以反映該更改。

資料綁定的一種典型用法是将伺服器或本地配置資料放置到窗體或其他 UI 控件中。在 WPF 中,此概念得到擴充,包括了大量屬性與各種資料源的綁定。在 WPF 中,元素的依賴項屬性可以綁定到 CLR 對象(包括 ADO.NET 對象或與 Web 服務和 Web 屬性相關聯的對象)和 XML 資料。

有關資料綁定的示例,請看一看來自資料綁定示範的以下應用程式 UI:

WPF資料綁定詳解

上面是顯示拍賣項清單的應用程式 UI。該應用程式示範資料綁定的以下功能:

ListBox 的内容綁定到 AuctionItem 對象的集合。AuctionItem 對象具有一些屬性,如 Description、StartPrice、StartDate、Category、SpecialFeatures。

ListBox 中顯示的資料(AuctionItem 對象)進行模闆化,以便顯示每個拍賣項的說明和目前價格。這是使用一個 DataTemplate 實作的。此外,每個項的外觀取決于要顯示的 AuctionItem 的 SpecialFeatures 值。如果 AuctionItem 的 SpecialFeatures 值為 Color,則該項具有藍色邊框。如果該值為 Highlight,則該項具有橙色邊框和一個星号。

使用者可以使用提供的 CheckBox 對資料進行分組、篩選或排序。在上面的圖像中,選中了“Group by category”(按類别分組)和“Sort by category and date”(按類别和日期排序)CheckBox。您可能已經注意到資料是根據産品類别分組的,而且類别名稱按字母順序排序。這些項在每個類别中也是按照起始日期排序的,雖然從該圖像中很難注意到這一點。這是使用集合視圖 實作的。

當使用者選中一個項時,ContentControl 會顯示標明項的詳細資訊。這稱為主從方案。

StartDate 屬性的類型為 DateTime,該類型傳回一個日期,包括精确到毫秒的時間。在此應用程式中,使用了一個自定義轉換器,以便顯示較短的日期字元串。

當使用者單擊“Add Product”(添加産品)按鈕時,會出現下面的窗體:

WPF資料綁定詳解

使用者可以編輯窗體中的字段,使用簡略預覽和詳細預覽窗格來預覽産品清單,然後單擊“submit”(送出)以添加新的産品清單。任何現有的分組、篩選和排序功能都會應用于新項。在這種特殊情況下,在上面圖像中輸入的項會作為 Computer 類别中的第二項顯示。

“Start Date”(起始日期)TextBox 中提供的驗證邏輯未在此圖像中顯示。如果使用者輸入一個無效日期(無效的格式或過去的日期),則會通過一個 ToolTip 和 TextBox 旁的一個紅色感歎号來通知使用者。資料驗證一節讨論了如何建立驗證邏輯。

在詳細介紹資料綁定的上述不同功能之前,我們會先在下一節中讨論一些對了解 WPF 資料綁定非常重要的基本概念。

基本資料綁定概念

不論要綁定什麼元素,不論資料源的特性是什麼,每個綁定都始終遵循下圖所示的模型:

WPF資料綁定詳解

如上圖所示,資料綁定實質上是綁定目标與綁定源之間的橋梁。該圖示範以下基本的 WPF 資料綁定概念:

通常,每個綁定都具有四個元件:綁定目标對象、目标屬性、綁定源,以及要使用的綁定源中的值的路徑。例如,如果要将 TextBox 的内容綁定到 Employee 對象的 Name 屬性,則目标對象是 TextBox,目标屬性是 Text 屬性,要使用的值是 Name,源對象是 Employee 對象。

目标屬性必須為依賴項屬性。大多數 UIElement 屬性都是依賴項屬性,而大多數依賴項屬性(除了隻讀屬性)預設情況下都支援資料綁定。(隻有 DependencyObject 類型可以定義依賴項屬性,所有 UIElement 都派生自 DependencyObject。)

盡管圖中并未指出,但應該注意,綁定源對象并不限于自定義 CLR 對象。WPF 資料綁定支援 CLR 對象和 XML 形式的資料。舉例來說,綁定源可以是 UIElement、任何清單對象、與 ADO.NET 資料或 Web 服務關聯的 CLR 對象,或是包含 XML 資料的 XmlNode。

在閱讀其他軟體開發工具包 (SDK) 主題時,請務必記住一點:當您建立綁定時,您是将綁定目标綁定到 綁定源。例如,如果您要使用資料綁定在一個 ListBox 中顯示一些基礎 XML 資料,就是将 ListBox 綁定到 XML 資料。

若要建立綁定,請使用 Binding 對象。本主題的剩餘部分會讨論與 Binding 對象相關的許多概念以及該對象的一些屬性和用法。

資料流的方向

正如上文所述和上圖中箭頭所示,綁定的資料流可以從資料目标流向資料源(例如,當使用者編輯 TextBox 的值時,源值會發生更改)和/或(如果綁定源提供正确的通知)從綁定源流向綁定目标(例如,TextBox 内容會随綁定源中的更改而進行更新)。

您可能希望應用程式使使用者可以更改資料并将資料傳播回源對象。或者,您可能不希望允許使用者更新源資料。您可以通過設定 Binding 對象的 Mode 屬性來對此進行控制。下圖示範不同類型的資料流:

WPF資料綁定詳解

OneWay 綁定導緻對源屬性的更改會自動更新目标屬性,但是對目标屬性的更改不會傳播回源屬性。此綁定類型适用于綁定的控件為隐式隻讀控件的情況。例如,您可能綁定到如股票行情自動收錄器這樣的源,或許目标屬性沒有用于進行更改的控件接口(如表的資料綁定背景色)。如果無需監視目标屬性的更改,則使用 OneWay 綁定模式可避免 TwoWay 綁定模式的系統開銷。

TwoWay 綁定導緻對源屬性的更改會自動更新目标屬性,而對目标屬性的更改也會自動更新源屬性。此綁定類型适用于可編輯窗體或其他完全互動式 UI 方案。大多數屬性都預設為 OneWay 綁定,但是一些依賴項屬性(通常為使用者可編輯的控件的屬性,如 TextBox 的 Text 屬性和 CheckBox 的 IsChecked 屬性)預設為 TwoWay 綁定。确定依賴項屬性在預設情況下是單向綁定還是雙向綁定的一種程式設計方式是使用 GetMetadata 擷取屬性的屬性中繼資料,然後檢查 BindsTwoWayByDefault 屬性的布爾值。

OneWayToSource 與 OneWay 綁定相反;它在目标屬性更改時更新源屬性。一個示例方案是您隻需要從 UI 重新計算源值的情況。

OneTime 綁定未在圖中顯示,該綁定會導緻源屬性初始化目标屬性,但不傳播後續更改。這意味着,如果資料上下文發生了更改,或者資料上下文中的對象發生了更改,則更改會反映在目标屬性中。如果您使用的資料的目前狀态的快照适于使用,或者這些資料是真正靜态的,則适合使用此綁定類型。如果要使用源屬性中的某個值初始化目标屬性,并且事先不知道資料上下文,則也可以使用此綁定類型。此綁定類型實質上是 OneWay 綁定的簡化形式,在源值不更改的情況下可以提供更好的性能。

請注意,若要檢測源更改(适用于 OneWay 和 TwoWay 綁定),則源必須實作一種合适的屬性更改通知機制(如 INotifyPropertyChanged)。

觸發源更新的原因

TwoWay 或 OneWayToSource 綁定偵聽目标屬性的更改,并将這些更改傳播回源。這稱為更新源。例如,可以編輯文本框中的文本以更改基礎源值。如上一節中所述,資料流的方向由綁定的 Mode 屬性的值确定。

但是,源值是在您編輯文本的同時進行更新,還是在您結束編輯文本并将滑鼠指針從文本框移走後才進行更新呢? 綁定的 UpdateSourceTrigger 屬性确定觸發源更新的原因。下圖中右箭頭的點示範 UpdateSourceTrigger 屬性的角色:

WPF資料綁定詳解

如果 UpdateSourceTrigger 值為 PropertyChanged,則 TwoWay 或 OneWayToSource 綁定的右箭頭指向的值會在目标屬性更改時立刻進行更新。但是,如果 UpdateSourceTrigger 值為 LostFocus,則僅當目标屬性失去焦點時,該值才會使用新值進行更新。

與 Mode 屬性類似,不同的依賴項屬性具有不同的預設 UpdateSourceTrigger 值。大多數依賴項屬性的預設值都為 PropertyChanged,而 Text 屬性的預設值為 LostFocus。這意味着,隻要目标屬性更改,源更新通常都會發生,這對于 CheckBox 和其他簡單控件很有用。但對于文本字段,每次鍵擊之後都進行更新會降低性能,使用者也沒有機會在送出新值之前使用倒退鍵修改鍵入錯誤。這就是為什麼 Text 屬性的預設值是 LostFocus 而不是 PropertyChanged 的原因。

下表使用 TextBox 作為示例提供每個 UpdateSourceTrigger 值的示例方案:

WPF資料綁定詳解

建立綁定

前面幾節中讨論的一些概念可以概括為:使用 Binding 對象建立綁定,每個綁定通常都具有四個元件:綁定目标、目标屬性、綁定源、要使用的源值的路徑。本節讨論如何設定綁定。

請看下面的示例,其中的綁定源對象是一個名為 MyData 的類,該類在 SDKSample 命名空間中定義。出于示範的目的,MyData 類具有一個名為 ColorName 的字元串屬性,該屬性的值設定為“Red”。是以,此示例生成一個具有紅色背景的按鈕。

<DockPanel

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

xmlns:c="clr-namespace:SDKSample">

<DockPanel.Resources>

<c:MyData x:Key="myDataSource"/>

</DockPanel.Resources>

<DockPanel.DataContext>

<Binding Source="{StaticResource myDataSource}"/>

</DockPanel.DataContext>

<Button Background="{Binding Path=ColorName}"

Width="150" Height="30">I am bound to RED!</Button>

</DockPanel>

如果将此示例應用于基本關系圖,則生成的圖如下所示。這是一個 OneWay 綁定,因為 Background 屬性在預設情況下支援 OneWay 綁定。

WPF資料綁定詳解

您可能會奇怪為什麼會這樣,即使 ColorName 屬性為類型字元串,而 Background 屬性為 Brush 類型。這是由于進行了預設類型轉換,此類型轉換在資料轉換一節中進行了讨論。

指定綁定源

請注意,在上一個示例中,綁定源是通過設定 DockPanel 元素上的 DataContext 屬性來指定的。Button 随後從 DockPanel(這是其父元素)繼承 DataContext 值。在這裡重複一下,綁定源對象是綁定的四個必需元件之一。是以,如果未指定綁定源對象,則綁定将沒有任何作用。

可通過多種方法指定綁定源對象。在将多個屬性綁定到相同源時,可以使用父元素上的 DataContext 屬性。但是,在各個綁定聲明上指定綁定源有時可能更為合适。對于上一個示例,可以不使用 DataContext 屬性,而是通過在按鈕的綁定聲明上直接設定 Source 屬性來指定綁定源,如下面的示例中所示:

<DockPanel.Resources>

<c:MyData x:Key="myDataSource"/>

</DockPanel.Resources>

<Button Width="150" Height="30"

Background="{Binding Source={StaticResource myDataSource},

Path=ColorName}">I am bound to RED!</Button>

除了在元素上直接設定 DataContext 屬性、從上級繼承 DataContext 值(如第一個示例中的按鈕)、通過設定 Binding 上的 Source 屬性來顯式指定綁定源(如最後一個示例中的按鈕),還可以使用 ElementName 屬性或 RelativeSource 屬性指定綁定源。當綁定到應用程式中的其他元素時(例如在使用滑塊調整按鈕的寬度時),ElementName 屬性是很有用的。當在 ControlTemplate 或 Style 中指定綁定時,RelativeSource 屬性是很有用的。

指定值的路徑

如果綁定源是一個對象,則可使用 Path 屬性指定要用于綁定的值。如果要綁定到 XML 資料,則可使用 XPath 屬性指定該值。在某些情況下,可以使用 Path 屬性,即使在資料為 XML 時。例如,如果要通路傳回的 XmlNode(作為 XPath 查詢的結果)的 Name 屬性,則應使用 Path 屬性和 XPath 屬性。

請注意,雖然我們已強調要使用的值的 Path 是綁定的四個必需元件之一,但在要綁定到整個對象的情況下,要使用的值會與綁定源對象相同。在這些情況下,不指定 Path 比較合适。請看下面的示例:

<ListBox ItemsSource="{Binding}"

IsSynchronizedWithCurrentItem="true"/>

上面的示例使用空綁定文法:{Binding}。在此情況下,ListBox 從父 DockPanel 元素繼承 DataContext(此示例中未示範)。當未指定路徑時,預設為綁定到整個對象。換句話說,在此示例中路徑已被省略,因為要将 ItemsSource 屬性綁定到整個對象。

除了綁定到集合以外,在希望綁定到整個對象,而不是僅綁定到對象的單個屬性時,也可以使用此方案。例如,在源對象為類型字元串,并且您僅僅希望綁定到該字元串本身時。另一種常見情況是您希望将一個元素綁定到具有多個屬性的一個對象。

請注意,您可能需要應用自定義邏輯,使資料對您的綁定目标屬性有意義。自定義邏輯的形式可以是自定義轉換器(如果預設類型轉換不存在)。

Binding 和 BindingExpression

在詳細介紹資料綁定的其他功能和用法之前,介紹 BindingExpression 類會十分有用。如前面幾節所述,Binding 類是用于綁定聲明的進階别類;Binding 類提供了很多屬性,您可以利用這些類來指定綁定的特征。相關類 BindingExpression 是維持源與目标之間的連接配接的基礎對象。一個綁定包含可以在多個綁定表達式之間共享的所有資訊。BindingExpression 是無法共享的執行個體表達式,其中包含有關 Binding 的所有執行個體資訊。

例如,請看下面的示例,其中 myDataObject 是 MyData 類的執行個體,myBinding 是源 Binding 對象,MyData 類是包含一個名為 MyDataProperty 的字元串屬性的已定義類。此示例将 mytext(TextBlock 的執行個體)的文本内容綁定到 MyDataProperty。

//make a new source

MyData myDataObject = new MyData(DateTime.Now);

Binding myBinding = new Binding("MyDataProperty");

myBinding.Source = myDataObject;

myText.SetBinding(TextBlock.TextProperty, myBinding);  

Dim data1 As New MyData(DateTime.Now)

Dim binding1 As New Binding("MyDataProperty")

binding1.Source = data1

Me.myText.SetBinding(TextBlock.TextProperty, binding1)  

您可以使用相同的 myBinding 對象來建立其他綁定。例如,可以使用 myBinding 對象将複選框的文本内容綁定到 MyDataProperty。在這種情況下,将有兩個 BindingExpression 執行個體共享 myBinding 對象。

可以通過對資料綁定對象調用 GetBindingExpression 的傳回值來擷取 BindingExpression 對象。以下主題示範 BindingExpression 類的一些用法:

如何:從綁定目标屬性擷取綁定對象

如何:控制文本框文本更新源的時間

資料轉換

在上面的示例中,按鈕是紅色的,因為其 Background 屬性綁定到一個值為“Red”的字元串屬性。可以這樣做的原因是 Brush 類型上提供了一個類型轉換器,可以将字元串值轉換為 Brush。

要将此資訊添加到建立綁定一節的圖中,關系圖如下所示:

WPF資料綁定詳解

但是,如果綁定源對象不是具有類型字元串的屬性,而是具有類型 Color 的 Color 屬性,該怎麼辦? 在這種情況下,為了使綁定正常工作,您需要先将 Color 屬性值轉換為 Background 屬性所接受的值。您需要通過實作 IValueConverter 接口來建立一個自定義轉換器,如下面的示例中所示:

[ValueConversion(typeof(Color), typeof(SolidColorBrush))]

public class ColorBrushConverter : IValueConverter

{

public object Convert(object value, Type targetType, object parameter,

System.Globalization.CultureInfo culture)

{

Color color = (Color)value;

return new SolidColorBrush(color);

}

public object ConvertBack(object value, Type targetType, object parameter,

System.Globalization.CultureInfo culture)

{

return null;

}

}  

現在使用自定義轉換器而不是預設轉換,關系圖如下所示:

WPF資料綁定詳解

在這裡重複一下,由于要綁定到的類型中提供了類型轉換器,因而可以使用預設轉換。此行為取決于目标中可用的類型轉換器。如果無法确定,請建立您自己的轉換器。

下面提供了一些典型方案,在這些方案中,實作資料轉換器是非常有意義的:

資料應根據區域性以不同方式顯示。例如,可能需要根據在特定區域性中使用的值或标準,來實作貨币轉換器或月曆日期/時間轉換器。

使用的資料不一定會更改屬性的文本值,但會更改其他某個值(如圖像的源,或顯示文本的顔色或樣式)。在這種情況下,可以通過轉換可能不合适的屬性綁定(如将文本字段綁定到表單元格的 Background 屬性)來使用轉換器。

将多個控件或控件的多個屬性綁定到相同資料。在這種情況下,主綁定可能僅顯示文本,而其他綁定則處理特定的顯示問題,但仍使用同一綁定作為源資訊。

到目前為止,我們尚未讨論 MultiBinding(其目标屬性具有綁定集合)。對于 MultiBinding,可以使用自定義 IMultiValueConverter 從綁定的值生成最終值。例如,可以從紅色、藍色和綠色的值來計算顔色,這些值可以是來自于相同或不同綁定源對象的值。

綁定到集合

綁定源對象可以視為其屬性包含資料的單個對象,也可以視為通常組合在一起的多态對象的資料集合(如查詢資料庫的結果)。到目前為止,我們僅讨論了綁定到單個對象,但是綁定到資料集合是一個常見方案。例如,一個常見方案是使用 ItemsControl(如 ListBox、ListView 或 TreeView)來顯示資料集合,如什麼是資料綁定?一節中的應用程式所示。

幸運的是,基本關系圖仍然适用。如果要将 ItemsControl 綁定到集合,則關系圖如下所示:

WPF資料綁定詳解

正如此圖中所示,若要将 ItemsControl 綁定到集合對象,應使用 ItemsSource 屬性。可以将 ItemsSource 屬性視為 ItemsControl 的内容。請注意,綁定是 OneWay,因為 ItemsSource 屬性預設情況下支援 OneWay 綁定。

如何實作集合

可以枚舉實作 IEnumerable 接口的任何集合。但是,若要設定動态綁定,以使集合中的插入或移除操作可以自動更新 UI,則該集合必須實作 INotifyCollectionChanged 接口。此接口公開一個事件,隻要基礎集合發生更改,都應該引發該事件。

WPF 提供 ObservableCollection<(Of <(T>)>) 類,它是公開 INotifyCollectionChanged 接口的資料集合的内置實作。請注意,為了完全支援将資料值從源對象傳送到目标,支援可綁定屬性的集合中的每個對象還必須實作 INotifyPropertyChanged 接口。

在實作自己的集合之前,請先考慮使用 ObservableCollection<(Of <(T>)>) 或一個現有的集合類,如 List<(Of <(T>)>)、Collection<(Of <(T>)>) 和 BindingList<(Of <(T>)>) 等。如果您有進階方案并且希望實作自己的集合,請考慮使用 IList,它提供可以按索引逐個通路的對象的非泛型集合,因而可提供最佳性能。

集合視圖

一旦 ItemsControl 綁定到資料集合,您可能希望對資料進行排序、篩選或分組。若要執行此操作,可以使用集合視圖,這是實作 ICollectionView 接口的類。

什麼是集合視圖?

可以将集合視圖視為位于綁定源集合頂部的層,您可以通過它使用排序、篩選和分組查詢來導航和顯示源集合,所有這些操作都無需操作基礎源集合本身。如果源集合實作了 INotifyCollectionChanged 接口,則 CollectionChanged 事件引發的更改将傳播到視圖。

由于視圖不會更改基礎源集合,是以每個源集合可以有多個關聯的視圖。例如,您可以有 Task 對象的集合。通過使用視圖,可以通過多種不同的方式來顯示相同資料。例如,您可能希望在頁面左側顯示按優先級排序的任務,而在頁面右側顯示按區域分組的任務。

如何建立視圖

建立和使用視圖的一種方式是直接執行個體化視圖對象并将其用作綁定源。例如,請考慮在什麼是資料綁定?一節中示範的資料綁定示範應用程式。 該應用程式的實作方式是将 ListBox 綁定到資料集合上的視圖,而不是直接綁定到資料集合。下面的示例摘自資料綁定示範應用程式。CollectionViewSource 類是 CollectionView 的可擴充應用程式标記語言 (XAML) 代理。在此特定示例中,視圖的 Source 綁定到目前應用程式對象的 AuctionItems 集合(類型為 ObservableCollection<(Of <(T>)>))。

<Window.Resource>

...

<CollectionViewSource

Source="{Binding Source={x:Static Application.Current}}",

x:Key="listingDataView"/>

...

</Window.Resource>  

資源 listingDataView 随後用作應用程式中元素(如 ListBox)的綁定源:

<ListBox Name="Master" Grid.Row="2" Grid.ColumnSpan="3" Margin="8"

ItemsSource="{Binding Source={StaticResource listingDataView}}">

...

</ListBox>  

若要為同一集合建立另一個視圖,可以建立另一個 CollectionViewSource 執行個體并為其提供另一個 x:Key 名稱。

将視圖用作綁定源并不是建立和使用集合視圖的唯一方式。所有集合都具有一個預設集合視圖。例如,對于所有實作 IEnumerable 的集合,CollectionView 都是預設視圖對象。ListCollectionView 是實作 IList 的集合的預設視圖對象,而 BindingListCollectionView 是用于那些實作 IBindingList 的集合的集合視圖類。若要擷取預設視圖,請使用 GetDefaultView 方法。

排序

如前所述,視圖可以将排序順序應用于集合。如同在基礎集合中一樣,資料可能具有或不具有相關的固有順序。通過集合上的視圖,您可以根據您提供的比較條件來應用順序或更改預設順序。由于這是基于用戶端的資料視圖,是以一種常見情況是使用者可能希望根據列對應的值,對多清單格資料進行排序。通過使用視圖,可以應用這種使用者驅動的排序,而無需對基礎集合進行任何更改,甚至不必再次查詢集合内容。

下面的示例示範什麼是資料綁定?一節中的應用程式 UI 的“Sort by category and date”(按類别和日期排序)CheckBox 的排序邏輯:

private void AddSorting(object sender, RoutedEventArgs args)

{

// This sorts the items first by Category and within each Category,

// by StartDate. Notice that because Category is an enumeration,

// the order of the items is the same as in the enumeration declaration

listingDataView.SortDescriptions.Add(

new SortDescription("Category", ListSortDirection.Ascending));

listingDataView.SortDescriptions.Add(

new SortDescription("StartDate", ListSortDirection.Ascending));

}  

篩選

視圖還可以将篩選器應用于集合。這意味着即使集合中可能存在一個項,此特定視圖也僅用于顯示整個集合的某個子集。可以根據條件在資料中進行篩選。例如,正如在什麼是資料綁定? 一節中的應用程式的工作方式那樣,“Show only bargains”(僅顯示成交商品)CheckBox 包含了篩選出成交價為 25 美元或更高的項的邏輯。執行下面的代碼可以在選中該 CheckBox 時将 ShowOnlyBargainsFilter 設定為 Filter 事件處理程式:

listingDataView.Filter += new FilterEventHandler(ShowOnlyBargainsFilter);  

ShowOnlyBargainsFilter 事件處理程式具有以下實作:

private void ShowOnlyBargainsFilter(object sender, FilterEventArgs e)

{

AuctionItem product = e.Item as AuctionItem;

if(product != null)

{

//Filter out products with price 25 or above

if(product.CurrentPrice < 25)

{

e.Accepted = true;

}

else

{

e.Accepted = false;

}

}

}  

如果直接使用一個 CollectionView 類而不是 CollectionViewSource,則應使用 Filter 屬性來指定回調。

分組

視圖支援分組功能,此功能使使用者能夠将集合視圖中的集合分區為邏輯組。這些組可以是顯式的,其中的使用者提供組清單,也可以是隐式的,其中的組依據資料動态生成。

下面的示例示範“Group by category”(按類别分組)CheckBox 的邏輯:

// This groups the items in the view by the property "Category"

PropertyGroupDescription groupDescription = new PropertyGroupDescription();

groupDescription.PropertyName = "Category";

listingDataView.GroupDescriptions.Add(groupDescription);  

目前記錄指針

視圖還支援目前項的概念。可以在集合視圖中的對象之間導航。在導航時,您是在移動記錄指針,該指針可用于檢索存在于集合中的特定位置的對象。

請注意,目前記錄指針的移動與應用于該集合的任何排序或篩選會互相産生某些影響。排序将目前記錄指針保留在所選的最後一條記錄上,但集合視圖現在是圍繞此指針重構的。(或許所選記錄以前曾位于清單的開頭,但現在所選記錄可能在中間的某個位置。) 如果所選内容在篩選之後保留在視圖中,則篩選操作會保留所選記錄。否則,目前記錄指針會設定到經過篩選的集合視圖的第一條記錄。

主-從綁定方案

目前項的概念不僅用于集合中項的導航,而且用于主-從綁定方案。再考慮一下什麼是資料綁定?中的應用程式 UI。 在該應用程式中,ListBox 的所選内容決定了在 ContentControl 中顯示的内容。換句話說,當選中一個 ListBox 項時,ContentControl 會顯示標明項的詳細資訊。

通過将兩個或更多控件綁定到同一視圖可以輕松地實作主-從方案。下面這個摘自資料綁定示範的示例示範什麼是資料綁定?中的應用程式 UI 上看到的 ListBox 和 ContentControl 的标記:

<ListBox Name="Master" Grid.Row="2" Grid.ColumnSpan="3" Margin="8"

ItemsSource="{Binding Source={StaticResource listingDataView}}">

...

</ListBox>

...

<ContentControl Name="Detail" Grid.Row="3" Grid.ColumnSpan="3"

Content="{Binding Source={StaticResource listingDataView}}"

ContentTemplate="{StaticResource detailsProductListingTemplate}"

Margin="9,0,0,0"/>  

請注意,這兩個控件都綁定到同一個源,即 listingDataView 靜态資源(請參見如何建立視圖一節中的此資源的定義)。可以這樣做的原因是當單一執行個體對象(此示例中為 ContentControl)綁定到一個集合視圖時,該對象會自動綁定到該視圖的 CurrentItem。請注意,CollectionViewSource 對象會自動同步貨币與所選内容。如果清單控件沒有像示例中那樣綁定到 CollectionViewSource 對象,則您需要将其 IsSynchronizedWithCurrentItem 屬性設定為 true 以達到此目的。

您可能已經注意到上面的示例使用了一個模闆。實際上,如果不使用模闆(由 ContentControl 顯式使用的模闆以及由 ListBox 隐式使用的模闆),則資料不會按照我們希望的方式顯示。現在,我們開始介紹下一節中的資料模闆化。

資料模闆化

如果不使用資料模闆,則什麼是資料綁定?一節中的應用程式 UI 将如下所示:

WPF資料綁定詳解

如上一節中的示例所示,ListBox 控件和 ContentControl 都綁定到 AuctionItem 的整個集合對象(更具體地說,是綁定到集合對象上的視圖)。如果沒有關于如何顯示資料集合的特定說明,則 ListBox 會顯示基礎集合中的每個對象的字元串表示形式,而 ContentControl 會顯示綁定到的對象的字元串表示形式。

若要解決該問題,應用程式應定義 DataTemplate。如上一節中的示例所示,ContentControl 顯式使用 detailsProductListingTemplate DataTemplate。在顯示集合中的 AuctionItem 對象時,ListBox 控件隐式使用下面的 DataTemplate:

<DataTemplate DataType="{x:Type src:AuctionItem}">

<Border BorderThickness="1" BorderBrush="Gray"

Padding="7" Name="border" Margin="3" Width="500">

<Grid>

<Grid.RowDefinitions>

<RowDefinition />

<RowDefinition />

<RowDefinition />

<RowDefinition />

</Grid.RowDefinitions>

<Grid.ColumnDefinitions>

<ColumnDefinition Width="20"/>

<ColumnDefinition Width="86"/>

<ColumnDefinition Width="*"/>

</Grid.ColumnDefinitions>

<Polygon Grid.Row="0" Grid.Column="0" Grid.RowSpan="4"

Fill="Yellow" Stroke="Black" StrokeThickness="1"

StrokeLineJoin="Round" Width="20" Height="20"

Stretch="Fill"

Points="9,2 11,7 17,7 12,10 14,15 9,12 4,15 6,10 1,7 7,7"

Visibility="Hidden" Name="Star"/>

<TextBlock Grid.Row="0" Grid.Column="1" Margin="0,0,8,0"

Name="descriptionTitle"

Style="{StaticResource smallTitleStyle}">Description:</TextBlock>

<TextBlock Name="DescriptionDTDataType" Grid.Row="0" Grid.Column="2"

Text="{Binding Path=Description}"

Style="{StaticResource textStyleTextBlock}"/>

<TextBlock Grid.Row="1" Grid.Column="1" Margin="0,0,8,0"

Name="currentPriceTitle"

Style="{StaticResource smallTitleStyle}">Current Price:</TextBlock>

<StackPanel Grid.Row="1" Grid.Column="2" Orientation="Horizontal">

<TextBlcok Text="$" Style="{StaticResource textStyleTextBlock}"/>

<TextBlock Name="CurrentPriceDTDataType"

Text="{Binding Path=CurrentPrice}"

Style="{StaticResource textStyleTextBlock}"/>

</StackPanel>

</Grid>

</Border>

<DataTemplate.Triggers>

<DataTrigger Binding="{Binding Path=SpecialFeatures}">

<DataTrigger.Value>

<src:SpecialFeatures>Color</src:SpecialFeatures>

</DataTrigger.Value>

<DataTrigger.Setters>

<Setter Property="BorderBrush" Value="DodgerBlue" TargerName="border" />

<Setter Property="Foreground" Value="Navy" TargerName="descriptionTitle" />

<Setter Property="Foreground" Value="Navy" TargerName="currentPriceTitle" />

<Setter Property="BorderThickness" Value="3" TargerName="border" />

<Setter Property="Padding" Value="5" TargerName="border" />

</DataTrigger.Setters>

</DataTrigger>

<DataTrigger Binding="{Binding Path=SpecialFeatures}">

<DataTrigger.Value>

<src:SpecialFeatures>Highlight</src:SpecialFeatures>

</DataTrigger.Value>

<DataTrigger.Setters>

<Setter Property="BorderBrush" Value="Orange" TargerName="border" />

<Setter Property="Foreground" Value="Navy" TargerName="descriptionTitle" />

<Setter Property="Foreground" Value="Navy" TargerName="currentPriceTitle" />

<Setter Property="Visibility" Value="Visible" TargerName="star" />

<Setter Property="BorderThickness" Value="3" TargerName="border" />

<Setter Property="Padding" Value="5" TargerName="border" />

</DataTrigger.Setters>

</DataTrigger>

</DataTemplate.Triggers>

</DataTemplate>   

使用這兩個 DataTemplate,生成的 UI 如什麼是資料綁定?所示。 如螢幕快照所示,除了可以在控件中放置資料以外,使用 DataTemplate 還可以為資料定義引人注目的視覺效果。例如,上面的 DataTemplate 中使用了 DataTrigger,因而 SpecialFeatures 值為 HighLight 的 AuctionItem 會顯示為帶有橙色邊框和一個星号。

資料驗證

接受使用者輸入的大多數應用程式都需要具有驗證邏輯,以確定使用者輸入了需要的資訊。驗證檢查可以基于類型、範圍、格式或其他應用程式特定的要求。本節讨論了資料驗證在 WPF 中的工作方式。

将驗證規則與綁定關聯

使用 WPF 資料綁定模型可以将 ValidationRules 與 Binding 對象相關聯。例如,下面是什麼是資料綁定?一節中的 Add Product Listing(添加産品清單)“Start Price”(起始價格)TextBox 的 XAML:

<TextBox Name="StartPriceEntryForm" Grid.Row="2" Grid.Column="1"

Style="{StaticResource textStyleTextBlock}" Margin="8,5,0,5">

<TextBox.Text>

<Binding Path="StartPrice" UpdateSourceTrigger="PropertyChanged">

<Binding.ValidationRules>

<ExceptionValidationRule />

</Binding.ValidationRules>

</Binding>

</TextBox.Text>

</TextBox>  

ValidationRules 屬性采用 ValidationRule 對象的集合。ExceptionValidationRule 是内置的 ValidationRule,用于檢查在綁定源屬性的更新過程中引發的異常。在此特定示例中,綁定源屬性為 StartPrice(屬于整數類型),而目标屬性為 TextBox.Text。當使用者輸入的值無法轉換為整數時,将引發異常,這會導緻将綁定标記為無效。

也可以通過從 ValidationRule 類派生和實作 Validate 方法來建立自己的驗證規則。下面的示例示範什麼是資料綁定?一節中的 Add Product Listing(添加産品清單)“Start Date”(起始日期)TextBox 使用的規則:

class FutureDateRule : ValidationRule

{

public override ValidationResult Validate(object value, CultureInfo cultureInfo)

{

DateTime date;

try

{

date = DateTime.Parse(value.ToString());

}

catch (FormatException)

{

return new ValidationResult(false, "Value is not a valid date.");

}

if(DateTime.Now.Date > date)

{

return new ValidationResult(false, "Please enter a date in the future.");

}

else

{

return ValidationResult.ValidResult;

}

}

}  

StartDateEntryForm TextBox 使用此 FutureDateRule,如下面的示例中所示:

<TextBox Name="StartDateEntryForm" Grid.Row="3" Grid.Column="1"

Validation.ErrorTemplate="{StaticResource validationTemplate}"

Style="{StaticResource textStyleTextBlock}" Margin="8,5,0,5">

<TextBox.Text>

<Binding Path="StartDate" UpdateSourceTrigger="PropertyChanged"

Converter="{StaticResource dateConverter}">

<Binding.ValidationRules>

<src:FutureDateRule />

</Binding.ValidationRules>

</Binding>

</TextBox.Text>

</TextBox>  

請注意,由于 UpdateSourceTrigger 值為 PropertyChanged,是以在每次鍵擊時,綁定引擎都會更新源值,這意味着該引擎還會在每次鍵擊時檢查 ValidationRules 集合中的每條規則。我們會在“驗證過程”一節中進行更深入的讨論。

提供視覺回報

如果使用者輸入的值無效,則您可能希望在應用程式 UI 上提供一些有關錯誤的回報。提供這種回報的一種方式是将 Validation..::.ErrorTemplate 附加屬性設定為自定義 ControlTemplate。如上一小節中所示,StartDateEntryForm TextBox 使用一個名為 validationTemplate 的 ErrorTemplate。下面的示例顯示 validationTemplate 的定義:

<ControlTemplate x:Key="validationTemplate">

<DockPanel>

<TextBlock Foreground="Red" FontSize="20">!</TextBlock>

<AdornedElementPlaceholder />

</DockPanel>

</ControlTemplate>  

AdornedElementPlaceholder 元素指定要裝飾的控件應放置的位置。

此外,您可能還要使用 ToolTip 顯示錯誤消息。StartDateEntryForm 和 StartPriceEntryForm TextBox 都使用樣式 textStyleTextBox,該樣式建立一個顯示錯誤消息的 ToolTip。下面的示例顯示 textStyleTextBox 的定義。當綁定元素的屬性的一個或多個綁定發生錯誤時,附加屬性 Validation.HasError 為 true。

<Style x:Key="textStyleTextBox" TargetType="TextBox">

<Setter Property="Foreground" Value="#333333" />

<Setter Property="MaxLength" Value="40" />

<Setter Property="Width" Value="392" />

<Style.Triggers>

<Trigger Property="Validation.HasError" Value="true">

<Setter Property="ToolTip"

Value="{Binding RelativeSource={RelativeSource Self},

Path=(Validation.Errors)[0].ErrorContent}"/}"

</Trigger>

</Style.Triggers>

</Style>  

使用自定義 ErrorTemplate 和 ToolTip,StartDateEntryForm TextBox 在發生驗證錯誤時會如下所示:

WPF資料綁定詳解

如果您的 Binding 具有關聯驗證規則,但是您未在綁定控件上指定 ErrorTemplate,則在出現驗證錯誤時會使用預設 ErrorTemplate 來通知使用者。預設 ErrorTemplate 是一個在裝飾器層中定義紅色邊框的控件模闆。使用預設 ErrorTemplate 和 ToolTip,StartPriceEntryForm TextBox 的 UI 在發生驗證錯誤時會如下所示:

WPF資料綁定詳解

有關如何提供邏輯以驗證對話框中的所有控件的示例,請參見對話框概述中的“自定義對話框”一節。

驗證過程

由于資料驗證與從目标到源的更新有關,是以它僅應用于 TwoWay 和 OneWayToSource 綁定。每次将輸入值傳輸到綁定源屬性時會發生驗證。在這裡重複一下,導緻源更新的原因取決于 UpdateSourceTrigger 屬性的值,如觸發源更新的原因一節中所述。

下面的插圖提供了在資料綁定過程中适合進行驗證的位置的可視表示形式:

WPF資料綁定詳解

如上圖所示,驗證在調用轉換器之前從目标到源的值傳輸過程中發生。下面将介紹驗證 過程,如上面的關系圖中所标記的那樣:

在将值從目标屬性傳輸到源屬性時,資料綁定引擎首先移除可能已添加到綁定元素的 Validation.Errors 附加屬性的任何 ValidationError。然後,資料綁定引擎檢查是否為該 Binding 定義了自定義 ValidationRule;如果已經定義,則它将調用每個 ValidationRule 上的 Validate 方法,直到其中一個規則出錯或者全部規則都通過為止。

如果某個自定義規則未通過,則綁定引擎會建立一個 ValidationError 對象,并将該對象添加到綁定元素的 Validation.Errors 集合。如果 Validation.Errors 不為空,則元素的 Validation.HasError 附加屬性會設定為 true。此外,如果 Binding 的 NotifyOnValidationError 屬性設定為 true,則綁定引擎将引發該元素上的 Validation.Error 附加事件。

如果所有規則都通過,則綁定引擎會調用轉換器(如果存在)。

如果轉換器通過,則綁定引擎會調用源屬性的 setter。

如果綁定具有與其關聯的 ExceptionValidationRule,并且在步驟 4 中引發異常,則綁定引擎将檢查是否存在 UpdateSourceExceptionFilter。您可以選擇使用 UpdateSourceExceptionFilter 回調來提供用于處理異常的自定義處理程式。如果未對 Binding 指定 UpdateSourceExceptionFilter,則綁定引擎将對異常建立 ValidationError 并将其添加到綁定元素的 Validation.Errors 集合。

還應注意,任何方向(目标到源或源到目标)的有效值傳輸操作都将清除 Validation.Errors 附加屬性。

調試機制

可以在綁定相關對象上設定附加屬性 PresentationTraceSources..::.TraceLevel 以接收有關特定綁定的狀态的資訊。

繼續閱讀