《深入淺出WPF》筆記——綁定篇(一)


  上一節,有記錄寫到:在WPF里,數據驅動UI,數據占核心地位,UI次之。怎么恢復數據的核心地位,那就要先了解一下Binding。

一、Binding 基礎

1.1WPF中Data Binding的帶來的方便

  在設計架構的時間,大家都很重視分層,為什么分層呢,其實分層就是為了更好的復用共同的類庫、更好的分工,以便更好的施工。。。,無論是為什么,就算它是一種時尚吧。為了追逐它,先記錄一下一般程序的層次:數據層,邏輯處理層,表現層。具體的每一個層可以再去細分。很多時間對於邏輯處理層和表現層經常混成一片,最終成了邯鄲學步。在WPF中,如果綁定用的好的話,這種不好的結果將會很簡單的避免。具體是怎么樣避免的呢?讓我們先總體的把握一下Binding。為了更好的理解Binding,讓我們先舉個例子,Binding就像一條河流,可以用來划分界限,又能作為通往兩個地方的必經之路。翻譯過來就是Binding是邏輯層和UI的分界線,當邏輯層處理了的數據,通過Binding界面能很快能感應得到,如果界面上的數據發生了變化,Binding會通知邏輯層做出相應的操作。(關於這一段,我只能弱弱的說,感覺不是特別的官方,因為對分層的理解我還處於菜鳥級的水平,希望大家狂噴啊,讓我能成長的更快)。

1.2 Binding基礎  

  我們可以把Binding比喻成一座數據橋梁,當一輛汽車要經過橋的話,就需要指明出發地-源(Source)和目的地-目標(Target),數據的從哪里來是源,到哪里去是Target,一般情況下,Binding源是邏輯層的對象,目標是UI層的控件對象。這樣數據不斷的通過Binding送達UI層,被UI層所展示也就完成了數據驅動UI的過程。我們可以想象在Binding這座橋上鋪了高速公路,不僅可以設置其方向是否是單向,還可以為雙向的,不僅如此,在雙向綁定的時間甚至可以設置一些"關卡",用來轉化數據類型或者是檢查數據的正確性。說了這么多,我想用一個例子,能更好的理解Binding。需求是:通過Binding,每次按一下按鈕為文本框的Age加1.具體的代碼說明在注釋里面已經寫明了。下面直接上代碼:

Person.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;

namespace Chapter_04
{
    class Person : INotifyPropertyChanged
    {//INotifyPropertyChanged向客戶端發出某一屬性值已更改的通知。
        public event PropertyChangedEventHandler PropertyChanged;
        private int age;
        public int Age
        {
            get { return age; }
            set
            {
                age = value;
                if (this.PropertyChanged != null)
                {
                    this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Age"));
                    // 通知Binding是“Age”這個屬性的值改變了
                }
            }
        }
    }
}
XAML
<Window x:Class="Chapter_04.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="230" Width="250">
    <Grid Background="Azure">
        <TextBlock Height="23" HorizontalAlignment="Left" Margin="12,39,0,0" Name="textBlock1" Text="年齡:" VerticalAlignment="Top" Width="44" />
        <TextBox Height="23" HorizontalAlignment="Left" Margin="62,36,0,0" x:Name="txtAge" VerticalAlignment="Top" Width="120" Text="22" />
        <Button Content="年齡加1" Height="23" HorizontalAlignment="Left" Margin="107,114,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
    </Grid>
</Window>
cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace Chapter_04
{
    /// <summary>
    /// MainWindow.xaml 的交互邏輯
    /// </summary>
    public partial class MainWindow : Window
    {
        Person p;
        public MainWindow()
        {
            InitializeComponent();
            //定義一個人的實例對象,並設置其初始值為文本框的值;
            p = new Person();
            p.Age = Convert.ToInt32(txtAge.Text);
            //准備binding 設置其源為剛剛創建的對象,並指定其訪問路徑為對象的Age字段,
            //
            Binding binding = new Binding();
            binding.Source = p;
            binding.Path = new PropertyPath("Age");
            //將數據源與目標連接在一起---其中把txtAge設置為綁定目標,並指定目標的屬性為            //靜態只讀DependencyProperty類型的依賴屬性TextBox.TextProperty.(運行是可以賦值的,注意與const的區別)
            //這類屬性的值可以通過binding依賴在其他對象的屬性值上,被其他隊形的屬性值所驅動。
            BindingOperations.SetBinding(this.txtAge, TextBox.TextProperty, binding);
        }

        private void button1_Click(object sender, RoutedEventArgs e)
        {
            p.Age += 1;
        }
    }
}

其中Cs的代碼可以把InitializeComponent();后面的代碼去掉,一句就搞定的

this.txtAge.SetBinding(TextBox.TextProperty, new Binding("Age") {Source= p=new Person(){Age=Convert.ToInt32(txtAge.Text)}});

最后的效果圖為圖1,並且點擊按鈕年齡加1:

圖1

