Anda di halaman 1dari 46

Custom Views

The View Heirarchy


• An NSView always exists within an NSWindow
• All view objects with an NSWindow are arranged in a
hierarchy.
• The hierarchy starts with the NSWindow’s content view
• An NSView’s superview and all views above that are
referred to as the NSView’s ancestors
• An NSView’s subviews and all of their subviews are
known as the NSView’s descenants
• Every NSView has its own area to draw in and it’s own
coordinate system.
• An NSView can scale, translate, or rotate its coordinates
dynamically
The View Hierarchy
• NSView provides access to it’s ancestor and decendents
and the window it lives in
– (NSView *)superView;
– (NSArray *)subViews;
– (NSWindow *)window;
• Any view can have subviews, the following five views
commonly have subviews
– The content view of a window
– NSBox, The contents of the box are subviews
– NSScrollView, any views in scroll view is a subview, also the
scroll bars are also subviews
– NSSplitView, Each view in a split view are subviews
– NSTabView, As tabs are selected different subviews are
swapped in and out
Lets get a view to draw itself
• We will create a very simple view that will appear and
paint itself green.
– Create a new application in Xcode of type “Cocoa
Application”
– Open the MainMenu.nib, and select NSView in the class
browser
– Subclass the NSView and call it “StretchView”
– Create the files for “StretchView”
– Save the files in the project dirctory
Create an instance of a view subclass
• Now drag out a CustomView placeholder and add it to
you window.
• Resize the view to fill most of the window and set the
class of the view to “StretchView”
• Open the size info panel and make the view “stretchy”
inside

• Save and close the nib file


drawRect:
• When a view needs to draw itself, it is sent the message
drawRect: with the rectangle that needs to be drawn or
redrawn.
• drawRect: is called automatically, you never need to call
it directly
• If you know that a view needs drawing, you send the
view the setNeedsDisplay: message
– [myView setNeedsDisplay:YES];
• When a view is told to redraw itself, before drawRect:,
the system locks focus on the view
• When the focus is locked on a view, the view’s graphics
context is active
drawRect:
• You can use NSBezierPath to draw lines, circles and
rectangles
• You can us NSImage to create composite images on the
view
• We will fill the view with a green rectangle
• Open StretchView.m and add the following code to the
drawRect: method
- (void)drawRect:(NSRect)rect
{
NSRect bounds = [self bounds];
[[NSColor greenColor] set];
[NSBezierPath fillRect:bounds];
}
drawRect:
• NSSize is a struct with two members: width and height
(both floats)
• NSPoint is a struct with two members: x and y (again
both floats)
• Cocoa occasionally uses structs rather tha full objects for
performance reasons.
• Cocoa structs:
– NSSize
– NSPoint
– NSRect
– NSRange
– NSDecimal
– NSZone
– NSAffineTransformStruct
and Voila!
Our Green View
Drawing with NSBezierPath
• If you want to draw lines, ovals, curves, or polygons, you
can use NSBezierPath
• Open StretchView.h and make it look like this

#import <Cocoa/Cocoa.h>

@interface StretchView:View
{
NSBezierPath *path;
}
- (NSPoint)randomPoint;

@end
Drawing with NSBezierPath
• Make StretchView.m look like this
#import “StretchView.h”

@implementation StretchView

- (id)initWithFrame:(NSRect)rect
{
int i;
NSPoint p;

if (self = [super initWithFrame:rect]) {


// Seed the random number generator
srandom(time(NULL));

//Create a path object


path = [[NSBezierPath alloc] init];
[path setLineWidth:3.0];
p = [self randomPoint];
[path moveToPoint: p];
for ( i = 0; i < 15; i++) {
p = [self randomPoint];
[path lintoToPoint: p];
}
[path closePath];
}
return self;
}
Drawing with NSBezierPath
- (NSPoint)randomPoint
• continued... {
NSPoint result;
NSRect r;
int width, height;
r = [self bounds];
width = round(r.size.width);
height = round(r.size.height);
result.x = (random() % width) + r.origin.x;
result.y = (random() % height) + r.origin.y;
return result;
}

- (void)drawRect:(NSRect)rect
{
NSRect bounds = [self bounds];
[[NSColor greenColor] set];
[NSBezierPath fillRect: bounds];

[[NSColor whiteColor] set];


[path stroke];
}

