Skip to main content

How to: Bind the Grid to a Collection of Columns

  • 7 minutes to read

This topic describes how to define columns in a View Model and display them in the GridControl.

View Example: How to Bind the GridControl to a Collection of Columns Specified in a ViewModel

Assign ViewModel Columns to the GridControl

This scenario demonstrates how to display information from an employee data model in the GridControl.

DevExpress WPF | Grid Control - MVVM Columns

  1. Create a class that describes a grid column:

    public class Column : BindableBase {
        public Column(string fieldname) {
            FieldName = fieldname;
        }
        public string FieldName { get; }
    }
    
  2. Specify a collection of columns in the ViewModel:

    public class ViewModel : ViewModelBase {
        public ViewModel() {
            // ...
            Columns = new ObservableCollection<Column>() {
                new Column(nameof(Employee.FirstName)),
                new Column(nameof(Employee.LastName)),
                new Column(nameof(Employee.StateProvinceName))
            };
        }
        // ...
        public ObservableCollection<Column> Columns { get; }
    }
    

    If you want the GridControl to reflect changes made in the Columns collection, this collection should implement the INotifyCollectionChanged interface.

    If the Columns collection is changed multiple times, wrap these changes into GridControl.Columns.BeginUpdate() and GridControl.Columns.EndUpdate() methods to process updates in a batch and increase performance.

  3. The GridControl generates columns based on column templates. Add a default column template.

    Use the DependencyObjectExtensions.DataContext attached property when you bind the GridControl to column properties. This attached property synchronizes data updates of bound properties to enhance grid performance:

    <Window.Resources>
        <DataTemplate x:Key="DefaultColumnTemplate">
            <dxg:GridColumn FieldName="{Binding Path=(dxci:DependencyObjectExtensions.DataContext).FieldName, RelativeSource={RelativeSource Self}}"/>
        </DataTemplate>
    </Window.Resources>
    
  4. Assign the column collection to the DataControlBase.ColumnsSource property. Set the DataControlBase.ColumnGeneratorTemplate property to a template that generates columns:

    <dxg:GridControl ...
                     ColumnsSource="{Binding Columns}" 
                     ColumnGeneratorTemplate="{StaticResource DefaultColumnTemplate}"/>
    

Add a Lookup Column

If you want to specify extra column types (for example, to display a list of states in a column’s combo box), follow the steps below:

DevExpress WPF | Grid Control - MVVM Lookup Column

  1. Add an enumeration that lists column types:

    public enum SettingsType { Default, Lookup }
    
  2. Create a class that describes a lookup column:

    public class Column : BindableBase {
        public Column(SettingsType settings, string fieldname) {
            Settings = settings;
            FieldName = fieldname;
        }
        public SettingsType Settings { get; }
        public string FieldName { get; }
    }
    
    public class LookupColumn : Column {
        public LookupColumn(SettingsType settings, string fieldname, IList source): base(settings, fieldname) {
            Source = source;
        }
        public IList Source { get; }
    }
    
  3. Add a lookup column to the ViewModel’s Columns collection and specify the lookup’s data source:

    public class ViewModel : ViewModelBase {
        public ViewModel() {
            // ...
            States = Source.Select(x => x.StateProvinceName).Distinct().ToList();
            Columns = new ObservableCollection<Column>() {
                new Column(SettingsType.Default, nameof(Employee.FirstName)),
                new Column(SettingsType.Default, nameof(Employee.LastName)),
                new LookupColumn(SettingsType.Lookup, nameof(Employee.StateProvinceName), States)
            };
        }
        // ...
        public ObservableCollection<Column> Columns { get; }
        public List<string> States { get; }
    }
    
  4. Create a template that generates lookup columns:

    <DataTemplate x:Key="LookupColumnTemplate">
        <dxg:GridColumn FieldName="{Binding Path=(dxci:DependencyObjectExtensions.DataContext).FieldName, RelativeSource={RelativeSource Self}}">
            <dxg:GridColumn.EditSettings>
                <dxe:ComboBoxEditSettings ItemsSource="{Binding Source}"/>
            </dxg:GridColumn.EditSettings>
        </dxg:GridColumn>
    </DataTemplate>
    

Add a Template Selector to Choose among Multiple Templates

Your application now contains the DefaultColumnTemplate and LookupColumnTemplate. To choose a template based on the column type, create a template selector and assign it to the DataControlBase.ColumnGeneratorTemplateSelector property:

using System.Windows;
using System.Windows.Controls;

namespace ColumnsSample {
    public class ColumnTemplateSelector : DataTemplateSelector {
        public DataTemplate DefaultColumnTemplate { get; set; }
        public DataTemplate LookupColumnTemplate { get; set; }

        public override DataTemplate SelectTemplate(object item, DependencyObject container) {
            Column column = item as Column;
            if(column == null) return null;
            switch(column.Settings) {
                case SettingsType.Default:
                    return DefaultColumnTemplate;
                case SettingsType.Lookup:
                    return LookupColumnTemplate;
            }
            return null;
        }
    }
}
<Window.Resources>
    <!-- ... -->
    <local:ColumnTemplateSelector x:Key="ColumnTemplateSelector"
                                  DefaultColumnTemplate ="{StaticResource DefaultColumnTemplate}"
                                  LookupColumnTemplate ="{StaticResource LookupColumnTemplate}"/>
</Window.Resources>
<dxg:GridControl ItemsSource="{Binding Source}" 
                 ColumnsSource="{Binding Columns}"
                 ColumnGeneratorTemplateSelector="{StaticResource ColumnTemplateSelector}"/>

Specify Column Settings

You can use the DataControlBase.ColumnGeneratorStyle property to create a style that specifies column settings. The GridControl applies these settings to all columns generated from templates:

DevExpress WPF | Grid Control - Column Generator Style

<dxg:GridControl.ColumnGeneratorStyle>
    <Style TargetType="dxg:GridColumn">
        <Setter Property="FilterPopupMode" Value="CheckedList" />
        <Setter Property="Width" Value="Auto" />
        <Setter Property="ReadOnly" Value="True" />
        <Setter Property="HorizontalHeaderContentAlignment" Value="Center" />
        <Setter Property="HeaderStyle">
            <Setter.Value>
                <Style TargetType="dxg:BaseGridHeader">
                    <Setter Property="FontWeight" Value="SemiBold" />
                </Style>
            </Setter.Value>
        </Setter>
    </Style>
</dxg:GridControl.ColumnGeneratorStyle>

You can also use multiple data annotation attributes to define validation rules and format options:

public class Employee {
    [ReadOnly(true)]
    public string FirstName { get; set; }
    [MinLength(3)]
    public string LastName { get; set; }
    public string StateProvinceName { get; set; }
}

Add Columns That Use the Binding Property

In some cases, you should use the ColumnBase.Binding property instead of the ColumnBase.FieldName property to associate columns with data source fields (for example, to implement custom converters or access collection members). The ColumnBase.Binding property is not a dependency property and cannot be bound. Use the following binding helper to assign a binding to the ColumnBase.Binding property:

using DevExpress.Xpf.Grid;
using System.Windows;
using System.Windows.Data;

namespace ColumnsSample {
    public static class BindingHelper {
        public static string GetPath(GridColumn obj) {
            return (string)obj.GetValue(PathProperty);
        }
        public static void SetPath(GridColumn obj, string value) {
            obj.SetValue(PathProperty, value);
        }
        public static readonly DependencyProperty PathProperty = DependencyProperty.RegisterAttached("Path", typeof(string), typeof(BindingHelper),
            new PropertyMetadata((d, e) => { 
                if (!string.IsNullOrWhiteSpace(e.NewValue as string)) 
                    ((GridColumn)d).Binding = new Binding((string)e.NewValue) { 
                        Mode = BindingMode.TwoWay 
                    }; 
            })
        );
    }
}
<DataTemplate x:Key="BindingColumnTemplate">
    <dxg:GridColumn local:BindingHelper.Path="{Binding Path=(dxci:DependencyObjectExtensions.DataContext).FieldName, RelativeSource={RelativeSource Self}}"
                    Header="{Binding Path=(dxci:DependencyObjectExtensions.DataContext).Header, RelativeSource={RelativeSource Self}}"/>
</DataTemplate>
public class ViewModel : ViewModelBase {
    public ViewModel() {
        Columns = new ObservableCollection<Column>() {
            // ...
            new BindingColumn( SettingsType.Binding, "Cities[0]", "City1" ),
            new BindingColumn( SettingsType.Binding, "Cities[1]", "City2" )
        };
    }
    public ObservableCollection<Column> Columns { get; }
}

public class BindingColumn : Column {
    public BindingColumn(SettingsType settings, string fieldname, string header): base(settings, fieldname) {
        Header = header;
    }
    public string Header { get; }
}
See Also