Pasting content to new rows on the WPF DataGrid


On a previous post I talked about how to implement Clipboard.Paste for the DataGrid.  The functionality was such that you could only paste to existing rows and cells.  I got a customer request on how to paste to new rows so I thought I’d capture it here. 


If you want to be able to paste to new rows, you just need to call IEditableCollectionView.AddNew before pasting to that row and optionally update selection.  Here is the updated OnExecutePaste code to the previous sample,



protected virtual void OnExecutedPaste(ExecutedRoutedEventArgs args)


{       


  // parse the clipboard data


  List<string[]> rowData = ClipboardHelper.ParseClipboardData();


 


  bool hasAddedNewRow = false;


 


  // call OnPastingCellClipboardContent for each cell


  int minRowIndex = Items.IndexOf(CurrentItem);


  int maxRowIndex = Items.Count – 1;


  int minColumnDisplayIndex = (SelectionUnit != DataGridSelectionUnit.FullRow) ? Columns.IndexOf(CurrentColumn) : 0;


  int maxColumnDisplayIndex = Columns.Count – 1;


 


  int rowDataIndex = 0;


  for (int i = minRowIndex; i <= maxRowIndex && rowDataIndex < rowData.Count; i++, rowDataIndex++)


  {


    if (CanUserPasteToNewRows && CanUserAddRows && i == maxRowIndex)


    {


        // add a new row to be pasted to


        ICollectionView cv = CollectionViewSource.GetDefaultView(Items);


        IEditableCollectionView iecv = cv as IEditableCollectionView;


        if (iecv != null)


        {


          hasAddedNewRow = true;


          iecv.AddNew();


          if (rowDataIndex + 1 < rowData.Count)


          {


             // still has more items to paste, update the maxRowIndex


             maxRowIndex = Items.Count – 1;
          }
        }


    }


    else if (i == maxRowIndex)


    {


          continue;
    }


 


    int columnDataIndex = 0;


    for (int j = minColumnDisplayIndex; j < maxColumnDisplayIndex && columnDataIndex < rowData[rowDataIndex].Length; j++, columnDataIndex++)


    {


      DataGridColumn column = ColumnFromDisplayIndex(j);


      column.OnPastingCellClipboardContent(Items[i], rowData[rowDataIndex][columnDataIndex]);


    }


  }


 


  // update selection


  if (hasAddedNewRow)


  {
    UnselectAll();


    UnselectAllCells();


    CurrentItem = Items[minRowIndex];


    if (SelectionUnit == DataGridSelectionUnit.FullRow)


    {


      SelectedItem = Items[minRowIndex];


    }


    else if (SelectionUnit == DataGridSelectionUnit.CellOrRowHeader || SelectionUnit == DataGridSelectionUnit.Cell)


    {
      SelectedCells.Add(new DataGridCellInfo(Items[minRowIndex],   Columns[minColumnDisplayIndex]));
    }
  }


}


 


So with this new logic you can paste a set of rows starting at the NewItemPlaceholder and it will create a set of new rows where all the content will be pasted.  For the selection logic, I just made it pretty basic and didn’t really enforce any specific requirements.  Other than that, I just added one DP, CanUserPasteToNewRows, just to turn it on and off for this example.  Here is the updated solution. 


 

DataGrid_V1_ClipboardPaste_Sample2.zip

Comments (12)

  1. Ron Larvick says:

    In your solution here, could you tell me how I would set a new value for the first hidden column of each new row?  Lets say I have OrderID, which is a GUID, and it is hidden.  When I paste in 10 new rows, all of them need to create a new Guid for that row.  To make it generic enough, I need to be able to always just set the value of the first column in every new row, rather than by name, since it might change.

  2. Ron Larvick,

    In the OnExecutePaste method you can hard code setting the first column to new GUIDs.  Alternatively, you can set new GUIDs when a new row is created through the InitializingNew event.  That would make more sense since the paste code calls that.

  3. Ron Larvick says:

    Can I ask you what would be the easiest way to paste text from a clipboard into a DataGridComboColumn and have it select the item which matches the text being pasted?

  4. Ron,

    The current functionality doesn’t do this?

  5. Varada says:

    Vincent, the technique works fine for the set of Columns shipped by the Toolkit. But if I have a column built on top of DataGridTemplateColumn (which does not derive from DataGridBoundColumn, then the OnPastingCellClipboardContent method just returns.

    Any other hints to help do the same?

  6. mb says:

    This isn’t invoking CellEditEnding or RowEditEnding events.  How can we invoke those upon paste?

  7. mb says:

    Oh–and calling CommitEdit on the grid isn’t doing it either.

  8. mb,

    To do that, you’d have to override column.OnPastingCellClipboardContent and to a BeginEdit and CommitEdit operation when updating the data.

  9. KlausG says:

    Isn’t the column loop too short by one, i.e. shouldn’t it be "j <= maxColumnDisplayIndex" instead of "j < maxColumnDisplayIndex"?

    Also, at least with the .NET 4 version of the DataGrid, I got better results when replacing the IEditableCollectionView use by BeginEditCommand like this:

    for (int i = minRowIndex; …)

    {

       CurrentItem = Items[i];

       BeginEditCommand.Execute(null, this);

       … column loop …

       CommitEditCommand.Execute(null, this);

       if (i == maxRowIndex)

       {

           maxRowIndex++;

           hasAddedNewRow = true;

       }

    }

  10. ZMAng says:

    Hi Vincent. Thanks for the great article and example. But like Varada, I was wondering whether there is a solution on getting this to work with DataGridTemplateColumns, and in particular with those containing WPF AutoCompleteBoxes.

    Thanks.

    ZM

  11. JerryZ says:

    Hi,

    Great sample.

    I have a problem with DataGridTemplateColumn.

    column.OnPastingCellClipboardContent(Items[i], rowData[rowDataIndex][columnDataIndex]);

    doesn’t do anything.

    Any suggestion?

    Jerry