這個不是跟以前的winform差不多嗎?初看差不多,但是這里讓年齡加1的是對象的屬性,而不是平時用的txtAge+=1。這樣做有個明顯得好處是,如果兩個文本框都綁定的p.Age,那么點擊下按鈕,兩個文本框都會加1.在這里源是p對象(沒有使用邏輯層),目標是txtAge控件。是不是對Binding有點感覺了。接下來介紹Binding的路徑(Path)和源(Source)。

 二、Binding的路徑和源

2.1把控件作為Binding的源與Binding標記擴展的完美結合

  除了邏輯層對象可以作為Binding的源外,控件也可以作為Binding的源,這樣是不僅能使UI元素產生聯動的效果,也會使Binding在控件間建立關聯。下面給出一個TextBox的值隨着Slider的值得變化而變化的例子。效果圖如圖2:

圖2

代碼如下:

XAML
<Window x:Class="Chapter_04.ControlBinding"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="ControlBinding" Height="300" Width="300">
    <StackPanel>
        <TextBox x:Name="textBox1" Text="{Binding Path=Value,ElementName=slider1}"/>
        <Slider x:Name="slider1" Maximum="100" Minimum="0" Margin="5"/>
    </StackPanel>
</Window>

下面講解主要代碼:

<TextBox x:Name="textBox1" Text="{Binding Path=Value,ElementName=slider1}"/>

  前面的文章XAML的語法記錄中已介紹過標簽擴展:特征是含有引號中{}而且開始部分是其類型。咋一看好像是Binding類型的變量賦值給Text了,在這里可以把Binding看成一個函數,整體的{}標簽擴展為一個函數,返回值為Value。由於Binding帶一個路徑參數的構造函數,所以path=可以省略,寫成這樣<TextBox x:Name="textBox1" Text="{Binding Value,ElementName=slider1}"/>,如果改成后台代碼(C#)的話可以這么寫:

this.textBox1.SetBinding(TextBox.TextProperty, new Binding("Value") { ElementName="slider1"});

  我們前面說過,綁定是可以雙向的,這里如果先輸入值會不會讓slider1根據文本框中的值指到相應的位置呢,答案是肯定的。那就去試試吧,試試的話好像沒有發現slider移動啊?在這里還需要知道一點,那就是Binding提供了數據流向的方向的屬性:Mode,它的類型為BindingMode的枚舉類型,可以取TwoWay、OneWay、OnTime、OneWayToSource、Default。在此肯定是默認的了,Default是根據情況來確定的。其實在上面的例子中是雙向的,slider為什么不能移動呢,是因為沒有textBox沒有失去焦點,按一下Tab就可以看到效果了,每次想讓Slider移動是不是必須要按一下Tab呢?肯定否定的,因為微軟對應Binding還提供了UpdateSourceTrigger屬性(描述綁定源更新的執行時間),其值可以為PropertyChanged(當綁定目標屬性更改時,立即更新綁定源,因為是更新源,用於目標對源的綁定,否則無效。多數依賴項屬性的默認值為PropertyChanged,TextBox默認值為LostFocus),Explicit(僅在調用 System.Windows.Data.BindingExpression.UpdateSource() 方法時更新綁定源),LostFocus。在這里,只需要在綁定里面加UpdateSourceTrigger=PropertyChanged就可以實現我們想要的結果了。除此之外Binding還有NotifyOnSourceUpdated和NotifyOnTargetUpdated屬性,設置為true的話,當源或目標被更新后會激發相應的SourceUpdated事件和TargetUpdated事件,實際工作中,我們還可以監聽兩個事件來找出有哪些數據更新了,或者是控件更新了。具體的用的時間在來細究,目前只記錄一下有這么一回事。

2.2 Binding的路徑(Path)

  Binding的源是一個對象,一個對象又會有多個屬性,通常我們要顯示的對象的某個屬性,就用Binding的路徑(Path)屬性來指定(有的時候也可以不指定Path),Path的實際類型為PropertyPath。除此之外,有的對象的屬性也是對象、對象里面含有多個相同類型的屬性取默認值以及對象的索引,我們同樣可以指定它,下面通過例子來說明,效果為圖3:

圖3

 

下面給出代碼:

XAML
<Window x:Class="Chapter_04.Path"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Path" Height="500" Width="805">
    <Grid Background="Gold">
        <Grid.RowDefinitions>
            <RowDefinition Height="1*" />
            <RowDefinition Height="1*" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <!--源的屬性作Path-->
        <Grid Background="Azure">
            <Grid.RowDefinitions>
                <RowDefinition Height="1*" />
                <RowDefinition Height="1*" />
                <RowDefinition Height="1*" />
                <RowDefinition Height="1*" />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="1*"/>
                <ColumnDefinition Width="2*"/>
            </Grid.ColumnDefinitions>
            <TextBlock Name="txtBlockPath1" Text="源的屬性作Path" VerticalAlignment="Bottom" HorizontalAlignment="Center" Grid.ColumnSpan="2" Grid.Row="0" />
            <TextBlock Name="txtBlock1" Text="計算文本框字符串的長度" Height="25"  VerticalAlignment="Bottom" HorizontalAlignment="Center" Grid.ColumnSpan="2" Grid.Row="1" />
            <TextBlock Name="textBlockString" Text="請輸入字符:" Height="25" Width="67" HorizontalAlignment="Right" Grid.Row="2" Grid.Column="0"/>
            <TextBox Name="textBox" Width="240" Height="25" Grid.Column="2" Grid.Row="2" />
            <TextBlock Name="textBlockStringLong" Text="字符串長度為:" HorizontalAlignment="Right" Height="23" Grid.Row="3" Grid.Column="0"/>
            <TextBox  Name="textBox1" Text="{Binding Path=Text.Length,ElementName=textBox,Mode=OneWay}" Width="240" Height="25" Grid.Row="3" Grid.Column="1" />
        </Grid>
        <!--源的索引作Path-->
        <Grid Background="Azure" Grid.Row="1" >
            <Grid.RowDefinitions>
                <RowDefinition Height="1*" />
                <RowDefinition Height="1*" />
                <RowDefinition Height="1*" />
                <RowDefinition Height="1*" />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="1*"/>
                <ColumnDefinition Width="2*"/>
            </Grid.ColumnDefinitions>
            <TextBlock Name="txtBlockPath2" Text="源的索引作Path" VerticalAlignment="Bottom" HorizontalAlignment="Center" Grid.ColumnSpan="2" Grid.Row="0" />
            <TextBlock Name="txtBlock2" Text="查看文本框字符串的第一個字符" Height="25"  VerticalAlignment="Bottom" HorizontalAlignment="Center" Grid.ColumnSpan="2" Grid.Row="1" />
            <TextBlock Name="textBlockString1" Text="請輸入字符:" Height="25" Width="67" HorizontalAlignment="Right" Grid.Row="2" Grid.Column="0"/>
            <TextBox Name="txtString1" Width="240" Height="25" Grid.Column="1" Grid.Row="2" />
            <TextBlock Name="textBlockStringLong1" Text="第一個字符為:" HorizontalAlignment="Right" Height="23" Grid.Row="3" Grid.Column="0"/>
            <TextBox  Name="txtFirstChar" Text="{Binding Path=Text.[0],ElementName=txtString1,Mode=OneWay}" Width="240" Height="25" Grid.Row="3" Grid.Column="1" />
        </Grid>
        <!--默認元素當path-->
        <Grid Background="Azure" Grid.Row="0" Grid.Column="1">
            <Grid.RowDefinitions>
                <RowDefinition Height="1*" />
                <RowDefinition Height="1*" />
                <RowDefinition Height="1*" />
                <RowDefinition Height="1*" />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="1*"/>
                <ColumnDefinition Width="2*"/>
            </Grid.ColumnDefinitions>
            <TextBlock Name="txtBlock3" Text="默認元素當path" Height="25"  VerticalAlignment="Bottom" HorizontalAlignment="Center" Grid.ColumnSpan="2" Grid.Row="0" Grid.Column="1"/>
            <TextBlock Name="textBlockString2" Text="名字:" Height="25" HorizontalAlignment="Right" Grid.Row="1" Grid.Column="0"/>
            <TextBox Name="txtName" Width="240" Height="25" Grid.Column="1" Grid.Row="1" />
            <TextBlock Name="txtBlockSecondChar" Text="名字的第二個字符:" HorizontalAlignment="Right" Height="23" Grid.Row="2" Grid.Column="0"/>
            <TextBox  Name="txtSecondChar" Width="240" Height="25" Grid.Row="2" Grid.Column="1" />
            <TextBlock Name="textBlockStringLong3" Text="字符長度為:" HorizontalAlignment="Right" Height="23" Grid.Row="3" Grid.Column="0"/>
            <TextBox  Name="txtLength"  Width="240" Height="25" Grid.Row="3" Grid.Column="1" />
        </Grid>
        <!--多級默認元素當path-->
        <Grid Background="Azure" Grid.Row="1" Grid.Column="1">
            <Grid.RowDefinitions>
                <RowDefinition Height="1*" />
                <RowDefinition Height="1*" />
                <RowDefinition Height="1*" />
                <RowDefinition Height="1*" />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="1*"/>
                <ColumnDefinition Width="2*"/>
            </Grid.ColumnDefinitions>
            <TextBlock Name="txtBlock4" Text="多級默認元素當path" Height="25"  VerticalAlignment="Bottom" HorizontalAlignment="Center" Grid.ColumnSpan="2" Grid.Row="0" Grid.Column="1"/>
            <TextBlock Name="textBlockCountry" Text="國家:" Height="25" HorizontalAlignment="Right" Grid.Row="1" Grid.Column="0"/>
            <TextBox Name="txtCountry" Width="240" Height="25" Grid.Column="1" Grid.Row="1" />
            <TextBlock Name="txtBlockProvince" Text="省份:" HorizontalAlignment="Right" Height="23" Grid.Row="2" Grid.Column="0"/>
            <TextBox  Name="txtProvince" Width="240" Height="25" Grid.Row="2" Grid.Column="1" />
            <TextBlock Name="textBlockCity" Text="城市:" HorizontalAlignment="Right" Height="23" Grid.Row="3" Grid.Column="0"/>
            <TextBox  Name="txtCity"  Width="240" Height="25" Grid.Row="3" Grid.Column="1" />
        </Grid>
    </Grid>
</Window>
CS
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace Chapter_04
{
    /// <summary>
    /// Path.xaml 的交互邏輯
    /// </summary>
    public partial class Path : Window
    {
        public Path()
        {
            InitializeComponent();
            //后台代碼綁定路徑為屬性或者索引(索引也可以理解為特殊的屬性),當綁定源本身就為string,int類型數據時
            //XAML中可以省略路徑,后台代碼中用"."代替,注意與下面“/”的區別
            //string str = "123";
            //this.txtName.SetBinding(TextBox.TextProperty, new Binding(".") { Source = str });
            //this.txtLength.SetBinding(TextBox.TextProperty, new Binding("Length") { Source = str, Mode = BindingMode.OneWay });
            //this.txtSecondChar.SetBinding(TextBox.TextProperty, new Binding("[1]") { Source = str, Mode = BindingMode.OneWay });

            //讓一個集合或者是DataView的第一個元素作為binding的路徑。如果路徑是集合的話可以繼續"/"下去===============================
            List<string> strList = new List<string>() { "Tim", "Tom", "Blog" };
            this.txtName.SetBinding(TextBox.TextProperty, new Binding("/") { Source = strList });
            this.txtLength.SetBinding(TextBox.TextProperty, new Binding("/Length") { Source = strList, Mode = BindingMode.OneWay });
            this.txtSecondChar.SetBinding(TextBox.TextProperty, new Binding("/[1]") { Source = strList, Mode = BindingMode.OneWay });

            //實現多級默認集合作為binding的源=========================================================================================
            //定義一個國家列表
            List<Country> ListCountry = new List<Country>();
            //定義一個省份列表
            List<Province> ListProvince = new List<Province>();
            //定義一個城市列表
            List<City> ListCity1 = new List<City>();
            List<City> ListCity2 = new List<City>();
            //為城市列表中添加城市

            ListCity1.Add(new City() { Name = "鄭州" });
            ListCity1.Add(new City() { Name = "許昌" });

            ListCity2.Add(new City() { Name = "福州" });
            ListCity2.Add(new City() { Name = "廈門" });

            //為省份列表添加省
            ListProvince.Add(new Province() { Name = "河南", CityList = ListCity1 });
            ListProvince.Add(new Province() { Name = "福建", CityList = ListCity2 });

            Country country = new Country() { Name = "中國", ProvinceList = ListProvince };
            ListCountry.Add(country);
            //當默認集合為對象的話,注意指明其屬性,注意與上面的區分
            this.txtCountry.SetBinding(TextBox.TextProperty, new Binding("/Name") { Source = ListCountry });
            this.txtProvince.SetBinding(TextBox.TextProperty, new Binding("/ProvinceList/Name") { Source = ListCountry });
            this.txtCity.SetBinding(TextBox.TextProperty, new Binding("/ProvinceList/CityList/Name") { Source = ListCountry });
        }
    }

    #region 創建國家、省份、城市類
    class City
    {
        public string Name { get; set; }
    }
    class Province
    {
        public string Name { get; set; }
        public List<City> CityList { get; set; }
    }
    class Country
    {
        public string Name { get; set; }
        public List<Province> ProvinceList { get; set; }
    }
    #endregion
}

相對比較簡單,代碼里面含有注釋,如有疑問和不足的地方,歡迎留言。

2.3 為Binding指定源的方式

  Binding指定源的方式有多種,總的概括一句話:只要一個對象包含數據並能通過屬性把數據暴露出來,就能當做Banding的源。下面列出常用的方法:

  • 把CLR類型單個對象指定為Source,包括自帶的類型對象和用戶自定義的對象,必要時使用INotifyPropertyChanged接口。可以通過在屬性的set語句里激發PropertyChanged事件來通知Binding數據已經發生改變。
  • 把依賴對象指定為Source。一般情況依賴對象是用來作為Binding的目標的,如果是作為源的話,就形成了Binding鏈(源-目標(源)-目標)。
  • 把DataContext指定為Source,由於每個WPF控件都含有DataContext屬性,DataContext常用於:如果不能確定Binding從哪里獲取數據,只知道路徑Path的話,他就會一直沿着控件樹往根的方向找下去,從而找到含有Path的元素的DataContext作為源。
  • 把普通CLR集合類型對象指定為Source:包括數組、泛型等集合類型。一般是把控件的ItemsSource屬性使用Binding關聯到一個集合對象上。
  • 把ADO.NET數據對象指定為Source:包括DataTable和DataView等對象。
  • XmlDataProvider把XML數據指定為source,經常和TreeView和Menu結合着用(除了TreeView,還涉及到DataTemplate相關的內容,如果不理解的話,到“話模板“的時間在具體記錄)。
  • ObjectDataProvider對象指定為Source,當數據源的屬性不是通過屬性而是通過方法來暴露的話,就可以用ObjectDataProvider對象。
  • 通過Binding的RelativeSource屬性相對的指定Source,主要用於當控件想關注自己或者自己容器的以及自己內部元素的某個值就需要這個方法。

  由於前兩個已經使用過,下面就直接從第三個開始逐個介紹。

2.4使用DataContext作為Binding的源

  先重點記錄一下DataContext,DataContext是每一個繼承FrameworkElement類的控件都有的屬性,所以每一個UI控件都有此屬性(包括布局控件),Binding只知道Path不知道Source的話就會向UI樹的根部找過,直到找到含有Path所指定的屬性的對象然后“借過來”用一下,如果最終沒有找到那么就不會得到數據。為了初步認識一下DataContext,下面看一個例子:

<Window x:Class="Chapter_04.ContextSource"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:Chapter_04"
        Title="ContextSource" Height="150" Width="219">
    <StackPanel Background="Beige">
        <StackPanel.DataContext>
            <local:Student Id="2008101132" Age="24" Name="李占朋"/>
        </StackPanel.DataContext>
        <Grid>
            <StackPanel>
                <TextBox Text="{Binding Path=Id}" Margin="5" BorderBrush="Black"/>
                <TextBox Text="{Binding Path=Name}" Margin="5" BorderBrush="Black"/>
                <TextBox Text="{Binding Path=Age}" Margin="5" BorderBrush="Black"/>
            </StackPanel>
        </Grid>
    </StackPanel>
</Window>

后台代碼為:

CS
XAML
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace Chapter_04
{
    /// <summary>
    /// ContextSource.xaml 的交互邏輯
    /// </summary>
    public partial class ContextSource : Window
    {
        public ContextSource()
        {
            InitializeComponent();
        }
    }
    public class Student
    {
        public int Id { get; set; }
        public int Age { get; set; }
        public string Name { get; set; }
    }
}

效果圖為圖4:

 

                                                                                                                    

                    圖4                                           圖5

  在上面的例子中一個Student實例作為下面TextBox的綁定源。當然{Binding Path=Age}部分可以把"Path="省略掉即{Binding Age}。突然發現DataContext指定源是沒有Source的Binding。是否能和沒有path(當源為string或int類型時)的連用,如果可以,是怎么樣的效果呢?為了驗證沒有找到Source只是不顯示數據但是不會報錯,我還用上面的部分代碼(圖5為效果圖):

<Window x:Class="Chapter_04.ContextSource"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        xmlns:local="clr-namespace:Chapter_04"
        Title="ContextSource" Height="150" Width="219">
    <StackPanel Background="Beige">
        <StackPanel.DataContext>
            <!--<local:Student Id="2008101132" Age="24" Name="李占朋"/>-->
            <sys:String>紅色部分為沒有Path的測試!</sys:String>
        </StackPanel.DataContext>
        <Grid>
            <StackPanel>
                <TextBlock Text="{Binding}" Background="Red"/>
                <TextBox Text="{Binding Path=Id}" Margin="5" BorderBrush="Black"/>
                <TextBox Text="{Binding Path=Name}" Margin="5" BorderBrush="Black"/>
                <TextBox Text="{Binding Path=Age}" Margin="5" BorderBrush="Black"/>
            </StackPanel>
        </Grid>
    </StackPanel>
</Window>

嘿嘿,既沒有指定Source有沒有指定path的Binding出現了。下面看一下控件是怎么借用DataContext的。

主要的XAML代碼為

    <Grid DataContext="借用測試">
        <Grid>
            <Grid>
                <Grid>
                    <Button x:Name="btn" Content="Ok" Click="btn_Click"/>
                </Grid>
            </Grid>
        </Grid>
    </Grid>

點擊按鈕事件代碼為:

        private void btn_Click(object sender, RoutedEventArgs e)
        {
            MessageBox.Show(btn.DataContext.ToString());
        }

 點擊按鈕效果圖為圖6:

圖6

  到這里應該來說基本上認識了DataContext。而且從第一個例子中我們還可以看出:當UI上多個控件使用Binding關注同一個對象,可以使用DataContext作為Binding的源。其實DataContext還是一個依賴屬性,我們可以使用Binding把他關聯到一個數據源上(先記着有這么一回事,在以后的記錄中會詳細分析)。

 2.5  集合類型對象指定列表控件的為ItemsSource

  前面的筆記中已經記錄了ItemsControl控件,應該有些熟悉了,它有個ItemsSource屬性,可以接受一個IEnumerable接口派生類的實例作為自己的值。我們現在想辦法把指定的Path

顯示在ListBox中,先實現單個屬性的實現,然后在實現多個屬性的顯示。單屬性顯示的代碼為:

XAML
<Window x:Class="Chapter_04.ItemsSource"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="ItemsSource" Height="300" Width="300">
    <Grid Background="Beige">
        <TextBlock Text="所選學生的學號:"></TextBlock>
        <TextBox x:Name="textBoxid" Margin="0,1,93,234" HorizontalAlignment="Right" Width="82"></TextBox>
        <ListBox x:Name="listview" Margin="0,89,62,0" Background="Azure">
            <!--<ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding Path=Id}" Width="30"/>
                    <TextBlock Text="{Binding Path=Name}" Width="30"/>
                    <TextBlock Text="{Binding Path=Age}" Width="30"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>-->
        </ListBox>
        <Button Content="添加學生" Height="23" HorizontalAlignment="Left" Margin="191,0,0,0"  VerticalAlignment="Top" Width="75" Click="button1_Click" />
    </Grid>
