WPF中的數據綁定提供了很強大的功能。與普通的WinForm程序相比,其綁定功能為我們提供了很多便利,例如Binding對象的自動通知/刷新,Converter,Validation Rules,Two Way Binding等功能,省去了很多維護的繁瑣工作。另外對於WPF中提供的數據模板功能,讓我們可以輕松定制可以被復用的控制呈現的模塊—但這是以數據綁定為前提來做到輕松易用的效果的。數據提供者例如XmlDataProvider和ObjectDataProvider更是簡化了將對象以特定方式綁定並呈現的過程。可以說,數據綁定是WPF中讓我們真正能夠開始體現其便利性的特征之一,而對以數據驅動的應用來講,其重要性不言而喻。
數據綁定的關鍵是System.Windows.Data.Binding對象,它會把兩個對象(UI對象與UI對象之間,UI對象與.NET數據對象之間)按照指定的方式粘合在一起,並在他們之間建立一條通信通道,綁定一旦建立,接下來的應用生命周期中它可以自己獨立完成所有的同步工作。根據其應用場合的不同我們將在本文中從以下幾個部分分別討論:
- 對象間的綁定
- 綁定到集合
- 數據模板
- 向綁定添加規則和轉換器
- 1. UI對象間的綁定
UI對象間的綁定,也是最基本的形式,通常是將源對象Source的某個屬性值綁定 (拷貝) 到目標對象Destination的某個屬性上。源屬性可以是任意類型,但目標屬性必須是依賴屬性(Dependency Property)。通常情況下我們對於UI對象間的綁定源屬性和目標屬性都是依賴屬性 (有些屬性不是) ,因為依賴屬性有垂直的內嵌變更通知機制,WPF可以保持目標屬性和源屬性的同步。
看個簡單的例子是如何在XAML中實現數據綁定的:
<Window x:Class="Allan.WpfBinding.Demo.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Basic Bindings" Height="400" Width="700" Style="{StaticResource windowStyle}"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="40" /> <RowDefinition Height="*" /> <RowDefinition Height="40" /> </Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="5" HorizontalAlignment="Right"> <Button x:Name="btnBasicBinding" Content="Basic" Style="{StaticResource buttonStyle}"></Button> <Button x:Name="btnCollectionBinding" Content="Collection" Style="{StaticResource buttonStyle}"></Button> <Button x:Name="btnDataTemplate" Content="Data Template" Style="{StaticResource buttonStyle}"></Button> <Button x:Name="btnAdvanceBindings" Content="Advance" Style="{StaticResource buttonStyle}"></Button> <Button x:Name="btnExit" Content="Exit" Style="{StaticResource buttonStyle}"></Button> </StackPanel>
<StackPanel Grid.Row="1" HorizontalAlignment="Left"> <TextBox x:Name="txtName" Margin="5" Width="400" BorderThickness="0" Height="50" Text="Source Element"></TextBox> <TextBlock x:Name="tbShowMessage" Margin="5" Width="400" Height="50" Text="{BindingElementName=txtName,Path=Text }" /> </StackPanel> </Grid> </Window> |
- XAML綁定語法:
上邊的代碼我們將名為txtName的對象的Text屬性作為源對象分別綁定給了兩個TextBlock的Text屬性。這里我們用了Binding關鍵字並指定了ElementName和Path,這兩個就是指定源對象(Source)和源屬性(Source Property). 通常我們在設定綁定時都用與StaticResource標記類似的語法{Binding… }並設置ElementName和Path屬性:
Text=”{Binding ElementName=SourceObjectName, Path=SourceProperty}” |
- 用Coding(C#)添加Binding
而對於C#里和綁定相關的代碼,則看起來會羅嗦很多。但它們都同樣的使用了Binding對象,然后指定PropertyPath的一個實例為源屬性,然后可以有兩個方法來加載綁定規則:
- 1. 調用FrameworkElement 或FrameworkContentElement對象的SetBinding方法
- 2. 調用BindingOperations.SetBinding靜態方法
以下代碼實現了和上邊XAML文件類似的功能:
Binding binding = new Binding(); //設置源對象 binding.Source = txtName; //設置源屬性 binding.Path = new PropertyPath("Text"); //添加到目標屬性 this.tbShowMessage.SetBinding(TextBlock.TextProperty, binding); //or //BindingOperations.SetBinding(tbShowMessage, TextBlock.TextProperty, binding); |
- 用Coding(C#)移除Binding
當你在應用程序中某個地方添加了綁定,而在某個時候又不想這個綁定在接下來繼續有效時,你可以有兩種方式來斷開這個綁定:
- 1. 用BindingOperations.ClearBinding靜態方法。
例如BindingOperations.ClearBinding(currentTextBlock, TextBlock.TextProperty); BindingOperations同時還提供了ClearAllBindings方法,只需要傳入要清除綁定的目標對象的名稱,它就會將所有這個對象的綁定移除。
- 2. 簡單的將目標屬性設置為一個新的值。
這個簡單的方法同樣有效,可以斷開與前邊設置的binding的連接。簡單的設置為任何值即可:如:currentTextBlock.Text = “it’s a new value.”;
- Binding對象的屬性
Property |
Description |
|
轉換器 |
|
綁定的源對象 |
|
綁定無法返回有效值時的默認顯示。 |
|
綁定方式 |
|
屬性 |
|
常用於自身綁定或者數據模板中來指定綁定的源對象。 |
|
源對象 |
|
格式化表達式 |
|
Sets the events on which binding will occur. |
|
驗證規則 |
總結:對於對象間的綁定,綁定源為ElementName,Path為綁定源屬性。ElementName必須為以下可選項之一:
|
DataContext是WPF最后才試圖查找的源。一旦RelativeSource和Source對象都沒有被設置,則會在邏輯樹種向上搜尋。 |
|
用來標識和當前控件關聯的對象,通常用於自我引用或數據模板。 |
|
數據提供者/對象 |
- 2. 綁定到集合
- 利用ItemsSource來綁定數據源
常用標記:{Binding Path =””} ItemSource DisplayMemberPath
通常來說這是我們在做以數據驅動為主的應用時最經常用到的綁定方式。WPF支持任何類型的.NET對象作為數據源綁定到WPF對象。對於所有的ItemsControl對象都有一個ItemsSource依賴屬性,這是專門為數據綁定而准備的。ItemsSource的類型是IEnumerable,所以對於我們幾乎所有的集合類型我們都可以輕易的改變成ItemsSource的源對象。通過以下語句我們可以將一個名為photos的集合賦予ListBox對象,並以顯示Name屬性的值:
<ListBox x:Name=”pictureBox” DisplayMemberPath=”Name” ItemsSource=”(Binding {DynamicResource photos}” |
我們知道,依賴屬性內建的垂直通知功能讓UI對象間的綁定可以自己負責同步處理,但是對於.NET集合/對象來講,它不具備這樣的能力。為了讓目標屬性與源集合的更改保持同步,源集合必須實現一個叫INotifyCollectionChanged的接口,但通常我們只需要將集合類繼承於ObservableCollection類即可。因為ObservableCollection實現了INotifyPropertyChanged和INotifyCollectionChanged接口。示例代碼中我們這么去定義Photos集合類:
public class Photos : ObservableCollection<Photo> |
- 利用DataContext來作為共享數據源
常用標記:{Binding Path=””} DataContext
顧名思義,DataContext就是數據上下文對象,它是為了避免多個對象共享一個數據源時重復的對所有對象顯式地用binding標記每個Source/RelativeSource/ElementName,而把同一個數據源在上下文對象的某個范圍內共享,這樣當一個綁定沒有顯式的源對象時,WPF會便利邏輯數找到一個非空的DataContext為止。
例如我們可以通過以下代碼給ListBox和Title設置綁定:
<StackPanel Orentation=”Vertical” Margin=”5” DataContext=”{DynamicResource photos}”> <Label x:Name=”TitleLabel” Content=”{Binding Path=Count}” DockPanel.Dock=”Bottom” /> <ListBox x:Name=”pictureBox” DisplayMemeberPath=”Name” ItemSource=”{Binding}” /> </StackPanel> |
對於這些簡單的綁定我們可以很靈活的組合他們的應用來達到我們的要求,這也是我們通常使用的方法。例如:
<Window.Resources> <local:Employee x:Key="MyEmployee" EmployeeNumber="123" FirstName="John" LastName="Doe" Department="Product Development" Title="QA Manager" /> </Window.Resources> <Grid DataContext="{StaticResource MyEmployee}"> <TextBox Text="{Binding Path=EmployeeNumber}"></TextBox> <TextBox Text="{Binding Path=FirstName}"></TextBox> <TextBox Text="{Binding Path=LastName}" /> <TextBox Text="{Binding Path=Title}"></TextBox> <TextBox Text="{Binding Path=Department}" /> </Grid> |
總結:對於集合的綁定,通常會需要用到以下幾個標記:
|
指定源對象中被顯示的屬性。ToString()方法會被默認調用。 |
|
指定要顯示的數據源 |
|
指定以什么樣的格式來顯示數據(類似於符合控件,可以在數據模板中利用多種控件來控制展現方式) |
|
數據源對象中的屬性—控制顯示 |
|
共享數據源 |
- 3. 數據模板 – Data Template
當源屬性和目標屬性為兼容的數據類型,且源所顯示的東西正是你需要顯示的東西時,數據綁定確實很簡單,你只需要向Section 1中講的來匹配對象關系即可。而通常情況下我們對數據綁定都要做一些定制,特別對於.NET對象的綁定,你需要將數據源按照不同的方式分割顯示。Data Template就負責來完成這樣的功能:按照預想的數據展現模式將數據源的不同部分顯示,而其作為可以被復用的獨立結構,一旦定義可以被添加到一個對象內部,將會創建一個全新的可視樹。
數據模板通常會被應用到以下幾類控件來填充其類型為DataTemplate的屬性:
- 內容控件(Content Control):ContentTemplate屬性,控制Content的顯示
- 項控件(Items Control) : ItemTemplate屬性,應用於每個顯示的項
- 頭控件(Header Content Control) : HeaderTemplate屬性,控制Header的展現。
每個數據模板的定義都是類似的方式,你可以像設計普通的窗體一樣來設計其展現的方式,而且他們共享數據模板父空間所賦予的綁定源。例如下邊的代碼我們用一個圖片來替代ListBox中的每一項:
<ListBox x:Name="pictureBox" ItemsSource="{Binding}"ScrollViewer.HorizontalScrollBarVisibility="Disabled"> <ListBox.ItemTemplate> <DataTemplate> <Image Source="{Binding Path=FullPath}" Margin="3,8" Height="35"> <Image.LayoutTransform> <StaticResource ResourceKey="st"/> </Image.LayoutTransform> <Image.ToolTip> <StackPanel> <TextBlock Text="{Binding Path=Name}"/> <TextBlock Text="{Binding Path=DateTime}"/> </StackPanel> </Image.ToolTip> </Image> </DataTemplate> </ListBox.ItemTemplate> </ListBox> |
最終的ListBox中每一項的展現將按照我們在數據模板中設定的樣式以圖片來顯示:
通常數據模板是不需要被內聯聲明的,它可以被定義成一個資源存放在Application.Resources這樣的全局資源辭典中,或者單獨的Resource Dictionary中在多個元素間共享。
- 4. 向綁定添加規則和轉換器
- 使用值轉換器Value Converter
無論你的綁定XAML寫得多么漂亮,所有的綁定值毫無疑問你都可以得到,但是它不總是可以滿足你不經過任何程序變化顯示出來就能滿足要求的。例如對於本文示例代碼的照片總數的顯示,我們還想顯示得更為智能一些:對於一些符合某種要求的數據我們將其背景顯示為黃色,而對於有多於一條記錄時我們顯示15 Items,僅有一條時顯示1 Item。這時Value Converter就派上用場了。
要定義一個Value Converter需要聲明一個類讓其繼承於System.Windows.Data.IValueConverter接口,並實現其中的兩個方法Convert和ConvertBack方法。
public class RawCountToDescriptionConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { // Let Parse throw an exception if the input is bad int num = int.Parse(value.ToString()); return num + (num == 1 ? " item" : " items"); }
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotSupportedException(); } } |
在XAML中聲明資源,然后將其通過靜態資源引用的方式賦予Binding對象的Converter屬性。
<Window.Resources> <local:CountToBackgroundConverter x:Key="myConverter"/> <local:RawCountToDescriptionConverter x:Key="myConverter2"/> </Window.Resources> <TextBlock x:Name="filePath" DockPanel.Dock="Top" Style="{StaticResource titleStyle}" Text="{Binding Count, Converter={StaticResource myConverter2}}"></TextBlock> |
同樣,我們可以對輸入進行轉換。如果數據的輸入是被驗證規則(如果有的話)標記為有效的,那么值轉換器將會被調用,來對輸入進行轉換后反應出來。 (參考附件代碼中的BindingConverter窗體)
- 向綁定添加規則
每個Binding對象都有一個ValidationRules屬性,可以被設置為一個或多個派生自ValidationRule的對象,每個規則都會檢查特定的條件並更具結果來標記數據的有效性。就像我們在ASP.NET中應用RequiredValidator, CustomValidator一樣,你只需要定義自己的規則,WPF會在每次調用數據時(通常是TextBox等輸入控件失去焦點)會調用驗證檢查。這些是在值轉換器之前發生的,如果數據無效,它會標記此次更新無效,並將數據標記為無效—這是通過設置目標元素的Validation.HasError屬性為true並觸發Validation.Error事件(ValidationResult會被返回,並且其IsValid屬性為false)。我們可以通過一個觸發器來設定當數據無效時對用戶的提示。例如下邊的代碼我們就通過定義一個JpgValidationRule,當數據無效時通過tooltip來提示用戶輸入無效。
public class JpgValidationRule : ValidationRule { public override ValidationResult Validate(object value, CultureInfo cultureInfo) { string filename = value.ToString();
// Reject nonexistent files: if (!File.Exists(filename)) { return new ValidationResult(false, "Value is not a valid file."); }
// Reject files that don’t end in .jpg: if (!filename.EndsWith(".jpg", StringComparison.InvariantCultureIgnoreCase)) { return new ValidationResult(false, "Value is not a .jpg file."); } else { return new ValidationResult(true, null); } } } |
上邊的代碼定義了我們驗證的規則。接下來在XAML中來應用這個規則。我們將這個規則用來檢測輸入框中的數據是否合法:
<TextBox Style="{StaticResource validateTextBoxStyle}"> <TextBox.Text> <Binding UpdateSourceTrigger="PropertyChanged" Path="Department"> <Binding.ValidationRules> <local:JpgValidationRule/> </Binding.ValidationRules> </Binding> </TextBox.Text> </TextBox> |
當數據不合法時我們以什么樣的方式來告訴用戶呢?這里有兩個方法可以做,一個是定義你自己的ErrorTemplate,另外一個是根據Trigger來設置一些可見信息。通常我們都可以來自己定義一些Error Provider和可以復用的ErrorTemplate,這個話題我們會在下一篇文章中講。這里我們只讓背景做改變並用tooltip來提示用戶—顯示的是ValidationRule返回的出錯信息。因為都是控制顯示的,所以定義成共用的Style:
<Style x:Key="validateTextBoxStyle" TargetType="{x:Type TextBox}"> <Setter Property="Width" Value="300" /> <Style.Triggers> <Trigger Property="Validation.HasError" Value="True"> <Setter Property="Background" Value="Red"/> <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/> </Trigger> </Style.Triggers> </Style> |
總的來說,對於驗證,我們常用一下幾個屬性來定義錯誤驗證規則和錯誤展現方式:
- Errors – 錯誤信息集合
- HasError – 是否有錯誤出現.
- ErrorTemplate – 錯誤提示的展現方式.
- Binding.ValidationRules 綁定驗證規則
Coming Next:
本文我們了解了有關Binding以及和綁定有關的附加驗證規則,轉換器等。附加驗證規則我們將在下一篇中了解更多自定義Error Provider,Error Template等。附加的Demo里提供了所有本文中的實例。在下一篇中我們會了解以下幾個問題:
- Validation Rules
- Triggers