資料繫結概觀

更新: 2008 年 7 月

Windows Presentation Foundation (WPF) 資料繫結在資料的展示和互動上,提供應用程式簡單而一致的方式。項目可以與來自各種資料來源的資料進行繫結,資料的型式可以是 Common Language Runtime (CLR) 物件和 XML。ContentControl (例如 Button) 以及 ItemsControl (例如 ListBoxListView) 具有內建的功能,可以讓單一資料項目或資料項目集合擁有彈性的樣式。您可以依據資料產生排序、篩選和群組檢視。

相較於傳統模型,WPF 中的資料繫結功能具有數個優點,包括本身就支援資料繫結的相當多屬性、資料的彈性 UI 表示,以及 UI 個別的清楚商務邏輯。

本主題會先討論 WPF 資料繫結的基本概念,然後再說明 Binding 類別的使用方式和資料繫結的其他功能。

這個主題包含下列章節。

  • 資料繫結是什麼
  • 基本資料繫結概念
  • 建立繫結
  • 資料轉換
  • 繫結至集合
  • 資料樣板化
  • 資料驗證
  • 偵錯機制
  • 相關主題

資料繫結是什麼

資料繫結是在應用程式 UI 和商務邏輯間建立連接的過程。如果繫結具有正確的設定,且資料有提供適當的告知,那麼,在資料值變更時,繫結到該資料的項目就會自動反映變更。資料繫結也代表在項目資料的外部表示變更時,那麼基礎資料也會自動更新以反映變更。舉例來說,當使用者編輯 TextBox 項目中的值時,基礎資料值也會自動更新以反映該變更。

一個資料繫結的常見用法是將伺服器或本機組態資料,放入表單或其他 UI 控制項中。WPF 中會擴充這個概念,以包含相當多屬性對各種資料來源的繫結。在 WPF 中,項目的相依性屬性可以繫結到 CLR 物件 (包括 ADO.NET 物件或與 Web 服務和 Web 屬性關聯的物件) 和 XML 資料。

如需資料繫結的範例,請參考下列來自資料繫結示範的應用程式 UI:

資料繫結範例螢幕擷取畫面

上面的應用程式 UI 會顯示拍賣項目的清單。應用程式會示範下列資料繫結功能:

  • ListBox 的內容會繫結到 AuctionItem 物件的集合。AuctionItem 物件具有這些屬性:DescriptionStartPriceStartDateCategorySpecialFeatures 等等。

  • ListBox 中顯示的資料 (AuctionItem 物件) 已套用樣板,所以會針對每個項目顯示描述和目前價格。這是藉由使用 DataTemplate 而達成的。除此之外,每個項目的外觀取決於所顯示 AuctionItemSpecialFeatures 值。如果 AuctionItemSpecialFeatures 值是 Color,項目就具有藍色框線。如果值是 Highlight,項目就具有橘色框線和星號。資料樣板化一節會提供資料樣板化的相關資訊。

  • 使用者可以使用所提供的 CheckBox 來群組、篩選或排序資料。在上圖中的 "Group by category" 和 "Sort by category and date" CheckBox 是選取的。您可能已經注意到,資料的群組化是依據產品的分類,且分類名稱是以字母順序排列。雖然在圖中很難辨識,但項目在每個分類內也有以開始日期排序。這是藉由使用集合檢視而達成的。繫結至集合一節會討論集合檢視。

  • 當使用者選取項目時,ContentControl 會顯示選取項目的詳細資料。這稱為主從式案例。主從式繫結案例一節會提供這類型繫結案例的相關資訊。

  • StartDate 屬性的型別是 DateTime,所傳回的日期包含以毫秒顯示的時間。這個應用程式中有使用自訂轉換子,好顯示較短的日期字串。資料轉換一節會提供轉換子的相關資訊。

當使用者按一下 [Add Product] 按鈕時,會出現下列表單:

加入產品清單頁面

使用者可以編輯表單中的欄位、使用簡短預覽和更為詳盡的預覽窗格來預覽產品清單,然後再按一下 [Submit] 以加入新產品清單。任何現有的群組、篩選和排序功能會套用到新項目上。在這種特定情形下,輸入上圖的項目會在 [Computer] 分類內顯示為第二個項目。

這個圖中沒有顯示出 Start Date TextBox 所提供的驗證邏輯。如果使用者輸入無效的日期 (無效的格式或過去的日期),就會以 ToolTip 告知使用者,且 TextBox 旁邊會有紅色驚嘆號。資料驗證一節會討論如何建立驗證邏輯。

