如何:在 DataGrid 控制項中分組、排序和篩選資料

透過分組、排序和篩選資料,以不同方式檢視資料 DataGrid 通常很有用。 若要將 中的資料 DataGrid 分組、排序和篩選,您可以將它系結至 CollectionView 支援這些函式的 。 然後,您可以使用 中的資料 CollectionView ,而不會影響基礎來源資料。 集合檢視中的變更會反映在使用者介面 (UI) 中 DataGrid

類別 CollectionView 會為實 IEnumerable 作 介面的資料來源提供分組和排序功能。 類別 CollectionViewSource 可讓您從 XAML 設定 的屬性 CollectionView

在此範例中 Task ,物件的集合會系結至 CollectionViewSourceCollectionViewSource做為 ItemsSourceDataGrid 。 群組、排序和篩選會在 上 CollectionViewSource 執行,並顯示在 UI 中 DataGrid

Grouped data in a DataGrid DataGrid 中的分組資料

使用 CollectionViewSource 做為 ItemsSource

若要將 控制項中的資料 DataGrid 分組、排序和篩選,您可以將 系結 DataGridCollectionView 支援這些函式的 。 在此範例中, DataGrid 會系結至 CollectionViewSource ,為 List<T>Task 物件的 提供這些函式。

將 DataGrid 系結至 CollectionViewSource

  1. 建立實作 介面的 IEnumerable 資料收集。

    如果您使用 List<T> 來建立集合,您應該建立繼承自 List<T> 的新類別,而不是具現化 實例 List<T> 。 這可讓您將資料系結至 XAML 中的集合。

    注意

    集合中的 物件必須實 INotifyPropertyChanged 作已變更的介面和 IEditableObject 介面,才能 DataGrid 正確回應屬性變更和編輯。 如需詳細資訊,請參閱實作屬性變更通知

    // Requires using System.Collections.ObjectModel;
    public class Tasks : ObservableCollection<Task>
    {
        // Creating the Tasks collection in this way enables data binding from XAML.
    }
    
    ' Requires Imports System.Collections.ObjectModel
    Public Class Tasks
        Inherits ObservableCollection(Of Task)
        ' Creating the Tasks collection in this way enables data binding from XAML.
    End Class
    
  2. 在 XAML 中,建立集合類別的實例,並設定 x:Key 指示詞

  3. 在 XAML 中,建立 類別的 CollectionViewSource 實例、設定 x:Key 指示詞 ,並將集合類別的實例設定為 Source

    <Window.Resources>
        <local:Tasks x:Key="tasks" />
        <CollectionViewSource x:Key="cvsTasks" Source="{StaticResource tasks}" 
                              Filter="CollectionViewSource_Filter">
        </CollectionViewSource>    
    </Window.Resources>
    
  4. 建立 類別的 DataGrid 實例,並將 屬性設定 ItemsSourceCollectionViewSource

    <DataGrid x:Name="dataGrid1" 
              ItemsSource="{Binding Source={StaticResource cvsTasks}}"
              CanUserAddRows="False">
    
  5. 若要從程式碼存取 CollectionViewSource ,請使用 GetDefaultView 方法來取得 的 CollectionViewSource 參考。

    ICollectionView cvTasks = CollectionViewSource.GetDefaultView(dataGrid1.ItemsSource);
    
    Dim cvTasks As ICollectionView = CollectionViewSource.GetDefaultView(dataGrid1.ItemsSource)
    

將 DataGrid 中的專案分組

若要指定專案在 中 DataGrid 分組的方式,您可以使用 PropertyGroupDescription 型別將來源檢視中的專案分組。