</Window>
CS
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Collections.ObjectModel;

namespace Chapter_04
{
    /// <summary>
    /// ItemsSource.xaml 的交互邏輯
    /// </summary>
    public partial class ItemsSource : Window
    {
        //注意ObservableCollection與List的區別
        List<Student> stuList = new List<Student>() 
        { 
            new Student(){Id=0,Name="Tim",Age=19},
            new Student(){Id=1,Name="Tom",Age=29},
            new Student(){Id=2,Name="jim",Age=39},
        };
        public ItemsSource()
        {
            InitializeComponent();

            listview.ItemsSource = stuList;
            this.listview.DisplayMemberPath = "Name";
            Binding binding = new Binding("SelectedItem.Id") { Source = this.listview };
            this.textBoxid.SetBinding(TextBox.TextProperty, binding);
        }

        private void button1_Click(object sender, RoutedEventArgs e)
        {
            this.stuList.Add(new Student() { Id = 2, Name = "jim", Age = 39 }); 
        }
    }
}

  在CS代碼中,我們看到了一句this.listview.DisplayMemberPath = "Name"中有一個DisplayMemberPath,這個就是指定綁定源的屬性。如果想顯示多個屬性,應該怎么做呢?答案是使用DataTemplate,因為在以后的筆記中還會記錄到DataTemplate,在此提出來知道有這么一回事就好了,接觸過webFrom的人或許會理解的更快點。具體的做法是把上面的XAML代碼中的注釋部分去掉,后台代碼的this.listview.DisplayMemberPath = "Name";去掉就可以實現顯示多個屬性。除此之外我在UI上還放了一個Button,主要用來區別ObservableCollection<T>與List<T>,因為ObservableCollection<T>實現了INotifyColletionChanged和INotifyProperty接口,能把集合的變化立即顯示給控件。可以把后台代碼的List用ObservableCollection替代,點擊按鈕查看兩者的區別(添加的條目會理解顯示出來,用list卻沒有這個效果)。

