Window.UpdateNow in Catalina and beyond

In the old days, you could force a screen update inside an event, like this:

Pushbutton1.Action
  label1.Text = "Starting..."
  for i as integer = 1 to 100
      label1.Text =  "Scanning " +str(i) 
      label1.Refresh()
      self.UpdateNow()
      app.SleepCurrentThread(50)
  next
  label1.Text = "Done"

Now arguably this is not great code, but sometimes it makes sense to do it this way and is sure easy to write and understand.

I know the alternatives:

  • refactor the code and UI/UX to use Threads and Timers, but this is rarely easy.
  • use App.DoEvents() - which works, but introduces a ton of other super nasty bugs

Has anyone found a way to get the old Window.UpdateNow() feature to work in modern macOS and modern Xojo? I’m using 2019R1.1.

would be nice if we just have a UIUpdate method that updates all controls that have Invalidate flag.

You can try with MBS methods like

CallDelegateOnMainThreadMBS AddressOf UpdateText

where UpdateText updates all labels. But even there I check that the updates aren’t done more than once a second.

In the good old days (Classic Mac OS) it was also possible to perform GUI tasks from background threads.
This is no longer possible with current OS versions.
So all GUI tasks need to be done on the main thread, only. This is enforced from the OS.
So when you keep your main thready busy there is no GUI update at all.

Put the loop in a Thread and update the GUI via callback on the Main thread. This is the way to go.
And there is really no need for a plugin here.

The problem here that bothers me most is that when you are in an event handler (e.g. MenuHandler, Button.Action, etc.) the UI state is automatically handled for you, and it’s not possible to receive a new event until it’s done. So, in my code above, when the user presses the button, the .Action handler runs, and while it’s running, the user can’t do anything else (they can’t click another button, choose a menu item, close a window…)

If I rewrite my code using threads and timers and callbacks, this built-in OS niceness is lost. So my code has to look something like this:

   Button1.Action
       flagToBlockOtherActivity = true
       ... starts the Thread running to do the processing
       Thread1.run
   
  Thread1.Run
          for i = 1 to 100
             ... do stuff ..
            use a timer to update the ui
         next
       flagToBlockOtherActivity = false

Timer1.Action
       label1.text = "scanning " + str(i) + " ..."


  MenuHandler.Foobar
        if flagToBlockOtherActivity then
             beep
             return
        end if

  Window1.CancelClose
        if flagToBlockOtherActivity then
             beep
             return
        end if


  Listbox1.keydown
        if flagToBlockOtherActivity then
             beep
             return
        end if

In other words, by moving my code to a thread, I now have to worry quite a bit about keeping a consistent UI state, which is a major pain, and is really something that the OS should be doing for us.

One solution is to put up a modal dialog box while the thread is running (which will effectively block most user input) but this is also kind of ugly.

What’s the simple, elegant solution here?

i think doevents or a thread cause that the user can click everything that is enabled.
i would prefer to disable all that the user should not click if some task is in progress.
try also not to program direct inside of events.

You can use

Timer.CallLater 0, AddressOf MySub

Will run on the next event loop in the main thread

In MySub you check the flags

[quote=488714:@Michael Diehr]The problem here that bothers me most is that when you are in an event handler (e.g. MenuHandler, Button.Action, etc.) the UI state is automatically handled for you, and it’s not possible to receive a new event until it’s done. So, in my code above, when the user presses the button, the .Action handler runs, and while it’s running, the user can’t do anything else (they can’t click another button, choose a menu item, close a window…)
[/quote]

Why is the action handler doing so much? If there’s a lot of work to do, the action handler hands it off to a thread which, when it’s finished, invokes the user-interface update mechanism to get the UI updated. That way the main thread is idle mostly and so is ready to respond to user actions.

I put all the work for the actual UI actions in one place, as a giant SELECT CASE. The updates happen in the order they are requested and I don’t have any flagToBlockOtherActivity.

The great thing about using event handlers is that you are guaranteed several things:

  • Events will happen in order
  • Events will not interrupt each other (Event A will complete before event B starts)
  • Some events that don’t make sense will simply be prevented (e.g. the user isn’t allowed to choose the Edit/Copy menu handler while the Edit/Paste handler is running.)

When you move to a threaded system, all of this is lost, and you basically have to recreate all this logic yourself.

Tim, your idea of a central dispatch event handler is not a bad one (it sounds essentially like a big State Machine) although it’s very hard to have these scale well. And in my expereicne, these designs go against modern OOP principles such as encapsulation, information hiding etc.

“I’m in the main thread, and I know what I’m doing, so would you please update the screen now, Hal?”
“I’m sorry Dave, I can’t do that”

Considering aspects of the UI from a thread means you’re thinking about threading wrong. What do we care about the menus in a thread? We don’t, never should. What do we care if the window is closing? We don’t, never should. Threads are meant to carry out one specific task, and carry them out well. Communication with the user adds to the overhead of what the thread needs to do.

Maybe I can help you understand threading in a different way. First I need to understand what problem you’re really trying to solve. Without describing any technology you think you need, describe the goal of the function you’re trying to implement.

Calling invalidate is asking the OS to update that control at the end of the run loop.

Depends on what the task is, doing this can severally damped the performance, especially with the current threading model. i.e. processing a video and occasionally displaying the frame to the screen, costs the equivalent of 2~3 frames each time.

Thank you for all the ideas.

The specific scenario I’m dealing with is a complex editor application which has a listbox - the user can drag & drop any number of files which are then processed and added to the listbox. I had a client report that it wasn’t working well, and sent in a bug report which revealed they were dropping in 25,000 files. My app had been processing file drops on the main thread in the drop event, which totally locked up the app.

Upon futher thought - although it would be really cool if the user could keep using this app while the files were being processed, the data model just gets too complicated - for example, what if the user deletes some items in the listbox as the listbox is being filled? What if they drag-rearrange the row order? What if they hit File/Save while this is happening? Etc. etc. It’s just too messy.

So the answer here is to do what many apps do:

  • put up a modal dialog box with a progress bar and a cancel button on the main thread,
  • do the file processing on a background thread.
  • On macOS you can even use the SheetWindow style which is attached to a specific window (which is great in a multi-window app).

This is similar to how the Xojo IDE works, and basically does the right thing:

  • while the modal dialog is up, all other events (menuhandlers, mouse & keyboard, window close, etc.) are blocked
  • the modal dialog’s UI can be updated from a thread (using timer callbacks of course)
  • if your thread yields appropriately, and you are careful to not update the UI too often (say, no more than about 60fps) then the UI remains quite responsive.

I’m actually not entirely sure why this works - I think the appkit API has some special rules for modal dialog boxes - although they do block events, they do not block all events, e.g. timer events and window updates are allowed, but user-initiated events (keyboard, mouse, menu, etc.) are blocked.

if you disable all user input in this app you could also use a timer to import a amount of files until the import list is done complete.
the app still react and you can show processing info.