天天看点

Cocoa自定义View

程序中所有的可视对象要么是window,要么是view.在这一章中,你将创建一个NSView的子类. 随着时间的推移,你一般会需要创建自定义的view来完成自定义画图和事件响应.即使你没有打算这样做,你也应该通过学习创建view类来了解cocoa的内部工作机制

window是NSWindow的对象.每个window都会有多个views,每个view描述window中的一个矩形区域. view负责该区域的画图动作以及鼠标事件响应. view也可以响应键盘消息. 你以及和多个view的子类打过交道了: NSButton, NSTextField,NSTableView,和NSColorWell 都是view (注意,window不是NSView的子类)

View的层次

view是按一定层次关系组织(如图17.1) .window包含了一个叫做content view的view.该view填满了整个window内部区域[除去title bar. 你可以在NSWindow类中找到contentView 和 setContentView方法] 通常,content view会有自己的子view.而这些子view有会有自己的子view.一个view知道自己的父view和子view,并知道自己所属的窗口 [到NSView类声明中,找找和它们相关的方法]

Cocoa自定义View

找到了么?

    - (NSView *)superview;

    - (NSArray *)subviews;

    - (NSWindow *)window;

任何类型的view [这么说是指 view的子类,如NSButton,NSTableView...]都可以包含多个子view.不过对于大部分类型的view,我们不会给它添加子view. 下面5中类型view通常有子view

1. window的content view

2. NSBox. box中的内容就是它的子view

3. NSScrollView. scroll view中包含的view就是它的子view. scroll bar也是它的子view

4. NSSplitView. SplitView中的view就是它的子view 如图 17.2

Cocoa自定义View

5. NSTabView.  当用户点选不同的tab时. 不同的子view交替切换如图17.3

Cocoa自定义View

-- 让一个View画自己 --

在本节中,将创建一个简单的view. 它将自己刷成绿色.就像17.4 [灰色的..呵呵]

Cocoa自定义View

新建一个Cocoa Application工程, 命名为ImageFun. 点选File->New file菜单,创建一个Objective-C NSView子类.命名为StretchView.

--创建一个View 子类的对象--

打开MainMenu.nib. 从Library中拖一个CustomView(view&cell->Layout View)放置在window上.如图17.5

Cocoa自定义View

将view大小改变接近window. 然后打开info panel. 将它的类设置成为StretchView.如图17.6

Cocoa自定义View

--大小检查--

StretcView对象是window content view的子view. 这就有个有意思的问题:当父view改变大小的时候, view有什么反应呢?在info panel中有一个tab页面来指定这样的行为. 打开Size Info Panel. 设置如图17.7. 现在你改变窗口大小. view的大小会跟着变化了.

Cocoa自定义View

-- drawRect--

当一个view需要刷新显示时,它将会收到drawRect:的消息,参数为一个需要重画的矩形区域, 这个方法会被自动调用-你不需要直接在代码中调用. 如果你需要让一个view重画,可以调用方法setNeedsDisplays

[myView setNeedsDisplay:YES];

该方法将myView设置成"脏"的. 在事件处理中,myView将被重画

[cocoa , 系统]在调用drawView:前, 会对这个view lock focus. 每一个view都有自己的graphics context-包含了view的坐标系统,当前颜色,当前字体等. 当view被lock focus,它的graphics context将激活,而当unlock focus后,它的graphics context将不是激活状态. 任何时候的draw命令都是在当前激活的graphics context进行 [对于Mac的画图draw, 可以看看Quarz 2D . graphics context也是它里面的概念咯. 其实cocoa view 画图也是通过Quarz 2D来实现, 要对屏幕显卡进行绘制,那么就要有一个绘制环境,这个环境也就是graphics context , cocoa 中在每个view中保存了一个各自的graphics context . 绘制到那个view时就将它的graphics context设置为当前Quarz用来绘制的graphics context - 通过lock focus]

你可以使用NSBezierPath来绘制线条,圆形,曲线和矩形. 你可以使用NSImage来在view上绘制合成图像. 在本节例子中, 绘制一个绿色的矩形