2.6 把ADO.NET數據對象指定為Source

  基本上和上面的用法相同,但是注意格式,特別是GridView的用法.

View Code
<Window x:Class="Chapter_04.Ado"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Ado" Height="300" Width="400">
    <StackPanel>
        <ListView x:Name="listView1">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="學號" Width="60" DisplayMemberBinding="{Binding UserNo}"/>
                    <GridViewColumn Header="昵稱" Width="60" DisplayMemberBinding="{Binding UserName}"/>
                    <GridViewColumn Header="姓名" Width="60" DisplayMemberBinding="{Binding UserRealName}"/>
                    <GridViewColumn Header="郵箱" Width="120" DisplayMemberBinding="{Binding UserEmail}"/>
                </GridView>
            </ListView.View>
        </ListView>
    </StackPanel>
</Window>
CS
View Code 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Data;

namespace Chapter_04
{
    /// <summary>
    /// Ado.xaml 的交互邏輯
    /// </summary>
    public partial class Ado : Window
    {
        public DataTable dt = null;
        public Ado()
        {
            IntiData();
            InitializeComponent();
            //this.listView1.ItemsSource = dt.DefaultView;
            this.listView1.DataContext = dt;
            this.listView1.SetBinding(ListView.ItemsSourceProperty,new Binding());
        }

