Cocoa自定义View

65 篇文章 0 订阅
47 篇文章 0 订阅
程序中所有的可视对象要么是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类声明中,找找和它们相关的方法]

找到了么?
    - (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


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

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

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

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


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


--大小检查--
StretcView对象是window content view的子view. 这就有个有意思的问题:当父view改变大小的时候, view有什么反应呢?在info panel中有一个tab页面来指定这样的行为. 打开Size Info Panel. 设置如图17.7. 现在你改变窗口大小. 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类型


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


首先你需要一个成员变量来保存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

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


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


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

-- 通过程序创建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

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


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

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


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曲线. 绘制看看咯.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值