4.4 - Drag and Drop#
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. 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