使用資料樣版

by Vivid 26. 七月 2011 10:00

作者:許薰尹 精誠資訊恆逸教育訓練中心 資深講師

WPF提供許多內建的功能篩選資料,或進行排序、分組,若這些都還不能夠滿足您的需求,您可以利用資料樣版(Data Template)來客製化資料的顯示方式,自行定義資料展現時視覺化的外觀,也可以搭配觸發器來控管更細部的行為。

資料樣版(Data Template)

WPF中的資料樣版(Data Template)是由XAML標籤組成,用來定義資料展現的方式,例如資料顯示時的畫面配置(Layout)、前景顏色、背景顏色、框線樣式等等外觀的展現 。

Microsoft Expression Blend 4也提供視覺化的介面來輔助資料樣版的的定義。在這篇文章中將介紹如何利用Microsoft Expression Blend 4來定義資料樣版,並且如何設計資料排序、分組以及進行資料篩選動作,讓程式設計師能更精確地掌控應用程式所需的資料與展示的外觀。

為了方便說明,定義一資料類別如下,包含兩個字串型別的屬性:

public class MyDataClass {
        public string MyProperty { get; set; }   
        public string MyProperty2 { get; set; }
    }

我們希望在WPF 視窗中使用Label控制項時能夠有多一點變化,在顯示資料時能夠前置一張固定的圖片,為達到此目地,可以利用Microsoft Expression Blend 4為Label控制項定義一個資料樣版。參考圖1所示,在設計畫面中,點選Label控制項,按滑鼠右鍵->選取「Edit Additional Templates」->「Edit Generated content (ContentTemplate)」->「Create Empty」,這樣就可以建立一個空白資料樣版,並進入到建立樣版畫面。

clip_image002

圖 1:建立一個空白資料樣版。

參考圖2所示,在「Create DataTemplate Resource」對話方塊中,您可以為樣版設定一個key,後續可以利用key來套用樣版。資料樣版可以定義在 應用程式(Application)、目前文件(This document)或資源字典中,你可以根據需求來決定定義的位置。

clip_image004

圖 2:「Create DataTemplate Resource」對話方塊。

當你按下「OK」按鈕後,Microsoft Expression Blend 4會自動切換到樣版編輯畫面。您可以參考「Objects and Timeline」視窗,目前資料樣版中有一個ContentTemplate項目,其下包含一個Grid控制項,這是樣版的根項目 (Root Element)。

Microsoft Expression Blend 4有一個好用的選項,可以直接將容器類型的控制項直接置換,從「Objects and Timeline」視窗->點選Grid->按滑鼠右鍵,選取「Change Layout Type」->選取想用的控制項,本例為「StackPanel」控制項,參考圖3所示。

clip_image006

圖 3:置換Grid控制項成StackPanel。

接下來從工具箱加入一個Image與Label控制項。您只要將滑鼠停留在「Objects and Timeline」視窗中StackPanel項目,然後雙擊工具箱上的Image與Label控制項即可,參考圖4所示。

clip_image008

圖 4:加入控制項到資料樣版。

接著您就可以調整一下這些控制項的設定,本範例利用屬性視窗設定StackPanel控制項的Orientation屬性值為「Horizontal」讓Image與Label控制項能以水平方式展現,參考圖5所示。

clip_image010

圖 5:設定控制項。

而Image控制項要展示的圖片便可以利用屬性視窗的Source屬性來做定義,當然,您需要先將圖片加入到專案之中,才能夠利用下拉式清單選取,參考圖6所示。

clip_image012

圖 6:設定顯示的圖片。

同樣地在資料樣版之中也可以設定資料繫結,選取樣版中的Label控制項,按滑鼠右鍵,選取「Data bind Content to Data…」項目,參考圖7所示。

clip_image014

圖 7:設定資料繫結。

在「Create Data Binding」視窗之中,點選「Data Field」頁下方的「+CLR Object」,以加入資料來源,參考圖8所示。

clip_image016

圖 8:加入資料來源。

在「Create Object Data Source」視窗中設定資料來源為MyDataClass類別,參考圖9所示。

clip_image018

圖 9:選取資料來源。

在「Create Data Binding」視窗之中,點選「Data Field」頁下方的「MyDataClassDataSource」項目,並設定Properties為「MyProperty」,參考圖10所示。

clip_image020

圖 10:設定繫結的屬性。

按下「OK」按鈕離開「Create Data Binding」視窗後,工具會自動產生DataTemplate樣版的標籤:

<Window.Resources>
        <local:MyDataClass x:Key = "MyDataClassDataSource" MyProperty = "Hello"  d:IsDataSource = "True" />
        <DataTemplate x:Key = "DataTemplate1" >
            <StackPanel Orientation = "Horizontal" DataContext = "{Binding Source={StaticResource MyDataClassDataSource}}" >
                <Image Height = "16" Width = "16" Source = "GreenBullet.png" />
                <Label Content = "{Binding MyProperty}" />
            </StackPanel>
        </DataTemplate>
</Window.Resources>

<Grid x:Name = "LayoutRoot" >
        <Label Content = "Label" HorizontalAlignment = "Left" Height = "32" Margin = "8,24,0,0" VerticalAlignment = "Top" Width = "136" ContentTemplate = "{DynamicResource DataTemplate1}" />
</Grid>

在資料樣版中的Label控制項繫結到MyDataClass的MyProperty,筆者手動輸入XAML,加上「MyProperty="Hello"」字串為MyDataClass的MyProperty設定初始值。而Window中的Label控制項則透過ContentTemplate來套用資料樣版。若是Items Control,例如ListBox控制項,則是利用ItemsTemplate屬性來套用。現在大功告成了,未來在顯示Label控制項時,前方都會有一個小圖示。

值得一提,使用資料樣版的好處是您可以在資料樣版中繫結到多個資料來源的屬性,這樣就可以一次顯示多種資料,例如以下XAML範例,在資料樣版中有兩個Label控制項分別繫結到MyDataClass的MyProperty與MyProperty2兩個屬性:

<Window
    xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local = "clr-namespace:UseTemplate" xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" xmlns:mc  ="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable = "d"
    x:Class = "UseTemplate.MainWindow" x:Name = "Window"     Title = "MainWindow" Width = "640" Height = "480">
    <Window.Resources>
        <local:MyDataClass x:Key = "MyDataClassDataSource" MyProperty = "Hello" MyProperty2 = "World" d:IsDataSource = "True" />
        <DataTemplate x:Key="DataTemplate1">
            <StackPanel Orientation = "Horizontal" DataContext = "{Binding Source={StaticResource MyDataClassDataSource}}" >
                <Image Height = "16" Width = "16" Source = "GreenBullet.png" />
                <Label Content = "{Binding MyProperty}" />
                <Label Content = "{Binding MyProperty2}" />
            </StackPanel>
        </DataTemplate>
    </Window.Resources>

    <Grid x:Name="LayoutRoot">
        <Label Content="Label" HorizontalAlignment="Left" Height="32" Margin="8,24,0,0" VerticalAlignment="Top" Width="136" ContentTemplate="{DynamicResource DataTemplate1}"/>
    </Grid>
</Window>

資料觸發器(Data Trigger)

在WPF應用程式資料繫結過程中,運用資料觸發器可以更精確地控制繫結的過程,例如我們可以為上例資料樣版加入資料觸發器,若繫結的MyProperty屬性值為「Hello」時,便利用Setter物件將label1的背景顏色設定為紅色。

<DataTemplate x:Key = "DataTemplate1">
            <StackPanel Orientation = "Horizontal" DataContext = "{Binding Source={StaticResource MyDataClassDataSource}}" >
                <Image Height = "16" Width = "16" Source = "GreenBullet.png" />
                <Label  Background = "AliceBlue" Name = "label1" Content = "{Binding MyProperty}" />
                <Label Content = "{Binding MyProperty2}" />
            </StackPanel>
            <DataTemplate.Triggers>
                <DataTrigger Binding = "{Binding MyProperty, Source={StaticResource MyDataClassDataSource}}" Value="Hello">
                    <Setter TargetName = "label1" Property = "Background" Value = "Red"> </Setter>
                </DataTrigger>
            </DataTemplate.Triggers>
        </DataTemplate>

資料排序

針對集合的資料,WPF提供一個Collection View 模型,包含一個記錄指標,指向目前存取的項目,因此你能夠透過它來一筆、一筆瀏覽資料來源集合中的資料。除此之外,Collection View 模型也支援資料排序、分組以及篩選的功能。WPF的資料繫結引擎都是透過預設的View來存取集合的。

你可以直接在XAML之中定義CollectionViewSource,它代表一個CollectionView物件,或者可以利用程式碼透過CollectionViewSource的GetDefaultView方法取得預設的檢視。資料排序可以使用預設的ICollectionView達成,舉例來說若在WPF應用程式中有Employee、Employees類別程式定義如下:

public class Employee {
        public int EmpID { get; set; }
        public string EmpName { get; set; }
        public int Age { get; set; }
        public string Gender { get; set; }
    }