在進入上面概要說明的各種功能之前,下節中我們會先討論對於了解 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 屬性,即可以控制這個情況。下圖說明不同類型的資料流程:

資料繫結資料流程

  • OneWay 繫結會讓來源屬性的變更自動更新到目標屬性,但目標屬性的變更不會散佈回來源屬性。這類型的繫結適用於所繫結控制項是隱含唯讀的。例如,您可能繫結到股票行情即時看板這類的來源,或者沒有提供目標屬性可以進行變更的控制項介面,例如資料表的資料繫結背景色彩。如果沒有需要監視目標屬性的變更,則使用 OneWay 繫結模式可以避免 TwoWay 繫結模式所帶來的負荷。

  • TwoWay 繫結會讓來源屬性或目標屬性的變更,自動更新另外一個。這類型的繫結適用於可編輯表單或其他完全互動式 UI 案例。大部分的屬性預設是 OneWay 繫結,但有些相依性屬性 (通常是使用者可編輯的控制項屬性,例如 TextBoxText 屬性和 CheckBoxIsChecked 屬性) 則預設為 TwoWay 繫結。有個程式設計方式可以判斷相依性屬性預設是單向或是雙向,就是使用 GetMetadata 以取得屬性的屬性中繼資料,然後再檢查 BindsTwoWayByDefault 屬性的布林值。

  • OneWayToSourceOneWay 繫結的反向,會在目標屬性變更時更新來源屬性。有一個範例案例是當您只需要從 UI 重新評估來源值時。

  • 圖中沒有說明的 OneTime 繫結,會讓來源屬性初始化目標屬性,但後續的變更並不會散佈。這代表如果資料內容發生變更或資料內容中的物件有變更,則變更會反映在目標屬性中。這類型的繫結適用於在您所使用的資料,其目前狀態的快照適合使用,或者是資料是非常靜態的。當您想要使用來源屬性的某個值初始化目標屬性時,而事前不知道到資料內容時,這類型的繫結也非常有用。基本上這是 OneWay 繫結的簡單形式,而這在來源值沒有變更的情況下可以提供更好的效能。

請注意,若要偵測來源變更 (適用於 OneWayTwoWay 繫結),來源必須實作適當的屬性變更告知機制,例如 INotifyPropertyChanged。請參閱 HOW TO:實作屬性變更通知,以取得 INotifyPropertyChanged 實作的範例。

Mode 屬性頁提供您繫結模式的詳細資訊,以及如何指定繫結方向的範例。

觸發來源更新的機制

TwoWayOneWayToSource 繫結會接聽目標屬性的變更,並散佈回來源。這種情況稱為更新來源。舉例來說,您可以編輯 TextBox 的文字以變更基礎來源值。如前一節所述,資料流程的方向是由繫結的 Mode 屬性值所決定的。

然而,當您編輯文字時,或者是在完成文字編輯且將滑鼠指標帶離 TextBox 後,來源值是否有更新?繫結的 UpdateSourceTrigger 屬性會決定觸發來源更新的機制。下圖中右箭頭的點說明 UpdateSourceTrigger 屬性的角色:

UpdateSourceTrigger 圖表

如果 UpdateSourceTrigger 值是 PropertyChanged,則 TwoWayOneWayToSource 繫結的右箭頭所指向的值,就會在目標屬性變更時立即更新。然而,如果 UpdateSourceTrigger 值是 LostFocus,則該值只會在目標屬性失去焦點時更新成新值。

Mode 屬性類似,不同的相依性屬性具有不同的預設 UpdateSourceTrigger 值。大部分相依性屬性的預設值是 PropertyChanged,而 Text 屬性的預設值為 LostFocus。這代表在目標屬性變更時通常會發生來源更新,這對 CheckBox 和其他簡單控制項而言是可以的。然而,對於文字欄位而言,在每個按鍵動作後進行更新會降低效能,並且會拒絕給使用者慣有的機會,在交付新值前按退格鍵和修正輸入錯誤。這就是 Text 屬性預設值為 LostFocus 而非 PropertyChanged 的原因。

如需如何找出相依性屬性的預設 UpdateSourceTrigger 值的詳細資訊,請參閱 UpdateSourceTrigger 屬性頁。

下表使用 TextBox 為範例,提供對於每個 UpdateSourceTrigger 值的範例案例:

UpdateSourceTrigger 值

來源值更新時機

TextBox 的範例案例

LostFocus (TextBox.Text 的預設設定)

TextBox 控制項失去焦點時

與驗證邏輯關聯的 TextBox (請參閱「資料驗證」一節)

PropertyChanged

當您在 TextBox 中輸入時

聊天室視窗中的 TextBox 控制項

Explicit

應用程式呼叫 UpdateSource

可編輯表單中的 TextBox 控制項 (只有在使用者按一下送出按鈕時才會更新來源值)

如需範例,請參閱 HOW TO:TextBox 文字更新來源時控制

建立繫結

這個章節包含下列子章節。

  • 指定繫結來源
  • 指定值的路徑
  • 繫結和 BindingExpression

若要重現上面幾節所討論的一些概念,您可以使用 Binding 物件建立繫結,且每個繫結通常要有四個元件:繫結目標、目標屬性、繫結來源,以及要使用的來源值路徑。本節討論如何設定繫結。

請考慮下列範例,其中的繫結來源物件是名為 MyData 的類別,定義於 SDKSample 命名空間中。為了便於示範,MyData 類別的字串屬性名為 ColorName,其值設為 "Red"。因此,本範例會產生具有紅色背景的按鈕。

<DockPanel
  xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="https://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 be RED!</Button>
</DockPanel>

如需繫結宣告語法的詳細資訊,以及如何在程式碼中設定繫結的範例,請參閱繫結宣告概觀

如果將這個範例套用到我們的基本圖表,結果會類似下圖。這是 OneWay 繫結,因為 Background 屬性預設支援 OneWay 繫結。

資料繫結圖表

您可能會覺得很奇怪,為什麼即使 ColorName 屬性的型別是字串,而 Background 屬性型別是 Brush,卻仍可以運作。這是因為預設型別轉換的作用,在資料轉換一節中會討論到。

指定繫結來源

請注意,前述範例中的繫結來源指定,是藉由設定 DockPanel 項目的 DataContext 屬性。Button 會接著繼承其父項目 DockPanelDataContext 值。再重複聲明一次,繫結來源物件是繫結的四個必要元件之一。因此,沒有指定繫結來源物件,就無法進行繫結。

有數種方式可以指定繫結來源物件。當您要將多個屬性繫結到相同來源時,在父項目上使用 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 be RED!</Button>

除了直接在項目上設定 DataContext 屬性、從祖系繼承 DataContext 值 (例如第一個範例的按鈕),以及藉由在 Binding 上設定 Source 屬性來明確指定繫結來源 (例如前一範例的按鈕) 外,您也可以使用 ElementName 屬性或 RelativeSource 屬性以指定繫結來源。當您要繫結到應用程式中的其他項目時,例如使用滑桿調整按鈕寬度時,ElementName 屬性是很好用的。當在 ControlTemplateStyle 中指定繫結時,RelativeSource 屬性就很好用。如需詳細資訊,請參閱 HOW TO:指定繫結來源

指定值的路徑

如果繫結來源是物件,則要使用 Path 屬性指定繫結要使用的值。如果是要繫結到 XML 資料,則要使用 XPath 屬性指定值。在某些情況下,即使資料是 XML,可能也適合使用 Path 屬性。舉例來說,在您想要存取所傳回 XmlNode (為 XPath 查詢的結果) 的 Name 屬性時,則除了 XPath 屬性之外,還應該使用 Path 屬性。

如需相關語法資訊和範例,請參閱 PathXPath 屬性頁。

請注意,雖然這裡強調要使用值的 Path 是繫結的四個必要元件之一,但在要繫結整個物件的案例中,要使用的值應該與繫結來源物件相同。在這些情況下,最好不要指定 Path。參考下列範例:

<ListBox ItemsSource="{Binding}"
         IsSynchronizedWithCurrentItem="true"/>

上述範例使用空白繫結語法 {Binding}。在這個情況下,ListBox 會從父 DockPanel 項目繼承 DataContext (未在此範例中顯示)。沒有指定路徑時,預設會繫結到整個物件。換句話說,本範例中的路徑保留空白,是因為要將 ItemsSource 屬性繫結到整個物件 (如需深入討論,請參閱繫結至集合一節)。