        private void IntiData()
        {
            dt = new DataTable("T_User");
            dt.Columns.Add("UserNo", typeof(string));
            dt.Columns.Add("UserName", typeof(string));
            dt.Columns.Add("UserRealName", typeof(string));
            dt.Columns.Add("UserEmail", typeof(string));
            dt.Columns.Add("UserAddress", typeof(string));

            for (int i = 0; i < 10; i++)
            {
                DataRow dr = dt.NewRow();
                dr["UserNo"] = "10000" + i.ToString();
                dr["UserName"] = "haiziguo";
                dr["UserRealName"] = "李占朋";
                dr["UserEmail"] = "lizhpeng@126.com";
                dt.Rows.Add(dr);
            }
        }
    }
}

 

顯示結果如圖7:

 

圖7

  通過上面的一個演示,我們應該知道ListView和GridView不是同一級別的控件,GridView是作為ListView的View屬性的值,其中View和GridView都是屬於ViewBase類型對象。如果不是很理解的話,在模板筆記里面還會具體的記錄。還有個地方是"this.listView1.ItemsSource = dt.DefaultView;“DataTable是不能直接賦值給ItemsSource的,如果賦值可以用DefaultView屬性。把這一句取消注釋,然后注釋掉下面的兩行,可以看到相同的結果。

