What is the appropriate way to get rearrangeObjects to be sent to an NSTreeController after changes to nodes in the tree? I have a sample application (full code below) using an NSOutlineView and NSTreeController with a simple tree of Node objects.
In Version1 of the app, when you edit the name of a Node, the tree doesn't get resorted until you click the column header or use the “Rearrange” item in the menu. The latter is set up to directly send rearrangeObjects to the NSTreeController.
In Version2, I tried sending rearrangeObjects from the Node's setName: method. This doesn't seem like a good solution because it means the model now has knowledge of the view/controller. Also, it has the side effect that the outline view loses focus after the rename (if you select a Node and edit its name, the selection bar turns from blue to gray) for some reason (why is that?).
NSArrayController has a method setAutomaticallyRearrangesObjects: but NSTreeController does not? So what is the appropriate way to solve this?
/* example.m
Compile version 1:
gcc -framework Cocoa -o Version1 example.m
Compile version 2:
gcc -framework Cocoa -o Version2 -D REARRANGE_FROM_SETNAME example.m
*/
#import <Cocoa/Cocoa.h>
NSTreeController *treeController;
NSOutlineView *outlineView;
NSScrollView *scrollView;
@interface Node : NSObject {
NSString *name;
NSArray *children;
}
@end
@implementation Node
- (id) initWithName: (NSString*) theName children: (id) theChildren
{
if (self = [super init]) {
name = [theName retain];
children = [theChildren retain];
}
return self;
}
- (void) setName: (NSString*) new
{
[name autorelease];
name = [new retain];
#ifdef REARRANGE_FROM_SETNAME
[treeController rearrangeObjects];
#endif
}
@end
NSArray *createSortDescriptors()
{
return [NSArray arrayWithObject: [NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES]];
}
void createTheTreeController()
{
Node *childNode1 = [[[Node alloc] initWithName:@"B" children:[NSArray array]] autorelease];
Node *childNode2 = [[[Node alloc] initWithName:@"C" children:[NSArray array]] autorelease];
Node *childNode3 = [[[Node alloc] initWithName:@"D" children:[NSArray array]] autorelease];
Node *topNode1 = [[[Node alloc] initWithName:@"A" children:[NSArray arrayWithObjects:childNode1,childNode2,childNode3,nil]] autorelease];
Node *topNode2 = [[[Node alloc] initWithName:@"E" children:[NSArray array]] autorelease];
Node *topNode3 = [[[Node alloc] initWithName:@"F" children:[NSArray array]] autorelease];
NSArray *topNodes = [NSArray arrayWithObjects:topNode1,topNode2,topNode3,nil];
treeController = [[[NSTreeController alloc] initWithContent:topNodes] autorelease];
[treeController setAvoidsEmptySelection:NO];
[treeController setChildrenKeyPath:@"children"];
[treeController setSortDescriptors:createSortDescriptors()];
}
void createTheOutlineView()
{
outlineView = [[[NSOutlineView alloc] initWithFrame:NSMakeRect(0, 0, 284, 200)] autorelease];
[outlineView bind:@"content" toObject:treeController withKeyPath:@"arrangedObjects" options:nil];
[outlineView bind:@"sortDescriptors" toObject:treeController withKeyPath:@"sortDescriptors" options:nil];
[outlineView bind:@"selectionIndexPaths" toObject:treeController withKeyPath:@"selectionIndexPaths" options:nil];
NSTableColumn *column = [[[NSTableColumn alloc] initWithIdentifier:@"NameColumn"] autorelease];
[[column headerCell] setStringValue:@"Name"];
[outlineView addTableColumn:column];
[outlineView setOutlineTableColumn:column];
[column bind:@"value" toObject:treeController withKeyPath:@"arrangedObjects.name" options:nil];
[column setWidth:250];
scrollView = [[[NSScrollView alloc] initWithFrame:NSMakeRect(0, 0, 300, 200)] autorelease];
[scrollView setDocumentView:outlineView];
[scrollView setHasVerticalScroller:YES];
}
void createTheWindow()
{
id window = [[[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 300, 200)
styleMask:NSTitledWindowMask backing:NSBackingStoreBuffered defer:NO]
autorelease];
[window cascadeTopLeftFromPoint:NSMakePoint(20,20)];
[window setTitle:@"Window"];
[window makeKeyAndOrderFront:nil];
[[window contentView] addSubview:scrollView];
}
void createTheMenuBar()
{
id menubar = [[NSMenu new] autorelease];
id appMenuItem = [[NSMenuItem new] autorelease];
[menubar addItem:appMenuItem];
[NSApp setMainMenu:menubar];
id appMenu = [[NSMenu new] autorelease];
#ifndef REARRANGE_FROM_SETNAME
id rearrangeMenuItem = [[[NSMenuItem alloc] initWithTitle:@"Rearrange"
action:@selector(rearrangeObjects) keyEquivalent:@"r"] autorelease];
[rearrangeMenuItem setTarget: treeController];
[appMenu addItem:rearrangeMenuItem];
#endif
id quitMenuItem = [[[NSMenuItem alloc] initWithTitle:@"Quit"
action:@selector(terminate:) keyEquivalent:@"q"] autorelease];
[appMenu addItem:quitMenuItem];
[appMenuItem setSubmenu:appMenu];
}
void setUpAutoReleasePoolAndApplication()
{
[NSAutoreleasePool new];
[NSApplication sharedApplication];
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
}
void activateAppAndRun()
{
[NSApp activateIgnoringOtherApps:YES];
[NSApp run];
}
int main(int argc, const char * argv[])
{
setUpAutoReleasePoolAndApplication();
createTheTreeController();
createTheOutlineView();
createTheWindow();
createTheMenuBar();
activateAppAndRun();
return 0;
}