Binding to a Collection of Conditional Formatting Rules

  • 5 min to read

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

View Model Implementation

The example used in this topic contains a view model code that includes the following classes.

  • Order - a data object that contains order information (e.g., quantity, discount, etc.).
  • ViewModel - the order view model. The ViewModel exposes the Orders property (a list of orders displayed within the grid) and the Rules property (a list of formatting rules).
  • OrderData - supplies order information to be displayed within the grid control.
  • FormattingRule - describes a conditional formatting rule. This class provides properties that correspond to settings common to all types of conditional formatting rules.
  • FormattingType - enumerates possible types of applied formattings.
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;

namespace GridDemo {
    // ... 
    public class ViewModel {
        public ViewModel() {
            Orders = GetOrders();
            Rules = GetFormattingRules();
        }

        private static List<FormattingRule> GetFormattingRules() {
            var rules = new List<FormattingRule>();
            rules.Add(new FormattingRule() {
                Expression = "([UnitPrice] * [Quantity] * (1 - [Discount]) - [Freight]) < 0",
                ApplyToRow = true,
                Type = FormattingType.Background
            });
            rules.Add(new FormattingRule() {
                FieldName = "Discount",
                Expression = "[Discount] > 0",
                ApplyToRow = false,
                Type = FormattingType.Font
            });
            rules.Add(new FormattingRule() {
                FieldName = "Discount",
                Expression = "[Discount] > 0",
                Type = FormattingType.Icon
            });
            return rules;
        }

        private static List<Order> GetOrders() {
            List<Order> list = new List<Order>();
            list.Add(new Order() { City = "Aachen", UnitPrice = 10, Quantity = 20, Discount = 0, Freight = 30.54 });
            // ...
            return list;
        }

        // A list of orders displayed within the grid control.  
        public List<Order> Orders { get; private set; }

        // A list of conditional formatting rules.  
        public List<FormattingRule> Rules { get; private set; }
    }

    // Corresponds to an order items displayed within grid.  
    public class Order {
        public string City { get; set; }
        public double Discount { get; set; }
        public double Freight { get; set; }
        public double Quantity { get; set; }
        public double UnitPrice { get; set; }
    }

    public enum FormattingType { Icon, Font, Background }

    public class FormattingRule {
        public virtual bool ApplyToRow { get; set; }
        public virtual string Expression { get; set; }
        public virtual string FieldName { get; set; }
        public virtual FormattingType Type { get; set; }
    }
}
NOTE

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

Format Condition Templates and Selector

The GridControl generates condition formattings based on condition formattings templates. Create multiple templates, one template for each condition formatting type. Using a single template, you can create an unlimited number of condition formattings in unlimited number of grid controls. In this example, there are three condition formatting templates: FontFormat, BackgroundFormat, and IconFormat.

To avoid performance issues when binding to column properties, use the dxci:DependencyObjectExtensions.DataContext attached property. See the example below.

<!---->
xmlns:dxci="http://schemas.devexpress.com/winfx/2008/xaml/core/internal"
<!---->
<DataTemplate x:Key="FontFormat">
    <ContentControl>
        <dxg:FormatCondition 
            ApplyToRow="{Binding Path=(dxci:DependencyObjectExtensions.DataContext).ApplyToRow, RelativeSource={RelativeSource Self}}"
            Expression="{Binding Path=(dxci:DependencyObjectExtensions.DataContext).Expression, RelativeSource={RelativeSource Self}}"
            FieldName="{Binding Path=(dxci:DependencyObjectExtensions.DataContext).FieldName, RelativeSource={RelativeSource Self}}">
            <dxg:FormatCondition.Format>
                <dx:Format FontWeight="Bold" />
            </dxg:FormatCondition.Format>
        </dxg:FormatCondition>
    </ContentControl>
</DataTemplate>

To choose the required template based on the column's type, use the Template Selector. In this example, the template selector is represented by the FormatConditionSelector class.

