Implementing single cell copy and full row Item Selection in WPF DataGrid

December 14, 2013

Recently, I came across a strange problem with a DataGrid while using the MVVM-Light pattern. The problem was that I needed to implement two, seemingly similar pieces of functionality:

  1. To copy any given cell in the data grid
  2. To search the database, based on a specific piece of data in the selected row of the datagrid (regardless of which cell was selected).

To implement the Copy feature was unbelievably easy - you don’t even need a custom command; just add the following setting to the DataGrid:


SelectionUnit ="CellOrRowHeader"

And the following context menu:


                       
                          
    

And the copy is done!

So, next, I wanted to implement the custom search. I needed to identify the currently selected row in my ViewModel, so I added a new property:


        private MyDataClass \_selectedItem;
        public MyDataClass SelectedItem
        {
            get { return \_selectedItem; }
            set
            {
                \_selectedItem = value;
                RaisePropertyChanged(() => SelectedItem);
            }
        }

I then bound this to the selected item of the datagrid, like this:


     < DataGrid ItemsSource ="{ Binding Path =MyDataCollection}"                   
                  SelectedItem="{ Binding Path =SelectedItem}"                   
                  SelectionUnit ="CellOrRowHeader"  

And finally, created my command:


         public RelayCommand MyCommand { get ; private set ; }

In the constructor, initialised it:


     this.MyCommand = new RelayCommand (() => this.MyCommandFunc());

Created the function:


         private void MyCommandFunc()
        {
            QueryDB(SelectedItem.ValueOne);
        }         

And, finally, setup the context menu:


                       
                            
         
    

But this didn’t work! (Queue dramatic music).

The problem is down to this:


SelectionUnit ="CellOrRowHeader"

So, change it to:


SelectionUnit="FullRow" 

And it works again… but now copy doesn’t work, because it copies the FULL ROW!

So, the two features are incompatible?

I did find a workaround which doesn’t breach the separation of concerns. Please note that, although this does work, and does not breach separation of concerns, it may or may not be the best way of doing it. If you have, or know of, a better way the PLEASE leave a comment, or mail me and let me know.

The Workaround

Instead of binding the SelectedItem property, bind the Tag property of the grid:


     < DataGrid ItemsSource ="{ Binding Path =MyDataCollection}"                   
                          Tag="{ Binding Path =SelectedItem}"                   
                  SelectionUnit ="CellOrRowHeader"  

Now, we need a little code-behind, which must remain agnostic to the ViewModel:


         private void DataGrid\_SelectedCellsChanged( object sender, SelectedCellsChangedEventArgs e)
        {
            (( DataGrid)sender).Tag = ((DataGrid)sender).SelectedCells.FirstOrDefault().Item;
        }

Now, because the SelectedItem is bound in the view to the Tag, updating the Tag, updates the SelectedItem. This means that when you call your function (as described above):


         private void MyCommandFunc()
        {
            QueryDB(SelectedItem.ValueOne);
        }         

SelectedItem is now set.

Conclusion

Whilst I can see why this works the way it does, it doesn’t seem sensible that I need to jump through so many hoops to get this to work without breaking the separation of concern. It may just be a quirk with the DataGrid, but it feels like a hack - which I don’t like. Nevertheless, ATM it’s the only solution that I can come up with that doesn’t mean directly referencing the View from the ViewModel.



Profile picture

A blog about one man's journey through code… and some pictures of the Peak District
Twitter

© Paul Michaels 2024