DataGrid and SelectedIndex

Jan 6, 2010 at 12:15 PM
Edited Jan 6, 2010 at 12:34 PM

I have a datagrid defined as follows:

 

 

        <v:MyDataGrid ItemsSource="{Binding List}" SelectionMode="Single" SelectedIndex="{Binding SelectedIndex}" IsReadOnly="True" SelectionUnit="FullRow"
                ui:DataGridBehavior.ColumnDefinitions="{Binding ColumnDe finitions}"
                FrozenColumnCount="{Binding ColumnDefinitions.FrozenColumnCount}"
                >

 

        <toolkit:DataGrid ItemsSource="{Binding List}" SelectionMode="Single" SelectedIndex="{Binding SelectedIndex}" IsReadOnly="True" SelectionUnit="FullRow">

...

As you can see, ItemsSource and SelectedIndex are bound to List, resp. SelectedIndex of the viewmodel (with INotifyPropertyChanged implemented)

There is a 2 way binding for SelectedIndex and it works: when I change the property in my viewmodel as a result of a command (for example), the row is selected correctly.

The problem is that when the control is initially loaded, the selection does not work. The list is displayed (ItemsSource contains the correct value), but no row is selected even though SelectedIndex contains the correct value.

The problem also exhibits itself when the ItemsSource and SelectedIndex are rebound. 

 

 

Anybody has an idea on how to circumvent this problem?

 

I noticed http://wpf.codeplex.com/WorkItem/View.aspx?WorkItemId=10659, which seems to be much of the same thing.

Jan 6, 2010 at 1:52 PM

I might as well anwer my own question.

I am using the datagrid in 'pure mvvm" mode, including the necessary plumbing to allow the Columns collection to be bound to a viewmodel (which does not reference the control).

There is a problem when using SelectedIndex to set the row selection in 2 cases:

- when an ItemsSource is changed (if Selectedndex==0, it is not shown). Since we cannot specify the binding order on properties, this is a problem

- when the ItemsSource is initially bound when the Columns collection is empty. Adding columns doesn't make the selection appear.

The workaround is to derive from Datagrid as follows:

 

    public class MyDataGrid : DataGrid
    {
        public MyDataGrid()
        {
            Loaded += MyDataGrid_Loaded;    // BUGBUG
            Unloaded += MyDataGrid_Unloaded;
            Columns.CollectionChanged += Columns_CollectionChanged;
        }

        private void Columns_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            // BUGBUG: if the columns collection is updated after the itemssource, the selection isn't shown
            if (SelectedIndex >= 0)
                Dispatcher.Invoke((Action)delegate
                {
                    var index = SelectedIndex;
                    SelectedIndex = -1;
                    SelectedIndex = index;
                    UpdateLayout();
                }, DispatcherPriority.Background);
        }

        private void MyDataGrid_Loaded(object sender, RoutedEventArgs e)
        {
            // BUGBUG: for some reason, the DataGrid will not update correctly when the datasource is populated while the control isn't loaded yet.
            // this seems to be a valid workaround.
            Items.Refresh();
        }

        private void MyDataGrid_Unloaded(object sender, RoutedEventArgs e)
        {
            INotifyCollectionChanged nco = ItemsSource as INotifyCollectionChanged;
            if (nco != null)
                nco.CollectionChanged -= ItemsSource_CollectionChanged;
        }

        protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
        {
            // keep track of itemssource changes and refresh the corresponding view if the collection changes.
            // this allows us to signal global operations (like show/hide row details) at the ItemsSource level, and keep the view in sync.
            INotifyCollectionChanged nco = oldValue as INotifyCollectionChanged;
            if (nco != null)
                nco.CollectionChanged -= ItemsSource_CollectionChanged;
            nco = newValue as INotifyCollectionChanged;
            if (nco != null)
                nco.CollectionChanged += ItemsSource_CollectionChanged;
            base.OnItemsSourceChanged(oldValue, newValue);
        }


        private void ItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            Items.Refresh();
            // BUGBUG: if the itemssource is updated, the selection corresponding to selectedIndex isn't shown
            UpdateLayout();
        }
    }

I hope, quite frankly, that the next update of the DataGrid will be of higher quality and more MVVM-friendly.

 

 

 

 

    public class MyDataGrid : DataGrid
    {
        public MyDataGrid()
        {
            Loaded += MyDataGrid_Loaded;    // BUGBUG
            Unloaded += MyDataGrid_Unloaded;
            Columns.CollectionChanged += Columns_CollectionChanged;
        }
        private void Columns_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            // BUGBUG: if the columns collection is updated after the itemssource, the selection isn't shown
            if (SelectedIndex >= 0)
                Dispatcher.Invoke((Action)delegate
                {
                    var index = SelectedIndex;
                    SelectedIndex = -1;
                    SelectedIndex = index;
                    UpdateLayout();
                }, DispatcherPriority.Background);
        }
        private void MyDataGrid_Loaded(object sender, RoutedEventArgs e)
        {
            // BUGBUG: for some reason, the DataGrid will not update correctly when the datasource is populated while the control isn't loaded yet.
            // this seems to be a valid workaround.
            Items.Refresh();
        }
        private void MyDataGrid_Unloaded(object sender, RoutedEventArgs e)
        {
            INotifyCollectionChanged nco = ItemsSource as INotifyCollectionChanged;
            if (nco != null)
                nco.CollectionChanged -= ItemsSource_CollectionChanged;
        }
        protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
        {
            // keep track of itemssource changes and refresh the corresponding view if the collection changes.
            // this allows us to signal global operations (like show/hide row details) at the ItemsSource level, and keep the view in sync.
            INotifyCollectionChanged nco = oldValue as INotifyCollectionChanged;
            if (nco != null)
                nco.CollectionChanged -= ItemsSource_CollectionChanged;
            nco = newValue as INotifyCollectionChanged;
            if (nco != null)
                nco.CollectionChanged += ItemsSource_CollectionChanged;
            base.OnItemsSourceChanged(oldValue, newValue);
        }
        private void ItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            Items.Refresh();
            // BUGBUG: if the itemssource is updated, the selection corresponding to selectedIndex isn't shown
            UpdateLayout();
        }
    }Frankl