Welcome to the MacNN Forums.

If this is your first visit, be sure to check out the FAQ by clicking the link above. You may have to register before you can post: click the register link above to proceed. To start viewing messages, select the forum that you want to visit from the selection below.

You are here: MacNN Forums > Software - Troubleshooting and Discussion > Developer Center > NSTimer / screen redraw troubles

NSTimer / screen redraw troubles
Thread Tools
Forum Regular
Join Date: Jan 2001
Location: San Luis Obispo, California, USA
Status: Offline
Reply With Quote
May 3, 2001, 05:55 PM
 
Ok, I have a simple app that moves a gif in an imageview. It uses NSTimer to trigger it every .5 seconds, when it moves up 10 pixels.

It works great, except for one major thing:

It only redraws when I'm moving the window around.

If I don't touch the window, I don't see anything at all. As soon as I move the window, everything that was drawn appears.

Also, if I click inside the imageview, it erases what was there.

Here's my code:

Controller.h:
Code:
#import <Cocoa/Cocoa.h> @interface Controller : NSObject { IBOutlet id imageView; NSTimer *timer; int y; NSImageRep *rep; NSSize size; } - (void)displayImage; - (void)initImage; @end
Controller.m:

Code:
#import "Controller.h" @implementation Controller - (void)applicationDidFinishLaunching: (NSNotification *)notification { [self initImage]; timer= [[NSTimer scheduledTimerWithTimeInterval: 0.5 target:self selector:@selector(displayImage) userInfo:nil repeats:YES ] retain]; //[self displayImage]; } - (void)initImage { y=0; rep = [[NSImage imageNamed:@"Planet.gif"] bestRepresentationForDevice:nil]; size = NSMakeSize(100, 100); [rep setSize:size]; } - (void)displayImage { [imageView lockFocus]; [rep drawAtPoint:NSMakePoint(150, y)]; y+=10; [imageView unlockFocus]; } @end
Thanks for any help!

-Nathaniel


[This message has been edited by robotic (edited 05-03-2001).]
     
Dedicated MacNNer
Join Date: Apr 2001
Location: San Francisco, USA
Status: Offline
Reply With Quote
May 3, 2001, 07:09 PM
 
I haven't done any real Cocoa programming yet, but looking at this documentation it seems that you need this at the end of your displayImage method:

[imageView setNeedsDisplay];
     
robotic  (op)
Forum Regular
Join Date: Jan 2001
Location: San Luis Obispo, California, USA
Status: Offline
Reply With Quote
May 3, 2001, 07:22 PM
 
Nope, that doesn't do it

Thanks though!

-Nathaniel
     
Dedicated MacNNer
Join Date: Apr 2001
Location: San Francisco, USA
Status: Offline
Reply With Quote
May 3, 2001, 09:53 PM
 
Oops, there should be a BOOL argument in there. Maybe you tried this already:
[imageView setNeedsDisplay: YES];
     
robotic  (op)
Forum Regular
Join Date: Jan 2001
Location: San Luis Obispo, California, USA
Status: Offline
Reply With Quote
May 3, 2001, 09:59 PM
 
Yeah, I tried it with both YES and with NO.... it actually makes it appear less than without that line there.


-Nathaniel
     
Dedicated MacNNer
Join Date: Jun 2000
Location: Dundas, Ontario, Canada
Status: Offline
Reply With Quote
May 4, 2001, 08:40 AM
 
If you use drawRect instead of displayImage to actually draw the image you might have better luck. That is the method that is called whenever the display needs to be re-drawn so you have to override it if you want complete control over the view. You could probably just rename displayImage as drawRect and then change the target of the timer to display (a method implemented in every NSView to redraw the buffer from the drawRect method - for whatever reason you cannot call drawRect directly). I am not sure, though, as I never work with imageReps but you could try it. I wrote a simple Tic Tac Toe game that used that once. The source is on my web site somewhere (www.indigox.dyndns.org) so you can take a look and see if it will help you. I didn't use the NSTimer but I did have to call display to redraw the view after an event was received so it should work the same way.

Hope that helps,
Jeff.
Spectral Class
"Shedding Light on Innovation"
     
Mac Enthusiast
Join Date: Feb 2000
Location: Storrs,Connecticut, USA
Status: Offline
Reply With Quote
May 4, 2001, 10:46 AM
 
