WPF datagrid - Group by datagrid with editable column reoders rows in group when editable cell is "opened" or edited

I am facing a strange problem in WPF datagrid after grouping. The rows inside groups start reordering. I am using the .net 3.5 framework's datagrid from codeplex.

The problem has been asked in msdn forum. Please find the link below.

http://social.msdn.microsoft.com/Forums/en/wpf/thread/3f915bf9-2571-43e8-8560-3def1d0d65bb

The msdn user has says at the end of the thread that there might be no workarounds. But I badly need one !!

Upvotes: 1

Views: 1393

Answers (2)

Ian Tompsett
Ian Tompsett

Reputation: 21

Have you tried doing your sorting via a CustomSort? I saw this posted somewhere and it worked for me. It took a little bit of finding, but it's available via the ListCollectionView class. So something like:

ListCollectionView lcv = 
    (ListCollectionView)CollectionViewSource.GetDefaultView(YourObjectCollection);

lcv.CustomSort = new YourObjectComparer();

Where YourObjectComparer implements IComparer and sorts on the properties that you want to.

Upvotes: 2

Vinit Sankhe
Vinit Sankhe

Reputation: 19895

EDIT:

For this when a cell edit takes place, we remove groups and add them again. This cuases the cell to stay on its intended order. But this poses another issue that is all the expanders are collapsed. So if we remember which expander(s) remains expanded after regrouping, we achieve what you seek.


There are a few events which can help you with achieving this functionality...

  1. DataGrid.CellEditEnding event
  2. Expander.Initialized, Expander.Expanded and Expander.Collpased events
  3. ICollectionView.CurrentChanging event

All you need to remember is the status of the expanders when they are expanded or collapsed. Each expander represents the grouped value represented by Name property. These grouped values are unique. So a dictionary where Dictionary.Key is this Name value and Dictionary.Value is the Expander.IsExpanded flag would just suffice.

With this raw material following code does what you seek...

Model Class: I represent a simple list of Key-Value objects in the DataGrid.

public class MyKeyValuePair<TKey, TValue> : INotifyPropertyChanged
{
    private TKey key;
    private TValue value;

    public MyKeyValuePair(TKey k, TValue v)
    {
        key = k;
        value = v;
    }

    public TKey Key
    {
        get { return key; }
        set {
            key = value;
            OnPropertyChanged("Key");
        }
    }

    public TValue Value
    {
        get { return value; }
        set {
            this.value = value;
            OnPropertyChanged("Value");
        }
    }

    public void OnPropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
        {
            this.PropertyChanged
              (this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public event PropertyChangedEventHandler  PropertyChanged;
}

XAML:

 <tk:DataGrid
     ItemsSource="{Binding}"
     CellEditEnding="MyDataGrid_CellEditEnding">
    <tk:DataGrid.GroupStyle>
       <GroupStyle>
          <GroupStyle.HeaderTemplate>
             <DataTemplate>
                <StackPanel>
                   <TextBlock Text="{Binding Path=Name}" />
                </StackPanel>
             </DataTemplate>
          </GroupStyle.HeaderTemplate>
        <GroupStyle.ContainerStyle>
        <Style TargetType="{x:Type GroupItem}">
           <Setter Property="Template">
              <Setter.Value>
                 <ControlTemplate TargetType="{x:Type GroupItem}">
                     <Expander
                        Initialized="Expander_Initialized"
                        Expanded="Expander_Expanded"
                        Collapsed="Expander_Expanded">
                        <Expander.Header>
                            <StackPanel Orientation="Horizontal">
                                <TextBlock
                                   Text="{Binding Path=Name}" />
                                <TextBlock Text=" (" />
                                <TextBlock 
                                   Text="{Binding Path=ItemCount}"/>
                                <TextBlock Text="( " />
                                <TextBlock Text="Items"/>
                            </StackPanel>
                        </Expander.Header>
                        <ItemsPresenter />
                     </Expander>
                 </ControlTemplate>
              </Setter.Value>
           </Setter>
        </Style>
     </GroupStyle.ContainerStyle>
   </GroupStyle>
 </tk:DataGrid.GroupStyle>
</tk:DataGrid>

Window.cs Code Behind:

public partial class Window8 : Window
{
    private Dictionary<string, bool> _dict;
    public Window8()
    {
        InitializeComponent();

        _dict = new Dictionary<string, bool>();

        var list1 = new List<MyKeyValuePair<string, int>>();
        var random = new Random();
        for (int i = 0; i < 50; i++)
        {
            list1.Add(new MyKeyValuePair<string, int>(
                i.ToString(), random.Next(300) % 3));
        }

        var colView = new ListCollectionView(list1);
        colView.GroupDescriptions.Add(
            new PropertyGroupDescription("Value"));
        this.DataContext = colView;
    }

    private void MyDataGrid_CellEditEnding
        (object sender, DataGridCellEditEndingEventArgs e)
    {
        var dg = sender as DataGrid;
        var cellInfo = dg.CurrentCell;
        var mySource = dg.ItemsSource as ListCollectionView;
        var oldDlg
            = new CurrentChangingEventHandler((obj, args) => { return; });
        var dlg = new CurrentChangingEventHandler(
            (obj, args) =>
            {
                if (cellInfo.Item == mySource.CurrentItem)
                {
                    var grpDescs = mySource.GroupDescriptions;
                    var oldGrpDescs
                        = grpDescs.Cast<PropertyGroupDescription>().ToList();
                    mySource.Dispatcher.BeginInvoke(
                        new Action(
                            () =>
                            {
                                grpDescs.Clear();

                                foreach (var grdpDesc in oldGrpDescs)
                                {
                                    grpDescs.Add(grdpDesc);
                                }

                                mySource.CurrentChanging -= oldDlg;
                            }));
                }
            });

        oldDlg = dlg;
        mySource.CurrentChanging -= oldDlg;
        mySource.CurrentChanging += oldDlg;
    }

    private void Expander_Expanded(object sender, RoutedEventArgs e)
    {
        var exp = sender as Expander;
        var dc = exp.DataContext as CollectionViewGroup;
        _dict[dc.Name.ToString()] = exp.IsExpanded;
    }

    private void Expander_Initialized(object sender, EventArgs e)
    {
        var exp = sender as Expander;
        var dc = exp.DataContext as CollectionViewGroup;
        if (_dict != null
            && _dict.ContainsKey(dc.Name.ToString())
            && _dict[dc.Name.ToString()])
        {
            exp.IsExpanded = true;
        }
    }
}

But there are two trade offs.

  1. This will make each cell edit attempt slow for large number of items in the datagrid. Because regrouping takes place after each of the cell edit attempt.
  2. May not work for multiple group descriptions as the Name key in the dictionary may / may not stay unique. E.g. Assume for employee list if you group on FirstName and also gropup by LastName there will be nested expanders. Now some first name grouping may match with some last name grouping such as George. So the dictionary will fall into a trick and will not work correctly.

Hope this helps.

Upvotes: 1

Related Questions