使用 XAML 將 DataGrid 中的專案分組

  1. PropertyGroupDescription建立 ,指定要分組依據的屬性。 您可以在 XAML 或程式碼中指定 屬性。

    1. 在 XAML 中,將 設定 PropertyName 為要分組的屬性名稱。

    2. 在程式碼中,將 屬性的名稱傳遞給 建構函式。

  2. PropertyGroupDescription將 加入至 CollectionViewSource.GroupDescriptions 集合。

  3. 將 的其他實例 PropertyGroupDescription 新增至 GroupDescriptions 集合,以新增更多群組層級。

    <CollectionViewSource.GroupDescriptions>
        <PropertyGroupDescription PropertyName="ProjectName"/>
        <PropertyGroupDescription PropertyName="Complete"/>
    </CollectionViewSource.GroupDescriptions>
    
    ICollectionView cvTasks = CollectionViewSource.GetDefaultView(dataGrid1.ItemsSource);
    if (cvTasks != null && cvTasks.CanGroup == true)
    {
        cvTasks.GroupDescriptions.Clear();
        cvTasks.GroupDescriptions.Add(new PropertyGroupDescription("ProjectName"));
        cvTasks.GroupDescriptions.Add(new PropertyGroupDescription("Complete"));
    }
    
    Dim cvTasks As ICollectionView = CollectionViewSource.GetDefaultView(dataGrid1.ItemsSource)
    If cvTasks IsNot Nothing And cvTasks.CanGroup = True Then
        cvTasks.GroupDescriptions.Clear()
        cvTasks.GroupDescriptions.Add(New PropertyGroupDescription("ProjectName"))
        cvTasks.GroupDescriptions.Add(New PropertyGroupDescription("Complete"))
    End If
    
  4. 若要移除群組,請從 GroupDescriptions 集合中移除 PropertyGroupDescription

  5. 若要移除所有群組,請呼叫 Clear 集合的 GroupDescriptions 方法。

    ICollectionView cvTasks = CollectionViewSource.GetDefaultView(dataGrid1.ItemsSource);
    if (cvTasks != null)
    {
        cvTasks.GroupDescriptions.Clear();
    }
    
    Dim cvTasks As ICollectionView = CollectionViewSource.GetDefaultView(dataGrid1.ItemsSource)
    If cvTasks IsNot Nothing Then
        cvTasks.GroupDescriptions.Clear()
    End If
    

當專案分組在 中 DataGrid 時,您可以定義 GroupStyle ,以指定每個群組的外觀。 您可以將它新增至 GroupStyle DataGrid 的集合,以套用 GroupStyle 。 如果您有多個群組層級,您可以將不同的樣式套用至每個群組層級。 樣式會依照定義樣式的順序套用。 例如,如果您定義兩個樣式,第一個樣式會套用至最上層資料列群組。 第二個樣式會套用至第二層和較低層級的所有資料列群組。 DataContextGroupStyleCollectionViewGroup 群組所代表的 。

變更資料列群組標頭的外觀

  1. GroupStyle建立 ,定義資料列群組的外觀。

  2. GroupStyle將 放在標籤內 <DataGrid.GroupStyle>

    <DataGrid.GroupStyle>
        <!-- Style for groups at top level. -->
        <GroupStyle>
            <GroupStyle.ContainerStyle>
                <Style TargetType="{x:Type GroupItem}">
                    <Setter Property="Margin" Value="0,0,0,5"/>
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="{x:Type GroupItem}">
                                <Expander IsExpanded="True" Background="#FF112255" BorderBrush="#FF002255" Foreground="#FFEEEEEE" BorderThickness="1,1,1,5">
                                    <Expander.Header>
                                        <DockPanel>
                                            <TextBlock FontWeight="Bold" Text="{Binding Path=Name}" Margin="5,0,0,0" Width="100"/>
                                            <TextBlock FontWeight="Bold" Text="{Binding Path=ItemCount}"/>
                                        </DockPanel>
                                    </Expander.Header>
                                    <Expander.Content>
                                        <ItemsPresenter />
                                    </Expander.Content>
                                </Expander>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </GroupStyle.ContainerStyle>
        </GroupStyle>
        <!-- Style for groups under the top level. -->
        <GroupStyle>
            <GroupStyle.HeaderTemplate>
                <DataTemplate>
                    <DockPanel Background="LightBlue">
                        <TextBlock Text="{Binding Path=Name, Converter={StaticResource completeConverter}}" Foreground="Blue" Margin="30,0,0,0" Width="100"/>
                        <TextBlock Text="{Binding Path=ItemCount}" Foreground="Blue"/>
                    </DockPanel>
                </DataTemplate>
            </GroupStyle.HeaderTemplate>
        </GroupStyle>
    </DataGrid.GroupStyle>
    

