做過ASP.NET或者Silverlight的童鞋對資料繫結比較熟悉,WP7是基於Silverlight的,silverlight for windows phone頁面也是使用XAML,所以頁面渲染的原理是一樣的。
資料繫結分為源(Source)和目標(Target),Source一般分為兩種,其他控制項的資料來源,或者資料對象。
先說說以控制項作為資料來源的吧,最簡單的格式是:目標控制項屬性="{Binding ElementName=原始檔控制名, Path=原始檔控制屬性}"。
為了便於一會介紹Converter,我放棄使用TextBox作為資料來源,轉而使用Silvder,免得一會有童鞋挑錯。
在頁面中放置一個Silder和一個TextBlock,XAML代碼如下
Slider Height="84" HorizontalAlignment="Right" Margin="0,41,0,0" Name="MySilder" Value="20" Minimum="0" Maximum="100" VerticalAlignment="Top" Width="450" /><TextBlock Height="30" HorizontalAlignment="Left" Margin="30,163,0,0" Name="TargetTB" Text="{Binding ElementName=MySilder, Path=Value}" VerticalAlignment="Top" />
看到代碼中,TextBlock的Text屬性的值使用了綁定,綁定的源是Silder,綁定的屬性是Value,意思是,TextBlock的Text屬性的值,來源於Silider的Value屬性的值
運行程式,行拖動Silder,效果如下
看到,拖動Silder時,TextBlock的Text值也會跟著變化,這個Binding的繫結模式Mode有關,Binding預設的Mode是OneWay,單項綁定,也就是說Silder的Value變化時,TextBlock的Text也會變,但是TextBlock的Text變化時Silder的Value不會跟著變,還有兩種叫做OneTime和TwoWay,OneTime是綁定依次,也就是TextBlock的Text擷取Silder的Value的第一個值,當Silder的Value再次變化時,TextBlock的Text不會變。TwoWay是雙向繫結,也就是說改變Silder的Value,TextBlock的Text跟著變,改變TextBlock的Text,Silder的Value也會跟著變。例子在Converter的效果中一起看,暫時不給了。
Converter的作用是資料轉換,比如我想要源的資料,但是源的資料格式又不是我想要的,那麼我就可以使用Converter來擷取我要真正顯示的值,Converter改變的只是頁面顯示出來的值,不貴改變來源資料的值。
實現Converter的的方式是定義一個繼承自IValueConverter介面,並實現Convert和ConvertBack方法,現在我們在項目中添加一個類,代碼如下
public class ValueConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { string strValue = ""; double doubleValue = (double)value; if (doubleValue > 50) strValue = "擷取的值比較大"; else strValue = "擷取的值比較小"; return strValue; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { return value; } }
然後還要將該類添加到靜態資源中,以便使用,在App.xaml中添加當前項目中的命名空間:xmlns:local="clr-namespace:DataBindings"
然後將ValueConverter類聲明為靜態資源:<local:ValueConverter x:Key="valueConverter" />
現在修改MainPage.xaml中的TextBlock的Text的內容:<TextBlock Height="30" HorizontalAlignment="Left" Margin="30,163,0,0" Name="TargetTB" Text="{Binding ElementName=MySilder, Path=Value, Mode=TwoWay, Converter={StaticResource valueConverter}}" VerticalAlignment="Top" />。
上面已經將Binding的Mode設定成了TwoWay,所以我們用代碼修改TextBlock的Text屬性的值,看看Silder是否跟著變化。
在Page_Loaded方式中添加一句代碼,用來在頁面初始化後修改TextBlock的Text屬性的值:TargetTB.Text = "40";
運行項目,效果如下
我沒在Page_Loaded方法中設定了TextBlock的Text值為40,看到Silder的值變成了40,證明Mode=TwoWay起作用了,看到,TextBlock的Text值顯示的不是40,而是"擷取的值比較小",證明我們的ValueConverter起作用了,那麼拖動Silder,使他的值大於50%,我們應該猜到TextBlock的Text值,應該是"擷取的值比較大",看看效果是不是
接下來我們說說,實際開發中使用更多的,使用資料對象作為資料來源
先添加一個用於提供資料來源的類DataSource和Person,代碼如下
public class DataSource { private Person _personData; public Person PersonData { get { return _personData; } set { _personData = value; } } public DataSource() { PersonData = new Person() { Name = "DHC", Sex = "男", Age = 27 }; } } public class Person: INotifyPropertyChanged { private string _name; public string Name { get { return _name; } set { _name = value; } } private string _sex; public string Sex { get { return _sex; } set { _sex = value; } } private int _age; public int Age { get { return _age; } set { _age = value; } } }
然後為MainPage版面設定資料來源
DataSource ds = new DataSource();DataContext = ds.PersonData;
在MainPage.xaml頁面中添加三個TextBlock,並綁定資料
<TextBlock Text="{Binding Name}"/><TextBlock Text="{Binding Sex}"/><TextBlock Text="{Binding Age}"/>
運行程式,效果如下
上面使用DataContext,DataContext就是表示的資料內容,上面設定的整個頁面的資料來源,幾乎任何空間都有DataContext屬性,設定了這個屬性,其自己的屬性或者子控制項的屬性就可綁定資料來源對象的中屬性的值。因為設定了父控制項的DataContext之後,其中的所有子控制項的DataContext屬性預設和父控制項一樣,除非子控制項設定了自己的DataContext。
綁定格式大同小異:{Binding Source=資料來源, Path=屬性名稱}至於Mode和Converter,使用方式與上面介紹控制項為資料來源一樣。Source預設為DataContext,除非不是,否則一般省略不寫,Path關鍵字一般省略,所以最簡格式一般為{Binding 屬性名稱}。
那麼我們改變資料看看,看看TextBlock的Text是否改變
在頁面中添加一個按鈕,在點擊事件中添加代碼
Person person = DataContext as Person;person.Age = 100;
運行並點擊,按鈕,效果如下
Age沒有變成100,這不是我們想要的結果,這事為什麼呢
INotifyPropertyChanged介面
上面的程式,點擊按鈕,並沒有像我們想象中的那樣,顯示Age的TextBlock的Text沒有改變,這事因為資料改變後,並沒有通知UI介面,為什麼Silder的Value改變後通知了,我們自己定義的資料沒有通知呢,這是因為我們上面定義的Person類沒有繼承INotifyPropertyChanged,看到這個介面的名字,就知道他是幹什麼的,通知屬性改變,他就是幹這個用的。
用法是直接繼承INotifyPropertyChanged,利用PropertyChangedEventHandler添加委託,通知數據已經改變,現在修改Person類
public class Person: INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(PropertyChangedEventArgs args) { if (PropertyChanged != null) PropertyChanged(this, args); } private string _name; public string Name { get { return _name; } set { _name = value; OnPropertyChanged(new PropertyChangedEventArgs("Name")); } } private string _sex; public string Sex { get { return _sex; } set { _sex = value; OnPropertyChanged(new PropertyChangedEventArgs("Sex")); } } private int _age; public int Age { get { return _age; } set { _age = value; OnPropertyChanged(new PropertyChangedEventArgs("Age")); } } }
再次運行程式,點擊按鈕,效果如下
好了,這就是我們想要的結果。
在最後補充一點東西,我們可以將資料聲明為靜態資源,然後也可以綁定
聲明靜態資源:<local:DataSource x:Key="data"/>
為版面設定資料來源:DataContext="{StaticResource data}"
為控制項屬性綁定資料
<TextBlock Text="{Binding PersonData.Name}"/><TextBlock Text="{Binding PersonData.Sex}"/><TextBlock Text="{Binding PersonData.Age}"/>
運行效果和上面是一樣的。我之所以留到最後將綁定靜態資源,是因為,如果不繼承INotifyPropertyChanged介面的資料來源,聲明為靜態資源,綁定後頁面中沒有資料,這事因為我們按照比較合理的方式編寫DataSource類,在建構函式中初始化資料,這樣,等於說是聲明屬性時,沒有賦值(為null),而在建構函式中改變資料,由於沒有繼承INotifyPropertyChanged介面,所以不會通知UI資料以改變(還是null),這就無法顯示了,我們需要改寫DataSource,在聲明PersonData變數時就給他賦值,這樣就能顯示了,但是這是不好的寫法,而且使用了get,set屬性訪問器,更會出問題,代碼醜陋不堪,而我如果不說明,童鞋用了我現在的代碼,而無法顯示資料,該罵我了,所以我講完INotifyPropertyChanged,在說綁定靜態資源,你繼承了INotifyPropertyChanged肯定不會出問題。