Search code examples
c#wpfmvvmdatagridcollectionviewsource

how to select first row in DataGrid and to sort the data I'm using CollectionViewSource in wpf MVVM


I am working on WPF application using MVVM model ,I am using DataGrid which is having 3 columns and second column is datatime column.In DataGrid I want to display last added record as first row means in descending order so I am using CollectionViewSource and sort the data based on second column. Here the problem is when the grid rows increases then scrollbar will come automatically but it is not going upwards so first row is not displaying and everytime after inserting new record I want to scroll the scrollbar to top manually and it is annoying to customer also

below is the code,

var collectionView = CollectionViewSource.GetDefaultView(this.GridDataSource);
collectionView.SortDescriptions.Add(new SortDescription("CreatedAt", ListSortDirection.Descending));
collectionView.Refresh();

In XAML, inside DataGrid I used selectedIndex=0, this will works when we launch the application but after inserting new records the selected row is not changing but here I want to selected the newly inserted row,

 <DataGrid AutoGenerateColumns="False" SelectedIndex="0"
  ItemsSource="{Binding Path=Commands}" >

I tried added a property and bind to selectedIndex and still the same issue but when I debugged the code I found that selectedIdex vaule is changing means 0 to 1 and 1 to 2 soon after adding new records but in grid these added data is there and i need to manually scroll up to see the added rows.

<DataGrid SelectedIndex="{Binding selectedRow}" >

After executing below code the SelectedIndex value means selectedRow value changing from 0 to 1 and so on

this.GridDataSource.Add("FirstColumn",datetime.now,"Thirdcolumn");

In Simple I want to select FirstRow of the Grid.

Thanks in advance


Solution

  • You need to clear the previous sort, or you're potentially just adding more and more sort descriptions.

     var collectionView = 
     CollectionViewSource.GetDefaultView(this.GridDataSource);
     collectionView.SortDescriptions.Clear(); 
    
     collectionView.SortDescriptions.Add(new SortDescription("CreatedAt", ListSortDirection.Descending));
     collectionView.Refresh();
    

    You can manipulate the selected item indirectly, via the collectionview. In fact you might have to in order to get at the first of the sorted items. On your datagrid:

    <DataGrid ...
         IsSynchronizedWithCurrentItem="True"
    

    You can then select the first one using collectionview:

     collectionView.MoveCurrentToFirst();
    

    Because of that setting on the datagrid that will try and make the first item selected. Except of course it can be virtualised so there's no UI there to select. You need to call scrollintoview.

     datagrid.ScrollIntoView(datagrid.SelectedItem);
    

    I use a behavior:

    class ScrollDataGridRowIntoView : Behavior<DataGrid>
    {
        protected override void OnAttached()
        {
            
            base.OnAttached();
            this.AssociatedObject.SelectionChanged += AssociatedObject_SelectionChanged;
        }
    
        void AssociatedObject_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (sender is DataGrid)
            {
                DataGrid datagrid = (sender as DataGrid);
                if (datagrid.SelectedItem != null)
                {
                    datagrid.Dispatcher.BeginInvoke( (Action)(() => 
                        {
                            datagrid.UpdateLayout();
                            if (datagrid.SelectedItem !=  null)
                            { 
                                datagrid.ScrollIntoView(datagrid.SelectedItem);
                            }
                        }));
                }
            }
        }
        protected override void OnDetaching()
        {
            base.OnDetaching();
            this.AssociatedObject.SelectionChanged -=  AssociatedObject_SelectionChanged;
        }
    }
    

    Since that's applied to the datagrid you can put it just inside the datagrid tag:

     <DataGrid ItemsSource="{Binding ItemsView}"
                        ......
               SelectionMode="Single"
               IsSynchronizedWithCurrentItem="True"
                            >
            <b:Interaction.Behaviors>
                <local:ScrollDataGridRowIntoView />
            </b:Interaction.Behaviors>
    

    Behaviors are now a nuget package which you need to add and the xmlns is different:

    https://github.com/Microsoft/XamlBehaviorsWpf

    <Window......
           xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
    

    Selecting a row doesn't turn it blue. You might be interested in another behavior

    class DataGridRowBehavior : Behavior<DataGridRow>
    {
        public static bool GetIsDataGridRowFocussedWhenSelected(DataGridRow dataGridRow)
        {
            return (bool)dataGridRow.GetValue(IsDataGridRowFocussedWhenSelectedProperty);
        }
    
        public static void SetIsDataGridRowFocussedWhenSelected(
          DataGridRow dataGridRow, bool value)
        {
            dataGridRow.SetValue(IsDataGridRowFocussedWhenSelectedProperty, value);
        }
    
        public static readonly DependencyProperty IsDataGridRowFocussedWhenSelectedProperty =
            DependencyProperty.RegisterAttached(
            "IsDataGridRowFocussedWhenSelected",
            typeof(bool),
            typeof(DataGridRowBehavior),
            new UIPropertyMetadata(false, OnIsDataGridRowFocussedWhenSelectedChanged));
    
        static void OnIsDataGridRowFocussedWhenSelectedChanged(
          DependencyObject depObj, DependencyPropertyChangedEventArgs e)
        {
            DataGridRow item = depObj as DataGridRow;
            if (item == null)
                return;
    
            if (e.NewValue is bool == false)
                return;
    
            if ((bool)e.NewValue)
                item.Selected += OndataGridRowSelected;
            else
                item.Selected -= OndataGridRowSelected;
        }
        static void OndataGridRowSelected(object sender, RoutedEventArgs e)
        {
            DataGridRow row = e.OriginalSource as DataGridRow;
            // If focus is already on a cell then don't focus back out of it
            if (!(Keyboard.FocusedElement is DataGridCell) && row != null)
            {
                row.Focusable = true;
                Keyboard.Focus(row);
            }
        }
    }
    

    Obviously, this is applied to the datagridrow

            <DataGrid.RowStyle>
                <Style TargetType="{x:Type DataGridRow}">
                    <Setter Property="support:DataGridRowBehavior.IsDataGridRowFocussedWhenSelected" Value="true"/>
                </Style>
            </DataGrid.RowStyle>