排序 DataGrid 中的專案

若要指定 在 中 DataGrid 排序專案的方式,您可以使用 SortDescription 型別來排序來源檢視中的專案。

排序 DataGrid 中的專案

  1. SortDescription建立 ,指定要排序的屬性。 您可以在 XAML 或程式碼中指定 屬性。

    1. 在 XAML 中,將 設定 PropertyName 為要排序的屬性名稱。

    2. 在程式碼中,將 屬性的名稱傳遞給 排序依據 ,並將 ListSortDirection 傳遞給建構函式。

  2. SortDescription將 加入至 CollectionViewSource.SortDescriptions 集合。

  3. 將 的其他實例 SortDescription 新增至集合, SortDescriptions 以依其他屬性排序。

    <CollectionViewSource.SortDescriptions>
        <!-- Requires 'xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"' declaration. -->
        <scm:SortDescription PropertyName="ProjectName"/>
        <scm:SortDescription PropertyName="Complete" />
        <scm:SortDescription PropertyName="DueDate" />
    </CollectionViewSource.SortDescriptions>
    
    // Requires using System.ComponentModel;
    ICollectionView cvTasks = CollectionViewSource.GetDefaultView(dataGrid1.ItemsSource);
    if (cvTasks != null && cvTasks.CanSort == true)
    {
        cvTasks.SortDescriptions.Clear();
        cvTasks.SortDescriptions.Add(new SortDescription("ProjectName", ListSortDirection.Ascending));
        cvTasks.SortDescriptions.Add(new SortDescription("Complete", ListSortDirection.Ascending));
        cvTasks.SortDescriptions.Add(new SortDescription("DueDate", ListSortDirection.Ascending));
    }
    
    Dim cvTasks As ICollectionView = CollectionViewSource.GetDefaultView(dataGrid1.ItemsSource)
    If cvTasks IsNot Nothing And cvTasks.CanSort = True Then
        cvTasks.SortDescriptions.Clear()
        cvTasks.SortDescriptions.Add(New SortDescription("ProjectName", ListSortDirection.Ascending))
        cvTasks.SortDescriptions.Add(New SortDescription("Complete", ListSortDirection.Ascending))
        cvTasks.SortDescriptions.Add(New SortDescription("DueDate", ListSortDirection.Ascending))
    End If
    

篩選 DataGrid 中的專案

若要使用 CollectionViewSource 篩選 中的 DataGrid 專案,您可以在 事件的處理常式 CollectionViewSource.Filter 中提供篩選邏輯。

篩選 DataGrid 中的專案

  1. 新增 事件的處理常式 CollectionViewSource.Filter

  2. 在事件處理常式中 Filter ,定義篩選邏輯。

    每次重新整理檢視時,都會套用篩選。

    <CollectionViewSource x:Key="cvsTasks" Source="{StaticResource tasks}" 
                          Filter="CollectionViewSource_Filter">
    
    private void CollectionViewSource_Filter(object sender, FilterEventArgs e)
    {
        Task t = e.Item as Task;
        if (t != null)
        // If filter is turned on, filter completed items.
        {
            if (this.cbCompleteFilter.IsChecked == true && t.Complete == true)
                e.Accepted = false;
            else
                e.Accepted = true;
        }
    }
    
    Private Sub CollectionViewSource_Filter(ByVal sender As System.Object, ByVal e As System.Windows.Data.FilterEventArgs)
        Dim t As Task = e.Item
        If t IsNot Nothing Then
            ' If filter is turned on, filter completed items.
            If Me.cbCompleteFilter.IsChecked = True And t.Complete = True Then
                e.Accepted = False
            Else
                e.Accepted = True
            End If
        End If
    End Sub
    

