Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sort ObservableCollection bound to DataGrid in MVVM

I have a DataGrid that I'm binding to an ObservableCollection in my view model and I need to be able to sort the collection when the DataGrid is sorted to so I can use this sorted order elsewhere. I'm currently using a wrapper on the ObvservableCollection to support sorting. When the DataGrid is sorted it only sorts the displayed data and not the underlying data in the collection. The data consists of one integer column and one string column and needs to support ascending and descending sort on both. I also want to maintain the same usage as the standard DataGrid sort where you click a column header and it toggles between ascending and descending sort. I'm relatively new to WPF so I don't know all the ins and outs of data and command binding, but I would think there would be a way to accomplish what I want to do. Here is a sample xaml to illustrate my view set up.

<DataGrid ItemsSource="{Binding mySource}" 
          Name="myDataGrid"
          CanUserResizeRows="False"
          AutoGenerateColumns="False"
          HeadersVisibility="Column" >
    <DataGrid.Columns>

      <DataGridTemplateColumn Header="Header 1" CanUserSort="True" SortMemberPath="firstValue">
        <DataGridTemplateColumn.CellTemplate>
          <DataTemplate>
            <TextBlock Text="{Binding firstValue}" />
          </DataTemplate>
        </DataGridTemplateColumn.CellTemplate>
      </DataGridTemplateColumn>

      <DataGridTemplateColumn Header="Header 2" Width="*" CanUserSort="True" SortMemberPath="secondValue">
        <DataGridTemplateColumn.CellTemplate>
          <DataTemplate>
            <TextBlock Text="{Binding secondValue}" />
          </DataTemplate>
        </DataGridTemplateColumn.CellTemplate>
      </DataGridTemplateColumn>

    </DataGrid.Columns>
</DataGrid>

The source data is of a type something like:

public class myType
{
    public int firstValue { get; set; }
    public string secondValue { get; set; }

    // some functions and variables...
}

Now, like I said above, I need access to the items in the collection in their sorted order, but it does not need to specifically be an ObservableCollection. As long as I can iterate through the items in the collection in whatever the current order is when I access them, all is good. I was thinking maybe a ListCollectionView or something. I also don't want the collection to re-sort itself when new items are added to the collection. Any new items should just be added to the end of the collection as would normally happen.

Any ideas?

like image 251
climbak Avatar asked Dec 25 '22 21:12

climbak


2 Answers

The DataGrid uses an underlying ICollectionView based on the DataSource, so if you directly bind a ICollectionView you can access the sorted values as the DataGrid will directly change the ICollectionView when sorting.

Small Example:

Code:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        // dummy data source / Your ObservableCollection
        var yourObservableCollection = Enumerable.Range(0, 30)
            .Select(i => new MyType { FirstValue = i, SecondValue = i.ToString() });

        // Create CollectionView based on the data you want to show
        MySource = CollectionViewSource.GetDefaultView(yourObservableCollection);
        InitializeComponent();
    }

    public ICollectionView MySource { get; set; }

    private void Button_Click_1(object sender, RoutedEventArgs e)
    {
        foreach (var item in MySource.OfType<MyType>())
        {
            Console.WriteLine("MyType - First: {0}, Second: {1}",
                item.FirstValue,
                item.SecondValue);
        }
    }
}

public class MyType
{
    public int FirstValue { get; set; }
    public string SecondValue { get; set; }
}

Xaml:

<Window x:Class="WpfApplication8.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow"`enter code here` Name="UI" Width="262" Height="318">
  <Grid DataContext="{Binding ElementName=UI}">
    <DataGrid Name="myDataGrid"
              ItemsSource="{Binding MySource}"
              CanUserSortColumns="True"
              Margin="0,0,0,37" />
    <Button Content="Print To Output Window"
            HorizontalAlignment="Left"
            VerticalAlignment="Bottom"
            Margin="10,0,0,10"
            Width="144"
            Click="Button_Click_1"/>
  </Grid>
</Window>
like image 180
sa_ddam213 Avatar answered Dec 28 '22 11:12

sa_ddam213


I know this is an old question but I struggled to find an answer when I had a similar issue where I wanted to print/export the data in the same order it was displayed on the screen. Although @sa_ddam213 answer is correct it needs a bit of adjusting to fit the MVVM pattern, so hopefully this answer is of use to somebody else. To achieve this first add to your View Model an ICollectionView (I believe this is the same data structure used by the UI):

private ObservableCollection<yourDataType> tableData = new ObservableCollection<yourDataType>();    // The DataGridknow  was originally bound to this
private ICollectionView tableDataWrapper;

Then add a new Getter/Setter for your ICollectionView:

public ICollectionView TableDataWrapper
{
    get { return this.tableDataWrapper; }
    set
    {
        this.tableDataWrapper = value;
        OnPropertyChanged("TableDataWrapper");
    }
}

And in your original TableData Setter add a line to set the wrapper each time the table data is set:

public ObservableCollection<yourDataType> TableData
{
    get { return this.tableData; }
    set
    {
        this.tableData = value;
        this.TableDataWrapper = CollectionViewSource.GetDefaultView(tableData); // Add this
        OnPropertyChanged("TableData");
    }
}

Now update the DataGrid's Bindings so it is bound to TableDataWrapper rather than TableData Now to access the data in the sorted order:

this.TableDataWrapper.Cast<yourDataType>().ToList<yourDataType>()

or if you want it as an ObservableCollection:

new ObservableCollection( this.TableDataWrapper.Cast<yourDataType>().ToList<yourDataType>() )
like image 32
stuicidle Avatar answered Dec 28 '22 12:12

stuicidle