打开StretchView.m. 添加如下代码

- (void)drawRect:(NSRect)rect

{

    NSRect bounds = [self bounds];

    [[NSColor greenColor] set];

    [NSBezierPath fillRect:bounds];

}

如图17.10, NSRect结构由两个成员组成:origin - NSPoint类型, 和size - NSSize类型

Cocoa自定义View

NSSize结构有两个成员: width和height(都是float类型)

NSPoint结构有两个成员:x和y(都是float类型)

因为性能的原因,Objective-C类中很少使用到结构. 你有可能用到的一些cocoa结构: NSPoint,NSRect,NSRange,NSDecimal 和NSAffineTransformStruct等等. NSRange描述区间. NSDecimal描述数字精度, NSAffineTransformStruct描述图形线性变换

注意,view通过bounds知道自己的位置. 在drawRect中得到bounds区域,将当前color设置为绿色,再使用当前色来填充整个bounds区域

通过参数传递的NSRect描述了这个view需要重画的"脏"的区域.它有可能会小于整个view的大小.如果绘制比较费时间的东西,可以只对该脏的区域进行重新绘制

setNeedsDisplay:将激发view整个可见区域重画. 如果需要激发view某个指定区域进行重话可以使用setNeedsDisplayInRect:

NSRect dirtyRect;

dirtyRect = NSMakeRect(0, 0, 50, 50);

[myView setNeedsDisplayInRect:dirtyRect];

编译运行程序.试着改变window的大小看看

-- 使用NSBezierPath绘制--

如果想绘制线条,曲线或多边形,可以使用NSBezierPath. 前面,你使用了NSBezierPath的fillRect 类方法来给view上色.在这节中你将使用NSBezierPath绘制随机点间的线条

如图17.11

Cocoa自定义View

首先你需要一个成员变量来保存NSBezierPath对象.并创建一个方法来返回随机点. 在StretchView.h中修改如下

#import <Cocoa/Cocoa.h>

@interface StretchView : NSView

{

    NSBezierPath *path;

}

- (NSPoint)randomPoint;

@end

在StretchView.m中,重载initWithFrame方法-这是NSView的designated initializer[还记得它吧] . 它会在view对象创建时自动调用[在这个例子中,是在nib文件加载是cocoa调用]. 修改StretchView.m 在initWithFrame中,创建了一个path对象

#import "StretchView.h"

@implementation StretchView

- (id)initWithFrame:(NSRect)rect

{

    if (![super initWithFrame:rect])

        return nil;

    // Seed the random number generator

    srandom(time(NULL));

    // Create a path object

    path = [[NSBezierPath alloc] init];

    [path setLineWidth:3.0];

    NSPoint p = [self randomPoint];

    [path moveToPoint:p];

    int i;

    for (i = 0; i < 15; i++) {

        p = [self randomPoint];

        [path lineToPoint:p];

    }

    [path closePath];

    return self;

}

- (void)dealloc

{

    [path release];

    [super dealloc];

}

// randomPoint returns a random point inside the view

- (NSPoint)randomPoint

{

    NSPoint result;

    NSRect r = [self bounds];

    result.x = r.origin.x + random() % (int)r.size.width;

    result.y = r.origin.y + random() % (int)r.size.height;

    return result;

}

- (void)drawRect:(NSRect)rect

{

    NSRect bounds = [self bounds];

    // Fill the view with green

    [[NSColor greenColor] set];

    [NSBezierPath fillRect: bounds];

    // Draw the path in white

    [[NSColor whiteColor] set];

    [path stroke];

}

@end

编译运行程序,怎么样?酷吧! 好了,现在用[path fill] 代替[path stroke]看看,有什么不一样?

-- NSScrollView--

在美术世界里,在同样的质量下,绘制的越大就越美观啊. 你的view很漂亮了.不过能不能让它更大一点呢., 你需要将它放置在scroll view中如图17.12

Cocoa自定义View