除了繫結到集合之外,當您要繫結到整個物件而非只是物件的單一屬性時,這個案例也很好用。舉例來說,如果來源物件的型別是字串,而您只是要繫結到字串本身時。另一個常用案例是當您要將項目繫結到具有許多屬性的物件時。

請注意,您可能必須套用自訂邏輯,如此資料對於繫結目標屬性來說才有意義。自訂邏輯的型式可以是自訂轉換子 (如果沒有預設型別轉換的話)。如需轉換子的詳細資訊,請參閱資料轉換一節。

繫結和 BindingExpression

進入資料繫結的其他功能和使用方式之前,先介紹 BindingExpression 類別會很有用。如您在前述小節所見,Binding 類別是繫結宣告的高階類別。Binding 類別提供許多屬性,可以讓您指定繫結的特性。而相關的類別 BindingExpression,是用於維持來源和目標間連接的基礎物件。繫結包含可以跨數種繫結運算式共用的所有資訊。BindingExpression 是無法共用的執行個體運算式,並包含 Binding 的所有執行個體資訊。

舉例來說,請考慮這個情形,當 myDataObjectMyData 類別的執行個體,myBinding 是來源 Binding 物件,而 MyData 類別是包含名為 MyDataProperty 的字串屬性的定義類別。本範例會將 TextBlock 的執行個體 mytext 的文字內容繫結到 MyDataProperty

Dim data1 As New MyData(DateTime.Now)
Dim binding1 As New Binding("MyDataProperty")
binding1.Source = data1
Me.myText.SetBinding(TextBlock.TextProperty, binding1)
//make a new source
  MyData myDataObject = new MyData(DateTime.Now);      
  Binding myBinding = new Binding("MyDataProperty");
  myBinding.Source = myDataObject;
  myText.SetBinding(TextBlock.TextProperty, myBinding);

您可以使用相同的 myBinding 物件建立其他繫結。舉例來說,您可以使用 myBinding 物件將核取方塊的文字內容繫結到 MyDataProperty。在這樣的案例中,會有兩個 BindingExpression 執行個體共用 myBinding 物件。

BindingExpression 物件可以透過在資料繫結物件上呼叫 GetBindingExpression 的傳回值而取得。下列主題示範 BindingExpression 類別的部分使用方式:

資料轉換

在前述範例中的按鈕是紅色的,這是因為其 Background 屬性是繫結到具有值 "Red" 的字串屬性。因為在 Brush 型別上有型別轉換子存在,可以將字串值轉換為 Brush,所以才能這樣運作。

若要將這項資訊加入到建立繫結一節的圖中,則圖表看起來如下:

資料繫結圖表

然而,如果繫結來源物件具有的不是型別字串屬性,而是型別 ColorColor 屬性,那會如何?在該情況下,為了讓繫結有作用,您需要先將 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;
    }
}

IValueConverter 參考頁會提供詳細資訊。

現在是使用自訂轉換子來代替預設轉換,且圖表看起來如下:

資料繫結圖表

再重複聲明一次,預設轉換也可以使用,因為要繫結的型別中存在有型別轉換子。這個行為會取決於目標中提供的型別轉換子。如果有疑問,請建立自己的轉換子。

下列是部分適合實作資料轉換子的常見案例:

  • 依據文化特性的不同,您的資料顯示方式會有所不同。例如,依據特定文化特性所使用的值或標準,您可能會想要實作貨幣轉換子或日曆日期/時間轉換子。

  • 要使用的資料不見得是要變更屬性的文字值,但有可能是改為變更某些其他值,例如影像的來源,或者是顯示文字的色彩或樣式。在這個情況下可能的轉換子使用方式,是藉由將看起來不太適合的屬性繫結加以轉換,例如將文字欄位繫結到表格儲存格的 Background 屬性。

  • 一個以上的控制項或者是控制項的多個屬性,會繫結到相同資料。在這個情況下,主要繫結可能只是顯示文字,而其他繫結會處理特定顯示問題,但仍然使用相同的繫結做為來源資訊。

  • 目前我們尚未討論到 MultiBinding,其中的目標屬性具有繫結集合。在 MultiBinding 的情況中,您使用自訂 IMultiValueConverter 從繫結值產生最後的值。舉例來說,顏色可以由紅藍綠的值計算而來,而這些值可以來自相同或不同的繫結來源物件。如需範例和詳細資訊,請參閱 MultiBinding 類別頁。

繫結至集合

這個章節包含下列子章節。

  • 如何實作集合
  • 集合檢視