public class Employees {
        public Employees ( ) {
         data.Add ( new Employee() { EmpID=1,EmpName="Mary",Age=20,Gender="F"} );
         data.Add ( new Employee ( ) { EmpID = 3 , EmpName = "Candy" , Age = 30 , Gender = "F" } );
         data.Add ( new Employee ( ) { EmpID = 2 , EmpName = "Jane" , Age = 30 , Gender = "M" } );
         }
        List<Employee> data = new  List<Employee> ( );
        public List<Employee> Items { get { return data; } set { data = value; } }
    }

 

目前WPF Window中包含一個ListBox繫結到Employees類別Items屬性:

<Window
    xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local = "clr-namespace:UseTemplate" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable = "d"
    x:Class = "UseTemplate.__SortData"
    x:Name = "Window"
    Title = "__SortData"
    Width = "640" Height = "480">
    <Window.Resources>
        <local:Employees x:Key = "EmployeesDataSource" d:IsDataSource = "True"/>
        <DataTemplate x:Key  ="DataTemplate1">
            <StackPanel Orientation = "Horizontal" >
                <Label Content = "{Binding EmpID}" />
                <Label Content = "{Binding EmpName}" />
                <Label Content = "{Binding Age}" />
            </StackPanel>
        </DataTemplate>
    </Window.Resources>
<Grid DataContext = "{Binding Source={StaticResource EmployeesDataSource}}">
    <ListBox HorizontalAlignment = "Left" Height = "144" Margin = "96,64,0,0" VerticalAlignment = "Top" Width = "128" ItemTemplate = "{DynamicResource DataTemplate1}" ItemsSource = "{Binding Items}" />
</Grid>
</Window>

在Window建構函式中,利用CollectionViewSource.GetDefaultView取得預設的ICollectionView,並設定排序的欄位為Age,以降冪方式進行排序:

System.ComponentModel.ICollectionView myView =
CollectionViewSource.GetDefaultView ( ( ( Employees ) this.FindResource ( "EmployeesDataSource" ) ).Items );
myView.SortDescriptions.Add ( new System.ComponentModel.SortDescription ( "Age" , System.ComponentModel.ListSortDirection.Descending ) );

ICollectionView的SortDescriptions屬性型別為SortDescriptionCollection,可包含多個SortDescription物件,每一個物件代表要排序的欄位名稱。SortDescription物件套用的順序就和他們被加到SortDescriptionCollection集合中的順序一致。因此,若想先依照Age降冪排序,再按照EmpName升冪排序可以將程式碼改為:

System.ComponentModel.ICollectionView myView =
CollectionViewSource.GetDefaultView ( ( ( Employees ) this.FindResource ( "EmployeesDataSource" ) ).Items );
myView.SortDescriptions.Add ( new System.ComponentModel.SortDescription ( "Age" , System.ComponentModel.ListSortDirection.Descending ) );
myView.SortDescriptions.Add ( new System.ComponentModel.SortDescription ( "EmpName" , System.ComponentModel.ListSortDirection.Ascending ) );

資料分組

ICollectionView介面也提供資料分組的能力,您可以建立PropertyGroupDescription物件來設定分組條件,然後將物件加入ICollectionView的GroupDescriptions集合中。以下範例程式利用Employee的Gender屬性來進行資料分組,我們利用GroupStyle來設定分組部分的外觀,其中<GroupStyle.HeaderTemplate>用來定義分組區段的標頭部分,範例中利用一個Label控制項來顯示性別。

<Window x:Class = "UseTemplate._3_Grouping"
        xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
        Title = "_3_Grouping" Height = "300" Width = "300"
        xmlns:local = "clr-namespace:UseTemplate" xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable = "d" >
    <Window.Resources>
        <local:Employees x:Key = "EmployeesDataSource" d:IsDataSource = "True"/>
        <DataTemplate x:Key = "DataTemplate1" >
            <StackPanel Orientation = "Horizontal">
                <Label Content = "{Binding EmpID}" />
                <Label Content = "{Binding EmpName}" />
                <Label Content = "{Binding Age}" />
            </StackPanel>
        </DataTemplate>
      </Window.Resources>
    <Grid DataContext = "{Binding Source={StaticResource EmployeesDataSource}}">
        <ListBox HorizontalAlignment = "Left" Height = "144" Margin = "32,24,0,0" VerticalAlignment = "Top" Width = "128" ItemTemplate = "{DynamicResource DataTemplate1}" ItemsSource = "{Binding Items}" >
            <ListBox.GroupStyle>
                <GroupStyle>
                <GroupStyle.HeaderTemplate>
                    <DataTemplate>
                            <Label Content = "{Binding}" Background ="Blue" Foreground="Red" />
                      </DataTemplate>
                    </GroupStyle.HeaderTemplate>
                    </GroupStyle>
            </ListBox.GroupStyle>
        </ListBox>
    </Grid>