- (void)dealloc
{
[path release];
[super dealloc];
}
@end
and Voila!
Our Green View with random lines
NSScrollView
• Open MainMenu.nib in Interface Builder
• Select the view, and choose “Make subview of Scroll
View” from the layout menu
• Make the ScrollView resize with the window
• Change the StretchView NOT resize with window
• Save the nib an build the application and run
and Voila!
Our Scrollable Green View with random lines
Images and Mouse Events
NSResponder
• The previous application drew random interconnected
lines
• A more interesting application would be some sort of
drawing application
• To write this sort of application we need to be able to
get and handle mouse events
• All event of the event-handling methods are declared in
NSResponder
• The argument to all of NSResponders methods is always
an NSEvent
NSEvent
• An event object has all the information about what the
user did to trigger the event
• When dealing with mouse events, you will be interested
in the following methods:
– (NSPoint)locationInWindow
• This method returns the location of the mouse event
– (unsigned int)modifierFlags
• This integer tells you which modifier key the user is
holding down on the keyboard
• This enables the programmer to tell a control-click from
a shift-click
NSEvent
• The code would look like this:

- (void)mouseDown:(NSEvent *)e
{
unsigned int flags;
flags = [e modifierFlags];
if (flags & NSControlKeyMask) {
...handle control click...
}
if (flags & NSShiftKeyMask) {
...handle shift click...
}
}
NSEvent
• Here are the constants you will AND (&) against the
modifier flags:
– NSAlphaShiftKeyMask
– NSShiftKeyMask
– NSControlKeyMask
– NSAlternateKeyMask
– NSCommandKeyMask
– NSNumericPadKeyMask
– NSHelpKeyMask
– NSFunctionKeyMask
NSEvent
• more methods...
– (NSTimeInterval)timestamp
• This method gives the time interval in seconds between
the machine booted and the event
– (NSWindow *)window
• This method returns the window associated with the
event
– (int)clickCount
• Was it a single, double or tripe click
– (float)pressure
• If the user is using an input device that gives pressure *a
tablet) this method returns the pressure (between 0 and
1)
Getting Mouse Events
• To get mouse events, you need to override the mouse
event methods in StretchView.m
- (void)mouseDown:(NSEvent *)event
{
NSLog(@”mouseDown: %d , [event clickCount]);
}
- (void)mouseDragged:(NSEvent *)event
{
NSLog(@”mouseDragged: “);
}
- (void)mouseUp:(NSEvent *)event
{
NSLog(@”mouseUp”);
}
Getting Mouse Events
• Build and run your application
• Try double-clicking, dragging etc
Using NSOpenPanel
• It would be fun to composite an image onto the view
• First we need to create a controller object that will read
the image from a file
• This is a good opportunity to learn how to use
NSOpenPanel
NSOpenPanel
• Open the nib file and create a new subclass on NSObject
called AppController
• Create two outlets called “stretchView” (of type
StretchView) and “slider” (of type NSSlider)
• Create two actions “open:” and “fade:”
• Create files for the AppController class and add them to
your project
• Now instantiate a new instance of AppController
• Ctrl-drag from the AppController instance to the
StretchView and connect to the stretchView outlet
NSOpen
• Drop a slider on the window beneath the view
• In the inspector set its range from 0 to 1
• Also check the box labelled “Continuously send action
while sliding”
• Connect the slider outlet to the instance of NSSlider
• Set the target of the slider to be the AppController
• Its action will be fade:
• Delete all the menu items from the File menu except
Open
• Ctrl-drag to connect the menu item to the
AppControllers open: action
awakeFromNib Versus Init
• WHen your nib file is loaded three things will happen (in
this order) to the AppController object (and all other
objects in your nib)
– It will be allocated and sent the init method
– Its outlets will be set
– It will be sent awakeFromNib
• Note that init is sent before any of its outlets are set, so
we cannot send messages to any of the other objects
from the nib file in the init method
• We can however send them messages from the
awakeFromNib method
Edit the code
Edit AppController.h and add the following line

#import <Cocoa/Cocoa.h>
@class StretchView;
Edit the code
Edit AppController.m

#import “AppController.h”
#import “StretchView.h”

@implementation AppController

- (void)awakeFromNib
{
[slider setFloatvalue:0.5];
[stretchView setOpacity:0.5];
}
- (IBAction)fade:(id)sender
{
[stretchView setOpacity:[sender floatValue]];
}
Edit the code
continued...

- (void)openPanelDidEnd:(NSOpenPanel *)openPanel
returnCode:(int)returnCode
contextInfo:(void *)x
{
NSString *path
NSImage *image

if (return code == NSOKButton) {


path = [openPanel filename];
image = [[NSImage alloc] initWithContentsOfFile:path];
[setretchView setImage:image];
[image release];
}
]
Edit the code
continued...

