25.01.2011
Tutorial: Statische Outline View (NSOutlineView)
In diesem Tutorial wird erklärt, wie die Outline View verwendet werden kann, um eine einfache hierarchische Struktur anzuzeigen. In dem Beispiel werden die Daten, die angezeigt werden sollen, statisch in einer Datenstruktur aus NSArrays
und NSDictionarys
hinterlegt. In realen Applikationen würde man die Outline View dynamisch füllen. Beispielsweise würde man sie mit CoreData verbinden oder die anzuzeigenden Daten aus einem RSS-Feed lesen (ein solches Beispiel findet man übrigens in dem Cocoa-Kochbuch)
Gestartet wird mit dem Anlegen einer Cocoa Application.

Als Projektname kann beispielsweise OutlineViewStatic verwendet werden. In der folgenden Abbildung ist das angelegte Projekt zu sehen.

In dem Beispiel wird eine DataSource verwendet, um die NSOutlineView
mit Daten zu befüllen. Dazu müssen vier Methoden des Outline View DataSource Protocols implementiert werden.
Die erste Methode liefert das Kind-Objekt mit dem Index index
von item
zurück.
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item { ... }
In dieser Methode wird festgestellt, ob item
Kinder hat.
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item { ... }
In der folgenden Methode wird die Anzahl der Kinder von item
bestimmt.
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item { ... }
In der letzten, der vier Methoden, wird der Wert, der in der Spalte tableColumn
des Objekts item
angezeigt werden soll, abgefragt. Über tableColumn
kann der Wert für die Spalte eindeutig identifiziert werden.
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item { ... }
Weitere Informationen zu dem Outline View DataSource Protocol findet man hier: http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Protocols/NSOutlineViewDataSource_Protocol/Reference/Reference.html
Die vier Methoden des Outline View DataSource Protocols werden in der Datei OutlineViewStaticAppDelegate.m hinzugefügt.
Bevor es mit den Methoden weitergeht, wird in der Header-Datei OutlineViewStaticAppDelegate.h noch eine Variable nodes
vom Typ NSArray
ergänzt, in der später die Datenstruktur gespeichert wird. Damit sieht die Header-Datei folgendermaßen aus:
#import <Cocoa/Cocoa.h> @interface OutlineViewStaticAppDelegate : NSObject <NSApplicationDelegate> { NSWindow *window; NSArray *nodes; } @property (assign) IBOutlet NSWindow *window; @end
In der Methode awakeFromNib
wird die Datenstruktur, die angezeigt werden soll, angelegt. Die Datenstruktur, die in dem Beispiel verwendet wird, sieht folgendermaßen aus:

Der Einfachheit halber wurde nur bei Knoten2 der children
-Verweis auf das leere NSArray
eingezeichnet. Bei den Knoten der zweiten Ebene fehlt der Verweis.
Jeder Knoten, der in der Outline View angezeigt werden soll, wird in einem NSDictionary
abgelegt. Beispielsweise so:
[NSDictionary dictionaryWithObjectsAndKeys: @"Knoten1", @"nodeName", @"Knotenbeschreibung 1", @"nodeDescription", [NSArray array], @"children", nil]
Es wird ein NSDictionary
angelegt, in dem unter dem Schlüssel (key) nodeName
der Wert Knoten1 gespeichert wird. Unter dem Schlüssel nodeDescription
wird der Wert Knotenbeschreibung 1 abgelegt. Der Schlüssel children
enthält ein NSArray
mit den Kindelementen des Knotens. In dem Beispiel oben ist das NSArray
leer und es existieren damit keine Kindelement.
Die Knoten (NSDictionarys
) werden wiederum in NSArray
s gespeichert. Um die oben abgebildete Datenstruktur auf diese Weise zu speichern, muß folgender Quellcode in der Methode awakeFromNib
ergänzt werden:
- (void) awakeFromNib { [super awakeFromNib]; NSArray *childrenNode1 = [[NSArray alloc] initWithObjects: [NSDictionary dictionaryWithObjectsAndKeys: @"Knoten11", @"nodeName", @"Knotenbeschreibung 11", @"nodeDescription", [NSArray array], @"children", nil], [NSDictionary dictionaryWithObjectsAndKeys: @"Knoten12", @"nodeName", @"Knotenbeschreibung 12", @"nodeDescription", [NSArray array], @"children", nil], nil]; NSArray *childrenNode3 = [[NSArray alloc] initWithObjects: [NSDictionary dictionaryWithObjectsAndKeys: @"Knoten31", @"nodeName", @"Knotenbeschreibung 31", @"nodeDescription", [NSArray array], @"children", nil], [NSDictionary dictionaryWithObjectsAndKeys: @"Knoten32", @"nodeName", @"Knotenbeschreibung 32", @"nodeDescription", [NSArray array], @"children", nil], [NSDictionary dictionaryWithObjectsAndKeys: @"Knoten33", @"nodeName", @"Knotenbeschreibung 33", @"nodeDescription", [NSArray array], @"children", nil], nil]; nodes = [[NSArray alloc]initWithObjects: [NSDictionary dictionaryWithObjectsAndKeys: @"Knoten1", @"nodeName", @"Knotenbeschreibung 1", @"nodeDescription", childrenNode1, @"children", nil], [NSDictionary dictionaryWithObjectsAndKeys: @"Knoten2", @"nodeName", @"Knotenbeschreibung 2", @"nodeDescription", [NSArray array], @"children", nil], [NSDictionary dictionaryWithObjectsAndKeys: @"Knoten3", @"nodeName", @"Knotenbeschreibung 3", @"nodeDescription", childrenNode3, @"children", nil], nil]; }
Nachdem jetzt die Datenstruktur angelegt ist, können die Methoden des Outline View DataSource Protocols implementiert werden.
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item { if(item == nil) { return [nodes objectAtIndex:index]; } else { return [[item valueForKey:@"children"] objectAtIndex:index]; } return nil; }
Bei der Methode müssen zwei Fälle unterschieden werden. Wenn das item
den Wert nil
hat, dann fragt die Outline View nach dem Wurzelknoten an der Position index
. Also kann aus dem NSArray
nodes
einfach das entsprechende NSDictionary
ausgelesen und zurückgeliefert werden. Beim zweiten Fall werden die Kinder von item
an der Position index
bestimmt. Dazu wird das NSArray
mit dem Schüssel children
aus dem NSDictionary
item
ausgelesen. Aus dem NSArray
wird dann das Element mit der Position index
herausgeholt und an die Outline View zurückgegeben.
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item { if([[item valueForKey:@"children"] count]>0) return YES; return NO; }
Diese Methode greift wiederum auf das NSArray
des NSDictionary
zu und bestimmt die Länge des NSArray
. Wenn das NSArray
mehr als ein Element hat, sind Kindelemente vorhanden und es wird YES
zurückgegeben. Sonst liefert die Methode NO
.
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item { if(item == nil) { return [nodes count]; } return [[item valueForKey:@"children"] count]; }
Diese Methode geht so vor, daß sie die Wurzelknoten in nodes
zählt, falls item
den Wert nil
hat und sonst einfach die Anzahl der Kinder bestimmt. Der Mechanismus ist dabei der gleiche, wie in den anderen beiden Methoden.
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item { return [item valueForKey:[tableColumn identifier]]; }
In der letzten der vier Methode wird aus dem Objekt NSTableColumn
der identifier
ausgelesen und verwendet, um den Spaltenwert aus dem NSDictionary
des item
herauszulesen. Dabei ist es natürlich wichtig, daß der Identifier in der Outline View auf den Wert nodeName
oder nodeDescription
gesetzt wird. Wie dies gemacht wird, beschreiben die nächsten Absätze.
Mit einem Doppelklick auf die Datei MainMenu.xib wird der InterfaceBuilder gestartet. Falls die Library noch nicht geöffnet ist, wird sie mit cmd-shift-L eingeblendet. In der Library wird die Outline View ausgewählt und mit gedrückter Mouse-Taste in das Fenster (OutlineViewStatic) gezogen.

Durch einen Doppelklick auf die Spaltennamen läßt sich Name und Beschreibung eintragen. Wie oben schon angesprochen, muß für die eindeutige Identifikation der Spalten in der Methode outlineView:objectValueForTableColumn:byItem
ein Identifier angegeben werden. Indem das Tab Attributes des Inspectors geöffnet wird und die erste Spalte in der OutlineViewStatic selektiert wird, kann in das Feld Identifier der Wert nodeName
eingetragen werden.

Mit der Beschreibung wird ebenso verfahren. Es wird der Wert nodeDescription
eingetragen.
Als letztes wird in das Fenster MainMenu.xib gewechselt und es wird mittels ctrl-mousedrag von der Outline View auf Outline View Static App Delegate die DataSource gesetzt.

Nach dem Speichern kann der InterfaceBuilder geschlossen und die Anwendung in Xcode kompiliert und gestartet werden. Dann sollte folgendes Ergebnis zu sehen sein:
