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 NSArrays 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: