Tuesday, July 29, 2008

Hillegass - NSUndoManager

In Ch. 8-10 of Hillegass, an app called RaiseMan is developed. It has an NSTableView bound to an array called "employees" which contains Person objects with personName and expectedRaise attributes. It's a document-based application. I'm pretty clear about table views, but I wanted to learn how "undo" works, so I translated the example to Python and PyObjC. However it doesn't seem to work when using an NSArrayController as described for the example. I confirmed that it does work as expected when written in Objective C.

In the first iteration of the project (Ch. 8) everything is standard. The Document class has this method:

- (void)setEmployees:(NSMutableArray *)a
if (a == employees)
[a retain];
[employees release];
employees = a;

In implementing the version with "Undo", two new methods are added. The first one is:

- (void)insertObject:(Person *)p inEmployeesAtIndex:(int)index
NSLog(@"adding %@ to %@", p, employees);
NSUndoManager *undo = [self undoManager];
[[undo prepareWithInvocationTarget:self]
if (![undo isUndoing]) {
[undo setActionName:@"Insert Person"];
[employees insertObject:p atIndex:index];

The second method (not shown) handles remove in a similar way. Interestingly, the addition of this code changes the behavior of the NSArrayController. It no longer calls the set method for the employees property but instead calls the new method. As Hillegass says, "this happens automatically." That is, the target / selector that are invoked after you send an NSArrayController an insert: action are supposed to be set automatically (where?). If you look in IB you'll see "System Defined":

which suggest you shouldn't go messing around here.

So, the failure of this automatic redirection is not easily solved for the PyObjC example. I haven't been able to find an explanation for this yet. Perhaps there is an example on the web. And since, in Ch. 12 he lets out the secret (that Undo is handled automatically if you use CoreData), I probably don't need to solve this.

However, I did get Undo working in PyObjC, and I'll show that in my next post.