Window Refresh Question

I have some intensive loops running through data and I am setting up a progress indicator and text showing the current item. However, these don’t update/refresh the window. I tired numerous functions — update, display, displayIfNeeded, setNeedsDisplay, etc — and nothing seems to make the window or controls refresh. The only method I have discovered that works is if I put a .00001 or greater delay in the loop. But that seems ridiculous. Is there a better way to configure objects or code trigger so that the interface updates during a repeat loop?

These days the screen is updated only at the end of each pass of the event loop. So what you need to do is tickle the event loop when you want the screen updated. You do this by effectively taking over its role. In theory, a handler like this is what you need:

on fordEvent()
	set theApp to current application's NSApp
	set theMode to current application's NSEventTrackingRunLoopMode
	set theMask to current application's NSAnyEventMask
	repeat
		set theEvent to (theApp's nextEventMatchingMask:theMask untilDate:(missing value) inMode:theMode dequeue:true)
		if theEvent is missing value then exit repeat
		theApp's sendEvent:theEvent
	end repeat
end fordEvent

There are two problems, though. First, NSAnyEventMask is NSUIntegerMax, which is too big to be an AS integer and too big to be represented with precision as an AS real. In practice, you can get by with a lesser mask.

The other is that, given this is likely to be called a lot, it makes more sense to define theApp etc as properties.

So:

property NSEventMaskDirectTouch : a reference to current application's NSEventMaskDirectTouch
property theApp : a reference to current application's NSApp
property NSEventTrackingRunLoopMode : a reference to current application's NSEventTrackingRunLoopMode

on fordEvent()
	repeat
		set theEvent to (theApp's nextEventMatchingMask:(NSEventMaskDirectTouch - 1) untilDate:(missing value) inMode:NSEventTrackingRunLoopMode dequeue:true)
		if theEvent is missing value then exit repeat
		theApp's sendEvent:theEvent
	end repeat
end fordEvent

Put calls to that in your repeat loop.

Shane so


aView’s setNeedsDisplay:true

Isn’t enough?
As I guess the view doesn’t update until the end of the loop?

Also how does the main window setting of:
“Automatically calculates event loop” toggle come into effect?
Or what does it do?
I’ve tried turning it on and off and never really noticed any difference.

One thing I’ve been doing lately is making calls to set a new tab.
And then right after I make a call to a class function that takes a while.
The tab changes but the view doesn’t update until after the other function gets called

No. All that does is mark the view as being in need of redrawing when the time comes. You use to be able to use displayIfNeeded(), but it changed a couple of versions of the OS back.
As I guess the view doesn’t update until the end of the loop?

That’s to do with the tabbing order when you tab from one field to another.

That’s right. If you don’t want the delay, call the latter part with performSelectorAfterDelay::: and a delay of 0.0. That means the second part is a new event on the run loop.

To clarify, in this example:

property NSEventMaskDirectTouch : a reference to current application's NSEventMaskDirectTouch
property theApp : a reference to current application's NSApp
property NSEventTrackingRunLoopMode : a reference to current application's NSEventTrackingRunLoopMode

on fordEvent()
   repeat
       set theEvent to (theApp's nextEventMatchingMask:(NSEventMaskDirectTouch - 1) untilDate:(missing value) inMode:NSEventTrackingRunLoopMode dequeue:true)
       if theEvent is missing value then exit repeat
       theApp's sendEvent:theEvent
   end repeat
end fordEvent

Am I calling fordEvent() in each iteration of my repeat loop, or does that show an example repeat loop into which I insert my own steps? I’m a little confused about why that has a repeat loop in it…

You call it whenever you want the display to update mid-handler. So in a slow repeat loop, then every time, but in a fast loop running a lot of times you might want to use mod with your counter and call it every xth time through the loop.

The handler is doing more than forcing an update. Try running some code with a repeat loop that takes a while to run, and keep moving your mouse around vigorously while it’s running. You’ll see the spinning cursor. Basically, while you handler is being handled, any other events – mouse movements and clicks, keystrokes, and more – get added to the event queue. A spinning cursor means the queue is banked up.

And the problem is worse than a spinning cursor, because if the user typed some keystrokes, they’ll happen when your handler finishes, which might not be what you want.

So what that handler is doing is getting the first event waiting in the queue and dispatching it, then repeating until the queue is empty. It avoids any bank up.

To run a long process safely you need to disable any controls you don’t want to change, force an update via the handler, run the script and call the handler periodically (and because you’ve disabled controls any spurious clicks or keystrokes will go nowhere), and re-enable the controls at the end.

That seems to be working perfectly. Thanks!

FWIW, it you download my Myriad Helpers package, you can copy the two Objective-C files that implement the same thing into your code, and it will be a bit more efficient.