NSOutlineView 是 NSTableView 的子類別. 唯一的差別是 NSOutlineView 的資料來源是樹狀結構, 可以展開或關閉, 而 NSTableView 則是使用陣列記錄每一筆資料. 在這裡示範使用 NSOutlineView 顯示系統相關資訊.
使用 Gorm 產生一個新的程式 (New Application). 拖一個 NSOutlineView 到視窗中.
改變 NSOutlineView 的自動改變大小屬性.
在 column 上點兩下以選擇該 column
取消 "Editable" 並將第一個 column 的 identifier 改成 "Attribute", 第二個改成 "Value".
使用者介面完成了. 要製做這個使用者介面的控制者. 繼承自 NSObject, 產生一個 "Controller" 類別.
加入一個 outlet 稱為 "outlineview".
產生 Controller 物件. 將 "outlineview" outlet 連到 NSOutlineView 上. 要確認不是連到 NSScrollView 或 NSTableColumn.
NSOutlineView 是 NSTableView, 因此也必需指定其資料來源 (data source) 及代理者 (delegate). 連結 NSOutlineView 的 dataSource 及 delegate 至 Controller.
最後, 指定 Controller 為 NSApp 的代理者.
將這個使用者介面存成 Overview.gorm 檔. 產生 Controller 的程式碼.
因為 NSOutlineView 的資料是樹狀結構, 在這裡使用很簡單的結點 (Node) 來建立樹狀結構.
Node.h
#ifndef _Overview_Node_ #define _Overview_Node_ #include <Foundation/NSObject.h> @class NSArray; @class NSMutableArray; @class NSString; @interface Node: NSObject { NSString *name; NSString *value; NSMutableArray *children; } - (void) setName: (NSString *) name; - (NSString *) name; - (void) setValue: (NSString *) value; - (NSString *) value; - (void) addChild: (id) child; - (NSArray *) children; @end #endif /* _Overview_Node_ */
Node.m
#include "Node.h" #include <Foundation/Foundation.h> @implementation Node - (id) init { self = [super init]; children = [NSMutableArray new]; return self; } - (void) setName: (NSString *) string { ASSIGN(name, string); } - (NSString *) name { return name; } - (void) setValue: (NSString *) string { ASSIGN(value, string); } - (NSString *) value { return value; } - (void) addChild: (id) child { [children addObject: child]; } - (NSArray *) children { return children; } - (void) dealloc { RELEASE(name); RELEASE(value); RELEASE(children); [super dealloc]; } @end
接下來會使用這些結點來建立樹狀結構.
NSTableView 需要這兩個 methods 來顯示資料:
- (int) numberOfRowsInTableView: (NSTableView *) tableView; - (id) tableView: (NSTableView *) tableView objectValueForTableColumn: (NSTableColumn *) column row: (int) row;
因為樹狀資料結構的關係, NSOutlineView 需要四個 methods 來顯示資料:
- (id)outlineView: (NSOutlineView *)outlineView child: (int)index ofItem: (id)item; - (BOOL)outlineView: (NSOutlineView *)outlineView isItemExpandable: (id)item; - (int)outlineView: (NSOutlineView *)outlineView numberOfChildrenOfItem: (id)item; - (id)outlineView: (NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item;
"Item" 即為資料結構中的結點, 如果 Item 是 nil, 表示在尋問根結點 (root node). 從根結點開始, NSOutlineView 會尋問每一個結點有多少個子結點, 這個結點是否能展開, 顯示結點的資料, 以及取得所有的子結點. 當 NSOutlineView 尋問過所有節點之後, 所有的資料便顯示出來了.
因此, 要先把樹狀結構建立起來:
Controller.h
/* All Rights reserved */ #include <AppKit/AppKit.h> @class Node; @interface Controller : NSObject { id outlineview; Node *root; } @end
Controller.m
/* All Rights reserved */ #include <AppKit/AppKit.h> #include "Controller.h" #include "Node.h" @implementation Controller - (id) init { Node *child, *temp; self = [super init]; root = [Node new]; child = [Node new]; [child setName: @"System"]; /* Add operating system */ temp = [Node new]; [temp setName: @"Operating System"]; [temp setValue: [[NSProcessInfo processInfo] operatingSystemName]]; [child addChild: temp]; RELEASE(temp); /* Add user name */ temp = [Node new]; [temp setName: @"User Name"]; [temp setValue: NSUserName()]; [child addChild: temp]; RELEASE(temp); /* Add home directory */ temp = [Node new]; [temp setName: @"Home Directory"]; [temp setValue: NSHomeDirectory()]; [child addChild: temp]; RELEASE(temp); [root addChild: child]; RELEASE(child); return self; } - (void) dealloc { RELEASE(root); [super dealloc]; }
首先建立根節點, 加上一個 "System" 節點. 再加入三個節點進入 "System" 節點. 這是一個非常簡單的樹狀結構. 有了樹狀結構, 便可以在 NSOutlineView 中顯示出來.
Controller.m
- (id) outlineView: (NSOutlineView *) outlineView child: (int) index ofItem: (id) item { /* Root */ if (item == nil) return [[root children] objectAtIndex: index]; /* Others */ if ([[item children] count]) return [[item children] objectAtIndex: index]; else return nil; } - (BOOL) outlineView: (NSOutlineView *) outlineView isItemExpandable: (id) item { /* Root */ if (item == nil) return YES; /* Others */ if ([[item children] count]) return YES; else return NO; } - (int) outlineView: (NSOutlineView *) outlineView numberOfChildrenOfItem: (id) item { /* Root */ if (item == nil) return [[root children] count]; /* Others */ return [[item children] count]; } - (id) outlineView: (NSOutlineView *) outlineView objectValueForTableColumn: (NSTableColumn *) tableColumn byItem: (id) item { if ([[tableColumn identifier] isEqualToString: @"Attribute"]) return [(Node *)item name]; else return [item value]; }
程式碼很清楚, 只是要特別處理根結點的狀況. 當 item 為 nil 時, 就是尋問根結點的資料.
再完成 main.m 及 GNUmakefile 即可. 程式碼在此: Overview-src.tar.gz
了解如何在 NSOutlineView 中顯示資料後, 便可輕易的加入更多資料.
Controller.m
- (id) init { Node *child, *temp; NSCalendarDate *date; NSRect frame; id object; #define ADD_NAME_VALUE(name, value) \ temp = [Node new]; \ [temp setName: name]; \ [temp setValue: value]; \ [child addChild: temp]; \ RELEASE(temp); self = [super init]; root = [Node new]; child = [Node new]; [child setName: @"System"]; /* operating system */ ADD_NAME_VALUE(@"Operating System", [[NSProcessInfo processInfo] operatingSystemName]); /* user name */ ADD_NAME_VALUE(@"User Name", NSUserName()); /* home directory */ ADD_NAME_VALUE(@"Home Directory", NSHomeDirectory()); /* gnustep root directory */ ADD_NAME_VALUE(@"GNUstep Directory", NSOpenStepRootDirectory()); /* host and address */ ADD_NAME_VALUE(@"Host", [[NSHost currentHost] name]); ADD_NAME_VALUE(@"Address", [[NSHost currentHost] address]); /* Screen */ ADD_NAME_VALUE(@"Screen Depth", [[NSNumber numberWithInt: [[NSScreen mainScree n] depth]] description]); frame = [[NSScreen mainScreen] frame]; object = [NSString stringWithFormat: @"%d x %d", (int)frame.size.width, (int)f rame.size.height]; ADD_NAME_VALUE(@"Screen Size", object); [root addChild: child]; RELEASE(child); child = [Node new]; [child setName: @"Date & Time"]; /* Time Zone */ ADD_NAME_VALUE(@"System Time Zone", [[NSTimeZone systemTimeZone] timeZoneName] ); ADD_NAME_VALUE(@"Local Time Zone", [[NSTimeZone localTimeZone] timeZoneName]); /* Date */ date = [NSCalendarDate calendarDate]; [date setCalendarFormat: @"%a, %b %e, %Y"]; ADD_NAME_VALUE(@"Date", [date description]); [date setCalendarFormat: @"%H : %M : %S"]; ADD_NAME_VALUE(@"Time", [date description]); [root addChild: child]; RELEASE(child); child = [Node new]; [child setName: @"Text Related"]; /* default encoding */ ADD_NAME_VALUE(@"Default Encoding", [NSString localizedNameOfStringEncoding: [NSString defaultCStringEncoding]]); /* Font */ ADD_NAME_VALUE(@"System Font", [[NSFont systemFontOfSize: [NSFont systemFontSize]] displayName]); ADD_NAME_VALUE(@"System Font Size", [[NSNumber numberWithFloat: [NSFont systemFontSize]] description]); ADD_NAME_VALUE(@"System Font Encoding", [[NSFont systemFontOfSize: [NSFont systemFontSize]] encodingScheme]); ADD_NAME_VALUE(@"System Bold Font", [[NSFont boldSystemFontOfSize: [NSFont systemFontSize]] displayName]); [root addChild: child]; RELEASE(child); return self; }
Now, it looks better:
在這個程式中, 當視窗關閉時程式還在. 在這裡使用 NSApp 的代理者來結束程式.
Controller.m
- (BOOL) applicationShouldTerminateAfterLastWindowClosed: (id) sender { return YES; }