Windows Store App 控制項資料繫結

by Anita 7. 五月 2014 02:27

.NET Magazine國際中文電子雜誌
者:羅慧真
稿:張智凱
文章編號:N140514801
出刊日期:2014/5/7
開發工具:Visual Studio 2012
版本:Windows Runtime 1.0

 

資料如何顯示在控制項上,並且能夠根據來源或目的端的變更自動更新?

答案是 -- 資料繫結技術。

在這一章中你將認識如何讓控制項與各類的資料來源繫結並根據一些設定更新資料。

資料通常具有以下特性:

  • 資料來源多元化:資料不絕對是從資料庫來的,它的型式可能會是物件、集合或是另一個控制項
  • 資料總是變動的:資料內容可能因各種因素而改變
  • 資料要被檢梘:資料會顯示在畫面上供使用者檢視
  • 資料可被修改:使用者可能需要直接在畫面上修改內容

要讓控制項能夠即時顯示各種資料來源的資料,那麼就要靠資料繫結的技術啦!

資料繫結是利用Binding物件將控制項的某個屬性與資料關連在一起。Binding物件會根據設定監控資料來源的變化更新資料來源,或是將控制項上的屬性值修改寫回資料來源。

XAML資料繫結所支援的資料來源非常多樣化,可以是:

  • 畫面上其他控制項
  • 物件
  • 集合物件

我們將介紹如何與其他控制項、物件、集合物件的資料繫結。

image

圖1 - 資料繫結

資料繫結的目的端必須是XAML控制項的依存屬性(Dependency Property)。

重要名詞 -- 依存屬性

依存屬性(Dependency Property)是XAML裡特有的功能之一,它改變原有的實體屬性的特性。實體屬性是將屬性值裝在物件實體之中,目的是為了封裝,避免被其他無關的程式修改到屬性值。但在需要大量處理畫面的WPF世界裡卻成為一項阻礙,WPF為了畫面控制的靈活性,每個控制項擁有非常多的屬性,而這些屬性又可能透過動態邏輯資源、資料繫結、動畫等影響而改變,為了解決記憶體容量及執行效能等問題,於是WPF發展出依存屬性。依存屬性是將屬性資料存到另一個靜態空間去,同時若是屬性值未設定(即為預設值)那麼並不會佔用記憶體。

與其他控制項繫結

XAML可以讓控制項的屬性與另一個控制項的屬性繫結。下面這個畫面TextBox的Text屬性是資料來源,TextBlock的Text屬性是資料繫結的目標控制項。

image

圖2 - 與其他控制項繫結

這個範例的執行效果像打字機一樣,在TextBox上打字會馬上顯示在Label上。它的XAML如下:

<TextBox x:Name="textBox" Text="TextBox" …部分內容省略…/>

<TextBlock Text="{Binding Text, ElementName=textBox}" />

要在TextBlock的Text屬性設定Binding物件包含以下幾個屬性:

  • ElementName,來源控制項的名稱
  • Path,要繫結的來源控制項屬性路徑,屬性型別可能是複雜型別,來源值可能是屬性的屬性…

使用屬性視窗指定資料繫結

Visual Studio 2012的XAML專案中,控制項屬性視窗可以直接套用資料繫結功能,它的步驟如下:

1. 在要繫結的控制項屬性上,例如Text屬性,按下屬性名稱右方小按鈕,在下拉選單選取「建立資料繫結…」

image

圖3 - 建立資料繫結

2. 設定ElementName與Path,在「繫結型別」下拉選取ElementName(圖3標示1),選取textBox(圖4標示2),然後按「路徑」清單選取Text屬性(圖4標示3)。

image

圖4 - 設定ElementName與路徑

與物件繫結

在物件導向的世界裡,程式可能以任何物件來處理某件事,同時又想將物件的內容呈現於畫面上,並可能透過控制項的操作變更物件的值,XAML的資料來源也可以是物件,滿足了這項需求。下圖資料來源是Employee類別所建立的物件,透過Binding將物件的屬性與多種控制項資料繫結。

 image

圖5 - 與物件繫結

與物件繫結的步驟是:

