macOS UI 列表!阶层(树状)显示 NSOutlineView

列表!阶层(树状)显示 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的使用方式再接着重新看此篇文章会比较容易理解。

范例程式-NSOutlineViewDemo