scroll view由3个部分组成: document view, content view,和scroll bar. 在本例中.你的view将成为document view,并显示在content view中-它是NSClipView的对象

虽然这个看上去复杂,其实很容易办到. 实际上都不需要添加代码. 打开mainmenu.nib文件,选中view.  从LayOut 菜单中选择Embed Objects in Scroll View如图17.13

Cocoa自定义View

当window改变大小时,你希望scroll view跟着改变,而document view确不改变. 打开Size Inspector, 选择Scroll view. 设置他的Size Inspector,这样它就跟着window改变了如图17.14

Cocoa自定义View

注意view的长和宽

双击scroll view内部,选中document view.你可以看到这是inspecotr的标题变成 Stretch View Size. 将view的大小修改为scroll view的2倍. 同时绑定左下角并不要跟随改变大小如图17.15. 编译运行程序

Cocoa自定义View

-- 通过程序创建View--

你可以在Interface Builder中实例化多个view. 有时候,你会需要通过程序来创建view.例如,假定你希望在window上创建一个button

NSView *superview = [window contentView];

NSRect frame = NSMakeRect(10, 10, 200, 100);

NSButton *button = [[NSButton alloc] initWithFrame:frame];

[button setTitle:@"Click me!"];

[superview addSubview:button];

[button release];

--思考:cells--

NSControl从NSView继承得到. 因为view有自己的graphics context. 这让view成为一个大,高价的类. 当初,在提供NSButton类时, 有人要编写一个计算器程序, 他第一件事就是创建10行10列的NSButton. 这样就有100个view别创建,效率是相当低啊. 后来,有人想到了一个聪明的主意: 将NSButton的大脑移到另外一个类[大脑? 就是button的主要功能了](不再是view类).并创建一个大的view(叫做NSMatrix). 用来装那100个button大脑. 我们把这个button 大脑内叫 NSButtonCell [这个就如设计原则所说,内的聚合咯,少用继承,多用聚合,看到好处了] 如图17.16

Cocoa自定义View

到最后,NSButton就是一个简单的view再加上它的大脑 NSButtonCell. button cell做了所有事情,而NSButton只是window上的一块绘制区域如图17.17

Cocoa自定义View

同样的.NSSlider就是一个包含了NSSliderCell的view.  NSTextField 就是一个包含了NSTextFieldCell 的view. NSColorWell, 抱歉,它没有cell :)

你拖一个control到window上,然后选择Embed Objects In -> Martix. 这样就创建了一个NSMatrix. 可以按住Option拖动martix 来设定它的行列数.如图17.18

Cocoa自定义View

NSMatrix 有一个Target和action. Cell也有target和action. 如果cell点击,cell的target ,action将激发,如果cell没有设置它的target 和action.那么matirx的target,acton将激发.

在处理matirx时,你常常要面对这样的问天,哪个cell激活?cell也可以设置它的tag

- (IBAction)myAction:(id)sender {

    id theCell = [sender selectedCell];

    int theTag = [theCell tag];

    ...

}

cell的tag可以通过Interface Builder来设定

-- 思考: isFlipped --

PDF和PostScript使用的是标准的迪卡尔坐标系统.当向上移动页面时,y值增加. Quartz使用了同样的模型. view的原点在左下点.

对于有些绘制,如果让原点在左上方会更方便. 也就是当向下移动页面是y增加,这时我们叫这个view是filpped的

你通过重载方法ifFlipped来filp一个view

- (BOOL)isFlipped

{

    return YES;

}

当我们讨论坐标系统时, x和y是使用点来计数的. 一般72点=1英寸. 默认的1.0point及时屏幕上的一个像素.不过,你可以通过改变坐标系统来改变point的大小

// Make everything in the view twice as large

NSSize newScale;

newScale.width = 2.0;

newScale.height = 2.0;

[myView scaleUnitSquareToSize:newScale];

[myView setNeedsDisplay:YES];

-- 挑战 --

NSBezierPath可以会在Bezier曲线. 绘制看看咯.

继续阅读