自定义NSView

自定义NSView,第1张

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

自定义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类声明中,找找和它们相关的方法]

找到了么?
    - (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,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曲线. 绘制看看咯.(请查看NSBezIErPath帮助文档)

总结

以上是内存溢出为你收集整理的自定义NSView全部内容,希望文章能够帮你解决自定义NSView所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1064970.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-26
下一篇 2022-05-26

发表评论

登录后才能评论

评论列表(0条)

保存