或者,您可以藉由建立方法來篩選 中的 DataGrid 專案,以提供篩選邏輯,並設定 CollectionView.Filter 屬性以套用篩選。 若要查看這個方法的範例,請參閱 在檢視 中篩選資料。

範例

下列範例示範 在 中分組、排序和篩選 Task 資料,以及在 中 CollectionViewSourceDataGrid 顯示分組、排序和篩選 Task 的資料。 CollectionViewSource做為 ItemsSourceDataGrid 。 群組、排序和篩選會在 上 CollectionViewSource 執行,並顯示在 UI 中 DataGrid

若要測試此範例,您必須調整 DGGroupSortFilterExample 名稱,以符合您的專案名稱。 如果您使用 Visual Basic,則必須將 的 Window 類別名稱變更為下列專案。

<Window x:Class="MainWindow"

<Window x:Class="DGGroupSortFilterExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:DGGroupSortFilterExample"
        xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
        Title="Group, Sort, and Filter Example" Height="575" Width="525">
    <Window.Resources>
        <local:CompleteConverter x:Key="completeConverter" />
        <local:Tasks x:Key="tasks" />
        <CollectionViewSource x:Key="cvsTasks" Source="{StaticResource tasks}" 
                              Filter="CollectionViewSource_Filter">
            <CollectionViewSource.SortDescriptions>
                <!-- Requires 'xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"' declaration. -->
                <scm:SortDescription PropertyName="ProjectName"/>
                <scm:SortDescription PropertyName="Complete" />
                <scm:SortDescription PropertyName="DueDate" />
            </CollectionViewSource.SortDescriptions>
            <CollectionViewSource.GroupDescriptions>
                <PropertyGroupDescription PropertyName="ProjectName"/>
                <PropertyGroupDescription PropertyName="Complete"/>
            </CollectionViewSource.GroupDescriptions>
        </CollectionViewSource>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="30" />
        </Grid.RowDefinitions>
        <DataGrid x:Name="dataGrid1" 
                  ItemsSource="{Binding Source={StaticResource cvsTasks}}"
                  CanUserAddRows="False">
            <DataGrid.GroupStyle>
                <!-- Style for groups at top level. -->
                <GroupStyle>
                    <GroupStyle.ContainerStyle>
                        <Style TargetType="{x:Type GroupItem}">
                            <Setter Property="Margin" Value="0,0,0,5"/>
                            <Setter Property="Template">
                                <Setter.Value>
                                    <ControlTemplate TargetType="{x:Type GroupItem}">
                                        <Expander IsExpanded="True" Background="#FF112255" BorderBrush="#FF002255" Foreground="#FFEEEEEE" BorderThickness="1,1,1,5">
                                            <Expander.Header>
                                                <DockPanel>
                                                    <TextBlock FontWeight="Bold" Text="{Binding Path=Name}" Margin="5,0,0,0" Width="100"/>
                                                    <TextBlock FontWeight="Bold" Text="{Binding Path=ItemCount}"/>
                                                </DockPanel>
                                            </Expander.Header>
                                            <Expander.Content>
                                                <ItemsPresenter />
                                            </Expander.Content>
                                        </Expander>
                                    </ControlTemplate>
                                </Setter.Value>
                            </Setter>
                        </Style>
                    </GroupStyle.ContainerStyle>
                </GroupStyle>
                <!-- Style for groups under the top level. -->
                <GroupStyle>
                    <GroupStyle.HeaderTemplate>
                        <DataTemplate>
                            <DockPanel Background="LightBlue">
                                <TextBlock Text="{Binding Path=Name, Converter={StaticResource completeConverter}}" Foreground="Blue" Margin="30,0,0,0" Width="100"/>
                                <TextBlock Text="{Binding Path=ItemCount}" Foreground="Blue"/>
                            </DockPanel>
                        </DataTemplate>
                    </GroupStyle.HeaderTemplate>
                </GroupStyle>
            </DataGrid.GroupStyle>
            <DataGrid.RowStyle>
                <Style TargetType="DataGridRow">
                    <Setter Property="Foreground" Value="Black" />
                    <Setter Property="Background" Value="White" />
                </Style>
            </DataGrid.RowStyle>
        </DataGrid>
        <StackPanel Orientation="Horizontal" Grid.Row="1">
            <TextBlock Text=" Filter completed items " VerticalAlignment="Center" />
            <CheckBox x:Name="cbCompleteFilter" VerticalAlignment="Center"
                      Checked="CompleteFilter_Changed" Unchecked="CompleteFilter_Changed" />
            <Button Content="Remove Groups" Margin="10,2,2,2" Click="UngroupButton_Click" />
            <Button Content="Group by Project/Status" Margin="2" Click="GroupButton_Click" />
        </StackPanel>
    </Grid>