- (IBAction)open:(id)sender
{
[panel beginSheetForDictionary:nil
file:nil
types:[NSImage imageFileTypes]
modaForWindow:[stretchView window]
modalDelegate:self
didEndSelector:@selector(openPanelDidEnd:reurnCode:contextInfo:)
contextInfo:NULL];
]

@end
Note
• Notice the line where we start the sheet

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

• This method brings up an open panel as a sheet


attached to thedocWindow
Note
• The didEndSelector should have the following signature

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

• It should be implemented in the modal delegate (which


in this case was the AppController)
Composite an Image onto our View
Edit StretchView.h

#import <Cocoa/Cocoa.h>

@interface StretchView : NSView


{
NSBezierPath *path;
NSImage *image;
float opacity;
}
- (void)setImage:(NSImage *)newImage;
- (void)setOpacity:(float)x;
- (NSPoint)randomPoint;

@end
Composite an Image onto our View
Add to StretchView.m

- (void)setImage:(NSImage *)newImage
{
[newImage retain];
[image release];
image = newImage;
[self setNeedsDisplay:YES];
}

- (void)setOpacity:(float)x
{
opacity = x;
[self setNeedsDisplay:YES];
}
Composite an Image onto our View
continued...
- (void)drawRect:(NSRect)rect
{
NSRect bounds = [self bounds];
[[NSColor greenColor] set];
[NSBezierPath fillRect:bounds];
[[NSColor whiteColor] set];
[path stroke];
if (image) {
NSRect imageRect;
NSRect drawingRect;
imageRect.origin = NSZeroPoint;
imageRect.size = [image size];
drawingRect = imageRect;
[image drawInRect:imageRect
fromRect:imageRect
operation:NSCompositeSourceOver
fraction:opacity];
}
}
Note
• Notice that the drawRect:fromRect:operatio:fraction:
method composites the image onto the view
• The fraction determines the opacity
• Also remember to be tidy

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

• Now Build and run, you will find some images in


/Developer/Example/AppKit/Sketch
The view’s coordinate system
• Now we will change the application so that we can
choose the location and dimensions of the image based
on the user’s dragging
• Each view has its own coordinate system
• If you have two views, a and b and you need to translate
an NSPoint p from b’s coordinate system to a’s
coordinate system, it would look like this

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

• If b is nil, the point is converted from the Windows


coordinate system
The view’s coordinate system
• Mouse events have their location in the Windows
coordinate system, so you need to convert the point to
the views coordinate system
• We will create variables to hold onto the corners of the
rectangle where the image will be drawn
• Add these instance variables to StretchView.h

NSPoint downPoint;
NSPoint currentPoint;

• downPoint will be the location of the mouseDown


• currentPoint will be updated by mouseDragged: and
mouseUp:
The view’s coordinate system
Edit StretchView.m
- (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];
}
The view’s coordinate system
Edit StretchView.m continued...
- (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];


}

Declare the currentRect method in StretchView.h


The view’s coordinate system
Edit StretchView.m continued...
- (void)setImage (NSImage *)newImage
{
[newImage retain];
[image release];
image = newImage;
NSSize imageSize;
imageSize = [newImage size];
downPoint = NSZeroPoint;
currentPoint.x = downPoint.x + imageSize.width;
currentPoint.y = downPoint.y + imageSize.height;
[self setNeedsDisplay:YES};
}
The view’s coordinate system
Edit StretchView.m continued...
- (void)drawRect:(NSRect)rect
{
NSRect bounds = [self bounds];
[[NSColor greenColor] set];
[NSBezierPath fillRect:bounds];
[[NSColor whiteColor] set];
[path stroke];
if (image) {
NSRect imageRect;
NSRect drawingRect;
imageRect.origin = NSZeroPoint;
imageRect.size = [image size];
drawingRect = [self currentRect];
[image drawInRect:imageRect
fromRect:imageRect
operation:NSCompositeSourceOver
fraction:opacity];
}
}
The view’s coordinate system
• Build and run the application
• Notice that the view does not scroll when you drag past
the edges
• Lets add autoscrolling to our application
Autoscrolling
• To add autoscrolling to the application, we will send the
message autoscroll: to the clip view when the user drags
• Edit StretchView.m

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

• Build and run the application


Challenge
• Create a new document-based application that allows
the user to draw ovals in arbitary locations and sizes
• NSBezierPath has the following method
– (NSBezierPath *)bezierPathWithOvalInRect:(NSRect *)rect

Anda mungkin juga menyukai