Binding to a Collection of Groups

  • 5 minutes to read

When engineering a WPF application using the Model View ViewModel (MVVM) architectural pattern, you may be required to describe field groups in a Model or ViewModel. The Pivot Grid can be bound to a collection of objects containing group settings, described in a Model or ViewModel, thus minimizing the need for 'code-behind'.

View Model Implementation

NOTE

In this tutorial PivotGrid is bound to the Sales Person view of the Northwind database. You can find it at C:\Users\Public\Documents\DevExpress Demos 20.1\Components\Data\nwind.mdb.

Create a view model. In this tutorial, the view model includes the following classes:

  • ViewModel - the Sales Person view model.
  • Field - describes the Pivot Grid fields. To get more information about field templates, see Binding to a Collection of Fields.
  • FieldTemplateSelector - describes the Pivot Grid template selector. This class allows you to choose the required template based on the specified condition.
  • Group - describes the Pivot Grid groups. This class provides properties that correspond to settings common to all Pivot Grid field groups.
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using DevExpress.Xpf.PivotGrid;

namespace PivotGridMvvmEnhancements {
    public class ViewModel {

        // The collection of Pivot Grid fields.
        public ObservableCollection<Field> Fields { get; private set; }
        // The collection of Pivot Grid groups.
        public ObservableCollection<FieldGroup> Groups { get; private set; }
        // The view model that contains the fields and groups settings.
        public ViewModel() {
            Fields = new ObservableCollection<Field>() {
                new Field() { FieldName="Country", AreaIndex=0, FieldArea = FieldArea.RowArea, Name="fieldCountry",
                    GroupName="groupSalesPerson", GroupIndex=0},
                new Field() { FieldName="Sales Person", AreaIndex=1, FieldArea = FieldArea.RowArea, Name="fieldSalesPerson",
                    GroupName="groupSalesPerson", GroupIndex=1},
                new Field() { FieldName="OrderDate", AreaIndex=0, FieldArea = FieldArea.ColumnArea, Name="fieldOrderYear",
                    Interval = FieldGroupInterval.DateYear, FieldCaption = "Year", GroupName="groupYearMonth", GroupIndex=0 },
                new Field() { FieldName="OrderDate", AreaIndex=1, FieldArea = FieldArea.ColumnArea, Name="fieldOrderMonth",
                    Interval = FieldGroupInterval.DateMonth, FieldCaption = "Month", GroupName="groupYearMonth", GroupIndex=1 },
                new Field() { FieldName="Extended Price", AreaIndex=0, FieldArea = FieldArea.DataArea, Name="fieldPrice" },
            };
            Groups = new ObservableCollection<FieldGroup>() {
                new FieldGroup() { GroupName = "groupYearMonth" },
                new FieldGroup() { GroupName = "groupSalesPerson" }
            };

        }
    }

    public class Field {
        public string FieldName { get; set; }
        public string Name { get; set; }
        public string FieldCaption { get; set; }
        public FieldArea FieldArea { get; set; }
        public int AreaIndex { get; set; }
        public FieldGroupInterval Interval { get; set; }
        public string GroupName { get; set; }
        public int GroupIndex { get; set; }
    }

    public class FieldTemplateSelector : DataTemplateSelector {
        public override DataTemplate SelectTemplate(object item, DependencyObject container) {
            Field field = (Field)item;
            if(field.Interval == FieldGroupInterval.DateMonth || field.Interval == FieldGroupInterval.DateYear) {
                return (DataTemplate)((Control)container).FindResource("IntervalFieldTemplate");
            }
            return (DataTemplate)((Control)container).FindResource("DefaultFieldTemplate");
        }
    }

    public class FieldGroup {
        public string GroupName { get; set; }
    }
}
NOTE

If the Groups collection might be changed after it has been assigned to the Pivot Grid control, it should implement the INotifyCollectionChanged interface, so that changes made within a View Model are automatically reflected by the Pivot Grid.

Group Templates and Selector

The Pivot Grid Control generates groups based on group templates. Using a template, you can create an unlimited number of field groups in an unlimited number of Pivot Grid controls. In this example, there is one group template: DefaultGroupTemplate.

To avoid performance issues when binding to field properties, use the dxci:DependencyObjectExtensions.DataContext attached property.

If all Pivot Grid groups can be described using a single template, you have no need to create a field template selector:

<Window x:Class="WpfPivotTestExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfPivotTestExample"
        xmlns:dxpg="http://schemas.devexpress.com/winfx/2008/xaml/pivotgrid" 
        xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core" 
        xmlns:dxci="http://schemas.devexpress.com/winfx/2008/xaml/core/internal"
        xmlns:nwindDataSetTableAdapters="clr-namespace:WpfPivotTestExample.nwindDataSetTableAdapters" 
        x:Name="Form1" 
        Title="MainWindow" Height="362" Width="627">
    <Window.DataContext>
        <local:ViewModel />
    </Window.DataContext>
    <Window.Resources>
        <local:FieldTemplateSelector x:Key="FieldTemplateSelector" />
        <dx:TypedSimpleSource x:Key="TypedSimpleSource" AdapterType="{x:Type nwindDataSetTableAdapters:SalesPersonTableAdapter}" 
                              ContextType="{x:Type local:nwindDataSet}" Path="SalesPerson" />
        <DataTemplate x:Key="DefaultFieldTemplate">
            <ContentControl>
                <dxpg:PivotGridField FieldName="{Binding Path=(dxci:DependencyObjectExtensions.DataContext).FieldName, RelativeSource={RelativeSource Self}}"
                                     Area="{Binding Path=(dxci:DependencyObjectExtensions.DataContext).FieldArea, RelativeSource={RelativeSource Self}}"
                                     AreaIndex="{Binding Path=(dxci:DependencyObjectExtensions.DataContext).AreaIndex, RelativeSource={RelativeSource Self}}"
                                     Caption="{Binding Path=(dxci:DependencyObjectExtensions.DataContext).FieldCaption, RelativeSource={RelativeSource Self}}"
                                     GroupName="{Binding Path=(dxci:DependencyObjectExtensions.DataContext).GroupName, RelativeSource={RelativeSource Self}}"
                                     GroupIndex="{Binding Path=(dxci:DependencyObjectExtensions.DataContext).GroupIndex, RelativeSource={RelativeSource Self}}"
                                     dx:XamlHelper.Name="{Binding Path=(dxci:DependencyObjectExtensions.DataContext).UniqueName, RelativeSource={RelativeSource Self}}"
                                     >
                </dxpg:PivotGridField>
            </ContentControl>
        </DataTemplate>
        <DataTemplate x:Key="IntervalFieldTemplate">
            <ContentControl>
                <dxpg:PivotGridField FieldName="{Binding Path=(dxci:DependencyObjectExtensions.DataContext).FieldName, RelativeSource={RelativeSource Self}}"
                                     Area="{Binding Path=(dxci:DependencyObjectExtensions.DataContext).FieldArea, RelativeSource={RelativeSource Self}}"
                                     AreaIndex="{Binding Path=(dxci:DependencyObjectExtensions.DataContext).AreaIndex, RelativeSource={RelativeSource Self}}"
                                     Caption="{Binding Path=(dxci:DependencyObjectExtensions.DataContext).FieldCaption, RelativeSource={RelativeSource Self}}"
                                     GroupName="{Binding Path=(dxci:DependencyObjectExtensions.DataContext).GroupName, RelativeSource={RelativeSource Self}}"
                                     GroupIndex="{Binding Path=(dxci:DependencyObjectExtensions.DataContext).GroupIndex, RelativeSource={RelativeSource Self}}"
                                     GroupInterval="{Binding Path=(dxci:DependencyObjectExtensions.DataContext).Interval, RelativeSource={RelativeSource Self}}"
                                     dx:XamlHelper.Name="{Binding Path=(dxci:DependencyObjectExtensions.DataContext).UniqueName, RelativeSource={RelativeSource Self}}"
                                     HeaderImage="{dx:DXImage Image=Calendar_16x16.png}">
                </dxpg:PivotGridField>
            </ContentControl>
        </DataTemplate>
        <DataTemplate x:Key="GroupTemplate">
            <ContentControl>
                <dxpg:PivotGridGroup 
                    dx:XamlHelper.Name="{Binding Path=(dxci:DependencyObjectExtensions.DataContext).GroupName, RelativeSource={RelativeSource Self}}">
                </dxpg:PivotGridGroup>
            </ContentControl>
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <!-- -->
    </Grid>
</Window>

To create more then one template for field groups to allow choosing the required template based on a specified condition, create the Template Selector. After that, assign this class containing the specified condition to the Pivot Grid's PivotGridControl.GroupGeneratorTemplateSelector property.

You can create a style to specify settings common to all field groups generated using different templates. You can specify bindings to ViewModel properties within a style. This style should be assigned to the PivotGridControl.GroupGeneratorStyle property.

Customizing the WPF DXPivotGrid

Finally, specify the Pivot Grid's PivotGridControl.DataSource, PivotGridControl.GroupsSource and PivotGridControl.GroupGeneratorTemplate.

Member Description
PivotGridControl.DataSource Specifies the Pivot Grid's data source.
PivotGridControl.GroupsSource Specifies the source from which the Pivot Grid generates groups.
PivotGridControl.GroupGeneratorTemplate Specifies the group template.

The following code shows how to configure the Pivot Grid control using the ViewModel.

<Grid>
        <dxpg:PivotGridControl x:Name="pivotGridControl1" 
                               DataSource="{Binding Data, Source={StaticResource TypedSimpleSource}}"
                               FieldsSource="{Binding Fields }" 
                               FieldGeneratorTemplateSelector="{StaticResource FieldTemplateSelector}"
                               GroupsSource="{Binding Groups}" 
                               GroupGeneratorTemplate="{StaticResource GroupTemplate}">
        </dxpg:PivotGridControl>
    </Grid>

The image below illustrates the result.

pivot-group-template

Example

How to: Bind the Pivot Grid to Fields and Groups specified in ViewModel