Showing hierarchical data in the NSOutlineView
Today I want to talk about representing hierarchical data in a Mac application.
Cocoa offers the nice UI control NSOutlineView for this.
As the hierarchical data we can use a Data Abstract Schema instance which can contain various tables, fields, parameters, statements and many other objects.
Getting the DASchema
Retrieving the DASchema object from a Data Abstract server is quite simple:
NSString*serviceName =@"DASampleService"; NSString*urlString =@"http://MyServer:8099/bin"; NSURL*url =[NSURL URLWithString:urlString]; DARemoteDataAdapter *rda =[[DARemoteDataAdapter alloc] initWithTargetURL:url]; [[rda dataService] setServiceName:serviceName]; [rda beginGetSchemaWithBlock:^(DASchema *result){[self setSchema:result]; }]; |
The first thing we need to do is specify our tree-like hierarchy.
In order to simplify the sample, we will not implement the complete DASchema structure, only parts of it. The root object will be presented by the schema object with the tables and commands collection as children. Each table has a collection of fields as children.
So in addition to the existing DASchema elements, we need to declare some virtual elements in our tree. The schema, for example, has a collection of tables and commands. To avoid them being messed up, we want to have the collections separately, each in its own subfolder. The same is true for the fields collection inside each schema table.
These virtual classes should expose their name, note, icon and collection of children. All this info will be used by NSOutlineView for building the tree.
//------------------------------------------------------//// OutlineNode.h// #import @interface DASchemaItem :NSObject-(NSString*)name; -(NSString*)note; -(BOOL)isLeaf; -(NSImage*)image; -(NSArray*)children; @end @interface DASchemaCollection : DASchemaItem { DASchemaBaseObject *parent; }-(id)initWithParent:(DASchemaBaseObject *)aParent; @end @interface DASchemaDataTableCollection : DASchemaCollection @end @interface DASchemaDataTableFieldCollection : DASchemaCollection @end @interface DASchemaCommandCollection : DASchemaCollection @end //------------------------------------------------------//// OutlineNode.m//#import "OutlineNode.h" @implementation DASchemaItem -(BOOL)isLeaf {returnNO; } -(NSImage*)image {returnnil; } -(NSArray*)children {returnnil; } -(NSString*)name {return@""; } -(NSString*)note {return@""; } @end @implementation DASchemaCollection -(id)initWithParent:(DASchemaBaseObject *)aParent { self =[super init]; if(self !=nil){ parent =(DASchemaBaseObject *)aParent; }return self; } -(void) dealloc {[super dealloc]; } -(NSImage*)image {return[NSImage imageNamed:@"Collection.png"]; } @end @implementation DASchemaDataTableCollection -(NSArray*)children {return[(DASchema *)parent tables]; } -(NSString*)name {return[NSString stringWithFormat:@"TABLES (%ld)", [[(DASchema *)parent tables] count]]; } @end @implementation DASchemaDataTableFieldCollection -(NSArray*)children {return[(DASchemaDataTable *)parent fields]; } -(NSString*)name {return[NSString stringWithFormat:@"Fields (%ld)", [[(DASchemaDataTable *)parent fields] count]]; } @end @implementation DASchemaCommandCollection -(NSArray*)children {return[(DASchema *)parent commands]; } -(NSString*)name {return[NSString stringWithFormat:@"COMMANDS (%ld)", [[(DASchema *)parent commands] count]]; } |
// Append it to OutlineNode.h @interface DASchemaBaseObject (OutlineNode)-(NSArray*)children; -(BOOL)isLeaf; @end //------------------------------------------------------// Append it to OutlineNode.m @implementation DASchemaBaseObject (OutlineItem) -(NSImage*)image {returnnil; } -(NSArray*)children {returnnil; } -(BOOL)isLeaf {return[[self children] count] < 1; } @end @implementation DASchema (OutlineItem) -(NSArray*)children { DASchemaCollection *tables =[[[DASchemaDataTableCollection alloc] initWithParent:self] autorelease]; DASchemaCollection *commands =[[[DASchemaCommandCollection alloc] initWithParent:self] autorelease]; NSArray*result =[NSArray arrayWithObjects:tables, commands, nil]; return result; } @end @implementation DASchemaDataTable (OutlineItem) -(NSArray*)children { DASchemaCollection *fields =[[[DASchemaDataTableFieldCollection alloc] initWithParent:self] autorelease]; NSArray*result =[NSArray arrayWithObjects:fields, nil]; return result; } -(NSImage*)image {return[NSImage imageNamed:@"Table.png"]; } @end @implementation DASchemaDataTableField (OutlineItem) -(NSArray*)children {returnnil; } -(NSImage*)image {if(self.isPrimaryKey)return[NSImage imageNamed:@"KeyField.png"]; return[NSImage imageNamed:@"RegularField.png"]; } @end @implementation DASchemaCommand (OutlineItem) -(NSArray*)children {returnnil; } -(NSImage*)image {return[NSImage imageNamed:@"Command.png"]; } @end |
Binding NSOutlineView
As the data source for the NSOutlineView, we can use the NSTreeController class. You can locate it in the Object Library, then drag and drop it to the document designer.
After that, select the recently added tree controller object and go to the *Utilities *view *Attribute Inspector *. In the *Tree Controller *section, we need to set the key paths for obtaining the children collection and for defining if the tree node is a leaf or can be expanded.
Also in the *Object Controller *section, we need to set the *Class *as the *Mode *and the *DASchemaBaseObject *as the *Class Name *, then add name & note as Keys. These keys will be available for selecting later when we will bind the outline view columns to this tree controller.
Go to the Bindings Inspector and bind the tree controller to the children method of the schema property.
Now the tree controller is configured and we can bind the outline view columns to the key paths exposed by the tree controller.
Bind the SchemaItem column.
And that’s all. When we run the application, we will get something like this:
Note that Cocoa Bindings provides two-way binding, so we can edit tree node items just in outline view without any additional efforts.
Adding Images to the outline view items
You may notice on the screens above that NSOutlineView shows its content with images. To do that, we’ve reused the ImageAndTextCell class taken from the SourceView Sample.
We have included it to our sample project and selected it as the custom class of the outline view column cell.
We then need to declare the support of the NSOutlineViewDelegate protocol for our window controller and implement the
outlineView:willDisplayCell:forTableColumn:item: method which will be called by the outline view control before drawing its cells.
-(void)outlineView:(NSOutlineView*)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn*)tableColumn item:(id)item {// draw images for first column onlyif([@"Schema Item" compare:[[tableColumn headerCell] title]]== NSOrderedSame){ NSImage*image =nil; if([item respondsToSelector:@selector(image)]) image =[item image]; elseif([item isKindOfClass:[NSTreeNode class]]) image =[[item representedObject] image]; ImageAndTextCell *c =(ImageAndTextCell *)cell; [c setImage:image]; }} |
Thanks for your attention.