1. 程式人生 > >WPF繫結入門

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繫結中最基礎的一些知識點,有關繫結更多知識的介紹會在以後的文章中慢慢來說。