1. 設計物件類別,請直接參考程式碼,類別結構如圖5。

2. 資料載入,在頁面載入時建立物件實體。先為Page中的第一個Grid取名為layoutRoot,然後開啟程式碼找到OnNavigatedTo方法,建立Employee物件,並指定給DataContext屬性。

protected override void OnNavigatedTo(NavigationEventArgs e)

{

    var emp = new Employee { ID = 101, Name = "Anita", HireDate = new DateTime(1998, 1, 1) , PhotoPath = @"me.jpg"};

    layoutRoot.DataContext = emp;

}

3. 開啟MainPage的設計視窗,從工具箱放入三個TextBox控制項。

4. 分別指定三個TextBox控制項的Text屬性繫結到ID、Name、HireDate。圖6是使用資料繫結視窗,繫結型別要選取「資料內容」,勾選「自訂」在「路徑」的位置上輸入要繫結的屬性名稱。

image

圖6 - 資料繫結視窗

以下是XAML的內容:

<Page xmlns:local="using:App2_BindingObject"

x:Class="App2_BindingObject.MainPage" …部份省略…>

    <Grid x:Name="layoutRoot" …部份省略… >

        <TextBox …部份省略… Text="{Binding ID, Mode=TwoWay}" />

        <TextBox …部份省略… Text="{Binding Name, Mode=TwoWay}" "/>

        <TextBox …部份省略… Text="{Binding HireDate, Mode=TwoWay}" />

    </Grid>

</Page>

資料繫結的方向

資料繫結方向可以由Binding物件的Mode屬性指定,它包含幾個選項:

l OneTime,只在載入時繫結一次

l OneWay,只會在資料來源變更時將資料更新到目標控制項的屬性

l TwoWay,雙向更新,資料來源變更會更新目標控制項的屬性,相反的目標控制項屬性變更時會資料更新資料來源

image

圖7 - Binding的Mode屬性

自訂值轉換器

上一個範例Employee有一個欄位PhotoPath是一個字串型別,它是用來記錄員工的照片檔案位置,若它是一個網路位置(http、ftp、ms-appx 或ms-resource)那麼可以直接與Image的Source屬性進行資料繫結。以下兩個步驟簡單修改前一個範例:

1. 放入Image控制項指定Source屬性與PhotoPath屬性繫結。

<Image HorizontalAlignment="Left" Height="254" Margin="133,342,0,0" VerticalAlignment="Top" Width="374" AutomationProperties.Name=" {Binding Name}" Source="{Binding PhotoPath}"/>

2. 修改OnNavigatedTo方法,指定PhotoPath為網路的位置:

var emp = new Employee { ID = 101, Name = "Anita", HireDate = new DateTime(2001, 1, 1),

PhotoPath="http://sphotos-b.ak.fbcdn.net/hphotos-ak-ash3/546598_10151291430628608_509530616_n.jpg"

};

3. 執行結果:

image

圖8 - 加入Image的Binding

如果試著將PhotoPath改成本機路徑就會有問題。這個問題有兩個層面,一個是安全性,一個是Image無法直接載入本機路徑。

Windows Store App是一個Sandbox的執行環境,應用程式對於本機檔案進行存取是有限制的,可以透過選取檔案、資料夾,或者使用者特定的資料夾,像是「文件庫」、「圖片庫」…等,不過這得要在應用程式進行指定可使用的系統功能才能使用。

另一個問題是Image的Source只能存取Uri識別的資源(http、ftp、ms-appx 或ms-resource),無法直接存取本機檔案路徑,因此必須寫程式載入檔案串流成為BitmapImage之後再指到Image的Source,問題是這裡我們希望能直接進行,這時就必須利用資料繫結功能中的值轉換器,在值轉換器中進行。

值轉換器必須是一個實作IValueConverter介面的類別,IValueConverter介面有兩個方法,一個是Convert、一個是ConvertBack。

image

圖9 - IValueConverter值轉換器的兩個方法執行時機

實作Convert方法必須將接收到的value參數轉換成要改變的值,例如,資料來源的值是300經過Convert處理後就變成$300.00。ConvertBack則是反向處理將接收到的,接收到的value參數$300.00轉換成300。

