Context
I have a DataGrid in a WPF MVVM application containing multiple rows from a bound collection. Each object in the collection has properties of different types e.g. Name is a string, InsertDate is a DateTime, etc.
For various reasons the DataGrid SelectionUnit needs to be set to "Cell".
Question
I'm setting up a key binding linked to a command that will set the currently selected cell to the value of the same cell in the row above. (I've already taken care of the case where there isn't a previous row).
What I've tried
I'm successfully passing the whole DataGrid into the Command as a CommandParameter thus:
<KeyBinding Command="{Binding RepeatFieldAboveCommand}"
CommandParameter="{Binding ., ElementName=identitiesGrid}"
Key="F5" />
...and then in the Command...
public override void Execute(object parameter)
{
var dataGrid = parameter as DataGrid;
}
I can then access objects bound to the current and previous rows with
Identity currentRow = dataGrid.CurrentItem as Identity;
int currentRowIndex = dataGrid.Items.IndexOf(dataGrid.CurrentItem);
Identity previousRow = dataGrid.Items[currentRowIndex - 1] as Identity;
I can also get the index of the selected column with
int displayIndex = dataGrid.CurrentColumn.DisplayIndex;
But I can't find a way of directly referencing the cell values in order to do the copy. I've considered passing something more useful through the CommandParameter but I can't think what.
The "cell values" are not just raw values. They are visual content created by the template associated with each type of column - e.g. DataGridTextColumn
contains a TextBlock
by default.
You can obtain the Content of a column using DataGridColumn.GetCellContent()
. For example, a DataGridTextColumn
:
TextBlock tb = dataGrid.Columns[x].GetCellContent(dataGrid.Items[y]) as TextBlock;
string theCellValue = tb.Text;
The above only works for DataGridTextColumn
and is clearly not robust if you have different types of columns, although in that case you could detect the type of the current column and use appropriate logic to get at its "value".
Another way is to obtain the bound property name and copy the value via your DataContext objects instead:
var currentItem = dataGrid.CurrentItem;
int currentRowIndex = dataGrid.Items.IndexOf(currentItem);
if (currentRowIndex <= 0) return;
var previousItem = dataGrid.Items[currentRowIndex - 1] as YourDataObject;
DataGridBoundColumn boundColumn = dataGrid.CurrentColumn as DataGridBoundColumn;
if (boundColumn != null)
{
var binding = boundColumn.Binding as Binding;
if (binding != null)
{
var propertyPath = binding.Path.Path;
var property = typeof(YourDataObject).GetProperty(propertyPath);
var value = property.GetValue(previousItem);
property.SetValue(currentItem, value);
}
}
However, this does not work for DatGridTemplateColumn
columns since they have no Binding
. A fallback in that scenario is to use SortMemberName
of the template column since you usually have this set to the appropriate property to make template columns sortable.
var currentItem = dataGrid.CurrentItem;
int currentRowIndex = dataGrid.Items.IndexOf(currentItem);
if (currentRowIndex <= 0) return;
var previousItem = dataGrid.Items[currentRowIndex - 1] as YourDataObject;
string propertyName = null;
DataGridBoundColumn boundColumn = dataGrid.CurrentColumn as DataGridBoundColumn;
if (boundColumn != null)
{
var binding = boundColumn.Binding as Binding;
if (binding != null)
{
propertyName = binding.Path.Path;
}
}
else if (dataGrid.CurrentColumn is DataGridTemplateColumn)
{
propertyName = dataGrid.CurrentColumn.SortMemberPath;
}
if (propertyName != null)
{
var property = typeof(YourDataObject).GetProperty(propertyName);
var value = property.GetValue(previousItem);
property.SetValue(currentItem, value);
}
There could be other considerations such as validity of cell values, editing mode, read only cells, and more complicated binding, specific to your case, which would also need to be handled.