Try using a buffered window, if you aren't using one already. I have had lots of redraw problems when I use unbuffered windows. It never seems to appear when I redraw. So use a buffered window.
     
robotic  (op)
Forum Regular
Join Date: Jan 2001
Location: San Luis Obispo, California, USA
Status: Offline
Reply With Quote
May 4, 2001, 11:20 AM
 
Thanks for your help everyone... but it's still not working.

Apocalypse: When I tried using drawRect and display, it compiled but I got this error (repeated every time the timer fired) during run time:

May 04 09:15:47 objCImageTest[2440] *** NSTimer ignoring exception 'NSInvalidArgumentException' (reason '*** -[Controller display]: selector not recognized') that raised during posting of timer with target ad13f0 and selector 'display'


Dalgo: How would I use a buffered display?

Thanks for you help!

-Nathaniel
     
Dedicated MacNNer
Join Date: Jun 2000
Location: Dundas, Ontario, Canada
Status: Offline
Reply With Quote
May 4, 2001, 12:03 PM
 
I am not exactly sure why it is doing that but maybe you could post the code and we could take a look. Maybe an NSTimer must only call methods that you directly implement (I am not sure). In that case, create a method that calls display and then get timer to fire at it. As for buffered windows, I know that you can set that in Interface Builder when you select the window (it is one of the options under the inspector).

Good luck,
Jeff
Spectral Class
"Shedding Light on Innovation"
     
Mac Enthusiast
Join Date: Jan 2001
Status: Offline
Reply With Quote
May 4, 2001, 02:25 PM
 
