关于NSCell的那些事儿

众所周知,任何一个成功的NSControl(见注释1)背后都有一个NSCell。因为多数功能都可以通过NSControl直接实现,因此NSCell一直是被人忽略的角色。今天就和大家一起走进NSCell的世界,简单谈谈NSCell的常用,实用用法。

大家打开itunes,mail,或者Finder,都会看到左边的树形列秒(NSOutlineView).每一行,即一个Cell中,既有图片,又有文字。并且整个outlineView只有一列,要实现这种效果,就只有靠操作Cell了。
就以NSOutlineView作为例子。利用outlineView的

- (id) outlineView: (NSOutlineView *) aOutlineView objectValueForTableColumn: (NSTableColumn *) tableColumn
            byItem: (id) item
这个delegate方法可以根据不同节点(item)显示不同的值,当然这个值不能是图片。因此我们要实现左边的图片就只能依靠cell了。
因为apple没有提供一个text and image的NSCell子类,因此我们需要自己建立这样一个类,建立一个新类,取名NBImageAndTextCell(恩,这个名字很规范,很低调又很华丽),并继承至NSTextFieldCell。怎么把这个类和我们的NSControl(即outlineView)联系起来呢,当然NSControl有个叫setCell的方法可以做,但是既然我们有IB(注释2),于是就在IB里设置即可,方便切安全。打开IB选中你的outlineView,再选中其中的column,然后再选中cell。这过程可以通过连续的 command+ctrl+ 方向键下 完成。在cell的inspector窗口(command+shift+I 打开)的identity中将 Class identity改成“NBImageAndTextCell"回车即可,现在我们的cell类就属于这个outlineView了


在outlineView的delegate类中使用delegate方法

- (void)outlineView:(NSOutlineView *)ov willDisplayCell:(NSCell *)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
        NBImageAndTextCell *imageAndTextCell = (NBImageAndTextCell *)cell;
        [imageAndTextCell setImage:[item itemImage]];

}

顾名思义,"willDisplayCell"就是在即将要显示cell又还没显示cell的那个时候,我们在这个时候把我们要显示的图片通过 setImage 方法 传给cell。 这个image一般就是node的某个属性,这样就可以根据不同的node显示不同的图片。比如Finder中文件夹node的图片就是蓝色小文件夹图

在 NBImageAndTextCell中声明一个 NSImage *image 和一个- (void)setImage:(NSImage *)anImage;方法。这个setImage方法就是我们上面用到的。
- (void)setImage:(NSImage *)anImage
{
    if (anImage != image)
    {
        [image release];
        image = [anImage retain];
    }
}

接下来就是如何在cell中画上这个image的方法了。这是核心所在
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
    if (image != nil)                                
    {
        [image setSize:(NSSize){16,16}];
        NSRect    imageFrame;
        NSSize imageSize = [image size];
        NSDivideRect(cellFrame, &imageFrame, &cellFrame,imageSize.width+5, NSMinXEdge);
        if ([self drawsBackground]) {
            [[self backgroundColor] set];
            NSRectFill(imageFrame);
        }
        imageFrame.origin.x += 3;
        imageFrame.size = imageSize;
               
        [image compositeToPoint:imageFrame.origin operation:NSCompositeSourceOver];
    }
    [super drawInteriorWithFrame:cellFrame inView:controlView];
}

这个draWithFrame继承至NSTextFieldCell,通过这个方法来画上Cell。参数cellFrame就是这个Cell在control(即outlineView)每一行的frame,因此你的outlineView的一行多高多宽,位置在哪儿就是这个cellFrame了。

看第一句 if (image != nil),这个image是在outlineView的delegate方法里通过setImage传进来的,因此如果我们想要某一个节点没有任何图片的话,就传个nil进来,于是就不会执行括号内画图的方法了。
接下来就是核心
NSDivideRect(cellFrame, &imageFrame, &cellFrame,imageSize.width+5, NSMinXEdge);

这个NSDivideRect就是一个切割cell的方法,里面的4个参数当然就是决定你怎么切的。
简单来说,
第一个参数是:在哪个里面切       这里是cellFrame,意思是我们在整个cell内切
第二个:切出来的东西放哪儿        这里是imageFrame一个出参,就是切出来的范围一个Frame 赋给imageFrame
第三个:切剩下的放哪儿             一个出参cellFrame,切后的放入cellFrame,也就说之后的cellFrame就自由我们切剩下的那么大了
第四个:切的边界举例cell边界多远   距离边界imageSize.width+5这么远
第五个:从cell的哪边切                 NSMinXEdge是靠cell左边,NSMaxXEdge是右边

于是现在我们的imageFrame就是我们要放图片的地方和大小,cellFrame就是剩下的cell大小和位置了

经过小的调整 imageFrame.origin.x += 3;  imageFrame.size = imageSize;  后,就调用[image compositeToPoint:imageFrame.origin operation:NSCompositeSourceOver];
在这个范围内画上我们的image即可

最后千万别忘了[super drawInteriorWithFrame:cellFrame inView:controlView];没有这个,cell都不会被draw上,自然我们切莱切去,画来画去的东西都不能显示了
这个也是我们要继承NSTextFieldCell的原因,就是为了用这个方法。

ok,大功告成,你的outlineView可以图文并茂了,以后还想切一块出来显示点其他什么图啊什么的,就随便你了。




小白注释1:NSControl 是NSView子类,如NSTextField,NSButton,NSTableView 等
小白注释2:IB=Interface Builder