</Window>
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Data;

namespace DGGroupSortFilterExample
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            // Get a reference to the tasks collection.
            Tasks _tasks = (Tasks)this.Resources["tasks"];

            // Generate some task data and add it to the task list.
            for (int i = 1; i <= 14; i++)
            {
                _tasks.Add(new Task()
                {
                    ProjectName = "Project " + ((i % 3) + 1).ToString(),
                    TaskName = "Task " + i.ToString(),
                    DueDate = DateTime.Now.AddDays(i),
                    Complete = (i % 2 == 0)
                });
            }
        }

        private void UngroupButton_Click(object sender, RoutedEventArgs e)
        {
            ICollectionView cvTasks = CollectionViewSource.GetDefaultView(dataGrid1.ItemsSource);
            if (cvTasks != null)
            {
                cvTasks.GroupDescriptions.Clear();
            }
        }

        private void GroupButton_Click(object sender, RoutedEventArgs e)
        {
            ICollectionView cvTasks = CollectionViewSource.GetDefaultView(dataGrid1.ItemsSource);
            if (cvTasks != null && cvTasks.CanGroup == true)
            {
                cvTasks.GroupDescriptions.Clear();
                cvTasks.GroupDescriptions.Add(new PropertyGroupDescription("ProjectName"));
                cvTasks.GroupDescriptions.Add(new PropertyGroupDescription("Complete"));
            }
        }

        private void CompleteFilter_Changed(object sender, RoutedEventArgs e)
        {
            // Refresh the view to apply filters.
            CollectionViewSource.GetDefaultView(dataGrid1.ItemsSource).Refresh();
        }

        private void CollectionViewSource_Filter(object sender, FilterEventArgs e)
        {
            Task t = e.Item as Task;
            if (t != null)
            // If filter is turned on, filter completed items.
            {
                if (this.cbCompleteFilter.IsChecked == true && t.Complete == true)
                    e.Accepted = false;
                else
                    e.Accepted = true;
            }
        }
    }

    [ValueConversion(typeof(Boolean), typeof(String))]
    public class CompleteConverter : IValueConverter
    {
        // This converter changes the value of a Tasks Complete status from true/false to a string value of
        // "Complete"/"Active" for use in the row group header.
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            bool complete = (bool)value;
            if (complete)
                return "Complete";
            else
                return "Active";
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            string strComplete = (string)value;
            if (strComplete == "Complete")
                return true;
            else
                return false;
        }
    }

    // Task Class
    // Requires using System.ComponentModel;
    public class Task : INotifyPropertyChanged, IEditableObject
    {
        // The Task class implements INotifyPropertyChanged and IEditableObject
        // so that the datagrid can properly respond to changes to the
        // data collection and edits made in the DataGrid.

        // Private task data.
        private string m_ProjectName = string.Empty;
        private string m_TaskName = string.Empty;
        private DateTime m_DueDate = DateTime.Now;
        private bool m_Complete = false;

        // Data for undoing canceled edits.
        private Task temp_Task = null;
        private bool m_Editing = false;

        // Public properties.
        public string ProjectName
        {
            get { return this.m_ProjectName; }
            set
            {
                if (value != this.m_ProjectName)
                {
                    this.m_ProjectName = value;
                    NotifyPropertyChanged("ProjectName");
                }
            }
        }

        public string TaskName
        {
            get { return this.m_TaskName; }
            set
            {
                if (value != this.m_TaskName)
                {
                    this.m_TaskName = value;
                    NotifyPropertyChanged("TaskName");
                }
            }
        }

        public DateTime DueDate
        {
            get { return this.m_DueDate; }
            set
            {
                if (value != this.m_DueDate)
                {
                    this.m_DueDate = value;
                    NotifyPropertyChanged("DueDate");
                }
            }
        }

        public bool Complete
        {
            get { return this.m_Complete; }
            set
            {
                if (value != this.m_Complete)
                {
                    this.m_Complete = value;
                    NotifyPropertyChanged("Complete");
                }
            }
        }

        // Implement INotifyPropertyChanged interface.
        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        // Implement IEditableObject interface.
        public void BeginEdit()
        {
            if (m_Editing == false)
            {
                temp_Task = this.MemberwiseClone() as Task;
                m_Editing = true;
            }
        }

        public void CancelEdit()
        {
            if (m_Editing == true)
            {
                this.ProjectName = temp_Task.ProjectName;
                this.TaskName = temp_Task.TaskName;
                this.DueDate = temp_Task.DueDate;
                this.Complete = temp_Task.Complete;
                m_Editing = false;
            }
        }

        public void EndEdit()
        {
            if (m_Editing == true)
            {
                temp_Task = null;
                m_Editing = false;
            }
        }
    }
    // Requires using System.Collections.ObjectModel;
    public class Tasks : ObservableCollection<Task>
    {
        // Creating the Tasks collection in this way enables data binding from XAML.
    }
}
Imports System.ComponentModel
Imports System.Collections.ObjectModel

