Reputation: 20384
I need to implement a "copy value to clipboard" function for a WPF DataGrid that should be available through all common channels: Right-click context menu item; menu-key context menu item; Ctrl+C hotkey. Data grid content comes from data binding, but since the copy command is a view-only thing, it's implemented completely in the view layer, not the viewmodel. So I don't use ICommand for this but only event handlers in code behind.
The DataGrid's SelectionUnit is set to FullRow, but the arrow navigation keys still work and the focus rectangle can be seen for single cells. So a single cell can be focused while a full row is selected. For this command I need to determine the focused cell.
The menu item is already pretty complicated. Its Click event gives me the MenuItem instance, from which I can navigate to the ContextMenu and further to the DataGrid. That's all, no clicked-on cell. I already know how to get the DataGrid, there's only one of them visible at a time.
I now need to find out which cell is focused. But I can't even get a list of selected cells or rows or even all rows. All properties I can find just point to some fragments of the information, and DataGrid.Rows just doesn't exist. I could scan the entire visual tree for some focused DataGridCell but that's probably not very efficient.
Any ideas?
Later I also need a second function "copy selected rows to clipboard" that copies the values from all cells in all selected rows in a format that can be pasted to Excel (tab-delimited lines).
Upvotes: 1
Views: 2663
Reputation: 2504
A helper function:
/// <summary>
/// Look for child element
/// </summary>
/// <typeparam name="childItem">Child Item</typeparam>
/// <param name="obj">Dependency Object</param>
/// <returns>The child or null</returns>
private childItem FindVisualChild<childItem>(DependencyObject obj) where childItem : DependencyObject
{
if (obj == null)
{
return null;
}
int childCount = VisualTreeHelper.GetChildrenCount(obj);
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is childItem)
{
return (childItem)child;
}
else
{
childItem childOfChild = this.FindVisualChild<childItem>(child);
if (childOfChild != null)
{
return childOfChild;
}
}
}
return null;
}
After the DataGrid is loaded, you can call this:
/// <summary>
/// Get the cell of the datagrid.
/// </summary>
/// <param name="dataGrid">The data grid in question</param>
/// <param name="cellInfo">The cell information for a row of that datagrid</param>
/// <param name="cellIndex">The row index of the cell to find. </param>
/// <returns>The cell or null</returns>
private DataGridCell TryToFindGridCell(DataGrid dataGrid, DataGridCellInfo cellInfo, int cellIndex = -1)
{
DataGridRow row;
DataGridCell result = null;
if (cellIndex < 0)
{
row = (DataGridRow)dataGrid.ItemContainerGenerator.ContainerFromItem(cellInfo.Item);
}
else
{
row = (DataGridRow)dataGrid.ItemContainerGenerator.ContainerFromIndex(cellIndex);
}
if (row != null)
{
int columnIndex = dataGrid.Columns.IndexOf(cellInfo.Column);
if (columnIndex > -1)
{
DataGridCellsPresenter presenter = this.FindVisualChild<DataGridCellsPresenter>(row);
if (presenter != null)
{
result = presenter.ItemContainerGenerator.ContainerFromIndex(columnIndex) as DataGridCell;
}
else
{
result = null;
}
}
}
return result;
}
Then after you get your cell, just check the IsFocused property.
Upvotes: 2