not sure, but why do you have a comment here (on the line with [self displayImage]:

------

- (void)applicationDidFinishLaunching: (NSNotification *)notification
{
[self initImage];
timer= [[NSTimer scheduledTimerWithTimeInterval: 0.5
target:self
selector:@selector(displayImage)
userInfo:nil
repeats:YES
] retain];
//[self displayImage]; <------------WHY COMMENT?
}

------

try un-commenting it....if not, why do you have it there?
     
Dedicated MacNNer
Join Date: Jun 2000
Location: Dundas, Ontario, Canada
Status: Offline
Reply With Quote
May 4, 2001, 03:32 PM
 
sorry, hit the back button.

[This message has been edited by Apocalypse (edited 05-04-2001).]
Spectral Class
"Shedding Light on Innovation"
     
robotic  (op)
Forum Regular
Join Date: Jan 2001
Location: San Luis Obispo, California, USA
Status: Offline
Reply With Quote
May 4, 2001, 04:55 PM
 
0101--- that line was commented out because all that does is display the image once..... I know that works I'm trying to animate the image moving though, so I commented that line out.

Here's my latest code.... It doesn't give me any errors anymore, but it doesn't draw anything either The window is buffered... when I made it retained (and used displayImage instead of drawRect), it does work! But, the window looks weird (no drop shadow, image goes over part of the title)

Controller.h :

Code:
#import <Cocoa/Cocoa.h> @interface Controller : NSObject { IBOutlet id imageView; NSTimer *timer; int y; NSImageRep *rep; NSSize size; } - (void)drawRect:(NSRect)rect; - (void)initImage; - (void)triggerImage; //- (void)displayImage; I'm trying drawRect, so I //took out the displayImage method. @end
Controller.m :

Code:
#import "Controller.h" @implementation Controller - (void)applicationDidFinishLaunching: (NSNotification *)notification { [self initImage]; timer= [[NSTimer scheduledTimerWithTimeInterval: 0.5 target:self selector:@selector(triggerImage) userInfo:nil repeats:YES ] retain]; } - (void)triggerImage { [imageView display]; [imageView setNeedsDisplay: YES]; } - (void)initImage { y=0; rep = [[NSImage imageNamed:@"Planet.gif"] bestRepresentationForDevice:nil]; size = NSMakeSize(100, 100); [rep setSize:size]; } - (void)drawRect:(NSRect)rect { [imageView lockFocus]; [rep drawAtPoint:NSMakePoint(150, y)]; y+=10; [imageView unlockFocus]; } @end

Thanks,
Nathaniel
     
ali
Forum Regular
Join Date: Sep 2000
Status: Offline
Reply With Quote
May 4, 2001, 08:52 PM
 
Buffered windows need to be flushed for the output to be copied from the backing store memory to the display. When you move or touch the window, or "dip" the window (behind the dock for instance), the backing store is copied to the screen, so the drawing appears updated.

When you go through the normal display machinery, flushing is done for you automatically. For this, you would subclass NSView, do your drawing in drawRect:, and simply call setNeedsDisplay: on the view in your timer code. Then the view will be redisplayed automatically by calling drawRect:, then the window will be flushed, again automatically.

The quick fix to your immediate problem in the first version of your sample is to call [[imageView window] flushWindow], which will do the flush. You can do this before you unlockFocus.

Another thing about your code is that you should provide an argument to your NSTimer callback, even if you might not use it, as that is the expected signature.

The second version of the sample is a bit confusing, as you implement drawRect:, but it's not in a subclass of NSView, so as far as I can tell, it's not being called...?


Finally, buffered windows are always preferred to non-buffered windows as they provide much better user experience.

Ali



[This message has been edited by ali (edited 05-04-2001).]
     
tie
Professional Poster
Join Date: Feb 2001
Status: Offline
Reply With Quote
May 4, 2001, 09:13 PM
 
Originally posted by ali:
Finally, buffered windows are always preferred to non-buffered windows as they provide much better user experience.
How, exactly. Do buffered windows provide double-buffering for you? Right now, I'm double-buffering by keeping two images. I draw in one of them, and then I switch them (with an NSImageView). Is this unnecessary?
The 4 o'clock train will be a bus.
It will depart at 20 minutes to 5.
     
robotic  (op)
Forum Regular
Join Date: Jan 2001
Location: San Luis Obispo, California, USA
Status: Offline
Reply With Quote
May 5, 2001, 12:06 AM
 
Great! Putting in the flushWindow worked!

A few more questions now:
How can I erase the old image before I draw a new one, so that it doesn't leave a "trail"? Do I have to make an all white gif, and display that? Or is there some method I can call that will erase the imageView?

Also, what do you mean by a NSTimer callback?

Thank you very much!

-Nathaniel
     
ali
Forum Regular
Join Date: Sep 2000
Status: Offline
Reply With Quote
May 5, 2001, 05:52 PM
 
Originally posted by tie:
How, exactly. Do buffered windows provide double-buffering for you? Right now, I'm double-buffering by keeping two images. I draw in one of them, and then I switch them (with an NSImageView). Is this unnecessary?
Yes, there is implicit double-buffering of sorts. All drawing goes to a backing store, and is flushed either when you flush it explicitly (or as a part of the normal drawing mechanism), or when your window is covered and uncovered. This helps the UI be almost totally flicker-free.

With retained windows, there's still a backing store, but drawing goes directly to the screen for parts that are exposed. One problem here is that you might see flicker; in addition, not everything works great with retained windows.

Ali
     
ali
Forum Regular
Join Date: Sep 2000
Status: Offline
Reply With Quote
May 5, 2001, 05:56 PM
 
Originally posted by robotic:
Great! Putting in the flushWindow worked!

A few more questions now:
How can I erase the old image before I draw a new one, so that it doesn't leave a "trail"? Do I have to make an all white gif, and display that? Or is there some method I can call that will erase the imageView?
You can draw a rectangle of the same size as the image, and same color as the background. You can use something like

[[NSColor whiteColor] set];
NSRectFill(NSMakeRect(x, y, size.width, size.height));


Also, what do you mean by a NSTimer callback?
I meant the selector you provide to the NSTimer. The selector is supposed to have an argument, so it should be specified as @selector(triggerImage and declared with an argument (which is an NSTimer). Even if you don't look at this argument, it should be there, as it's supposed to be there...

Ali

     
robotic  (op)
Forum Regular
Join Date: Jan 2001
Location: San Luis Obispo, California, USA
Status: Offline
Reply With Quote
May 5, 2001, 09:01 PM
 
Thanks! That worked great!


-robotic
     
   
Thread Tools
Forum Links
Forum Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts
BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are On
Top
Privacy Policy
All times are GMT -5. The time now is 09:56 PM.
All contents of these forums © 1995-2011 MacNN. All rights reserved.
Branding + Design: www.gesamtbild.com
vBulletin v.3.8.7 © 2000-2011, Jelsoft Enterprises Ltd., Content Relevant URLs by vBSEO 3.3.2