Page 1 of 1
How to update UI gadgets inside a procedure
Posted: Mon Nov 10, 2025 10:26 am
by susan
Inside a procedure, I want to update the UI to tell the user to wait.
Code: Select all
Procedure on_takeslongtime(EventType)
SetGadgetText(pGadget, "Please wait while this takes a long time...")
; WindowEvent() thought this might allow the gadgets to update
; Delay(10) or maybe this?
; Sleep(1) I found this with a web search, and it looked promising but it is no longer part of Purebasic
For index = 0 To 100000
; doing something that takes many seconds
Next
EndProcedure
But the gadget shows the text when the procedure has finished. I looked in the Help and searched online, and tried a few ideas (shown in the code comments above), but I can't figure out how to make the SetGadgetText be shown before the loop begins, and before the procedure ends.
Am I missing something, or is it not possible, and I need to rethink how the procedures are organised?
Re: How to update UI gadgets inside a procedure
Posted: Mon Nov 10, 2025 12:11 pm
by Axolotl
That question leads to event handling. I can answer this for windows only. Still too far away from Linux.
Maybe this can help you.
Handling Events - two different ways
Easy answer: You can add this in the loop.
Important Note:
You remove events from the System-Message-Loop. So any action on your app are lost.
I prefer to use something like this, because I want to cancel long running loops (mostly).
Code: Select all
Procedure PerformEvents(Gadget=-1) ; BOOL .. e.g. Gadget == Cancel - Button
Repeat
Select WindowEvent()
Case #PB_Event_None
ProcedureReturn #False
Case #PB_Event_Gadget
If IsGadget(Gadget) And EventGadget() = Gadget ; Gadget == Cancel - Button
ProcedureReturn #True
EndIf
EndSelect
Delay(1) ;
ForEver
If GetAsyncKeyState_(#VK_ESCAPE) & $8000 ; check the ESC key
ProcedureReturn #True
EndIf
ProcedureReturn #False
EndProcedure
CompilerIf 0 ; Call it like:
For index = 0 To 999999
If PerformEvents(#GDT_btnCancel) ; #GDT_btnCancel is a already created ButtonGadget()
bCanceledByUser = #True ; set flag to handle the user wish later ....
Break ; leave the loop
EndIf
; .....
; do the stuff
, ......
Next index
CompilerEndIf
Re: How to update UI gadgets inside a procedure
Posted: Tue Nov 11, 2025 1:44 am
by susan
Axolotl wrote: Mon Nov 10, 2025 12:11 pm
Easy answer: You can add this in the loop.
Important Note:
You remove events from the System-Message-Loop. So any action on your app are lost.
Thank you for the tips and link. Changing the lines to these works exactly as I hoped.
Code: Select all
Procedure on_takeslongtime(EventType)
SetGadgetText(pGadget, "Please wait while this takes a long time...")
While WindowEvent() : Wend
For index = 0 To 100000
; doing something that takes many seconds
Next
EndProcedure
I will keep the more complex lines that allows for cancelling the action, as an idea for the future. With the binding technique, that also might be for later after I have mastered the event loops because I want to start at the beginning of PureBasic and do everything the most basic way, just because.
I have since found that the While WindowEvent() : Wend technique has been mentioned many times before on the forum. Part of my problem, right now, is knowing the best words to search for in both the Help and online to find solutions. It feels like I still have a lot to absorb (and understand!) before I can efficiently find my own answers to problems.
Re: How to update UI gadgets inside a procedure
Posted: Tue Nov 11, 2025 8:58 am
by infratec
All UI stuff should be handled in the (one and only) Event loop.
If you want to do stuff from a procedure you should use PostEvent().
If your procedure takes longer ... this procedure should be an own thread to avoid blocking of the program,
which is a bad user experience.
Re: How to update UI gadgets inside a procedure
Posted: Tue Nov 11, 2025 9:29 am
by susan
infratec wrote: Tue Nov 11, 2025 8:58 am
All UI stuff should be handled in the (one and only) Event loop.
If you want to do stuff from a procedure you should use PostEvent().
If your procedure takes longer ... this procedure should be an own thread to avoid blocking of the program,
which is a bad user experience.
I have no reason to think that these are not good advice, but...
I am struggling to learn everything at once. For example, I did look at the Threads related topics here and in the help and could see the potential. But just like I can see that lists are probably better than using a big array for my data, for now, I am being expedient to get used to forms and organising the lines, and even only just figuring out how to name things in a way that looks easy to read and makes sense to me.
My goal is to get something non-trivial working. It is a utility for personal use and as a learning exercise, and if the window stops responding for a few seconds that's ugly but okay. An argument can be made for doing things the "best" (right?) way the first time, but that may have to wait for my second or third attempt.
I have not heard of PostEvent() before, but that sounds like it will be useful, I appreciate the tips!
Re: How to update UI gadgets inside a procedure
Posted: Tue Nov 11, 2025 12:07 pm
by infratec
Code: Select all
EnableExplicit
Enumeration CustomEvents #PB_Event_FirstCustomValue
#CustomEvent_ShowGadgetInteger
#CustomEvent_ShowGadgetText
EndEnumeration
Procedure on_takeslongtime(*Dummy)
Protected.i index
PostEvent(#CustomEvent_ShowGadgetText, 0, 0, #PB_EventType_Change, @"Please wait while this takes a long time...")
For index = 0 To 100000
PostEvent(#CustomEvent_ShowGadgetInteger, 0, 1, #PB_EventType_Change, index)
Delay(1000)
Next
EndProcedure
Define.i Event, Quit
If OpenWindow(0, 0, 0, 300, 60, "PureBasic Window", #PB_Window_SystemMenu | #PB_Window_MinimizeGadget | #PB_Window_MaximizeGadget |#PB_Window_ScreenCentered)
TextGadget(0, 10, 10, 200, 20, "")
TextGadget(1, 220, 10, 50, 20, "", #PB_Text_Border)
CreateThread(@on_takeslongtime(), #Null)
Repeat
Event = WaitWindowEvent()
Select Event
Case #CustomEvent_ShowGadgetInteger
Select EventType()
Case #PB_EventType_Change
SetGadgetText(EventGadget(), Str(EventData()))
EndSelect
Case #CustomEvent_ShowGadgetText
Select EventType()
Case #PB_EventType_Change
SetGadgetText(EventGadget(), PeekS(EventData()))
EndSelect
Case #PB_Event_CloseWindow ; If the user has pressed on the close button
Quit = #True
EndSelect
Until Quit
EndIf
Re: How to update UI gadgets inside a procedure
Posted: Tue Nov 11, 2025 8:59 pm
by susan
infratec wrote: Tue Nov 11, 2025 12:07 pm
Code: Select all
;more code
Procedure on_takeslongtime(*Dummy)
Protected.i index
PostEvent(#CustomEvent_ShowGadgetText, 0, 0, #PB_EventType_Change, @"Please wait while this takes a long time...")
For index = 0 To 100000
PostEvent(#CustomEvent_ShowGadgetInteger, 0, 1, #PB_EventType_Change, index)
Delay(1000)
Next
EndProcedure
;more code
Thank you for the example, that looks easy! (I didn't know that Delay allowed events to continue.)
Re: How to update UI gadgets inside a procedure
Posted: Tue Nov 11, 2025 10:18 pm
by spikey
susan wrote: Tue Nov 11, 2025 8:59 pm
Thank you for the example, that looks easy! (I didn't know that Delay allowed events to continue.)
It doesn't. What infratec is doing in this example is creating a thread which runs alongside the main program. It uses PostEvent to tell the
main thread that a UI update is appropriate. The main thread processes the events as usual but is never blocked by the worker thread (or isn't if the program is bug free!).
Threading is the only way to make a
fully concurrent program but comes with some additional implications. There are some problems that you will face in a multi-threaded application that just don't appear in single threaded one, and debugging multi-threaded programs is harder than single-threaded ones.
If you haven't studied threaded programming before I'd strongly recommend some background reading first, it will save you some headaches later. For example,
https://algocademy.com/blog/introductio ... ogramming/ is a reasonable introduction. It's not talking about PureBasic but the problems are the same and the solutions too, with the exception of 'Monitor's which PB doesn't provide intrinsically (but can be implemented).
Re: How to update UI gadgets inside a procedure
Posted: Wed Nov 12, 2025 12:04 am
by mk-soft
The example is kept very simple and some important things about threads are missing. But more on that later (it's late).
In the main thread where the GUI runs (WaitWindowEvent), it must not be interrupted by a Delay() or long processes,
as this is where all events from the OS and user input are passed to the user programme.
And there are a lot of events. Mouse, keyboard, changes to windows and controls.
These must always have passed through the main thread. Otherwise, a message such as ‘The programme is not responding’ will appear.
Re: How to update UI gadgets inside a procedure
Posted: Wed Nov 12, 2025 3:25 am
by susan
Okay, I confess I didn't try to run @infratec example, and if I had, what is does might have been more obvious, (and hopefully I would have examined the code more carefully).
For now I am happy with this trick.
No, I have not used threads before, and I come from using some JavaScript in the browser and a little Python on a server, with only basic Python skills, where I have only scratched the surface.
I picked PureBasic because I thought it would be nice to make simple executable utilities that are self-contained with a UI that is easy to construct and that will just run on Windows without the hoops of forcing either JavaScript or Python into desktop programs.
I admit that I am a little surprised that threads are the recommended way (which feels like diving in the deep end) in a
BASIC language to update the UI while doing something else in what is essentially a beginners program in the language.
That is not a complaint, but as I said it's just a surprise, and I am really satisfied with what I have achieved so far with the PureBasic IDE and language. It's a real pleasure to work in.
Re: How to update UI gadgets inside a procedure
Posted: Wed Nov 12, 2025 9:25 am
by Fred
This is how UI works, it processes the UI messages one by one on the main thread, so if you are doing something which locks the main thread, UI can't be updated. That's why if you have a real blocking operation (like compressing a big file etc.), you need to use thread. It's not something specific to PureBasic, it's the same in every other languages.
Re: How to update UI gadgets inside a procedure
Posted: Wed Nov 12, 2025 10:15 am
by susan
Fred wrote: Wed Nov 12, 2025 9:25 am
This is how UI works
Understood!
Re: How to update UI gadgets inside a procedure
Posted: Wed Nov 12, 2025 11:42 am
by BarryG
susan wrote: Mon Nov 10, 2025 10:26 amI can't figure out how to make the SetGadgetText be shown before the loop begins, and before the procedure ends.
This works just fine for me (and it assumes your app doesn't need to do anything else at the time):
Code: Select all
Procedure DoLongProcedure()
SetGadgetText(0, "Please wait...")
DisableGadget(1,1)
For index = 0 To 50000000
; doing something that takes many seconds
Next
SetGadgetText(0, "(Awaiting start)")
DisableGadget(1,0)
EndProcedure
OpenWindow(0,300,300,300,80,"test",#PB_Window_SystemMenu)
TextGadget(0,10,10,280,25,"(Awaiting start)")
ButtonGadget(1,10,40,280,25,"Click here to start")
Repeat
ev=WaitWindowEvent()
If ev=#PB_Event_Gadget And EventGadget()=1
DoLongProcedure()
EndIf
Until ev=#PB_Event_CloseWindow
Re: How to update UI gadgets inside a procedure
Posted: Wed Nov 12, 2025 6:15 pm
by TI-994A
susan wrote: Wed Nov 12, 2025 3:25 am...I am a little surprised that threads are the recommended way (which feels like diving in the deep end) in a
BASIC language to update the UI while doing something else in what is essentially a beginners program in the language.
Hi @susan. What you should be
(pleasantly) surprised about is to find a BASIC language that supports threading so easily.
Here's a simple example which uses a timer to demonstrate the unblocked functionality of the main event loop and UI when a thread is running.
Code: Select all
; create your own custom event
#PB_MyProcedureIsDone = #PB_Event_FirstCustomValue
Procedure slowProcedure(dummy)
For i = 0 To 300000000
Pow(27.0, 1/3.0)
Next
PostEvent(#PB_MyProcedureIsDone)
EndProcedure
OpenWindow(0, 0, 0, 300, 200, "Simple Thread Example",
#PB_Window_SystemMenu | #PB_Window_ScreenCentered)
TextGadget(0, 10, 10, 280, 30, "", #PB_Text_Center)
ButtonGadget(1, 10, 160, 280, 30, "Start Procedure")
Repeat
event = WaitWindowEvent()
Select event
Case #PB_Event_CloseWindow
appQuit = #True
Case #PB_Event_Timer
If EventTimer() = 0
SetGadgetText(0, "Procedure running for " + timer + " seconds...")
timer + 1
EndIf
Case #PB_Event_Gadget
If EventGadget() = 1
timer = 1
DisableGadget(1, #True)
AddWindowTimer(0, 0, 1000)
CreateThread(@slowProcedure(), 0)
EndIf
Case #PB_MyProcedureIsDone
DisableGadget(1, #False)
RemoveWindowTimer(0, 0)
SetGadgetText(0, "Procedure completed in " + timer + " seconds!")
EndSelect
Until appQuit
And here's an expanded example to further demonstrate how to stop a thread safely and elegantly. Please note that although there is a function to kill threads, aptly named
KillThread(), it's not recommended.
Code: Select all
; create your own custom event
#PB_MyProcedureIsDone = #PB_Event_FirstCustomValue
Global killThreadSwitch = #False
Procedure slowProcedure(dummy)
For i = 0 To 300000000
Pow(27.0, 1/3.0)
If killThreadSwitch
Break
EndIf
Next
If Not killThreadSwitch
PostEvent(#PB_MyProcedureIsDone)
EndIf
EndProcedure
OpenWindow(0, 0, 0, 300, 200, "Simple Thread Example",
#PB_Window_SystemMenu | #PB_Window_ScreenCentered)
TextGadget(0, 10, 10, 280, 30, "", #PB_Text_Center)
ButtonGadget(1, 10, 120, 280, 30, "Start Procedure")
ButtonGadget(2, 10, 160, 280, 30, "Force Stop Procedure")
DisableGadget(2, #True)
Repeat
event = WaitWindowEvent()
Select event
Case #PB_Event_CloseWindow
appQuit = #True
Case #PB_Event_Timer
If EventTimer() = 0
SetGadgetText(0, "Procedure running for " + timer + " seconds.")
timer + 1
EndIf
Case #PB_Event_Gadget
Select EventGadget()
Case 1
timer = 1
killThreadSwitch = #False
DisableGadget(1, #True)
DisableGadget(2, #False)
AddWindowTimer(0, 0, 1000)
CreateThread(@slowProcedure(), 0)
Case 2
killThreadSwitch = #True
DisableGadget(1, #False)
DisableGadget(2, #True)
RemoveWindowTimer(0, 0)
SetGadgetText(0, "Procedure killed after " + timer + " seconds.")
EndSelect
Case #PB_MyProcedureIsDone
DisableGadget(1, #False)
DisableGadget(2, #True)
RemoveWindowTimer(0, 0)
SetGadgetText(0, "Procedure completed in " + timer + " seconds.")
EndSelect
Until appQuit
Hope it helps.