接下來我們要修改前一個範例的專案,在專案中加入一個類別檔,取名為PathConvertToImageSource,實作IValueConverter,接著選取IValueConverter,在I字下方會出現一個按鈕,按下按鈕選取「實作介面IValueConverter」,如圖:

image

圖10 - 實作介面IValueConverter

類別中會自動出現Convert及ConvertBack方法:

public object Convert(object value, Type targetType, object parameter, string language)

{

    throw new NotImplementedException();

}

public object ConvertBack(object value, Type targetType, object parameter, string language)

{

    throw new NotImplementedException();

}

這兩個方法都有value、targetType、parameter、language四個參數,其中Convert方法的value是來源的原始值,targetType是目標物件的型別,以此範例為例,資料來源要與Image的Source繫結,targetType就是Image.Source的型別。

在這個類別之內撰寫以下程式碼,讀取指定的檔案並處理為BitmapImage物件(Image.Source的型別可接收的型別):

private static async Task<BitmapImage> GetImageSource(string path)

{

    var imgFile = await StorageFile.GetFileFromPathAsync(path);

    var imgStrm = await imgFile.OpenReadAsync();

    imgStrm.Seek(0);

    BitmapImage img = new BitmapImage();

    img.SetSource(imgStrm);

    return img;

}

  • StorageFile.GetFileFromPathAsync方法是非同步方法,傳入字串型的路徑參數,它會取得指定位置的檔案,傳回StorageFile型別。
  • imgFile.OpenReadAsync方法亦是非同步方法,會傳回IRandomAccessStreamWithContentType型別,是一個隨機存取的輸入、輸出資料流。
  • Seek(0),會將資料流移到位置零。
  • BitmapImage(),影像載入程式\
  • SetSource(),指定影像的資料流以載入影像

修改Convert方法,從value值取得影像來源的位置。

public object Convert(object value, Type targetType, object parameter, string language)

{

    string path = value.ToString();

    if (path.StartsWith("http://") || path.StartsWith("ftp://") || path.StartsWith("ms-appx://"))

        return path;

    else

        return GetImageSource(path).Result;

    }

  • string path,從value取得資料來源的影像位置
  • if,判斷是否為Uri資源,如果是直接回傳影像位置
  • else,呼叫GetImageSource方法取得影像檔案的內容,Convert方法是介面實作無法使用async因此也不能用await關鍵字,這就會使GetImageSource非同步方法的回傳型別變成Task<BitmapImage>而不是BitmapImage,Result屬性可以讓我們取得非同步的執行結果(BitmapImage型別的物件,也就是Binding目標可以接收的型別)

此範例是OneWay模式,所以就不修改ConverBack的程式碼。

套用自訂轉換器

完成自訂轉換器的編寫後,接著要將它套用到Binding的物件上。開啟MainPage.xaml,找到先前Image的物件,然後在Source屬性開啟資料繫結的畫面,然後在下方的Converter下拉選取「加入值轉換器」,在新增值轉換器的清單中選取PathConverToImageSource,按「確定」。

image

圖11 - 加入值轉換器

這個動作會讓你的XAML變成下圖,它會在這份文件中加入你指定型別的邏輯資源,並設定Binding的Converter為這個邏輯資源。這會讓資料在呈現之前先執行前面所寫的Converter方法。

image

圖12 - XAML,Binding的Converter屬性