繫結來源物件可以視為其屬性包含資料的單一物件,或是通常會群組在一起的多型物件的資料集合 (例如資料庫查詢的結果)。目前為止,我們只討論到單一物件的繫結,然而,繫結到資料集合是常見的案例。舉例來說,有個常見案例是使用 ItemsControl (例如 ListBoxListViewTreeView) 顯示資料集合,例如資料繫結是什麼一節中的應用程式。區段。

所幸,我們的基本圖表仍然適用。如果是將 ItemsControl 繫結到集合,圖表看起來如下:

資料繫結 ItemsControl 圖表

如圖表所示,為了將 ItemsControl 繫結到集合物件,則要使用 ItemsSource 屬性。您可以將 ItemsSource 屬性想像成 ItemsControl 的內容。請注意,繫結是 OneWay,因為 ItemsSource 屬性預設支援 OneWay 繫結。

如何實作集合

您可以列舉實作 IEnumerable 介面的任何物件。然而,若要設定動態繫結,讓集合中的插入或刪除作業能自動更新 UI,則集合必須實作 INotifyCollectionChanged 介面。這個介面所公開的事件,應該在基礎集合變更時引發。

WPF 提供 ObservableCollection<T> 類別,這是會公開 INotifyCollectionChanged 介面的資料集合的內建實作。請注意,對於從來源物件到目標的傳輸資料值的完全支援,集合中支援可繫結屬性的每個物件,也都必須實作 INotifyPropertyChanged 介面。如需範例,請參閱繫結至集合範例。如需詳細資訊,請參閱繫結來源概觀

實作自己的集合之前,請考慮使用 ObservableCollection<T> 或其中一個現有的集合類別,例如 List<T>, Collection<T>BindingList<T> 等等。如果您擁有進階案例,而想實作自己的集合,請考慮使用 IList,提供可依索引個別存取,也因而產生最佳效能之物件的非泛型集合。

集合檢視

一旦 ItemsControl 繫結到資料集合,您可能會想要排序、篩選或群組資料。若要這樣做,您可以使用集合檢視,這是實作 ICollectionView 介面的類別。

這個章節包含下列子章節。

  • 集合檢視是什麼
  • 如何建立檢視
  • 排序
  • 篩選
  • 群組
  • 目前項目指標
  • 主從式繫結案例

集合檢視是什麼

集合檢視是以繫結來源集合為基礎的一層,可以讓您依據排序、篩選和群組查詢來巡覽和顯示來源集合,而不需要變更基礎來源集合本身。集合檢視也會保留集合中目前項目的指標。如果來源集合實作 INotifyCollectionChanged 介面,則由 CollectionChanged 事件所引發的變更會散佈到檢視中。

因為檢視不會變更基礎來源集合,每個來源集合可以有多個相關聯的檢視。舉例來說,您可以有一個 Task 物件集合。隨著檢視的使用,您可以不同方式顯示相同資料。舉例來說,在頁面左方您可以顯示以優先順序排序的工作,而在右方則以區域群組方式列出。

如何建立檢視

一個建立和使用檢視的方式,是直接具現化檢視物件,然後再用來做為繫結來源。舉例來說,請考慮資料繫結是什麼一節所顯示的資料繫結示範應用程式。區段。該應用程式的實作讓 ListBox 繫結至資料集合的檢視,而非直接繫結到資料集合。下列範例節錄自資料繫結示範應用程式。CollectionViewSource 類別是繼承自 CollectionView 之類別的可延伸標記語言 (XAML) Proxy。在這個特別的範例中,檢視的 Source 是繫結到目前應用程式物件的 AuctionItems 集合 (型別 ObservableCollection<T>)。

<Window.Resources>


...


<CollectionViewSource 
      Source="{Binding Source={x:Static Application.Current}, Path=AuctionItems}"   
      x:Key="listingDataView" />


...


</Window.Resources>

資源 listingDataView 接著會做為應用程式中項目的繫結來源,例如 ListBox

<ListBox Name="Master" Grid.Row="2" Grid.ColumnSpan="3" Margin="8"
    ItemsSource="{Binding Source={StaticResource listingDataView}}">


...


</ListBox>

若要建立相同集合的另一個檢視,您可以建立另一個 CollectionViewSource 執行個體,並給它不同的 x:Key 名稱。

下表顯示哪些檢視資料型別會建立為預設集合檢視,或是由 CollectionViewSource 根據來源集合型別建立。

