PDA

View Full Version : Basic question about drawing to a view




bahlquist
Oct 25, 2010, 07:14 PM
I am having trouble drawing to a view more than once. I want the following program to fill my view with white and then draw a black path from a given point (coordinates (100,100)) to the coordinates of a mouseDown event.

I have set up a custom view in IB that uses my class BADraw which is as follows:

BADraw.h:

#import <Cocoa/Cocoa.h>
@interface BADraw : NSView {}
@end



BADraw.m:

#import "BADraw.h"

@implementation BADraw
-(void)mouseDown:(NSEvent*)event{
NSPoint p;
p.x = 100;
p.y = 100;
NSBezierPath* path = [[NSBezierPath alloc] init];
[path moveToPoint:p];
[path setLineWidth:1.0];
[[NSColor blackColor] set];
[path lineToPoint:[event locationInWindow]];
[path stroke];

//I tried using the following methods to get the path to display:
[self lockFocus];
[self setNeedsDisplay:YES];
[self display];
[self unlockFocus];

[path release];
}

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

@end

IB information:
I dragged a custom view from the IB palette into my window and set it to use BADraw.

Output:
The output is a white rectangle filling the window. No paths appear when I click.

I'm obviously missing something basic here, but I don't know what. What do I need to do?



kainjow
Oct 25, 2010, 09:07 PM
Several things.

1. To get the mouse location relative to your view's bounds, use:
[self convertPoint:[event locationInWindow] fromView:nil]

2. All drawing for your view should go in the drawRect: method.

3. lockFocus/unlockFocus/display are really only used on an NSView when you're drawing into it from a background thread, and you probably will never be doing that :)

4. To get the view to redraw, just use setNeedsDisplay:YES

So, here's how it should look:
#import <Cocoa/Cocoa.h>
@interface BADraw : NSView
{
NSPoint mouseLoc;
}
@end

#import "BADraw.h"

@implementation BADraw

- (void)awakeFromNib
{
mouseLoc = NSMakePoint(-1, -1);
}

- (void)mouseDown:(NSEvent*)event
{
mouseLoc = [self convertPoint:[event locationInWindow] fromView:nil];
[self setNeedsDisplay:YES];
}

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

if (NSEqualPoints(mouseLoc, NSMakePoint(-1, -1)))
return;

NSPoint p;
p.x = 100;
p.y = 100;
NSBezierPath *path = [NSBezierPath bezierPath];
[path moveToPoint:p];
[path setLineWidth:1.0];
[path lineToPoint:mouseLoc];
[[NSColor blackColor] set];
[path stroke];
}

@end

bahlquist
Oct 26, 2010, 12:20 PM
Thanks! If I wanted the previously drawn paths to remain in the view, how could I do that? I could just append to a global path variable each time I click and then redisplay this path using setNeedsDisplay:, but in the application I am actually making each click creates a path that contains upwards of 50,000 points and this will slow things down after a dozen clicks. Is there a way to draw the new path without redisplaying everything? (This question is relevant to the above code.)

Sydde
Oct 26, 2010, 12:37 PM
Thanks! If I wanted the previously drawn paths to remain in the view, how could I do that? I could just append to a global path variable each time I click and then redisplay this path using setNeedsDisplay:, but in the application I am actually making each click creates a path that contains upwards of 50,000 points and this will slow things down after a dozen clicks. Is there a way to draw the new path without redisplaying everything? (This question is relevant to the above code.)

-setNeedsDisplayInRect: might reduce the amount of drawing needed, if you have such a method, and it works to minimize drawing appropriately.

Your other option might be to draw the existing lines into a temporary NSImage object, with a bitmap image rep, which you could use as a sort of offscreen cache (I would start the cache image after you have a few hundred lines and add lines to it incrementally so that you still have a few dozen uncached lines to draw over it).