列表!階層(樹狀)顯示 NSOutlineView
NSOutlineView 是 NSTableView 的子類別,但它具有階層(樹狀)顯示的功能,可以將子項目展開或關閉,然而NSTableView實現方式使用陣列來表示,NSOutlineView具有子項目的關系,會使用自訂類型的物件來儲存整個結構的資料當做DataSource,另外以下文章會以階層來描述具有樹狀、階層結構的說明。
自訂結構資料來源
具有階層的結構來說,一個結構分為上層與下層,當下層中有資料時,下層的角色即為轉為上層,然而資料則為下層以此類推,所以就能先完成一個基礎結構:
這裡以一間公司的組成來說明,一間公司內可能會有很多部門,部門中有主管,主管又分為一級主管,一級主管下有二級主管,二級主管下有它所帶領的同仁,整 個組織結構下來可以先訂定一個結構資料物件名為Staff:
//-----------start----------- // // Staff.h // NSOutlineViewDemo // // Created by danny on 2015/3/3. // Copyright (c) 2015年 danny. All rights reserved. // #import <Foundation/Foundation.h> @interface Staff : NSObject + (id)staffName:(NSString*)name; - (id)initWithName:(NSString*)name; - (BOOL)isBoss; - (NSString*)name; - (void)addStaff:(Staff*)staff; - (NSArray*) getStaffs; - (NSInteger) getStaffNumbers; @end //------------end------------
以員工為主要物件,員工也能是老闆(isBoss),而老闆下也會有員工(addStaff:),將幾個功能實作為method:
//-----------start----------- // // Staff.m // NSOutlineViewDemo // // Created by danny on 2015/3/3. // Copyright (c) 2015年 danny. All rights reserved. // #import "Staff.h" @implementation Staff { NSMutableArray *_staffInStaff; NSString *_name; } - (id)init { return [self initWithName:@"name"]; } + (id)staffName:(NSString*)name { return [[Staff alloc] initWithName:name]; } //設定員工名字 - (id)initWithName:(NSString*)name { self = [super init]; if (self) { // Initialize self. _staffInStaff = [NSMutableArray array]; _name = [name copy]; } return self; } - (NSString*)name { return _name; } //是否為老闆,此員工下還有掛員工即為老闆 - (BOOL)isBoss { return (_staffInStaff.count>0?YES:NO); } - (void)addStaff:(Staff*)staff { [_staffInStaff addObject:staff]; } - (NSInteger)getStaffNumbers { return [_staffInStaff count]; } - (NSArray*)getStaffs { return [_staffInStaff copy]; } - (NSString*)description { return _name; } @end //------------end------------
配置NSOutlineView UI
增加NSOutlineView
將NSOutlineView拖至IB:
因我們只使用一個Table Column
,所以刪除Table Column
使之保持1個Table Column
:
已經刪除結果如下:
刪除Table Cell View
此次並無要客製化,所以先將Table Cell View
刪除,保留Text Cell
讓階層顯示資料時只顯示其字串內容:
如果你有使用Table Cell View
客制化顯示內容時,就可以實際像如下:
實作上比較覆雜,這往後有機會再介紹,上圖片結果為開發者Perspx實作好的PXSourceList的Demo,有興趣可以下載回來試試。
物件與UI進行連結
以上準備工作完成後,接下來要將UI與物件及程式整個做連結完成一個Demo。
NSOutlineView連結
程式中加上IBOutlet準備與UI做連結:
@interface AppDelegate () : : @property (weak) IBOutlet NSOutlineView *outlineV; : : @end
進行與UI的連結:
選擇剛建立好的outlineV
IBOutlet:
連結完成如圖下:
寫程式產生內容
首先利用建立好的Staff
物件進行建立員工資料:
//-----------start----------- Staff *dept1 = [Staff staffName:@"王董"]; [dept1 addStaff:[Staff staffName:@"小王"]]; [dept1 addStaff:[Staff staffName:@"楊吉"]]; //------------end------------
目前有個部門主管為王董
,它帶領了小王
、楊吉
,所以先建立主管,因它也是員工,所以以Staff
物件建立,之後因它帶領2位同仁,所以直接將同仁加入王董Staff
物件中,此時王董的角色為部門主管也是公司員工,這裡結構來說是以員工
主,所以顯示的都是以名字為階層,並非部門,上面例子可以知道王董
下有小王
、楊吉
。
之後再將整個部門結構加入公司,公司以陣列的型態以便能加上很多部門,先將dept1加入:
//-----------start----------- company = [NSMutableArray array]; Staff *dept1 = [Staff staffName:@"王董"]; [dept1 addStaff:[Staff staffName:@"小王"]]; [dept1 addStaff:[Staff staffName:@"楊吉"]]; [company addObject:dept1]; //------------end------------
依照此模式範例要加入三個部門,其中二個部門下有同仁,其一個部門只有主管自已一個人:
//-----------start----------- company = [NSMutableArray array]; Staff *dept1 = [Staff staffName:@"王董"]; [dept1 addStaff:[Staff staffName:@"小王"]]; [dept1 addStaff:[Staff staffName:@"楊吉"]]; [company addObject:dept1]; Staff *dept2 = [Staff staffName:@"張市"]; [dept2 addStaff:[Staff staffName:@"林林"]]; [dept2 addStaff:[Staff staffName:@"陳林"]]; [company addObject:dept2]; Staff *dept3 = [Staff staffName:@"枸本"]; [company addObject:dept3]; //------------end------------
加入後呈現結果如下圖結構:
不過加入結構後,此時要以此結構當做DataSource,讓NSOutletView使用時能取得結構上的資料:
//-----------start----------- //.m _outlineV.dataSource = self; _outlineV.delegate = self; //------------end------------
.h要增加DataSource、Delegate:
//-----------start----------- @interface AppDelegate : NSObject <NSApplicationDelegate, NSOutlineViewDataSource,NSOutlineViewDelegate> //------------end------------
將NSOutletView指定為self,並在該物件下實作DataSource需要的內容:
//-----------start----------- - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item { if (!item) { return [company count];//無item內容為第一層,所以顯示第一層的Staff數量 } return [(Staff*)item getStaffNumbers];//非第一層時會將目前這層的物件傳入,此時我們列出這層下的Staff數量 } //顯示index陣列值中的內容 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item { if (!item) { return [company objectAtIndex:index];//無item內容為第一層,所以顯示第一層的內容 } return [[(Staff*)item getStaffs] objectAtIndex:index];//非第一層時會將目前這層的物件傳入,此時我們列出這層下是否有還有 } //返回YES代表下層還有物件要列出 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item { if (!item) { return NO;//無item內容時代表已經無下層物件 } return [(Staff*)item isBoss];//非第一層時會將目前這層的物件傳入,此時我們列出這層下還有Staff時會將isBoss=YES } /* NOTE: this method is optional for the View Based OutlineView. */ - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item { NSLog(@"%@",item); //提供Staff的名字 return [(Staff*)item name]; } //------------end------------
程式內容:
//-----------start----------- // // AppDelegate.h // NSOutlineViewDemo // // Created by danny on 2015/3/2. // Copyright (c) 2015年 danny. All rights reserved. // #import <Cocoa/Cocoa.h> @interface AppDelegate : NSObject <NSApplicationDelegate, NSOutlineViewDataSource,NSOutlineViewDelegate> @end //------------end------------
//-----------start----------- // // AppDelegate.m // NSOutlineViewDemo // // Created by danny on 2015/3/2. // Copyright (c) 2015年 danny. All rights reserved. // #import "AppDelegate.h" #import "Staff.h" @interface AppDelegate () @property (weak) IBOutlet NSWindow *window; @property (weak) IBOutlet NSOutlineView *outlineV; @end @implementation AppDelegate { NSMutableArray *company; } - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { // Insert code here to initialize your application company = [NSMutableArray array]; Staff *dept1 = [Staff staffName:@"王董"]; [dept1 addStaff:[Staff staffName:@"小王"]]; [dept1 addStaff:[Staff staffName:@"楊吉"]]; [company addObject:dept1]; Staff *dept2 = [Staff staffName:@"張市"]; [dept2 addStaff:[Staff staffName:@"林林"]]; [dept2 addStaff:[Staff staffName:@"陳林"]]; [company addObject:dept2]; Staff *dept3 = [Staff staffName:@"枸本"]; [company addObject:dept3]; _outlineV.dataSource = self; _outlineV.delegate = self; } - (void)applicationWillTerminate:(NSNotification *)aNotification { // Insert code here to tear down your application } - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item { if (!item) { return [company count];//無item內容為第一層,所以顯示第一層的Staff數量 } return [(Staff*)item getStaffNumbers];//非第一層時會將目前這層的物件傳入,此時我們列出這層下的Staff數量 } //顯示index陣列值中的內容 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item { if (!item) { return [company objectAtIndex:index];//無item內容為第一層,所以顯示第一層的內容 } return [[(Staff*)item getStaffs] objectAtIndex:index];//非第一層時會將目前這層的物件傳入,此時我們列出這層下是否有還有 } //返回YES代表下層還有物件要列出 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item { if (!item) { return NO;//無item內容時代表已經無下層物件 } return [(Staff*)item isBoss];//非第一層時會將目前這層的物件傳入,此時我們列出這層下還有Staff時會將isBoss=YES } /* NOTE: this method is optional for the View Based OutlineView. */ - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item { NSLog(@"%@",item); //提供Staff的名字 return [(Staff*)item name]; } @end //------------end------------
結論
NSOutlineView的使用與NSTableView方式很相近,都是需要提供DataSource、Delegate的方法,可以先參考列出正在執行的應用程式文章中的篇例程式來理解一下NSTableView的使用方式再接著重新看此篇文章會比較容易理解。