在這裡製做一個計帳簿程式, 主要是可以記錄時間, 項目及金額. 對文字編輯器有興趣的, 可以參考Ink. 在這裡使用 NSTableView. 這是一個比較複雜的圖形介面, 但 GNUstep 已經處理好許多細節了.
有關 NSTableView, 可參考 Getting Started With NSTableView
使用開啟 Document.gorm. 在視窗中加入一個表格, 並將大小拖放至與視窗同大.
勾選 "Horizontal" scroller. 這在目前不是很重要.
查看 NSTableView 的 Size 屬性. 點選方框中的直線, 會變成彈簧狀, 表示會隨視窗大小改變.
方框代表 NSTableView. 直線表示其相對距離不改變, 彈簧表示其距離隨視窗大小改變. 在方框外的線表示 NSTableView 與其 superview 的距離, 在這裡 NSTableView 的 superview 即為視窗本身. 方框內的線則為 NSTableView 的大小. 在這裡, 表格的大小將隨視窗的大小而有改變.
點兩下表格中的 column 可以改變其標題. 在這裡不太重要. 接下來會在程式碼中改變表格的介面.
在 Document 類別中加入一個 tableView 的 outlet.
將 NSOwner, 即 Document 類別, 做為表格的代理者 (delegate) 及資料來源 (data source).
將 NSOwner 中的 tableView 連結到表格上.
將 Document.gorm 存檔後離開.
將新的 outlet 加入 Document.h.
Document.h:
#import <AppKit/AppKit.h>
#import <AppKit/NSDocument.h>
@interface Document : NSDocument
{
id tableView;
}
@end
NSTableView 顯示資料的方式是, 首先尋問其資料來源有多少資料, 並一一尋問每個欄位的資料. 因此在資料來源中有兩個必需實作的 methods.
Document.m:
- (int) numberOfRowsInTableView: (NSTableView *) view { return 5; } - (id) tableView: (NSTableView *) view objectValueForTableColumn: (NSTableColumn *) column row: (int) row { return [NSString stringWithFormat: @"column %@ row %d", [column identifier], row]; }
-numberOfRowsInTableView: 傳回有多少列的資料, -tableView:objectValueForTableColumn:Row: 則一一傳回每個欄位的資料. 欄位由其所在的行 (column) 及列 (row) 所定義. 每行有其專用的 NSTableColumn. 每個 NSTableColumn 有其專用的辨識字串, 因為行的順序可能會被使用者所改變.
這個程式目前已經能執行了. 基本上只是顯示五列的資料. NSTableView 可以顯示任何的物件, 包含圖檔. 接下來要製做有真正功能的程式.
到目前為止的程式碼在此: Table-1-src.tar.gz
首先將 NSTableView 的介面調整一下. NSTableView 是由一系例 NSTableColumn 所組成. 在 Gorm 中拖拉出來的表格已有兩個內定的 NSTableColumn, 還需要額外的一個.
Document.m:
- (void) windowControllerDidLoadNib: (NSWindowController *) controller { NSTableColumn *column;
取得所有 NSTableColumn
NSArray *columns = [tableView tableColumns];
改變 NSTableColumn 的屬性
column = [columns objectAtIndex: 0]; [column setWidth: 100]; [column setEditable: NO]; [column setResizable: YES]; [column setIdentifier: @"date"]; [[column headerCell] setStringValue: @"Date"]; column = [columns objectAtIndex: 1]; [column setWidth: 100]; [column setEditable: NO]; [column setResizable: YES]; [column setIdentifier: @"item"]; [[column headerCell] setStringValue: @"Item"];
製做一個新的 NSTableColumn, 並加入 NSTableView 中
column = [[NSTableColumn alloc] initWithIdentifier: @"amount"]; [column setWidth: 100]; [column setEditable: NO]; [column setResizable: YES]; [[column headerCell] setStringValue: @"Amount"]; [tableView addTableColumn: column]; RELEASE(column);
調整 NSTableColumn 的大小.
[tableView sizeLastColumnToFit]; [tableView setAutoresizesAllColumnsToFit: YES]; }
在 -windowControllerDidLoadNib: 中調整表格的介面, 因為在這時 Gorm 檔已經被載入了, -windowControllerDidLoadNib: 的作用類似 -awakeFromNib. 重新編譯並執行這個範例, 便可見到新的欄位.
NSTableColumn 最重要的就是其辨識字串 (identifier). 每個 NSTableColumn 都有獨一無二的 identifier. 根據這個 identifier 可以得知其所在的 NSTableColumn. Identifier 不一定需要是字串, 只是習慣上使用字串. 在 GNUstep 中很多物件都使用 identifier 來做為區分物件的方法.
使用者介面完成了, 接著就是資料來源的部份. 在 MVC 架構 中, NSTableView 為 View, 資料來源即可 Model.
習慣上, 每一筆 NSTableView 的資料可以被在 NSDictionary. NSDictionary 的 key 可以使用與 NSTableColumn 的 identifier 相同字串, 好方便存取. 而所有資料則放在 NSArray 中. 如此每一列資料就相當放 NSArray 中的每一個 NSDictionary. 因此, 在這裡使用 NSMutableArray 來做為 NSTableView 的資料來源.
Document.h:
#import <AppKit/AppKit.h>
#import <AppKit/NSDocument.h>
@interface Document : NSDocument
{
id tableView;
NSMutableArray *records;
}
@end
有關 NSMutalbeArray 的使用可參考 Basic GNUstep Base Library Classes.
為了方便使用者輸入, 會多一個空白列以方便使用者輸入. 先實做顯示資料的方法.
Document.m:
- (id) init { self = [super init]; records = [NSMutableArray new]; return self; } - (void) dealloc { RELEASE(records); [super dealloc]; } - (int) numberOfRowsInTableView: (NSTableView *) view { return [records count] + 1; } - (id) tableView: (NSTableView *) view objectValueForTableColumn: (NSTableColumn *) column row: (int) row { if (row >= [records count]) { return @""; } else { return [[records objectAtIndex: row] objectForKey: [column identifier]]; } }
因為要多一個空白列, 在 -numberOfRowsInTableView: 中的傳回值加一. 在 -tableView:objectValueForTableColumn:row: 中就要特別處理多出來的空白標. 傳回空白字串. 最重要的是 NSTableColumn 的 identifier 與 NSDictionary 中的 key 相等, 如此直接使用 NSTableColumn 的 identifier 做為 NSDictionary 的 key 來讀取資料即可, 非常方便. 如果不使用 NSDictionary 做為每一筆資料的資料結構, 可以考慮使用 Key Value Coding (KVC) 這個程式技術, 與 NSDictionary 有異曲同功之妙.
接著加入輸入的功能. 首先讓 NSTableColumn 可以接受輸入.
- (void) windowControllerDidLoadNib: (NSWindowController *) controller { NSTableColumn *column; NSArray *columns = [tableView tableColumns]; column = [columns objectAtIndex: 0]; [column setWidth: 100]; [column setEditable: YES]; [column setResizable: YES]; [column setIdentifier: @"date"]; [[column headerCell] setStringValue: @"Date"]; column = [columns objectAtIndex: 1]; [column setWidth: 100]; [column setEditable: YES]; [column setResizable: YES]; [column setIdentifier: @"item"]; [[column headerCell] setStringValue: @"Item"]; column = [[NSTableColumn alloc] initWithIdentifier: @"amount"]; [column setWidth: 100]; [column setEditable: YES]; [column setResizable: YES]; [[column headerCell] setStringValue: @"Amount"]; [tableView addTableColumn: column]; RELEASE(column); [tableView sizeLastColumnToFit]; [tableView setAutoresizesAllColumnsToFit: YES]; }
當使用者在欄位中點兩下時, 即可輸入新的數值與字串.
資料來源使用 -tableView:setObjectValue:forTableColumn:row: 來儲存資料, 其概念與顯示資料正好相反, 一讀一寫.
Document.h:
- (void) tableView: (NSTableView *) view setObjectValue: (id) object forTableColumn: (NSTableColumn *) column row: (int) row { if (row >= [records count]) { [records addObject: [NSMutableDictionary new]]; } [[records objectAtIndex: row] setObject: object forKey: [column identifier]]; [tableView reloadData]; }
在此特別處理空白列的問題. 如果是輸入在空白列, 表示是新的一筆資料, 因此要先加入新的 NSDictionary. 接著依照所處的 NSTableColumn 及列 (row) 將資料加入. 最後使用 -reloadData 更新 NSTableView 的顯示畫面.
現在便可以自行輸入資料了. 程式碼在此: Table-2-src.tar.gz. 在 GNUstep 的文件程式架構中, 只要專心在處理文件的本身就好, 而不需要耽心管理文件的問題. 這就是使用 GNUstep 的方便之處.