I’m setting a Cell in a listbox to a string, then immediately calling a function that contains a call to Shell.execute.
The change made to the cell (ie setting to a string) does not appear until after the shell call returns.
I’ve tried calling Listbox.Refresh, Listbox.CellInvalidate(), Listbox.Invalidate
Calling Listbox.Invalidate actually hung the program.
This is on Mac 2018 Release 1.1
Any ideas for other things to try?
any of the INVALIDATE processes basicly say “Hey there Mr. Application, when you get a chance, please refresh <listbox, canvas, cell etc>”… the “chance” comes and the end of the run loop… something your Shell call happens to be smack in the middle of… meaning the actual update requested by the “invalidate” doesn’t happen until after the Shell is done…
So what you are seeing is in fact the designed behaviour.
I suppose so, that makes sense for invalidate, but I thought refresh acts immediately.
Ultimately I need to know how to display the string in the listbox before the shell function is called. The shell execute is running GCC (C compiler) and I want the listbox to say “compiling” while it is compiling.
Later in the same function I call another shell.execute, and using Shell events, I’ve managed to get it to display other strings in the listbox eg “uploading” (because it then uploads the compiled code to a microcontroller).
Perhaps the trick is to use the shell events on the compile also?
What you need to do is introduce an event loop before invoking your shell command.
DoEvents would appear to be an obvious solution, but as the documentation says “can” lead to instability.
Another option is to use Calllater with an aftermillisec delay of 0 to run your shell command (the delay of 0 simply pushes the method into the next event loop cycle). This will allow the framework to update the UI before running the command. The problem with this solution is that it adds an Italian aspect to your code i.e. spaghetti
CallLater sounds like the right approach, but you can also try :
-Add a timer with a very short period to your window,set it to mode = 0 (off)
- put the shell command into the timer event, followed by mode = 0
-Set the cell text
-set the timer mode to 1
windows should refresh, timer should fire then turn itself off
I recommend Wayne’s method of using CallLater if you’re averse to using App.DoEvents. However, in this case, calling App.DoEvents() before kicking off the Shell will also be successful. Another recommendation is to set the Shell’s mode to 1 and then Do … Loop after the call to the Shell’s Execute waiting for IsRunning to be false:
theListBox.Cell(row, column) = "Some Text Value"
theShell.Mode = 1
Loop Until Not theShell.IsRunning
That adds to the responsiveness by eliminating the block that the shell can case when running in Mode = 0.
To further describe this - if you call a synchronous (mode = 0 default) shell immediately after a UI update event, the Shell WILL block the UI because the main loop doesn’t regain control until the shell finishes. By running the shell as Asynchronous (1) or Interactive (2), you allow the main loop to continue while the shell kicks off almost as another thread - no, it’s not a thread in the normal Xojo sense of a thread, but the result is similar.