Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WPF

An alternate implementation to Paste data into the WPF DataGrid

5.00/5 (3 votes)
28 Aug 2011CPOL2 min read 30.1K  
Howto Paste and not make a mess

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.
C#
// 2-dim array containing clipboard data
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();

// the index of the first DataGridRow
int startRow = dataGrid.ItemContainerGenerator.IndexFromContainer(
    (DataGridRow)dataGrid.ItemContainerGenerator.ContainerFromItem
    (dataGrid.CurrentCell.Item));

// the destination rows 
//  (from startRow to either end or length of clipboard rows)
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();

// the destination columns 
//  (from selected row to either end or max. length of clipboard colums)
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)