Wednesday, October 19, 2011

DataGrid Support for Copy and Paste


This article describes how to add support for copy and paste to the Flex DataGrid component.  It enables one to copy and paste DataGrid selections to/from most desktop applications, notably spreadsheet apps like Excel.

The implementation enables copying the selected rows or cells to the clipboard in a conventional text format, and pasting similarly formatted clipboard text into a DataGrid.  The paste operation applies to the region of cells anchored by the origin of the current selection.

Providing generic support for copy and paste isn't trivial because the DataGrid's data "model" is just a list of Object valued data items.  DataGrid columns typically corespond to data item properties - named by the column's dataField - however the type of the item property displayed in each column isn't specified explicitly.  Even if the type was specified explictly, there is a vast number of data interchange formats one could choose to support and providing a transcoder for any one of them could be a great deal of work.  To keep this example manageable and to ensure the broadest support for its clipboard format, we'll just transcode the DataGrid selection as tab separated values.

To prepare to copy the selected cells to the clipboard we'll convert the dataField property for each selected column in each selected row to a string, using the Object toString() method.

var column:GridColumn = columns.getItemAt(cp.columnIndex) as GridColumn;
var value:Object = dataProvider.getItemAt(rowIndex)[column.dataField];
text += exportText(value, column);  // returns value.toString()

A "fieldSeparator" tab character is inserted in the text in between column values, and a "rowSeparator" newline character is appended to the text at the end of each row.   The resulting text is added to the clipboard as text, using the Flash clipboard API:

Clipboard.generalClipboard.clear();
Clipboard.generalClipboard.setData(ClipboardFormats.TEXT_FORMAT, text);

The clipboard is cleared first so that applications which support multiple formats will not be misled by data copied to the clipboard from other sources.

Handling the paste operation is a little bit more complicated because we need to convert the incoming strings to data item property values of the correct type.   We're not trying to handle structured data, so it's only necessary to detect each target property's primitive type.   The name of the type of a data item property named dataField can be discovered (not terribly efficiently) with the describeType() function and some E4X expressions that check for the definition of a public variable or a read/write accessor:

private function columnTypeName(item:Object, column:GridColumn):String
{
    const dataField:String = column.dataField;
    const itd:XML = describeType(item);  // "item type description"
    const variableType:XMLList = itd.variable.(@name == dataField).@type;

    if (variableType.length() == 1)
        return variableType.toString();

    const accessorType:XMLList = 
        itd.(@name == dataField && @access=="readwrite").@type;

    if (accessorType.length() == 1)
        return accessorType.toString();
    
    return null;
}

The code that handles actually pasting the clipboard text into the DataGrid only recognizes the primitive ActionScript types: the numbers, Boolean, String.

If this code were to become a feature of the Flex DataGrid, we'd want to provide a way for developers to change how column values are converted to and from strings.   The example's exportText() and importText() methods are an example of how this might be done.  The exportText method allows one to change how a column's value for a dataProvider is converted to a String, and the importText method enables one to change how an incoming string value for a column is stored in the corresponding data item:

exportText(item:Object, column:GridColumn):String
importText(item:Object, column:GridColumn, text:String):void

By redefining these methods, one could add support for non-primitive datatypes so long as all sources of such clipboard data cooperated.  It would also be possible to convert a different data item property than the one specified by column.dataField.   For example a column whose datafield specified an image valued property might redefine import,exportText() to manage a URL for the resource, rather than the image itself.

There's one other implementation detail that's worth mentioning: listening for the copy and paste keyboard events. You might think that adding a keyDown listener and checking for control-C and control-V keyboard events would be the thing to do (I did).  You would be mistaken (I was).   In fact you might be confused by the fact that although most keyboard events are delivered to the keyDown listener, control-C and control-V are not (I have  may have indulged in some swearing).   It turns out that the events to listen for are Event.COPY and Event.PASTE and the Flash player deals with detecting platform-specific key sequences like command-C, and command-V and mapping them to "copy" and "paste" events (much to my relief).

Here is the source code for the example: DataGridCopyPasteExample.mxml, and here's the example itself.  Try copying and pasting, from this DataGrid to other desktop applications, and from desktop applications that  add tab-separated grid data to the clipboard, to this DataGrid.

6 comments:

  1. Excellent code! Thanks for sharing.

    ReplyDelete
  2. WOW i could reorder columns!!! this is not in 4.6 sdk for sure)
    Hans, are u using trunk version for samles? I am interested in column reordering and locking column/rows, should i implement it myself or can just wait for this to be done?

    ReplyDelete
    Replies
    1. VERSION:String="5.0.0.0";
      oh ... when?

      Delete
    2. Yes, the example was built with the version of Flex5 we were working on in late 2011. It should work equally well on 4.6.

      Delete
  3. Mr. Hans

    Please can you make an example of a DataGrid, which being located in a cell by pressing the ENTER key moves to the next cell in the same line and when we reached the last cell in the cell line go to the next line. I want to change the TAB key by ENTER key in the movement between cells of the DataGrid.

    ReplyDelete