</Window>

接著利用程式碼在建構函式中建立PropertyGroupDescription物件,設定依據Gender進行排序,然後將PropertyGroupDescription物件加入ICollectionView的GroupDescriptions之中:

namespace UseTemplate {
    public partial class _3_Grouping : Window {
        public _3_Grouping ( ) {
            InitializeComponent ( );
            System.ComponentModel.ICollectionView myView = CollectionViewSource.GetDefaultView ( ( ( Employees ) this.FindResource ( "EmployeesDataSource" ) ).Items );
            myView.GroupDescriptions.Add ( new PropertyGroupDescription ( "Gender" ) );
         
        }
    }
}

這個範例的執行結果如圖11所示:

clip_image022

圖 11:資料分組。

資料篩選

ICollectionView介面利用一個Predicate委派來提供資料篩選的功能,Predicate委派會將集合中的物件取出,根據指定的條件進行篩選,若篩選條件運算的結果為false,則將此物件排除在View之外,若運算的結果為True則將物件涵蓋在View中。底下的範例延續上節分組一例,利用Lambda 運算式」(Lambda Expression)在匿名方法中設定要篩選條件是Age大於25的Employee物件,由於Filter屬性只接受Predicate<object>,因此在匿名方法中,您需要適當地進行轉型:

System.ComponentModel.ICollectionView myView = CollectionViewSource.GetDefaultView ( ( ( Employees ) this.FindResource ( "EmployeesDataSource" ) ).Items ); myView.GroupDescriptions.Add ( new PropertyGroupDescription ( "Gender" ) ); myView.Filter = new Predicate

資料篩選-ADO.NET

若WPF控制項繫結的資料來源是ADO. NET的類別,您就不能夠使用上一節ICollectionView的Filter屬性來設定篩選條件。你可以改用BindingListCollectionView物件 的CustomFilter屬性,它會透過ADO.NET的DataView物件進行篩選,因此CustomFilter屬性只要設定成DataView物件能接受的字串運算式即可。例如底下WPF視窗中包含一個ListBox控制項:

<Window
    xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class = "UseTemplate.__FilterADONET"
    x:Name = "Window"
    Title = "__FilterADONET"
    Width = "640" Height = "480">
    <Grid x:Name = "LayoutRoot">
        <ListBox ItemsSource = "{Binding}"
                 DisplayMemberPath = "FirstName"
                 HorizontalAlignment = "Left" Height = "152" Margin = "144,72,0,0" VerticalAlignment = "Top"
                 Width = "144" Name = "listBox1" />
    </Grid>
</Window>

我們利用程式建立ADO.NET的SqlDataAdapter類別讀取AdventureWorks資料庫Contact資料表的ContactID、FirstName與LastName欄位資料後放到DataTable,並ListBox控制項繫結到DataTable。接著利用BindingListCollectionView物件 的CustomFilter屬性設定篩選所有FirstName欄位值是從S開始的資料:

public partial class @__FilterADONET : Window {
        public @__FilterADONET ( ) {
            DataTable contacts = new DataTable ( );
            this.InitializeComponent ( );
            // Insert code required on object creation below this point.
            using ( SqlConnection connection = new SqlConnection
    ( @"Server=.\SQLExpress;Database=AdventureWorks; Integrated Security=SSPI" ) ) {
                SqlDataAdapter da = new SqlDataAdapter (
                     "SELECT ContactID, FirstName, LastName FROM Person.Contact" ,
                    connection );
                da.Fill ( contacts );
                listBox1.DataContext = contacts;
                BindingListCollectionView myView =
                  ( BindingListCollectionView ) CollectionViewSource.GetDefaultView ( listBox1.DataContext );
                myView.CustomFilter = "FirstName like 'S%'";
            }
        }
    }

總結

在WPF應用程式中,所有集合都有一個關聯的預設的Collection View,它是位於Binding Source集合之上的一層介面,能夠讓你瀏覽與顯示繫結來源的集合,以進行排序、篩選與分組查詢功能。若來源集合實作了INotifyCollectionChanged介面,當集合觸發CollectionChanged 事件時,WPF資料繫結引擎會將異動通知Collection View。

Tags:

Expression Blend | WPF | 許薰尹Vivid Hsu | .NET Magazine國際中文電子雜誌

新增評論




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






NET Magazine國際中文電子雜誌

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

月分類Month List