來源集合型別

集合檢視型別

注意

IEnumerable

CollectionView 為基礎的內部型別

無法群組項目。

IList

ListCollectionView

最快。

IBindingList

BindingListCollectionView

使用預設檢視

指定集合檢視做為繫結來源是建立和使用集合檢視的方式之一。WPF 也會為做為繫結來源使用的每個集合建立預設集合檢視。如果您直接繫結至集合,WPF 會繫結至它的預設檢視。請注意,此預設檢視是由相同集合的所有繫結所共用,因此若其中一個繫結控制項或程式碼 (例如排序或變更目前的項目指標,這會在稍後討論) 變更預設檢視,此變更會反映在相同集合的所有其他繫結中。

若要取得預設檢視,您可以使用 GetDefaultView 方法。如需範例,請參閱 HOW TO:取得資料集合的預設檢視

包含 ADO.NET DataTables 的集合檢視

為了改善效能,ADO.NET DataTableDataView 物件的集合檢視會將排序和篩選委派給 DataView。這會使得資料來源的所有集合檢視共用排序和篩選。若要讓每個集合檢視能夠獨立排序和篩選,請用它自己的 DataView 物件初始化每個集合檢視。

排序

如先前所述,檢視可以將排序順序套用到集合上。當資料存在於基礎集合中時,資料本身可能有也可能沒有相關的順序。對集合的檢視可以讓您依據所提供的比較準則,安排順序或變更預設順序。因為是資料的用戶端檢視,常見的案例是使用者會想要針對資料行對應的值,而排序表格式資料的資料行。藉由使用檢視,就可以套用這個使用者驅動的排序,同樣不需要對基礎集合進行任何變更,或甚至不需要重新查詢集合內容。如需範例,請參閱 HOW TO:在按一下行首時排序 GridView 資料行

下列範例顯示資料繫結是什麼一節中應用程式 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 屬性指定回呼。如需範例,請參閱 HOW TO:篩選檢視中的資料

群組

除了檢視 IEnumerable 集合的內部類別外,所有集合檢視都支援群組的功能,讓使用者可以將集合檢視中的集合分割成數個邏輯群組。群組可以是明確的,由使用者提供群組清單,或者是隱含的,讓群組依據資料動態產生。

下列範例顯示 "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);

如需另一個群組範例,請參閱 HOW TO:實作 GridView 的 ListView 中的群組項目

目前項目指標

檢視也支援目前項目的概念。您可以在集合檢視中逐一巡覽物件。當您巡覽時,移動項目指標可以讓您擷取集合中存在該特定位置上的物件。如需範例,請參閱 HOW TO:透過資料 CollectionView 中的物件巡覽

由於 WPF 只使用檢視 (可能是您指定的檢視或集合的預設檢視) 繫結至集合,因此集合的所有繫結都有目前項目指標。當繫結至檢視時,Path 值中的斜線 ("/") 字元會指定檢視的目前項目。在下列範例中,資料內容是集合檢視。第一行繫結至集合。第二行繫結至集合中的目前項目。第三行繫結至集合中目前項目的 Description 屬性。

<Button Content="{Binding }" />
<Button Content="{Binding Path=/}" />
<Button Content="{Binding Path=/Description}" /> 

斜線和屬性語法也可以堆疊來周遊集合的階層架構。下列範例繫結至名為 Offices 之集合的目前項目,此集合是來源集合目前項目的屬性。

<Button Content="{Binding /Offices/}" />

目前項目指標可能會受套用至集合的任何排序或篩選所影響。排序會將目前項目指標保留在最後選取的項目上,但現在集合檢視已在其周圍重組結構 (或許選取的項目先前是在清單的開頭,但現在選取的項目可能是在中間的某處)。如果該選取內容在篩選之後仍然在檢視範圍中,篩選就會保留選取的項目。否則,目前項目指標會設定在篩選集合檢視的第一個項目。

主從式繫結案例

目前項目的概念不但對巡覽集合項目很有用,對於主從式繫結案例也很好用。請再回想資料繫結是什麼一節中的應用程式 UI。在該應用程式中,ListBox 內的選取項目會決定 ContentControl 中顯示的內容。若要以其他方式放置,在 ListBox 項目選取時,ContentControl 會顯示選取項目的詳細資料。

