Wednesday, December 12, 2018

XamDataGrid expand all

Take a look at this later blog entry that makes this functionality visually more appealing.

I have received a request to add a check box to a page that contains a two level hierarchical XamDataGrid. When the user checks the box all top level rows must be expanded. When the user clears the box all top level rows must be collapsed. Rows can still be expanded and collapsed individually as normal. This is a common requirement.


At first I tried to find a way to bind the IsExpanded property of the DataRecord to a new property on the data item but I abandoned that for two reasons.

1. It doesn't work unless you create a new dependency property
2. Data and visual state should not be intermingled - this is poor design

When I thought about this more I realized this is event driven. At the time the check box's IsChecked property is changed, all the rows' IsExpanded properties need to be changed. At other times there is no relationship between the check box and the data grid. It doesn't seem like a good solution for binding.

Therefore I chose to bind the check box's IsChecked property to a property in the code behind and add code to that setter to force the IsExpanded property of each row in the grid to the same value. This means we have to save a reference to the XamDataGrid once it has loaded.

Start a new WPF project called ExpandAll.


Add the Infragistics references.

I put some styles into the WPF to make the grid easier to read. They are not essential to the solution.


<Window x:Class="ExpandAll.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:igDP="http://infragistics.com/DataPresenter"
        xmlns:local="clr-namespace:ExpandAll"
        mc:Ignorable="d"
        Title="MainWindow"
        SizeToContent="WidthAndHeight"
        ResizeMode="NoResize"
        DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Window.Resources>
        <Style TargetType="igDP:HeaderLabelArea" x:Key="ManagerHeaderStyle">
            <Setter Property="FontWeight" Value="Bold"/>
        </Style>
        <Style TargetType="igDP:CellValuePresenter" x:Key="Manager">
            <Setter Property="FontWeight" Value="Bold"/>
        </Style>
        <Style TargetType="igDP:CellValuePresenter" x:Key="Grunt">
            <Setter Property="Margin" Value="20,0,0,0"/>
        </Style>
    </Window.Resources>
   
    <StackPanel Orientation="Vertical">
        <CheckBox Content="Expand All" IsChecked="{Binding IsExpanded}"/>
        <igDP:XamDataGrid DataSource="{Binding Managers}" GroupByAreaLocation="None" Height="200" Width="400" ScrollingMode="Immediate" Loaded="XamDataGrid_Loaded">
            <igDP:XamDataGrid.FieldLayoutSettings>
                <igDP:FieldLayoutSettings SelectionTypeRecord="Single" AutoGenerateFields="False"  AllowFieldMoving="No" ExpansionIndicatorDisplayMode="CheckOnDisplay"/>
            </igDP:XamDataGrid.FieldLayoutSettings>
            <igDP:XamDataGrid.FieldSettings>
                <igDP:FieldSettings AllowSummaries="False" SummaryUIType="SingleSelect" LabelTextAlignment="Center" AllowEdit="false"/>
            </igDP:XamDataGrid.FieldSettings>
            <igDP:XamDataGrid.FieldLayouts>
                <igDP:FieldLayout Description="Managers" Key="Managers">
                    <igDP:FieldLayout.Settings>
                        <igDP:FieldLayoutSettings HeaderLabelAreaStyle="{StaticResource ManagerHeaderStyle}"/>
                    </igDP:FieldLayout.Settings>
                    <igDP:TextField Label="Manager" Name="Name" Width="*" CellValuePresenterStyle="{StaticResource Manager}"/>
                    <igDP:Field Name="Reports"/>
                </igDP:FieldLayout>
                <igDP:FieldLayout ParentFieldLayoutKey="Managers" ParentFieldName="Reports">
                    <igDP:TextField Label="Employee" Name="Name" CellValuePresenterStyle="{StaticResource Grunt}"/>
                </igDP:FieldLayout>
            </igDP:XamDataGrid.FieldLayouts>
        </igDP:XamDataGrid>
    </StackPanel>
</Window>

The code behind is...


using Infragistics.Windows.DataPresenter;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;

namespace ExpandAll
{
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        public XamDataGrid dg;

        public class cEmployee
        {
            public String Name { get; set; }
            public List<cEmployee> Reports { get; set; }
        }

        public List<cEmployee> Managers { get; set; }

        private bool _IsExpanded;
        public bool IsExpanded
        {
            get { return _IsExpanded; }
            set
            {
                _IsExpanded = value;
                NotifyPropertyChanged("IsExpanded");
                foreach (object o in dg.DataSource)
                    dg.GetRecordFromDataItem(o, recursive:false).IsExpanded = value;
            }
        }

        public MainWindow()
        {
            Managers = new List<cEmployee>()
            { new cEmployee { Name = "Amber", Reports = new List<cEmployee>() { new cEmployee { Name = "Bob" }, new cEmployee { Name = "Carol" } } },
              new cEmployee { Name = "David", Reports = new List<cEmployee>() { new cEmployee { Name = "Emily" }, new cEmployee { Name = "Frank" } } },
              new cEmployee { Name = "Genevive", Reports = new List<cEmployee>() { new cEmployee { Name = "Harry" }, new cEmployee { Name = "Isabell" } } },
              new cEmployee { Name = "John"}
            };

            InitializeComponent();
        }

        public event PropertyChangedEventHandler PropertyChanged;
        public void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }

        private void XamDataGrid_Loaded(object sender, RoutedEventArgs e)
        {
            dg = (XamDataGrid)sender;
        }
    }
}

The important piece of code is

                foreach (object o in dg.DataSource)
                    dg.GetRecordFromDataItem(o, recursive:false).IsExpanded = value;

This loops through each item in the datagrid's DataSource, finds the DataRecord that corresponds to that item, and sets IsExpanded true or false. Note that I set recursive = false because I know the item I'm looking for is a top level item. I also know it exists so it probably makes no difference what I set recursive to.

Expanding all by checking the check box
Expanding some by clicking on individual row expanders

No comments:

Post a Comment