Drag and drop in table

Drag and drop in general is easy, but even easier in NSTableView. Here is an article about it: Drag and Drop Destinations. Basically, drag and drop is copy and paste with graphic interactivity. Therefore, it involves the pasteboard. I'll add the fuction of drag and drop in this tutorial so that you can drag and drop in-between different document. NSTableView has its built-in drag and drop support. Therefore, it is sort of different from drag and drop in general. Here is the drag and drop in NSTableView from Cocoa.

Firstly, I need to set up the dragging source, the source you can drag something out. I need to register the table as dragging source, and implement one method.

Document.m:

- (void) windowControllerDidLoadNib: (NSWindowController *) controller
{
   NSTableColumn *column;
   NSArray *columns = [tableView tableColumns];

   column = [columns objectAtIndex: 0];
   [column setWidth: 100];
   [column setEditable: YES];
   [column setResizable: YES];
   [column setIdentifier: @"date"];
   [[column headerCell] setStringValue: @"Date"];

   column = [columns objectAtIndex: 1];
   [column setWidth: 100];
   [column setEditable: YES];
   [column setResizable: YES];
   [column setIdentifier: @"item"];
   [[column headerCell] setStringValue: @"Item"];

   column = [[NSTableColumn alloc] initWithIdentifier: @"amount"];
   [column setWidth: 100];
   [column setEditable: YES];
   [column setResizable: YES];
   [[column headerCell] setStringValue: @"Amount"];
   [tableView addTableColumn: column];
   RELEASE(column);

   [tableView sizeLastColumnToFit];
   [tableView setAutoresizesAllColumnsToFit: YES];
   
   [tableView registerForDraggedTypes: [NSArray arrayWithObjects: NSGeneralPboardType, nil]];
}

- (BOOL) tableView: (NSTableView *) view
         writeRows: (NSArray *) rows
         toPasteboard: (NSPasteboard *) pboard
{
   id object = [records objectAtIndex: [[rows lastObject] intValue]];
   NSData *data = [NSArchiver archivedDataWithRootObject: object];

   [pboard declareTypes: [NSArray arrayWithObject: @"NSGeneralPboardType"]
                                            owner: nil];
   [pboard setData: data forType: @"NSGeneralPboardType"];
   return YES;
}

In method -windowControllerDidLoadNib:, NSTableView register what kind of pasteboard it will use. Once it registers, it is ready to drag and drop. The usage of pasteboard is not covered in this tutorial because it is too easy. Basically, you set the type of pasteboard, put data in, and take it out.

Method -tableView:writeRows:toPasteboard will be called when user try to drag some rows out of the table, It is the methods in the data source of table, not in the delegate of table. Therefore, in this method, I only need to put the data into pasteboard. Since this table only allow single selection, I only need to handle the data in one row, which is a NSDictionary. I archive NSDictionary into NSData, put it into pasteboard of NSGeneralPboardType, then return YES. Now, you can drag the row out of the table.

NSTableView will handle all the graphic interactivity. When users drop the data into another document, I need to implement two methods.

Document.m:

- (NSDragOperation) tableView: (NSTableView *) view
                    validateDrop: (id <NSDraggingInfo>) info
                    proposedRow: (int) row
                    proposedDropOperation: (NSTableViewDropOperation) operation
{
   if (row > [records count])
   return NSDragOperationNone;

   if (nil == [info draggingSource]) // From other application
     {
       return NSDragOperationNone;
     }
   else if (tableView == [info draggingSource]) // From self
     {
       return NSDragOperationNone;
     }
   else // From other documents 
     {
       [view setDropRow: row dropOperation: NSTableViewDropAbove];
       return NSDragOperationCopy;
     }
}

- (BOOL) tableView: (NSTableView *) view
         acceptDrop: (id <NSDraggingInfo>) info
         row: (int) row
         dropOperation: (NSTableViewDropOperation) operation
{
   NSPasteboard *pboard = [info draggingPasteboard];
   NSData *data = [pboard dataForType: @"NSGeneralPboardType"];

   if (row > [records count])
     return NO;

   if (nil == [info draggingSource]) // From other application
     {
       return NO;
     }
   else if (tableView == [info draggingSource]) // From self
     {
       return NO;
     }
   else // From other documents
     {
       id object = [NSUnarchiver unarchiveObjectWithData: data];
       [records insertObject: object atIndex: row];
       [tableView reloadData];

       return YES;
     }
   return NO;
}

When users hold the mouse button and move above the table, the table will keep receiving the call for method -tableView:validateDrop:proposedRow:ProposedDropOperation:. The only thing I need to do in this method is to examine the proposed drop and return the action I can accept. If the source is not from other documents, return NSDragOperationNone to indicate that this table doesn't accept drop. Otherwise, return NSDragOperationCopy to indicate that this table accept copy. In table, you can drop between rows or over rows. That's NSTableViewDropOperation means. I only want to insert the data. When it is proposed to drop, I change the propose by -setDropRow:dropOperation:. By this way, I can change all the drop in-between rows, not over rows. So this method gives the table chances to change its interface when mouse is moving above it.

Once users release the mouse button, method -tableView:acceptDrop:row:dropOperation: will be called. I get the dropped data from this method. Extract the data from pbastebord, unarchive into NSDictionary, and add into records. Don't forget to reload the table to reflect the change.

NSTableView offers the basic fuction of drag and drop, which is useful for general applications. If you want more support for drag and drop, you have to subclass NSTableView and write your own using the general drag and drop support of GNUstep. For example, NSTableView doesn't support drag and drop from other application. If you want to do that, you have to subclass NSTableView. I'll talk about the drag and drop in general later.

Here is the source code: DragDropInTable-src.tar.gz