WPF繫結入門
一 概述
文章一開始,將給出一個使用WPF繫結的小例項。並以此為起點,逐步展開對WPF繫結知識的探討。
二 例項演示
1新建WPF應用程式WpfBindingExp,下面是程式主畫面的程式碼。
<pre name="code" class="html"><Window x:Class="WpfBindingExp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid > <Grid.RowDefinitions> <RowDefinition Height="2*"></RowDefinition> <RowDefinition Height="2*"></RowDefinition> <RowDefinition Height="2*"></RowDefinition> <RowDefinition Height="4*"></RowDefinition> </Grid.RowDefinitions> <StackPanel Grid.Row="2" Orientation="Horizontal" Background="Aqua"> <TextBlock Text="學號:" Height="30" Margin="10,0,0,0"/> <TextBlock x:Name="TextBlock_StudentId" Width="100" Height="30" Margin="0,0,50,0"/> <TextBlock Text="姓名:" Height="30"/> <TextBlock x:Name="TextBlock_StudentName" Width="100" Height="30"/> </StackPanel> </Grid> </Window>
主畫面被分割成4行,並在第3行放置容器控制元件StackPanel,再在容器控制元件中放入兩個TextBlock控制元件,被命名為TextBlock_StudentId和TextBlock_StudentName,分別用來顯示學生的學號和姓名資訊。
2 在畫面的後端程式碼中定義Student類,並定義型別為Student的屬性,該屬性被初始化為Id為1000,Name為“三五月兒”的學生物件。
using System.Windows; namespace WpfBindingExp { public partial class MainWindow : Window { public Student Student { get; set; } public MainWindow() { InitializeComponent(); Student = new Student() { Id = 10000, Name = "三五月兒" }; } } public class Student { public int Id { get; set; } public string Name { get; set; } } }
3 將Student物件的Id屬性、Name屬性分別與畫面中的TextBlock_StudentId控制元件、TextBlock_StudentName控制元件的Text屬性進行繫結,繫結使用以下程式碼完成。
this.TextBlock_StudentId.SetBinding(TextBlock.TextProperty, new Binding("Id") { Source = Student,Mode=BindingMode.TwoWay }); this.TextBlock_StudentName.SetBinding(TextBlock.TextProperty, new Binding("Name") { Source = Student,Mode=BindingMode.TwoWay });
4 執行程式,得到以下效果圖。
圖1 例項執行效果圖
三 例項說明
下面將對例項程式碼進行分析說明。
1 控制元件的SetBinding方法
從下面這行程式碼開始吧。
this.TextBlock_StudentId.SetBinding(TextBlock.TextProperty, new Binding("Id") { Source = Student,Mode=BindingMode.TwoWay});
程式碼中呼叫TextBlock控制元件的SetBinding方法完成Student物件的Id屬性與TextBlock控制元件的Text屬性的繫結工作。
SetBinding方法接受兩個引數:
- 第一個引數表示繫結的目標屬性。這裡傳入的值為TextBlock.TextProperty,檢視其定義,是一個型別為DependencyProperty的靜態只讀屬性,說明它是一個依賴屬性,只有依賴屬性才可以作為繫結的目標屬性。關於依賴屬性,在這裡不做過多說明。大家只要記住:作為繫結目標的屬性必須是依賴屬性。
- 第二個引數傳入一個Binding物件。在構造這個Binding物件時,通過Binding類的建構函式傳入繫結的源屬性名,這裡為“Id”(也可以通過設定Binding物件的Path屬性來指定繫結物件的源屬性名);隨後通過設定Binding物件的Source屬性來指定繫結的源物件,這裡被設定為Student物件;再通過設定繫結的Mode屬性來指定繫結的資料流動方向,這裡被設定為TwoWay,表示雙向繫結。構造好Binding物件後,就可以使用這個物件將源物件和目標物件關聯起來,Binding物件是源物件與目標物件溝通的橋樑,也可以將其看成源物件和目標物件之間的資料傳遞通道。
2 Binding的Mode屬性
關於Binding的Mode屬性,這裡稍微再囉嗦幾句,Mode屬性用來指定繫結的資料流動方向,該屬性可以被設定為下列值之一:
- OneWay:使用OneWay繫結時,每當源發生變化,資料就會從源流向目標。
- OneTime: 繫結也會將資料從源傳送到目標;但是,僅當啟動了應用程式或DataContext發生更改時才會執行此操作,因此,它不會偵聽源中的更改通知。
- OneWayToSource: 繫結會將資料從目標傳送到源。
- TwoWay: 繫結會將源資料傳送到目標,但如果目標屬性的值發生變化,則會將它們發回給源。
- Default: Binding的模式根據實際情況來定,如果是可編輯的就是TwoWay,只讀的就是OneWay。
3 做個小小的總結
可見,要想完成繫結,首先需要構造用於繫結的Binding物件,該物件包含繫結的源物件(由Soure屬性指定),繫結的源屬性(由Path屬性指定,也可通過建構函式來指定)以及繫結的資料流動方向(由Mode屬性指定),當然還可以使用Binding類提供的其他屬性來完成更多的設定,這裡就不做一一說明了。
4 換種玩法
我們還可以將繫結的程式碼翻譯成以下程式碼。
Binding binding = new Binding();
binding.Source = Student;
binding.Path = new PropertyPath("Id");
binding.Mode = BindingMode.TwoWay;
this.TextBlock_StudentId.SetBinding(TextBlock.TextProperty, binding);
5 BindingOperations類的SetBinding方法
除了使用控制元件的SetBinding方法,還可以使用工具類BindingOperations的SetBinding方法來完成繫結操作:
BindingOperations.SetBinding(this.TextBlock_StudentId, TextBlock.TextProperty, binding);
工具類BindingOperations的SetBinding方法接收三個引數:
- 第一個引數指定繫結的目標物件;
- 第二個引數指定繫結的目標屬性,必須是依賴屬性;
- 第三個引數指定使用哪個Binding物件將源物件與目標物件聯絡起來。
6 Binding模型示意圖
通過前面的學習,可以總結出WPF繫結模型的基本構成。
圖2 Binding模型示意圖
Binding模型包括:源物件、目標物件及Binding物件。這裡的Binding物件使用的是雙向箭頭,表示雙向繫結。
在WPF中,常常使用Binding模型將資料以繫結的形式與介面元素聯絡起來。
7 升級我們的Student物件
只要稍加修改Student類的定義,便可以使例項中的繫結功能更上一層樓。
在升級Student類前,需要引入名稱空間System.ComponentModel,因為升級Student類用到的INotifyPropertyChanged介面的定義就位於該名稱空間中。
下面是升級後的Student類的程式碼。
public class Student:INotifyPropertyChanged
{
private int id;
public int Id
{
get
{
return id;
}
set
{
id = value;
NotifyPropertyChanged("Id");
}
}
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
NotifyPropertyChanged("Name");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
使用升級後的Student物件作為繫結源的話,當Student物件的Id屬性或者Name屬性發生變化時,便會觸發PropertyChanged事件,Binding物件接收到這個事件訊息後,會得知名為Id或者Name的屬性的值發生改變,便會通知Binding的目標物件更新變化後的值。為了驗證這點,我們為顯示學生姓名的TextBlock控制元件增加MouseEnter事件的處理方法,在該方法中修改Student物件Name屬性的值,程式碼如下所示:
private void TextBlock_StudentName_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
Student.Name = "sanwuyueer";
}
再次執行程式,將滑鼠移至TextBlock_StudentName控制元件,得到以下效果圖:
圖3 使用升級後的Student物件進行繫結的效果
學生姓名由“三五月兒”變成了“sanwuyueer”,Name屬性的變更是不是更新至畫面啦。
8 為繫結物件增加值校驗器和值轉換器
其實,還可以為Binding物件指定值轉換器,使用值轉換器可以將值從一種型別轉換成另外一種型別,關於值轉換器,請閱讀文章《WPF值轉換器 》;還可以為繫結指定值校驗器,通過值校驗器可以校驗資料的正確性,關於值校驗器請閱讀文章《WPF值轉換器 》。至此,Binding模型中又增加了更多功能,也變得更加強大,升級後的Binding模型如下圖所示。
圖4 升級後的Binding模型示意圖
9 在XAML中實現繫結
除了可以在後端程式碼中完成繫結操作,還可以在XAML程式碼中也可以完成這個操作。
為了在XAML中實現繫結,需要修改XAML程式碼,修改後的程式碼如下所示:
<Window x:Class="WpfBindingExp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local ="clr-namespace:WpfBindingExp"
Title="MainWindow" Height="350" Width="525">
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="2*"></RowDefinition>
<RowDefinition Height="2*"></RowDefinition>
<RowDefinition Height="2*"></RowDefinition>
<RowDefinition Height="4*"></RowDefinition>
</Grid.RowDefinitions>
<StackPanel Grid.Row="2" Orientation="Horizontal" Background="Aqua">
<StackPanel.DataContext>
<local:Student Id="10000" Name="三五月兒"/>
</StackPanel.DataContext>
<TextBlock Text="學號:" Height="30" Margin="10,0,0,0"/>
<TextBlock x:Name="TextBlock_StudentId" Text="{Binding Path=Id,Mode=TwoWay}" Width="100" Height="30" Margin="0,0,50,0"/>
<TextBlock Text="姓名:" Height="30"/>
<TextBlock x:Name="TextBlock_StudentName" Text="{Binding Path=Name,Mode=TwoWay}" Width="100" Height="30" MouseEnter="TextBlock_StudentName_MouseEnter" />
</StackPanel>
</Grid>
</Window>
程式碼中使用xmlns:local ="clr-namespace:WpfBindingExp"引入名稱空間WpfBindingExp,因為需要使用的Student類的定義位於該名稱空間中。設定StackPanel控制元件的DataContext屬性值為Student物件,這樣一來,DataContext屬性的值將作為StackPanel容器中所有控制元件繫結的資料來源,而內部控制元件在進行繫結操作時就不再需要設定繫結的Source屬性了,只需要設定繫結的Path屬性和Mode屬性即可。
下面將對DtataContext的工作原理進行說明。
10 使用DataContext屬性
前面的例項中,在構造Binding物件時,需要使用Source屬性指定繫結的源,其實也可以使用WPF控制元件的DataContext屬性來指定繫結的源。DataContext屬性被定義在FrameworkElement類裡,這個類是所有WPF控制元件的基類,所以,WPF中所有控制元件都將具有該屬性。這樣一來,在WPF控制元件樹(畫面中所有控制元件構成了樹形結構,簡稱為控制元件樹)的每一個節點上都將具有該屬性,所以,我們可以通過在控制元件樹的任意節點設定其DataContext屬性來指定繫結的源,而初始化繫結物件時就不再需要指定其Source屬性了,只需要設定其Path屬性即可。當一個Binding物件只知道自己的Path而不知道Source時,它就會沿著控制元件樹一路向樹的根部找去,每路過一個節點就看看該節點的DataContext中是否具有Path指定的屬性,若有就將該物件作為繫結的Source,若沒有,就繼續找下去,直到找到為止,如果到了樹根都還沒有找到滿足條件的Source,那就得不到需要的資料了。所以,在本文的例項中,也可以通過設定Grid或者Window的DataContext屬性來指定繫結的資料來源,同樣可以達到要求。
<Grid.DataContext>
<local:Student Id="10000" Name="三五月兒"/>
</Grid.DataContext>
在實際開發時,往往會將用於繫結的所有物件封裝進一個類,再將該類的例項指定為Window的DataContext屬性,這樣一來,畫面中所有控制元件的繫結源均可以在Window的DataContext中找到,下面所給的例項就是這麼幹的。
四 再來一個例項
請忘掉前面的程式碼吧,下面給出一個新例項,也可以說是一個升級版的例項。
下面是完整的程式碼。關於該例項不作任何說明,大家就自己去研究吧。
1 XAML程式碼
<Window x:Class="WpfBindingExp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local ="clr-namespace:WpfBindingExp"
Title="MainWindow" Height="350" Width="525">
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="2*"></RowDefinition>
<RowDefinition Height="2*"></RowDefinition>
<RowDefinition Height="2*"></RowDefinition>
<RowDefinition Height="4*"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock x:Name="TextBlock_Title" Text="{Binding Path=Title}" FontSize="32" Grid.Row="1" Width="100" Height="50"/>
<StackPanel Grid.Row="2" Orientation="Horizontal" Background="Aqua" DataContext="{Binding Path=Student}">
<TextBlock Text="學號:" Height="30" Margin="10,0,0,0"/>
<TextBlock x:Name="TextBlock_StudentId" Text="{Binding Path=Id,Mode=TwoWay}" Width="100" Height="30" Margin="0,0,50,0"/>
<TextBlock Text="姓名:" Height="30"/>
<TextBlock x:Name="TextBlock_StudentName" Text="{Binding Path=Name,Mode=TwoWay}" Width="100" Height="30" MouseEnter="TextBlock_StudentName_MouseEnter" />
</StackPanel>
<ListBox Grid.Row="3" x:Name="ListBox_CourseList" ItemsSource="{Binding Path=CourseList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="TextBlock_CourseId" Text="{Binding Path=Id}" Width="100" Height="30" Margin="10,0,10,0"/>
<TextBlock x:Name="TextBlock_CourseName" Text="{Binding Path=Name}" Width="100" Height="30" Margin="10,0,10,0"/>
<TextBlock x:Name="TextBlock_CourseTeacher" Text="{Binding Path=Teacher}" Width="100" Height="30" Margin="10,0,10,0"/>
<TextBlock x:Name="TextBlock_CourseScore" Text="{Binding Path=Score}" Width="100" Height="30" Margin="10,0,10,0"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
2 CS程式碼
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.ComponentModel;
using System.Collections.Generic;
namespace WpfBindingExp
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new BindingExpViewModel();
}
}
public class Student:INotifyPropertyChanged
{
private int id;
public int Id
{
get
{
return id;
}
set
{
id = value;
NotifyPropertyChanged("Id");
}
}
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
NotifyPropertyChanged("Name");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class Course
{
public int Id { get; set; }
public string Name { get; set; }
public string Teacher { get; set; }
public int Score { get; set; }
}
public class BindingExpViewModel
{
public string Title { get; set; }
public Student Student { get; set; }
public List<Course> CourseList { get; set; }
public BindingExpViewModel()
{
Title = "公告";
Student = new Student() { Id = 10000, Name = "三五月兒" };
CourseList = new List<Course>()
{
new Course(){Id = 223,Name="作業系統",Teacher="張三",Score=85},
new Course(){Id = 224,Name="資料庫",Teacher="李月",Score=81},
new Course(){Id = 223,Name="資料結構",Teacher="烏拉",Score=80}
};
}
}
}
3 執行程式後的效果圖。
圖5 升級版示例程式的執行效果圖
五 總結
本文通過例項介紹了WPF繫結中最基礎的一些知識點,有關繫結更多知識的介紹會在以後的文章中慢慢來說。