Class MainWindow
    Public Sub New()
        InitializeComponent()

        ' Get a reference to the tasks collection.
        Dim _tasks As Tasks = Me.Resources("tasks")

        ' Generate some task data and add it to the task list.
        For index = 1 To 14
            _tasks.Add(New Task() With _
                         {.ProjectName = "Project " & ((index Mod 3) + 1).ToString(), _
                           .TaskName = "Task " & index.ToString(), _
                           .DueDate = Date.Now.AddDays(index), _
                           .Complete = (index Mod 2 = 0) _
                         })
        Next
    End Sub

    Private Sub UngroupButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
        Dim cvTasks As ICollectionView = CollectionViewSource.GetDefaultView(dataGrid1.ItemsSource)
        If cvTasks IsNot Nothing Then
            cvTasks.GroupDescriptions.Clear()
        End If
    End Sub

    Private Sub GroupButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
        Dim cvTasks As ICollectionView = CollectionViewSource.GetDefaultView(dataGrid1.ItemsSource)
        If cvTasks IsNot Nothing And cvTasks.CanGroup = True Then
            cvTasks.GroupDescriptions.Clear()
            cvTasks.GroupDescriptions.Add(New PropertyGroupDescription("ProjectName"))
            cvTasks.GroupDescriptions.Add(New PropertyGroupDescription("Complete"))
        End If
    End Sub

    Private Sub CompleteFilter_Changed(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
        ' Refresh the view to apply filters.
        CollectionViewSource.GetDefaultView(dataGrid1.ItemsSource).Refresh()
    End Sub

    Private Sub CollectionViewSource_Filter(ByVal sender As System.Object, ByVal e As System.Windows.Data.FilterEventArgs)
        Dim t As Task = e.Item
        If t IsNot Nothing Then
            ' If filter is turned on, filter completed items.
            If Me.cbCompleteFilter.IsChecked = True And t.Complete = True Then
                e.Accepted = False
            Else
                e.Accepted = True
            End If
        End If
    End Sub
End Class

Public Class CompleteConverter
    Implements IValueConverter
    ' This converter changes the value of a Tasks Complete status from true/false to a string value of
    ' "Complete"/"Active" for use in the row group header.
    Public Function Convert(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert
        Dim complete As Boolean = value
        If complete = True Then
            Return "Complete"
        Else
            Return "Active"
        End If
    End Function

    Public Function ConvertBack1(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack
        Dim strComplete As String = value
        If strComplete = "Complete" Then
            Return True
        Else
            Return False
        End If
    End Function
End Class

' Task class
' Requires Imports System.ComponentModel
Public Class Task
    Implements INotifyPropertyChanged, IEditableObject
    ' The Task class implements INotifyPropertyChanged and IEditableObject 
    ' so that the datagrid can properly respond to changes to the 
    ' data collection and edits made in the DataGrid.

    ' Private task data.
    Private m_ProjectName As String = String.Empty
    Private m_TaskName As String = String.Empty
    Private m_DueDate As DateTime = Date.Now
    Private m_Complete As Boolean = False

    ' Data for undoing canceled edits.
    Private temp_Task As Task = Nothing
    Private m_Editing As Boolean = False

    ' Public properties.
    Public Property ProjectName() As String
        Get
            Return Me.m_ProjectName
        End Get
        Set(ByVal value As String)
            If Not value = Me.m_ProjectName Then
                Me.m_ProjectName = value
                NotifyPropertyChanged("ProjectName")
            End If
        End Set
    End Property

    Public Property TaskName() As String
        Get
            Return Me.m_TaskName
        End Get
        Set(ByVal value As String)
            If Not value = Me.m_TaskName Then
                Me.m_TaskName = value
                NotifyPropertyChanged("TaskName")
            End If
        End Set
    End Property

    Public Property DueDate() As Date
        Get
            Return Me.m_DueDate
        End Get
        Set(ByVal value As Date)
            If Not value = Me.m_DueDate Then
                Me.m_DueDate = value
                NotifyPropertyChanged("DueDate")
            End If
        End Set
    End Property

    Public Property Complete() As Boolean
        Get
            Return Me.m_Complete
        End Get
        Set(ByVal value As Boolean)
            If Not value = Me.m_Complete Then
                Me.m_Complete = value
                NotifyPropertyChanged("Complete")
            End If
        End Set
    End Property

    ' Implement INotifyPropertyChanged interface. 
    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

    Public Sub NotifyPropertyChanged(ByVal propertyName As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    End Sub

    ' Implement IEditableObject interface.
    Public Sub BeginEdit() Implements IEditableObject.BeginEdit
        If Not Me.m_Editing Then
            Me.temp_Task = Me.MemberwiseClone()
            Me.m_Editing = True
        End If
    End Sub

    Public Sub CancelEdit() Implements IEditableObject.CancelEdit
        If m_Editing = True Then
            Me.ProjectName = Me.temp_Task.ProjectName
            Me.TaskName = Me.temp_Task.TaskName
            Me.DueDate = Me.temp_Task.DueDate
            Me.Complete = Me.temp_Task.Complete
            Me.m_Editing = False
        End If
    End Sub

    Public Sub EndEdit() Implements IEditableObject.EndEdit
        If m_Editing = True Then
            Me.temp_Task = Nothing
            Me.m_Editing = False
        End If
    End Sub
End Class

' Requires Imports System.Collections.ObjectModel
Public Class Tasks
    Inherits ObservableCollection(Of Task)
    ' Creating the Tasks collection in this way enables data binding from XAML.
End Class

另請參閱