开发者

How to use a custom view correctly?

I have been trying to make a simple drawing program. Recently, I have figured out to draw shapes in a custom view for this purpose. My problem is that I have to draw everything at a single point in time. I don't know if that actually makes sense, but it seems to me that it calls the drawRect method only 开发者_JAVA百科once, at that "once" is on startup.

Here is my code so far:

Header file.

NSBezierPath *thePath;
NSColor *theColor;
NSTimer *updateTimer;
NSPoint *mousePoint;
int x = 0;
int y = 0;

@interface test : NSView {
    IBOutlet NSView *myView;

}

@property (readwrite) NSPoint mousePoint;

@end

Then, implementation in the .m file.

@implementation test

@synthesize mousePoint;

- (void) mouseDown:(NSEvent*)someEvent {         
    CGEventRef ourEvent = CGEventCreate(NULL);
    mousePoint = CGEventGetLocation(ourEvent);
    NSLog(@"Location: x= %f, y = %f", (float)mousePoint.x, (float)mousePoint.y);
    thePath = [NSBezierPath bezierPathWithRect:NSMakeRect(mousePoint.x, mousePoint.y, 10, 10)];
    theColor = [NSColor blackColor];  

} 

- (void) mouseDragged:(NSEvent *)someEvent {
    mousePoint = [someEvent locationInWindow];
    NSLog(@"Location: x= %f, y = %f", (float)mousePoint.x, (float)mousePoint.y);
    x = mousePoint.x;
    y = mousePoint.y;
    [myView setNeedsDisplay:YES];

}

- (void) drawRect:(NSRect)rect; {
    NSLog(@"oisudfghio");
    thePath = [NSBezierPath bezierPathWithRect:NSMakeRect(x, y, 10, 10)];
    theColor = [NSColor blackColor];
    [theColor set];
    [thePath fill];

}

@end

On startup, it draws a rectangle in the bottom left corner, like it should. The problem is, the drawRect method is only called on startup. It just won't fire no matter what I do.

EDIT: I have just updated the code. I hope it helps.

SECOND EDIT: I have really simplified the code. I hope this helps a bit more.


Short Answer: When your view's state is changed such that it would draw differently, you need to invoke -[NSView setNeedsDisplay:]. That will cause your view's drawRect: method to be called in the near future. You should never call drawRect: yourself. That's a callback that's invoked on your behalf.

When events occur in your application that cause you to want to change your drawing, capture state about what happened into instance variables, invoke setNeedsDisplay: and then later when drawRect: is called do the new drawing.

Long Answer: In Cocoa, window drawing is done with an pull/invalidation model. That means the window has an idea of whether or not it needs to draw, and when it thinks it needs to draw it draws once per event loop.

If you're not familiar with event loops you can read about them on Wikipedia

At the top level of the application you can imagine that Cocoa is doing this:

while (1) {
   NSArray *events = [self waitForEvents];
   [self doEvents:events];
}

Where events are things like the mouse moving, the keyboard being pressed, and timers going off.

NSView has a method -[NSView setNeedsDisplay:]. It takes a boolean parameter. When that method is invoked the window invalidates the drawing region for that view, and schedules an event for the future to do redrawing - but only if there isn't a preexisting redrawing event scheduled.

When the runloop spins next time, the views that were marked with setNeedsDisplay: are re-drawn. This means you can call setNeedsDisplay: several times in a row and drawing will be batched to one call of drawRect: in the future. This is important for performance reasons and means you can do things like change the frame of a view several times in one method but it will only be drawn once at the final location.


The code in your example has a couple of problems. The first is that all drawing code must be in the drawRect: method or a method called from drawRect:, so the drawing code you've placed in your other methods will have no effect at runtime. The second problem is that your code should never directly call drawRect:; instead, the framework will call it automatically (if necessary) once per event cycle.

Instead of hardcoding all the values, consider using instance variables for things you want to be able to change at runtime, for example, the drawing color and rectangle. Then in your mouseDragged: method, send the view (myView in your example) a setNeedsDisplay: message. If you pass YES as the argument, the drawRect: method will be called for you by the framework.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