2.7 XmlDataProvider把XML數據指定為source

  XML是什么以及xml的用途,在此不作為重點,本文重點介紹怎么把XML綁定到UI控件中。 XmlDataProvider就是把XML數據作為數據源提供給Binding。下面實現一個需求,就是把XML的數據顯示在TreeViewItem里面。先給出XML文件,然后分別給出XAML和CS代碼:

XML
<?xml version="1.0" encoding="utf-8" ?>
<Department Name="公司部門">
  <Department Name="軟件組">
    <Department Name="Tom"/>
    <Department Name="Tom1"/>
  </Department>
  <Department Name="客服組">
    <Department Name="Tim"/>
    <Department Name="Tim1"/>
  </Department>
</Department> 
XAML
<Window x:Class="Chapter_04.Source"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Source" Height="312" Width="1090">
    <StackPanel Orientation="Horizontal">
        <Grid Width="150">
            <TreeViewItem>
                <TreeViewItem.Header>各個部門以及其成員</TreeViewItem.Header>
                <TreeView x:Name="treeViewDepartment">
                    <TreeView.ItemTemplate>
                        <HierarchicalDataTemplate ItemsSource="{Binding XPath=Department}">
                            <TextBlock Text="{Binding XPath=@Name}"/>
                        </HierarchicalDataTemplate>
                    </TreeView.ItemTemplate>
                </TreeView>
            </TreeViewItem>
        </Grid>
    </StackPanel>
