Image和鼠标事件

第十八章: Image和鼠标事件

 

在前一章中,你在一些随机点间画线.编写成一个绘制程序会更有趣的. 为了能编写这样的程序,你必须要获取和处理鼠标事件

 

- NSResponder

NSView继承至NSResponder类. NSResponder类定义了所有的事件处理方法. 现在,我们只对鼠标事件感兴趣.至于键盘事件,留到下一章来讨论吧. NSResponder定义了如下方法:

    - (void)mouseDown:(NSEvent *)theEvent;

    - (void)rightMouseDown:(NSEvent *)theEvent;

    - (void)otherMouseDown:(NSEvent *)theEvent;

 

    - (void)mouseUp:(NSEvent *)theEvent;

    - (void)rightMouseUp:(NSEvent *)theEvent;

    - (void)otherMouseUp:(NSEvent *)theEvent;

 

    - (void)mouseDragged:(NSEvent *)theEvent;

    - (void)scrollWheel:(NSEvent *)theEvent;

    - (void)rightMouseDragged:(NSEvent *)theEvent;

    - (void)otherMouseDragged:(NSEvent *)theEvent;

 

注意,所有的参数都是NSEvent对象

 

-- NSEvent

一个事件对象包含了所有激发该时间相关的信息.当你要处理一个鼠标事件的时候,你应该会对这个方法感兴趣

- (NSPoint)locationInWindow

它返回事件发生的位置

 

- (unsigned int)modifierFlags

返回的整型告诉你用户按住了哪个键盘上的键,这会让程序员获得Control-click和Shift-Click. 下面是一个例子

- (void)mouseDown:(NSEvent *)e

{

    unsigned int flags;

    flags = [e modifierFlags];

    if (flags & NSControlKeyMask) {

         ...handle control click...

    }

    if (flags & NSShiftKeyMask) {

         ...handle shift click...

    }

}

 

下面是一些常量,你也可以使用AND(&)来修饰modifier flag

   NSShiftKeyMask

   NSControlKeyMask

   NSAlternateKeyMask

   NSCommandKeyMask

 

- (NSTimeInterval)timestamp

这个方法返回一个按秒计量的时间值, 指的是从机器启动开始,到该事件发送时的时间值. NSTimeInterval为double类型

 

- (NSWindow *)window

事件发生在哪个window上

 

- (int)clickCount

单击还是双击.

 

- (float)pressure

如果用户使用一个压力输入设备,这个方法将返回压力,0-1的值

 

- (float)deltaX;

- (float)deltaY;

- (float)deltaZ;

这些方法获得鼠标和滚轮的位置改变量

 

-- 获取鼠标事件

为了能获取到鼠标事件,你需要在StrechView.m中重载鼠标事件方法

#pragma mark Events

 

- (void)mouseDown:(NSEvent *)event

{

    NSLog(@"mouseDown: %d", [event clickCount]);

}

 

- (void)mouseDragged:(NSEvent *)event

{

    NSPoint p = [event locationInWindow];

    NSLog(@"mouseDragged:%@", NSStringFromPoint(p));

}

 

- (void)mouseUp:(NSEvent *)event

{

    NSLog(@"mouseUp:");

}

编译运行程序,试试双击,并检查输出的click count. 注意噢,首先是获取到第一次点击事件,它的的click count为1.接着第二次点击事件到了,它的click count为2

 

在XCode 的编辑窗口顶部又一个弹出菜单, 使用#pragma mark.可以给这个菜单添加一项,这样你或是阅读你代码的人更方便,

 

 

-- 使用 NSOpenPanel

如果在我们的view上显示一张图像,这会更有意思点. 不过首先,你的创建一个controller对象来从一个文件中读取图像数据. 这时是一个学习使用NSOpenPanel的好机会. 还记得RaiseMan函数中使用到了NSOpenPanel吧,那时是NSDocument类帮我们做的. 而这里,你将自己来使用NSOpenPanel.图18.1显示了你的程序将是什么样.

 

窗口下面的slide用来控制图像的透明度. 图18.2是对象关系图

--修改nib文件

在XCode中,创建一个新的Objective-c类,命名为AppController.在.h中,添加outlet StretchView和一个action,用来启动Open Panel

#import <Cocoa/Cocoa.h>

@class StretchView;

 

@interface AppController : NSObject

{

    IBOutlet StretchView *stretchView;

}

- (IBAction)showOpenPanel:(id)sender;

 

打开MainMenu.nib. 从Libray中拖动一个对象到window. 在Identity Inspector中,将它的class设为AppController.如图18.3

拖动一个slider到window. 在Inspector中,设置范围为0-1. 勾选Continuous. 这个slider将用来控制图像显示的透明度-opaque如图18.4

 

将slider的value绑定到AppController的stretchView.opacity key path如图18.5

在AppController上Control-click,将他的outlet stretchView和window上的StretchView连接上如图18.6

 

回到nib文件中的main menu,打开File菜单,删除open菜单项以外的所有菜单项。然后Control拖拽该菜单项到AppController的action:showOpenPanel:(如图18.7)

 

 

保存文件

--编辑代码

编辑AppController.m文件如下:

#import "AppController.h"
#import "StretchView.h"
 
@implementation AppController
 
- (void)openPanelDidEnd:(NSOpenPanel *)openPanel
             returnCode:(int)returnCode
            contextInfo:(void *)x
{
    // Did they choose "Open"?
    if (returnCode == NSOKButton) {
       NSString *path = [openPanel filename];
       NSImage *image = [[NSImage alloc] initWithContentsOfFile:path];
       [stretchView setImage:image];
       [image release];
    }
}
 
- (IBAction)showOpenPanel:(id)sender
{
    NSOpenPanel *panel = [NSOpenPanel openPanel];
 
    // Run the open panel
    [panel beginSheetForDirectory:nil
                             file:nil
                            types:[NSImage imageFileTypes]
                   modalForWindow:[stretchView window]
                    modalDelegate:self
                   didEndSelector:
        @selector(openPanelDidEnd:returnCode:contextInfo:)
                      contextInfo:NULL];
}
 
@end
 
看看启动sheet的那行代码。这是一个非常handy的方法:


 

- (void)beginSheetForDirectory:(NSString *)path
                          file:(NSString *)name
                         types:(NSArray *)types
                modalForWindow:(NSWindow *)docWindow
                 modalDelegate:(id)delegate
                didEndSelector:(SEL)didEndSelector
                   contextInfo:(void *)contextInfo

 

这个方法以薄板的方式弹出一个和docWindow相关文件打开面板。DidEndSelector有如下的声明:

- (void)openPanelDidEnd:(NSWindow *)sheet
             returnCode:(int)returnCode
            contextInfo:(void *)contextInfo;

 

一般在模式对话框的代理中实现该方法。参数path指定了文件浏览时将打开的初始位置。参数name制定了所选文件的初始名字。Path和name参数都可以为空。

 

-         在View中合成一个图像

你将需要修改StretchView来使用opacity和image。首先,在StretchView.h中声明变量和方法:

#import <Cocoa/Cocoa.h>
 
@interface StretchView : NSView
{
    NSBezierPath *path;
    NSImage *image;
    float opacity;
}
@property (readwrite) float opacity;
- (void)setImage:(NSImage *)newImage;
- (NSPoint)randomPoint;
 

@end

 

然后在StretchView.m实现这些方法:

#pragma mark Accessors
 
- (void)setImage:(NSImage *)newImage
{
    [newImage retain];
 
    [image release];
    image = newImage;
    [self setNeedsDisplay:YES];
}
 
- (float)opacity
{
    return opacity;
}
 
- (void)setOpacity:(float)x
{
    opacity = x;
    [self setNeedsDisplay:YES];
}

 

在每个方法的最后,通知view需要重画自己。在方法initWithFrame最后,将opacity设置为1.0:

        [path closePath];
        opacity = 1.0;
        return self;

}

 

同样在StrechView.m中,你需要在drawRect:方法中合成图像:

- (void)drawRect:(NSRect)rect
{
    NSRect bounds = [self bounds];
    [[NSColor greenColor] set];
    [NSBezierPath fillRect:bounds];
    [[NSColor whiteColor] set];
    [path fill];
    if (image) {
        NSRect imageRect;
        imageRect.origin = NSZeroPoint;
        imageRect.size = [image size];
        NSRect drawingRect = imageRect;
        [image drawInRect:drawingRect
                 fromRect:imageRect
                operation:NSCompositeSourceOver
                 fraction:opacity];
    }
}

注意到drawInRect:fromRect:operation:fraction:方法用力合成图像到view上,fraction决定了图像的透明度。

 

你需要在dealloc方法中释放image:

- (void)dealloc
{
    [path release];
    [image release];
    [super dealloc];
}

 

编译并运行程序。你可以再/Developer/Examples/AppKit/Sketch中找到一些图像,当你打开一个图像时,它将出现在StretchView对象的左下方。

 

--View的坐标系统

最后的一点乐趣呢,来自通过拖拽鼠标修改image的显示位置和尺寸。MouseDown确定图像将要显示区域的其中一个角,而mouseUp确定了相对的另外一个角。最后程序将如图18.8.

 

 

每个view都有自己的坐标系统。缺省情况下,(0,0)为左下角。这和PDF以及PostScript一致。如果你愿意,你可以修改view的坐标系统。可以移动原点;修改刻度;以及旋转整个坐标系统。窗口也有一个坐标系统。

 

如果有两个view,a和b,而你需要将NSPoint p从b的坐标系统转换到a的坐标系统。可以这样做:

NSPoint q = [a convertPoint:p fromView:b];

 

如果b为空,将从window的坐标系统转换到a的坐标系统。

 

鼠标消息包含了窗口坐标系统下的位置信息。所以,你一般必须将位置点转换到本地坐标系统。你将创建成员变量来保存图像将显示的区域的角位置。

 

在StretchView.h中定义这些变量:

NSPoint downPoint;

NSPoint currentPoint;

 

mouseDown:的位置为downPoint,而mouseDragged:和mouseUp:将更新currentPoint。

 

编辑鼠标事件处理方法来更新downPoint和currentPoint:

- (void)mouseDown:(NSEvent *)event
{
    NSPoint p = [event locationInWindow];
    downPoint = [self convertPoint:p fromView:nil];
    currentPoint = downPoint;
    [self setNeedsDisplay:YES];
}
 
- (void)mouseDragged:(NSEvent *)event
{
    NSPoint p = [event locationInWindow];
    currentPoint = [self convertPoint:p fromView:nil];
    [self setNeedsDisplay:YES];
}
 
- (void)mouseUp:(NSEvent *)event
{
    NSPoint p = [event locationInWindow];
    currentPoint = [self convertPoint:p fromView:nil];
    [self setNeedsDisplay:YES];
}

 

Add a method to calculate the rectangle based on the two points:

- (NSRect)currentRect
{
    float minX = MIN(downPoint.x, currentPoint.x);
    float maxX = MAX(downPoint.x, currentPoint.x);
    float minY = MIN(downPoint.y, currentPoint.y);
    float maxY = MAX(downPoint.y, currentPoint.y);
 
    return NSMakeRect(minX, minY, maxX-minX, maxY-minY);
}

 

(我不知道为什么,很多人都将最后一个方法输入错误。在继续前仔细再检查一次,否则结果将是令人失望的)

 

StretchView.h中声明currentRect方法。

 

为了让用户在没有任何拖拽的情况下也能够看到图像,在setImage:方法中初始化downPoint和currentPoint:

- (void)setImage:(NSImage *)newImage
{
    [newImage retain];
    [image release];
    image = newImage;
    NSSize imageSize = [newImage size];
    downPoint = NSZeroPoint;
    currentPoint.x = downPoint.x + imageSize.width;
    currentPoint.y = downPoint.y + imageSize.height;
    [self setNeedsDisplay:YES];
}

 

drawRect:中,让iamge显示在矩形区域内:

- (void)drawRect:(NSRect)rect
{
    NSRect bounds = [self bounds];
    [[NSColor greenColor] set];
    [NSBezierPath fillRect:bounds];
    [[NSColor whiteColor] set];
    [path stroke];
    if (image) {
        NSRect imageRect;
        imageRect.origin = NSZeroPoint;
        imageRect.size = [image size];
        NSRect drawingRect = [self currentRect];
        [image drawInRect:drawingRect
                 fromRect:imageRect
                operation:NSCompositeSourceOver
                 fraction:opacity];
    }
}

 

编译运行程序。注意当拖动到边缘时,view不能滚动。如果能让scroll view能自动移动让用户看到他们拖拽到哪里了将更好一点。这个技术叫autoscrolling。

 

 

--Autoscrolling

当用户拖到鼠标时,通过发送消息autoscroll:给clip view来使程序实现autoscrolling. 将事件作为该消息的参数。打开StretchView.m,在方法mouseDragged:中添加下面的代码:

- (void)mouseDragged:(NSEvent *)event
{
    NSPoint p = [event locationInWindow];
    currentPoint = [self convertPoint:p fromView:nil];
    [self autoscroll:event];
    [self setNeedsDisplay:YES];
}

编译运行程序。

注意,目前只有当你拖拽时才会自动滚动。为了实现更加平滑的自动滚动,很多开发者会生成一个计时器,在用户拖拽时周期性的给view发送autoscroll:消息。我们将在第24章讨论计时器。

 

思考:NSImage

大多数情况下,如读取图像,改变图像大小,以及上面练习中的在view中合成图像, NSimage足够胜任。

 

一个NSImage对象包含一个“表示”数组。例如,图像为一头绘制好的母牛。它可以绘制在PDF中,或是一个彩色位图中,或是一个黑白位图中。每个版本的“表示”都是一个NSImageRep子类的对象。你可以给图像添加一个“表示”,也可以从图像中删除一个“表示”。当你坐下了重写Adobe Photoshop程序时,你就会要维护图像的“表示”

 

下面是NSImageRep子类列表:

·         NSBitmapImageRep

·         NSEPSImageRep

·         NSPICTImageRep

·         NSCachedImageRep

·         NSCustomImageRep

·         NSPDFImageRep

 

虽然NSImageRep只有5个子类,不过请注意,NSImage大约可以支持24个类型的图像文件,其中包含了所有的常用图像格式:PICT, GIF, JPG, PNG, PDF, BMP, TIFF等等。

 

挑战:

创建一个新的基于文档的程序,来容许用户使用随机位置和大小绘制椭圆形。NSBezierPath包含下面的绘制方法:

NSBezierPath *)bezierPathWithOvalInRect:(NSRect)rect;

 

如果你想多做点,就给它添加保持和打开文件功能

如果你还想多做点,添加undo功能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值