接著修改MainPage.xaml.cs中的OnNavigateTo,將PhotoPath指到本地端圖片庫的位置(記得設定

protected override void OnNavigatedTo(NavigationEventArgs e)

{

    var emp = new Employee {ID = 101,Name = "Anita",HireDate = new DateTime(2001, 1, 1), PhotoPath = @"C:\Users\Anita_Lo\Pictures\me.jpg"};

指定可使用的系統功能「圖片庫」

在前面曾談到使用本地資料夾時必須經過設定,這個範例現在要用到「圖片庫」,因此必須要指定可使用「圖片庫」的功能。

每一個Windows市集的專案都會有一個Package.appxmanifest檔,開啟這個檔案然後在它的「功能」頁勾選允許程式存取「圖片庫」的功能即可。設定如下圖。

image

圖 13 - 宣告應用程式會用到的功能

執行結果如圖8。

與集合物件繫結

XAML可以與集合物件繫結,要進行繫結的首要條件便是建立一個集合類別。這個集合類別必須符合以下幾個條件:

  • 實作IEnumerable及IList介面,或繼承任何實作這些介面的類別,像是:List(Of T)、Collection(Of T)、ObservableColletion(of T) …。
  • 必須實作INotifyCollectionChanged介面,以通知Binding物件集合內容的更新。

XAML比較建議使用ObservableColletion<T>做為繼承之類別,內部已實作INotifyCollectionChangeda等介面。

語法如下:

public class Employees : ObservableCollection<Employee>

{

}

延續前一節範例,在專案中加入一個類別取名為Employees,並繼承ObservableColletion<T>。如上面的語法。

接著在MainPage.xaml.cs的OnNavigatedTo方法建立Employees的物件,並且與指向layoutPage(最外層的Grid物件)的DataContext屬性。程式碼如下:

var empList = new Employees(){ new Employee { ID = 101, Name = "Anita", HireDate = new DateTime(2001, 1, 1),

PhotoPath = @"http://sphotos-b.ak.fbcdn.net/hphotos-ak-ash3/546598_10151291430628608_509530616_n.jpg" },

new Employee { ID=102, Name="Frog", HireDate = new DateTime (2012,12,20),

PhotoPath = "http://sphotos-h.ak.fbcdn.net/hphotos-ak-snc6/220101_10151296834563608_36631110_o.jpg"

},

new Employee { ID=103,  Name="Tina", HireDate = new DateTime (2011, 8,8),

PhotoPath = "http://sphotos-d.ak.fbcdn.net/hphotos-ak-prn1/14605_10151292952078608_847611490_n.jpg"

} };

layoutRoot.DataContext = empList;

開啟MainPage.xaml設計介面,從「工具箱」放入ListView到畫面上。指定ItemsSource屬性為Binding。XML如下:

<ListView x:Name="listView" …部份省略… ItemsSource="{Binding}"/>

清單控制項與資料範本

清單控制項可以顯示集合多個項目的內容,個別項目的呈現方式有兩種:

l DisplayMemberPath屬性,指定要顯示的物件屬性名稱,型別String,只能顯示一個屬性。

l ItemTemplate屬性,可以顯示多欄的內容,必須設定XAML範本,安排要顯示內容項目的版面,這個版面稱為資料範本,型別DataTemplate。

使用DisplayMemberPath屬性

指定DispalyMemberPath屬性為目標屬性,此範例可以指定為Employee的Name屬性,XAML如下:

<ListView x:Name="listView" …部份省略… ItemsSource="{Binding}" DisplayMemberPath="Name"/>

執行結果如下:

image

圖14 - 使用DisplayMemberPath屬性的結果

使用ItemTemplate屬性

在ListView按滑鼠右鍵選取「編輯其他範本」-「編輯產生的項目」-「建立空白」。

image

圖15 - 建立空白範本

接下來會出現建立資源的視窗,指定一個適當的名稱,並定義於此文件中,按「確定」。

image

圖16 - 定義DataTemplate的名稱及存放位置

安排畫面如下,Grid切割三欄,分別是1*、10、1*。第一欄放Image物件,第二欄留白、第三欄放StackPanel,並在StackPanel放三個TextBlock。

image

圖17 - DataTemplate內容安排

接著分別建立資料繫結的屬性,它們的XAML如下:

<DataTemplate x:Key="DataTemplate1">

    <Grid d:DesignWidth="233" d:DesignHeight="98">

        <Grid.ColumnDefinitions>

            <ColumnDefinition Width="1*"/>

            <ColumnDefinition Width="10"/>

            <ColumnDefinition Width="1*"/>

        </Grid.ColumnDefinitions>

        <Image Source="{Binding PhotoPath, Converter={StaticResource PathConvertToImageSource}}"/>

        <StackPanel Grid.Column="2">

            <TextBlock Text="{Binding ID}"/>

            <TextBlock Text="{Binding Name}"/>

           <TextBlock Text="{Binding HireDate}"/>

        </StackPanel>

    </Grid>

</DataTemplate>

執行結果如下:

image

圖18 - ListView & DataTemplate

集合物件的繫結方向

若要顯示集合物件中所有項目,可以選用GridView、ListBox、ListView、ComboBox…等清單控制項。這些控制項適合用來顯示多筆記錄的集合物件。

它們都有一個ItemsSource屬性及Items屬性,前面曾提供這兩個屬性只能選擇其一使用。資料繫結使用ItemsSource屬性,此時若要新增或是移除清單項目就再也不能使用Items的Add、Insert及Remove方法操作清單項目,而是直接對集合物件進行新增或是移除的操作。Binding物件會在集合物件發生變更時主動更新清單控制項(必須是有實作INotifyCollectionChanged介面的集合)。因此,當清單控制項與集合物件繫結時,它們的繫結方向是單向的﹙OneWay﹚。

image

圖19 - 集合與清單控制項繫結是單項關係

延續前面的集合範例,在畫面上放一個Button,並定義它的Content為「新增項目」,在Button的Click事件處理新增項目的程序。先前已將集合物件與layoutRoot(最外層的Grid)的DataContext進行繫結,所以現在可以從DataContext取回集合物件,然後呼叫Add方法將新項目加入到集合中,注意這裡是將項目加入集合物件而不是加到ListView物件的Items集合屬性哦!程式碼如下:

private void Button_Click_2(object sender, RoutedEventArgs e)

{

    var empList = layoutRoot.DataContext as Employees;

    empList.Add(new Employee {ID = 104, Name = "多啦A夢", HireDate = new DateTime(2112, 9, 3),

PhotoPath = http://1.bp.blogspot.com/-anAMtK5G69c/TzTwdIzlzrI/AAAAAAAAAAY/ufFr8NduoeQ/s1600/0.jpg});

}

執行結果如:

image

圖20 - 加入新項目

與單筆資料關聯

自清單控制項選取一個項目,便會顯示該項目的詳細資訊,這是常見的操作模式。

這很簡單,只需要將清單控制項與詳細資訊的控制項繫結。這裡將前面單一物件資料繫結的控制項先置入群組之中,然後指定DataContext的來源是清單項目即可。

下圖是在「文件大綱」中將先選取控制項,按滑鼠右鍵選取「群組置入」-「Grid」,這樣控制項就會被包在Grid中。

image

圖21 - 群組置入

設定這個Grid的DataContext與ListView的SelectedItem繫結,選取Grid物件然後在屬性視窗找到DataContext屬性,按下最右方的小按鈕選取「建立資料繫結」。接著在建立資料繫結的視窗選取繫結型別為ElementName,及元素名稱為listView,路徑為SelectedItem。

image

圖22 - Grid與清單控制項的繫結

它的XAML如下:

<Grid Margin="864,144,118,188" DataContext="{Binding SelectedItem, ElementName=listView}">

<TextBox HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding ID, Mode=TwoWay}" VerticalAlignment="Top" Width="183"/>

…其他省略…

總結

XAML資料繫結的來源可以是:

  • 另一個控制項的某個屬性
  • 物件
  • 集合物件
  • Web 服務…

要讓清單項目顯示更多詳細資訊而非單一欄位,可以為清單控制項設計DataTemplate,會有很好的效果。

Tags:

Windows Store App | XAML | 羅慧真Anita Lo

新增評論




  Country flag
biuquote
  • 評論
  • 線上預覽
Loading






NET Magazine國際中文電子雜誌

NET Magazine國際中文電子版雜誌,由恆逸資訊創立於2000,自發刊日起迄今已發行超過500篇.NET相關技術文章,擁有超過40000名註冊讀者群。NET Magazine國際中文電子版雜誌希望藉於電子雜誌與NET Developer達到共同學習與技術新知分享,歡迎每一位對.NET 技術有興趣的朋友們多多支持本雜誌,讓作者群們可以有持續性的動力繼續爬文。<請加入免費訂閱>

月分類Month List