</Window>
CS
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace Chapter_04
{
    /// <summary>
    /// Source.xaml 的交互邏輯
    /// </summary>
    public partial class Source : Window
    {
        public Source()
        {
            InitializeComponent();
            XmlDataProvider xdp = new XmlDataProvider();
            xdp.Source = new Uri(@"E:\WPFCode\Chapter_04\Chapter_04\Department.xml");
            xdp.XPath = @"/Department";
            treeViewDepartment.DataContext = xdp;
            treeViewDepartment.SetBinding(TreeView.ItemsSourceProperty, new Binding());
        }
    }
}

另外一種寫法思路是把XML文件當做資源,然后在前台綁定:

View Code
<Window x:Class="Chapter_04.Source"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Source" Height="312" Width="1090">
    <StackPanel Orientation="Horizontal">
        <Grid Width="150">
            <TreeViewItem>
                <TreeViewItem.Header>各個部門以及其成員</TreeViewItem.Header>
                <TreeView x:Name="treeViewDepartment" ItemsSource="{Binding Source={StaticResource ResourceKey=xmlDepartment}}">
                    <TreeView.ItemTemplate>
                        <HierarchicalDataTemplate ItemsSource="{Binding XPath=Department}">
                            <TextBlock Text="{Binding XPath=@Name}"/>
                        </HierarchicalDataTemplate>
                    </TreeView.ItemTemplate>
                </TreeView>
            </TreeViewItem>
        </Grid>
    </StackPanel>
</Window>
CS
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace Chapter_04
{
    /// <summary>
    /// Source.xaml 的交互邏輯
    /// </summary>
    public partial class Source : Window
    {
        public Source()
        {
            InitTreeView();
            InitializeComponent();
        }
        protected void InitTreeView()
        {
            XmlDataProvider xdp = new XmlDataProvider();
            xdp.Source = new Uri(@"E:\WPFCode\Chapter_04\Chapter_04\Department.xml");
            xdp.XPath = @"/Department";
            this.Resources.Add("xmlDepartment",xdp);
        }
    }
    
}

效果圖為圖8:

 圖8

   簡單的實現了讓XML的文件顯示到了TreeViewItem,但是美中不足的是這里用到了HierarchicalDataTemplate模板,不過作為一個參考吧,或者等到介紹完Template再回來看看也不錯。最后要注意的是:關於綁定的時間<TextBlock Text="{Binding XPath=@Name}"/> 里面加@表示Attribute,不加@的話表示子集元素。

 2.8 ObjectDataProvider對象指定為Source

  Binding的源不僅可以是通過XmlDataProvider提供的XML數據,還可以是ObjectDataProvider類提供的類的實例。XmlDataProvider與ObjectDataProvider都是繼承於抽象類DataSourceProvider。下面用個圖來解釋一下ObjectDataProvider對象,如圖9:

圖9

   其中ObjectDataProvider對象的ObjectInstance屬性是類的實例化對象(Obj),MethodParamers屬性為Obj的方法,其中方法中還可以含有參數。最后返回結果為ObjectDataProvider對象的Data屬性。 這樣做的結果是更方便了程序員的分工,很簡單的就綁定了一個返回值。 下面實現一個加法的運算:

Calculator.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Chapter_04
{
    public class Calculator
    {
        public string Add(string arg1, string arg2)
        {
            double x = 0; double y = 0; double z = 0;
            if (double.TryParse(arg1, out x) && double.TryParse(arg2, out y))
            {
                z = x + y;
                return z.ToString();
            }

            return "input Error";
        }
    }
}
XAML
<Window x:Class="Chapter_04.ObjectDataProviderTest"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="ObjectDataProviderTest" Height="300" Width="300">
    <StackPanel Background="LightBlue">
        <TextBox Name="FristParam"  Margin="5"  ></TextBox>
        <TextBox Name="SecondParam"  Margin="5" ></TextBox>
        <TextBox Name="Result"  Margin="5"></TextBox>
    </StackPanel>
</Window>
CS
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace Chapter_04
{
    /// <summary>
    /// ObjectDataProviderTest.xaml 的交互邏輯
    /// </summary>
    public partial class ObjectDataProviderTest : Window
    {
        public ObjectDataProviderTest()
        {
            InitializeComponent();
            SetBinding();
        }
        private void SetBinding()
        {
            ObjectDataProvider odp = new ObjectDataProvider();
            odp.ObjectInstance = new Calculator();
            odp.MethodName = "Add";
            odp.MethodParameters.Add("0");
            odp.MethodParameters.Add("0");
            //MessageBox.Show(odp.Data.ToString());
            Binding bindArg1 = new Binding("MethodParameters[0]")
            {
                Source = odp,
                //把UI元素收集到的元素直接賦給source
                BindsDirectlyToSource = true,
                //當目標更改時,立即設置新的源
                UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
            };
            Binding bindArg2 = new Binding("MethodParameters[1]")
            {
                Source = odp,
                BindsDirectlyToSource = true,

                UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
            };
            Binding bindingToResult = new Binding(".") { Source = odp };

            this.FristParam.SetBinding(TextBox.TextProperty, bindArg1);
            this.SecondParam.SetBinding(TextBox.TextProperty, bindArg2);
            this.Result.SetBinding(TextBox.TextProperty, bindingToResult);
        }
    }
}

運行,如果在前兩個文本框里面填上數字,結果就會出現了;如圖10

圖10

  ObjectDataProvider對象作為綁定源還是很遵守“使用數據對象作為源,UI控件作為綁定對象”的原則,但是關於this.Result.SetBinding(TextBox.TextProperty,new Binding(".") { Source=odp});用“.”而不用“Data”來表示路徑,我不是很理解,如果有大牛知道的話,歡迎留言。 

 2.9 通過Binding的RelativeSource屬性相對的指定Source

  綁定的源不僅用"絕對"的對象,還可以用“相對”的作為源。例如有的時間我們需要顯示出上級的控件的寬度。需求是:將上級的第一個(從自身向上數起第一個)Grid的寬度顯示在TextBox里面,由於比較簡單,現在直接貼出代碼。  

XAML
<Window x:Class="RelativeSounceOfBinding.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 x:Name="g1" Background="Red" Margin="10">
        <DockPanel x:Name="d1" Background="Orange" Margin="10">
            <Grid x:Name="g2" Background="Yellow" Margin="10" Width="300">
                <DockPanel x:Name="d2" Background="LawnGreen" Margin="10">
                    <TextBox Name="textBox1" FontSize="24" Margin="10" Background="AliceBlue" />
                </DockPanel>
            </Grid>
        </DockPanel>
    </Grid>
</Window>
CS
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace RelativeSounceOfBinding
{
    /// <summary>
    /// MainWindow.xaml 的交互邏輯
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            RelativeSource rs = new RelativeSource();
            rs.AncestorLevel = 1;
            rs.AncestorType = typeof(Grid);
            Binding bind=new Binding("Width"){RelativeSource=rs};
            this.textBox1.SetBinding(TextBox.TextProperty, bind);
        }
    }
}

效果圖如圖11:

圖11

            RelativeSource rs = new RelativeSource();
            rs.AncestorLevel = 1;
            rs.AncestorType = typeof(Grid);

此處的源是RelativeSource類型的實例。AncestorLevel是指定向上查第幾個類型為AncestorType的控件。順便附上XAML實現的方法:

<TextBox Name="textBox1" FontSize="24" Margin="10" Background="AliceBlue" Text="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type Grid},AncestorLevel=1},Path=Width}" />

三、總結

  由於綁定與模板聯系比較緊密,所以有的地方會用到模板,難免會有似懂非懂的感覺,不過等做完模板的筆記的話,再回來看,應該會慢慢的消化的。綁定在WPF中是比較重要的一章,在本文中僅僅是幾個記錄。鑒於文章寫的太長自己堅持不住寫,另一方面到時間復習的話也沒有耐心看,所以就把綁定分為兩篇去記錄,下一篇《深入淺出WPF》筆記——綁定篇(二)會記錄Binding對數據的轉化和校驗以及多路綁定。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM