NSArrayController

在面向对象编程世界里,有个很通用的设计模式: Model-View-Controller [cocoa可以说把这个模式使用到了极致,我们在编写任何cocoa程序时,首先得考虑到MVC]. 在这个模式思想里面, 我们创建的任何一个类都应该归纳下面3个组的一组内:

.Model : Model类描述了我们的数据结构.比如,我们要创建一个银行系统, 可能会生成一个SavingsAccount类,来保存交易和结算列表.最完美的Model 类不会包含任何用户界面相关的信息.并可以使用在多个程序中.

.View : View类是GUI的一部分. 例如. NSSlider是一个View 类.最完美的View类都是通用类.可以使用在多个程序中

.Controller: 程序特定的Controller类负责控制程序流程的[业务逻辑类]. 用户需要浏览数据-controller对象读取从文件或数据库读取Model数据,并通过view对象来显示. 当用户对数据做些改变时. View对象会通知controller.随后controller更新model对象. 同时可能会把数据保存到文件或是数据库

在Mac OS 10.3前,cocoa程序员往往创建controller对象,编写大量代码在Mode对象和View对象之间传递数据.为了使编写常用controller对象更简单.Apple提供了NSControler类和绑定

NSController是一个抽象类(图8.1). NSObjectController是NSController的子类,内部包含一个对象. NSArrayController内部包含数据对象列表.在这个练习中,我们使用NSArrayController
[转载]第八章:NSArrayController

开始RaiseMan程序

在接下来的几个章节,我们会创建一个丰富功能的程序. 该程序记录了雇员以及他们每个人在一年中的薪资增长比例. 所在本书的进度,我们会增加文件存储, undo, 用户偏好,打印等功能. 而这一章,程序会是这样 图8.2


[转载]第八章:NSArrayController

(恩.有经验的Cocoa程序员,会使用CoreData来创建类似程序.不过我想让大家看看怎么使用手动的方法实现. 让大家看到CoreData的实现也不是多么的神奇.)

--XCode
使用XCode创建一个新的工程. 选择Cocoa Document-based Application. 并命名为RaiseMan

什么是document-based程序.这种程序可以同时打开多个窗口.比如TextEdit就是一个document-based程序. 而System Preferences则不是一个document-based程序.我们会在下一节学习更多的相关知识.

程序的对象关系图如图8.3, talbe columns和NSArrayController的连接是通过绑定而不是outlet来建立的.
[转载]第八章:NSArrayController 
注意,程序模板已经创建好了MyDocument类. MyDocument是NSDocument的子类.负责文件的读写.在这个练习中,我们会使用NSArrayController和绑定来构建我们的简单程序界面.所以,我们不会去给MyDocument添加任何代码.

选择 File->New File...,创建一个Person类.选择Objective-C 类,命名为Person.m. 同时勾选创建Person.h选项. 如图8.4
[转载]第八章:NSArrayController

在Person.h中声明两个property
#import <Foundation/Foundation.h>

@interface Person : NSObject {
    NSString *personName;
    float expectedRaise;
}
@property (readwrite, copy) NSString *personName;
@property (readwrite) float expectedRaise;
@end

编辑Person.m文件. 实现init和dealloc方法来重载父类的
#import "Person.h"

@implementation Person

- (id)init
{
    [super init];
    expectedRaise = 5.0;
    personName = @"New Person";
    return self;
}

- (void)dealloc
{
    [personName release];
    [super dealloc];
}
@synthesize personName;
@synthesize expectedRaise;
@end

我们知道Person类是一个Model类-它没有包含任何的界面信息. 所以,不需要知道所有的Cocoa frameworks, 我们使用Foundation/Foundation.h来替换Cocoa/Cocoa.h. 包含较小的framework会更优雅. 同时,Person类也可以在命令行程序中重用.

在MyDocument.h中声明employees array. 该array中保存的是Person对象.
@interface MyDocument : NSDocument
{
    NSMutableArray *employees;
}
- (void)setEmployees:(NSMutableArray *)a;

在MyDocument.m中,创建setEmployees:方法. 在dealloc中,释放该array
- (id)init
{
    [super init];
    employees = [[NSMutableArray alloc] init];
    return self;
}

- (void)dealloc
{
    [self setEmployees:nil];
    [super dealloc];
}

- (void)setEmployees:(NSMutableArray *)a
{
    // This is an unusual setter method.  We are going to add a lot
    // of smarts to it in the next chapter.
    if (a == employees)
        return;

    [a retain];
    [employees release];
    employees = a;
}

--InterFace Builder

双击打开MyDocument.nib.删除文本框 Your document here. 给window添加一个talbe view 和两个 button. 组织布局如图8.5
[转载]第八章:NSArrayController 
从cocoa->Objects&Controller->Controllers. 拖动NSArrayontroller到doc window.  在他的Inspector中, 设置 Object Class Name为Person, 并添加personName和expectedRainse key. 如图8.6
[转载]第八章:NSArrayController 
将array controller的Content Array 绑定至 File' Owner(也就是MyDocument对象)的employees array. 如图8.7
[转载]第八章:NSArrayController
table view的第一列将会显示每个employee的名字.点击,再双击第一列.(注意不要绑定的是scroll view,table view,或是cell. 所以请时刻关注Inspector窗口的标题). 在Bindings Inspector中,设置value显示NSArraycontroller的arrangedObjects的personName. 如图8.8
[转载]第八章:NSArrayController
table view的第二列显示每个employee的预期的提升值. 拖动一个number formatter(在Library->Cocoa->Views&Cells -> Formatters)到这列的cell中. 在Inspector中[选择这个number formatter,然后设置佛formatter的属性], 设置formatter显示成百分比.如图8.9

[转载]第八章:NSArrayController
再次选定第二列,在Bindings Inspector中,设置vaule显示为NSArrayController的arrangedObjects的expectedRaise.如图8.10

[转载]第八章:NSArrayControllerControl-Drag使这个array controller成为Add New Employee button的target. 设置action为add: [想象一下NSArrayController是我们自己写的类,实现了add: action]

同样Control-Drag使array controller成为 Delete button的target.设置其action为remove: . 并在不Bindings Inspector中,将button的enabled绑定到 NSArrayController的 canRemove属性.如图8.11
[转载]第八章:NSArrayController
用户也许需要使用 Delete 键来删除所选择的employees. 在Attributes Inspector中,甚至键盘快捷键为Delete 如图8.12

[转载]第八章:NSArrayController
编译运行我们的程序. 我们可以创建和删除Person对象. 也可以在table view中编辑Person对象的属性. 我们还可以同时打开多个document.(当然,现在我们还没有办法将这些document保存成文件.)


Key-Vaule Coding 和 nil

到现在,我们只编写了少量的代码. 我们通过Interface Builder来指定每一列显示什么样的信息,但是没有代码来调用的Person类的accessor方法,程序是怎么实现的呢? 那是因为有 Key-Value Coding. Key-Vaule Coding使一般,复用类成为可能,比如NSArrayController.

Key-vaule coding方法会自动强制转换类型.比如,当用户输入一个新的期望提升值时, formatter类会生成一个NSNumber对象. Key-Vaule coding方法 setValue:forKey:会在调用setExpectedRaise:前自动将NSNumber对象转换成float.

不过这里可能会产生问题. 指针可以为nil.但是float类型不行. 如果把nil作为setValue:forKey:参数,转换时会调用类[setValue:forKey: 所属的类,在例子中为Person]的方法
- (void)setNilValueForKey:(NSString *)s

该方法在NSObject定义.默认实现为抛出异常.所以,当用户使 Expected Raise栏位为空时. 对象[Person对象]会抛出异常. 我们可以重载setNilValueForKey: 将成员变量设置为默认值. 在这个例子中,我们在Person类中重载这个方法,把expectedRaise设置为0.0 . 在Person.m中添加这个方法
- (void)setNilValueForKey:(NSString *)key
{
    if ([key isEqual:@"expectedRaise"]) {
       [self setExpectedRaise:0.0];
    } else {
       [super setNilValueForKey:key];
    }
}
当然,在我们设置一个formatter的时候,不是都需要重载setNilVauleForKey:方法. 我们可以配置让formatter不接受nil的输入.

增加排序
当程序运行后,我们可以点击一列的头部来进行排序,但是排序处理不太好. 默认的使用了大小写敏感的compare:方法来对name进行排序. 比如"Z" 会在 "a"之前. 让我们来更改排序的方法

打开MyDocument.nib , 我们可以通过Attributes Inspector来设置每一列的排序条件. 选择显示personName这一列. 将Sort Key设为personName , 将slector设为 caseInsensitiveCompare: 如图8.13
[转载]第八章:NSArrayController
caseInsensitiveCompare:是NSString的方法. 你可能会这样使用它:
NSString *x = @"Piaggio";
NSString *y = @"Italjet"
NSComparisonResult result = [x caseInsensitiveCompare:y];

// Would x come first in the dictionary?
if (result == NSOrderedAscending)  {
      ...
}

NSComparisonResult是整型. NSOrderedAscending为-1. NSOrderedSame为0. NSOrderedDescending为1.
编译运行程序. 点击列的头部来进行排序. 是不是不一样了.


思考: 不使用NSArrayController来进行排序
在第6章中,我们实现了一个tabel view以及它的一些dataSource方法.你可能想知道怎么来实现排序功能.

所有添加到每一列里的信息都会放到一个NSSortDescriptor对象中[table view 有一个array来保存NSSortDescriptor对象数组,每一列对应一个NSSortDescriptor对象]. NSSortDescriptor对象包含了key,一个selector,和一个向上还是向下排序的指示量. 对于NSMutableArray对象,我们可以使用下面的方法来对它进行排序
- (void)sortUsingDescriptors:(NSArray *)sortDescriptors

而当点击列头的时候,一个可选[你可以实现该方法,也可以不实现-使用默认的]的dataSource方法将会触发调用:
- (void)tableView:(NSTableView *)tableView
   sortDescriptorsDidChange:(NSArray *)oldDescriptors
所以,如果我们使用一个mutable array来保存table view的数据,那么我们可以这样来进行排序
- (void)tableView:(NSTableView *)tableView
              sortDescriptorsDidChange:(NSArray *)oldDescriptors
{
    NSArray *newDescriptors = [tableView sortDescriptors];
    [myArray sortUsingDescriptors:newDescriptors];
    [tableView reloadData];
}

噢! 程序可以支持排序功能了

挑战1
让程序可以通过people名字的长度来进行排序. 你可以使用Interface Builder来完成这个功能. 诀窍是使用key path(提示: NSString 有 length 方法)

挑战2
这本书的第一版,我们没有使用NSArrayController和绑定来实现RaiseMan程序(这些特性是在Mac OS
 10.3才添加的). 我们使用的是前面章节介绍的方法来实现的. 所以,这个挑战就是, 不使用NSArrayController和绑定来重写RainMan程序. 绑定机制看上去是具有神奇魔力的.不过我们最好来看看这些魔力是怎么产生

请一定创建一个新的工程来完成这个练习.在下一章,我们将要在你创建的工程上进行.

Person类不需要做修改. 对于MyDocument.nib,你需要设置每一列的identifier为将有显示的变量名.所以,MyDocument类将会是table view的dataSource. 同时也是Create New和Delete 按钮的target. MyDocument 会有一个array来保存Person对象. 开始,你会修改MyDocument.h
#import <Cocoa/Cocoa.h>
@class Person;

@interface MyDocument : NSDocument
{
    NSMutableArray *employees;
    IBOutlet NSTableView *tableView;
}
- (IBAction)createEmployee:(id)sender;
- (IBAction)deleteSelectedEmployees:(id)sender;
@end

修改Mydocument.m
- (id)init
{
    [super init];
    employees = [[NSMutableArray alloc] init];
    return self;
}
- (void)dealloc
{
    [employees release];
    [super dealloc];
}

#pragma mark Action methods

- (IBAction)deleteSelectedEmployees:(id)sender
{
    // Which row is selected?
    NSIndexSet *rows = [tableView selectedRowIndexes];

    // Is the selection empty?
    if ([rows count] == 0) {
        NSBeep();
        return;
    }
    [employees removeObjectsAtIndexes:rows];
    [tableView reloadData];
}
- (IBAction)createEmployee:(id)sender
{
    Person *newEmployee = [[Person alloc] init];
    [employees addObject:newEmployee];
    [newEmployee release];
    [tableView reloadData];
}

#pragma mark Table view dataSource methods

- (int)numberOfRowsInTableView:(NSTableView *)aTableView
{
    return [employees count];
}

- (id)tableView:(NSTableView *)aTableView
        objectValueForTableColumn:(NSTableColumn *)aTableColumn
                              row:(int)rowIndex
{
    // What is the identifier for the column?
    NSString *identifier = [aTableColumn identifier];

    // What person?
    Person *person = [employees objectAtIndex:rowIndex];

    // What is the value of the attribute named identifier?
    return [person valueForKey:identifier];
}

- (void)tableView:(NSTableView *)aTableView
    setObjectValue:(id)anObject
    forTableColumn:(NSTableColumn *)aTableColumn
               row:(int)rowIndex
{
    NSString *identifier = [aTableColumn identifier];
    Person *person = [employees objectAtIndex:rowIndex];

    // Set the value for the attribute named identifier
    [person setValue:anObject forKey:identifier];
}
如果上面的程序可以正常工作了.不要忘了添加排序

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值