Introduction
I was inspired by the article
Implementing Copy & Paste for WPF DataGrid .NET 4[
^] to have a go at an alternate implementation.
The solution in the above article parses the clipboard and copies the data into the model.
That technique has several drawbacks. The first is that it easily gets confused when column/rows are filtered or sorted. The second drawback is that it needs converters to convert the string data from the clipboard to the proper type required by the model.
The function presented here tries to circumvent those types of problems by copying the data back into the grid instead of the model. The tricky part of the implementation is to figure out how the cells are laid out on the screen so that the data from the clipboard gets pasted where expected.
Using the code
I’ll start with the code and then try to explain what the function does.
string[][] clipboardData =
((string)Clipboard.GetData(DataFormats.Text)).Split('\n')
.Select(row =>
row.Split('\t')
.Select(cell =>
cell.Length > 0 && cell[cell.Length - 1] == '\r' ?
cell.Substring(0, cell.Length - 1) : cell).ToArray())
.Where(a => a.Any(b => b.Length > 0)).ToArray();
int startRow = dataGrid.ItemContainerGenerator.IndexFromContainer(
(DataGridRow)dataGrid.ItemContainerGenerator.ContainerFromItem
(dataGrid.CurrentCell.Item));
DataGridRow[] rows =
Enumerable.Range(
startRow, Math.Min(dataGrid.Items.Count, clipboardData.Length))
.Select(rowIndex =>
dataGrid.ItemContainerGenerator.ContainerFromIndex(rowIndex) as DataGridRow)
.Where(a => a != null).ToArray();
DataGridColumn[] columns =
dataGrid.Columns.OrderBy(column => column.DisplayIndex)
.SkipWhile(column => column != dataGrid.CurrentCell.Column)
.Take(clipboardData.Max(row => row.Length)).ToArray();
for (int rowIndex = 0; rowIndex < rows.Length; rowIndex++)
{
string[] rowContent = clipboardData[rowIndex];
for (int colIndex = 0; colIndex < columns.Length; colIndex++)
{
string cellContent =
colIndex >= rowContent.Length ? "" : rowContent[colIndex];
columns[colIndex].OnPastingCellClipboardContent(
rows[rowIndex].Item, cellContent);
}
}
Note that the codeblock above needs a
DataGrid
named
dataGrid
to compile. The most sensible thing to do with above code is to pack it into a handler for the Ctrl-V key or into a handler for
ApplicationCommands.Paste
. I’m not showing that code here because it can be easily looked up elsewhere on the net.
The first step is to parse the clipboard and create a 2-dimensional array of strings (
string[][] clipboardData
) we want to copy into the grid.
Next we need the first index (as on the screen) for the row currently selected. The result is stored in
startRow
for later use.
The function
ContainerFromIndex
is then used to create the destination rows (
DataGridRow[] rows
). The indexed function is used because we need the array ordered as laid out on the screen.
Now the same for the columns is necessary (
DataGridColumn[] columns
). Here the important part is the sorting by
DisplayIndex
. By doing that, invisible columns are dropped (because they have an index of -1). The
Take
filter function is called because we need no more columns then we have in the clipboard.
The only thing left to do is to iterate over all cells and copy the content from the parsed clipboard (
clipboardData
) into the DataGrid cell. The function that does that for us is
OnPastingCellClipboardContent
. The nice thing about that function is that is uses normal WPF binding (including type converters) to convert the string passed in to the type required by the model.
Wrap-Up
The code above is far from finished and is definitely not capable of handling all expected situations. It’s more of starting point to show that there is an alternate way of implementing a Paste function for the WPF DataGrid.
While already finished writing that post, I found
http://blogs.msdn.com/b/vinsibal/archive/2008/09/19/wpf-datagrid-clipboard-paste-sample.aspx[
^] which uses the same technique for handling columns and copying but handles rows differently. I'll post the article anyway because I think it has less bugs ;) and some people might find the functional approach more readable.