How to update UI gadgets inside a procedure

Just starting out? Need help? Post your questions and find answers here.
User avatar
susan
User
User
Posts: 17
Joined: Mon Aug 08, 2022 4:06 am

How to update UI gadgets inside a procedure

Post 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?
Axolotl
Addict
Addict
Posts: 885
Joined: Wed Dec 31, 2008 3:36 pm

Re: How to update UI gadgets inside a procedure

Post 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.

Code: Select all

  While WindowEvent() : Wend 
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 

Just because it worked doesn't mean it works.
PureBasic 6.04 (x86) and <latest stable version and current alpha/beta> (x64) on Windows 11 Home. Now started with Linux (VM: Ubuntu 22.04).
User avatar
susan
User
User
Posts: 17
Joined: Mon Aug 08, 2022 4:06 am

Re: How to update UI gadgets inside a procedure

Post by susan »

Axolotl wrote: Mon Nov 10, 2025 12:11 pm Easy answer: You can add this in the loop.

Code: Select all

  While WindowEvent() : Wend 
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.
infratec
Always Here
Always Here
Posts: 7676
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: How to update UI gadgets inside a procedure

Post 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.
User avatar
susan
User
User
Posts: 17
Joined: Mon Aug 08, 2022 4:06 am

Re: How to update UI gadgets inside a procedure

Post 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!
infratec
Always Here
Always Here
Posts: 7676
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: How to update UI gadgets inside a procedure

Post 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

User avatar
susan
User
User
Posts: 17
Joined: Mon Aug 08, 2022 4:06 am

Re: How to update UI gadgets inside a procedure

Post 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.)
User avatar
spikey
Enthusiast
Enthusiast
Posts: 782
Joined: Wed Sep 22, 2010 1:17 pm
Location: United Kingdom

Re: How to update UI gadgets inside a procedure

Post 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).
User avatar
mk-soft
Always Here
Always Here
Posts: 6356
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: How to update UI gadgets inside a procedure

Post 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.
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
User avatar
susan
User
User
Posts: 17
Joined: Mon Aug 08, 2022 4:06 am

Re: How to update UI gadgets inside a procedure

Post 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.

Code: Select all

While WindowEvent() : Wend 
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.
Fred
Administrator
Administrator
Posts: 18372
Joined: Fri May 17, 2002 4:39 pm
Location: France
Contact:

Re: How to update UI gadgets inside a procedure

Post 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.
User avatar
susan
User
User
Posts: 17
Joined: Mon Aug 08, 2022 4:06 am

Re: How to update UI gadgets inside a procedure

Post by susan »

Fred wrote: Wed Nov 12, 2025 9:25 am This is how UI works
Understood!
BarryG
Addict
Addict
Posts: 4233
Joined: Thu Apr 18, 2019 8:17 am

Re: How to update UI gadgets inside a procedure

Post 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
User avatar
TI-994A
Addict
Addict
Posts: 2756
Joined: Sat Feb 19, 2011 3:47 am
Location: Singapore
Contact:

Re: How to update UI gadgets inside a procedure

Post 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. :D
Texas Instruments TI-99/4A Home Computer: the first home computer with a 16bit processor, crammed into an 8bit architecture. Great hardware - Poor design - Wonderful BASIC engine. And it could talk too! Please visit my YouTube Channel :D
Post Reply