<Window
    x:Class="GridDemo.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:GridDemo"
    xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
    xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"
    xmlns:dxci="http://schemas.devexpress.com/winfx/2008/xaml/core/internal"
    xmlns:dxt="http://schemas.devexpress.com/winfx/2008/xaml/core/themekeys"
    Height="350"
    Width="525"
    mc:Ignorable="d"
    Title="MainWindow">

    <Window.DataContext>
        <local:ViewModel />
    </Window.DataContext>

    <Grid>
        <Grid.Resources>
            <DataTemplate x:Key="FontFormat">
                <ContentControl>
                    <dxg:FormatCondition 
                        ApplyToRow="{Binding Path=(dxci:DependencyObjectExtensions.DataContext).ApplyToRow, RelativeSource={RelativeSource Self}}"
                        Expression="{Binding Path=(dxci:DependencyObjectExtensions.DataContext).Expression, RelativeSource={RelativeSource Self}}"
                        FieldName="{Binding Path=(dxci:DependencyObjectExtensions.DataContext).FieldName, RelativeSource={RelativeSource Self}}">
                        <dxg:FormatCondition.Format>
                            <dx:Format FontWeight="Bold" />
                        </dxg:FormatCondition.Format>
                    </dxg:FormatCondition>
                </ContentControl>
            </DataTemplate>
            <DataTemplate x:Key="BackgroundFormat">
                <ContentControl>
                    <dxg:FormatCondition 
                        ApplyToRow="{Binding Path=(dxci:DependencyObjectExtensions.DataContext).ApplyToRow, RelativeSource={RelativeSource Self}}"
                        Expression="{Binding Path=(dxci:DependencyObjectExtensions.DataContext).Expression, RelativeSource={RelativeSource Self}}"
                        FieldName="{Binding Path=(dxci:DependencyObjectExtensions.DataContext).FieldName, RelativeSource={RelativeSource Self}}">
                        <dxg:FormatCondition.Format>
                            <dx:Format Background="LightPink" />
                        </dxg:FormatCondition.Format>
                    </dxg:FormatCondition>
                </ContentControl>
            </DataTemplate>
            <DataTemplate x:Key="IconFormat">
                <ContentControl>
                    <dxg:FormatCondition 
                        ApplyToRow="{Binding Path=(dxci:DependencyObjectExtensions.DataContext).ApplyToRow, RelativeSource={RelativeSource Self}}"
                        Expression="{Binding Path=(dxci:DependencyObjectExtensions.DataContext).Expression, RelativeSource={RelativeSource Self}}"
                        FieldName="{Binding Path=(dxci:DependencyObjectExtensions.DataContext).FieldName, RelativeSource={RelativeSource Self}}">
                        <dxg:FormatCondition.Format>
                            <dx:Format Icon="{dx:IconSet Name=Stars3_1}" />
                        </dxg:FormatCondition.Format>
                    </dxg:FormatCondition>
                </ContentControl>
            </DataTemplate>
            <local:FormatConditionSelector 
                x:Key="selector"
                BackgroundTemplate="{StaticResource BackgroundFormat}"
                FontTemplate="{StaticResource FontFormat}"
                IconTemplate="{StaticResource IconFormat}" />
        </Grid.Resources>

    </Grid>

</Window>
public class FormatConditionSelector : DataTemplateSelector {
    public override DataTemplate SelectTemplate(object item, DependencyObject container) {
        if(!(item is FormattingRule)) return null;
        var vm = item as FormattingRule;
        switch(vm.Type) {
            case FormattingType.Icon:
                return IconTemplate;
            case FormattingType.Font:
                return FontTemplate;
            case FormattingType.Background:
                return BackgroundTemplate;
            default: return null;
        }
    }

    public DataTemplate BackgroundTemplate { get; set; }
    public DataTemplate FontTemplate { get; set; }
    public DataTemplate IconTemplate { get; set; }
}
NOTE

If all conditional formattings can be described using a single template, you have no need to create a template selector. Instead, assign this template to the grid View's TableView.FormatConditionGeneratorTemplate (TreeListView.FormatConditionGeneratorTemplate for the TreeListView) property.

Customizing the GridControl

Finally, specify the grid View's FormatConditionGeneratorTemplateSelector and FormatConditionsSource properties. The TableView.FormatConditionsSource (TreeListView.FormatConditionsSource) property specifies the source from which the grid generates conditional formattings. The TableView.FormatConditionGeneratorTemplateSelector (TreeListView.FormatConditionGeneratorTemplateSelector) property specifies the template selector, which returns a template for each conditional formatting based on its type.

<dxg:GridControl 
    AutoGenerateColumns="AddNew"
    EnableSmartColumnsGeneration="True"
    ItemsSource="{Binding Orders}">
    <dxg:GridControl.View>
        <dxg:TableView 
            FormatConditionGeneratorTemplateSelector="{StaticResource selector}"
            FormatConditionsSource="{Binding Rules}" />
    </dxg:GridControl.View>
</dxg:GridControl>

The image below shows the result.

WPF_Grid_MVVM_ConditionalFormatting