您可以實作主從式案例,只要藉由讓兩或多個控制項繫結到相同檢視即可。下列來自資料繫結示範的範例,顯示 ListBox 的標記,以及您在資料繫結是什麼一節中應用程式 UI 所見到的 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,這樣才能運作。

如需其他範例,請參閱 HOW TO:繫結至集合並根據選取項目顯示資訊HOW TO:使用含階層式資料的主從式模式

您可能已經注意到上述範例有使用樣板。事實上,如果沒有使用樣板,資料的顯示不會如我們所預期 (ContentControl 所明確使用的樣板,以及 ListBox 所隱含使用的樣板)。現在,下節中要說明資料樣板化。

資料樣板化

沒有使用資料樣板的話,資料繫結是什麼一節中應用程式 UI 看起來會類似下圖:

不含資料範本的資料繫結示範

如上一節中的範例所示,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">
                <TextBlock 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" TargetName="border" />
            <Setter Property="Foreground" Value="Navy" TargetName="descriptionTitle" />
            <Setter Property="Foreground" Value="Navy" TargetName="currentPriceTitle" />
            <Setter Property="BorderThickness" Value="3" TargetName="border" />
            <Setter Property="Padding" Value="5" TargetName="border" />
          </DataTrigger.Setters>
        </DataTrigger>
        <DataTrigger Binding="{Binding Path=SpecialFeatures}">
            <DataTrigger.Value>
                <src:SpecialFeatures>Highlight</src:SpecialFeatures>
            </DataTrigger.Value>
            <Setter Property="BorderBrush" Value="Orange" TargetName="border" />
            <Setter Property="Foreground" Value="Navy" TargetName="descriptionTitle" />
            <Setter Property="Foreground" Value="Navy" TargetName="currentPriceTitle" />
            <Setter Property="Visibility" Value="Visible" TargetName="star" />
            <Setter Property="BorderThickness" Value="3" TargetName="border" />
            <Setter Property="Padding" Value="5" TargetName="border" />
        </DataTrigger>
    </DataTemplate.Triggers>
</DataTemplate>

在有使用這兩個 DataTemplate 的情況下,結果 UI 就是資料繫結是什麼一節中所顯示的。區段。如同您在快照中所見,除了讓您將資料放置在控制項中外,DataTemplate 也可以讓您為資料定義引人注目的視覺效果。舉例來說,上述 DataTemplate 中使用的 DataTrigger,讓具有 HighLightSpecialFeatures 值的 AuctionItem 會使用橘色框線和星號來顯示。

如需資料樣板的詳細資訊,請參閱資料範本化概觀

資料驗證

這個章節包含下列子章節。

  • 建立驗證規則與繫結的關聯
  • 提供視覺化回應
  • 驗證過程

大部分會採用使用者輸入的應用程式都需要驗證邏輯,以確保使用者輸入的是預期的資訊。驗證會依據類型、範圍、格式或其他應用程式特定的需求而進行檢查。本節討論 WPF 中的資料驗證運作方式。

建立驗證規則與繫結的關聯

WPF 資料繫結模型可以讓您建立 ValidationRulesBinding 物件的關聯。舉例來說,下列是資料繫結是什麼一節中 Add Product Listing "Start Price" TextBox 的 XAML:

<TextBox Name="StartPriceEntryForm" Grid.Row="2" Grid.Column="1"
    Style="{StaticResource textStyleTextBox}" 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 textStyleTextBox}" 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 使用稱為 validationTemplateErrorTemplate。下列範例顯示 validationTemplate 的定義:

<ControlTemplate x:Key="validationTemplate">
  <DockPanel>
    <TextBlock Foreground="Red" FontSize="20">!</TextBlock>
    <AdornedElementPlaceholder/>
  </DockPanel>
</ControlTemplate>

AdornedElementPlaceholder 項目指定要裝飾的控制項要放於何處。

除此之外,您也可以使用 ToolTip 顯示錯誤訊息。StartDateEntryFormStartPriceEntryForm 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>

搭配自訂 ErrorTemplateToolTipStartDateEntryForm TextBox 在有驗證錯誤時看起來類似下圖:

資料繫結驗證錯誤

如果 Binding 具有關聯的驗證規則,但您沒有指定繫結控制項的 ErrorTemplate,就會使用預設 ErrorTemplate 在有驗證錯誤時告知使用者。預設 ErrorTemplate 是控制項樣板,會定義裝飾項層的紅色框線。搭配預設 ErrorTemplateToolTipStartPriceEntryForm TextBox 的 UI 在有驗證錯誤時看起來類似下圖:

資料繫結驗證錯誤

如需如何提供邏輯以驗證對話方塊中的所有控制項的範例,請參閱對話方塊概觀中的<自訂對話方塊>一節。

驗證過程

通常驗證發生的時機,是將目標的值傳輸到繫結來源屬性的時候。這種情形會發生在 TwoWayOneWayToSource 繫結上。再重複聲明一次,造成來源更新的原因取決於 UpdateSourceTrigger 屬性的值,如觸發來源更新的機制一節所討論。

以下將描述「驗證」(Validation) 程序。請注意,如果在此程序執行期間發生驗證錯誤或其他型別錯誤,處理序 (Process) 會中止。

  1. 繫結引擎會檢查是否有為該 Binding 定義 ValidationStep 的任何自訂 ValidationRule 物件設定為 RawProposedValue,如果有的話,就針對每個 ValidationRule 呼叫 Validate 方法,直到其中一個規則發生錯誤或是所有規則都通過為止。

  2. 接著,繫結引擎就會在有轉換子的情況下呼叫轉換子。

  3. 如果轉換子成功,繫結引擎會檢查是否有為該 Binding 定義 ValidationStep 的任何自訂 ValidationRule 物件設定為 ConvertedProposedValue ,如果有的話,就針對每個已將 ValidationStep 設定為 ConvertedProposedValueValidationRule 呼叫 Validate 方法,直到其中一個規則發生錯誤或是所有規則都通過為止。

  4. 繫結引擎會設定來源屬性。

  5. 繫結引擎會檢查是否有為該 Binding 定義 ValidationStep 的任何自訂 ValidationRule 物件設定為 UpdatedValue,如果有的話,就針對每個將 ValidationStep 設定為 UpdatedValueValidationRule 呼叫 Validate 方法,直到其中一個規則發生錯誤或是所有規則都通過為止。

  6. 繫結引擎會檢查是否有為該 Binding 定義 ValidationStep 的任何自訂 ValidationRule 物件設定為 CommittedValue,如果有的話,就針對每個將 ValidationStep 設定為 CommittedValueValidationRule 呼叫 Validate 方法,直到其中一個規則發生錯誤或是所有規則都通過為止。

在整個程序期間如果有一個 ValidationRule 沒有通過,繫結引擎就會建立 ValidationError 物件,並將其加入至該繫結項目的 Validation.Errors 集合。在繫結引擎於任何指定步驟中執行 ValidationRule 物件之前,它會移除任何已在該步驟期間加入至繫結項目之 Validation.Errors 附加屬性的 ValidationError。例如,若某個將 ValidationStep 設定為 UpdatedValueValidationRule 失敗,則在下一次執行驗證時,繫結引擎會在呼叫任何已將 ValidationStep 設定為 UpdatedValueValidationRule 之前,立即移除該 ValidationError

Validation.Errors 不是空白時,該項目的 Validation.HasError 附加屬性會設定為 true。此外,如果 BindingNotifyOnValidationError 屬性設定為 true,則繫結引擎會針對項目引發 Validation.Error 附加事件。

請注意,以任何一個方向 (目標至來源或來源至目標) 傳輸有效值時,都會清除 Validation.Errors 附加屬性。

如果繫結具有關聯的 ExceptionValidationRule,而且當繫結引擎設定來源時有擲回例外狀況 (Exception) 的話,繫結引擎就會查看是否有 UpdateSourceExceptionFilter。您可以選擇使用 UpdateSourceExceptionFilter 回呼,以提供處理例外狀況的自訂處理常式。如果未在 Binding 上指定 UpdateSourceExceptionFilter,則繫結引擎會建立具有例外狀況的 ValidationError,並將其加入繫結項目的 Validation.Errors 集合。

偵錯機制

您可以對繫結相關的物件設定附加屬性 PresentationTraceSources.TraceLevel,以接收特定繫結的狀態資訊。

請參閱

工作

HOW TO:繫結至 LINQ 查詢結果

資料繫結示範

概念

Windows Presentation Foundation 3.5 版的新功能

最佳化效能:資料繫結

參考

DataErrorValidationRule

其他資源

資料繫結範例

資料繫結 HOW TO 主題

變更記錄

日期

記錄

原因

2008 年 7 月

更新驗證過程 一節,以反映 Service Pack 1 的變更。

SP1 功能變更。