Handling Events - two different ways

Share your advanced PureBasic knowledge/code with the community.
Axolotl
Addict
Addict
Posts: 802
Joined: Wed Dec 31, 2008 3:36 pm

Handling Events - two different ways

Post by Axolotl »

I was inspired by some discussions about how to deal with events and I thought about it.

For me the relevant question is:
How can I work through the long processes without restricting or slowing down the responsiveness of my program interface?

-. Use Threads (and deal with it, not part of this, because there are already many beautiful examples here)
1. The way PB_IDE itself does it (at least in some places)
2. Using BindEvent(), BindGadgetEvent()

Now I hope that the examples will help interested beginners with their basic architectural considerations. And if the decision is, I won't do it under any circumstances -- then that's fine too.

to 1. The way PB_IDE itself does it (at least in some places) with some modifications

Code: Select all

;| Advanced Event Handling (concept like PB_IDE) 

EnableExplicit 
;DebugLevel 9  ; 9 .. show all debug messages 

Enumeration Window 
  #WINDOW_Main 
EndEnumeration 

Enumeration Gadget 
  #GADGET_btnStart 
  #GADGET_btnStop 
  #GADGET_chkTest
  #GADGET_txtLoopOutput 
  #GADGET_lbTrace 
EndEnumeration 

#WINDOW_Main_Flags = #PB_Window_SystemMenu | #PB_Window_ScreenCentered | #PB_Window_SizeGadget

#LOOP_MaxLoops        = 1000 
#LOOP_SlowDownDelay   =  100 


; ---------------------------------------------------------------------------------------------------------------------

Global ApplicationQuit  = #False   ; Application flag 
Global LoopRunningState = #False   ; init --> not running 


; ---------------------------------------------------------------------------------------------------------------------

Declare MainWindowEvents(Event)  
Declare MainWindowSizeEvent()   ; bind (for a nicer display when adjusting the size) 

Declare FlushEvents() 
Declare DispatchEvent(Event)  


; ---------------------------------------------------------------------------------------------------------------------

Macro Trace(MessageText) 
  AddGadgetItem(#GADGET_lbTrace, -1, MessageText) 
  SetGadgetState(#GADGET_lbTrace, CountGadgetItems(#GADGET_lbTrace) - 1) 
EndMacro 


;/---------------------------------------------------------------------------------------------------------------------
;| Test Functions 
;| 
;|  the stupid long lasting code in one function (i.e. scanning dirs, copying files, parsing files, etc. 
;| 
;| use FlushEvents() 
;| --> process all events 
;| 
;\ 
Procedure Test_LongLoop() 
  Protected index, msg$  
  
  For index = 0 To #LOOP_MaxLoops 
    Delay(#LOOP_SlowDownDelay)  ; spend some time with nothing 

    msg$ = " Item " + Str(index) + "/" + Str(#LOOP_MaxLoops) + " | Checkbox.State == " + GetGadgetState(#GADGET_chkTest) 
    SetGadgetText(#GADGET_txtLoopOutput, msg$) 

    If index % 10  ; ............................................. only every 10th iteration we call the event loop !!! 
      FlushEvents()  ; ........................................... check the message queue 
      If ApplicationQuit 
        Trace("ApplicationQuit == " + ApplicationQuit) 
        Break ; .................................................. leeve the loop, now  
      EndIf 
    EndIf 
    
    If Not LoopRunningState  
      Debug #PB_Compiler_Procedure + "() ::" + "LoopRunningState == FALSE " ;,9 
      Trace("LoopRunningState == FALSE ") 
      Break 
    EndIf 

  Next index 

  If index >= #LOOP_MaxLoops 
    SetGadgetText(#GADGET_txtLoopOutput, "Loop finished.") 
    Trace("Loop finished (reached MaxLoops == " + index + ")") 
  EndIf 
  LoopRunningState = #False 
EndProcedure 


;-=== GUI Main Window =================================================================================================

Procedure OpenMainWindow(Width = 400, Height = 320) 
  Protected ww 

  If OpenWindow(#WINDOW_Main, 0, 0, Width, Height, "Event handling example...", #WINDOW_Main_Flags) 
    StickyWindow(#WINDOW_Main, 1)  ; show test app always above the PB_IDE 

    ww = (Width - 16) / 2 
    ButtonGadget(#GADGET_btnStart,  8, 4, ww, 32, "Start Looping") 
    ButtonGadget(#GADGET_btnStop, ww + 8, 4, ww, 32, "Stop Looping") 

    CheckBoxGadget(#GADGET_chkTest, 8, 40, Width - 16, 24, "Check me -- (see the event handling while the loop is running)") 

    TextGadget(#GADGET_txtLoopOutput, 8, 72, Width - 16, 20, "Loop is stopped.") 
    ;SetGadgetColor(#GADGET_txtLoopOutput, #PB_Gadget_BackColor, GetSysColor_(#COLOR_INFOBK)) ; windows only 

    ListViewGadget(#GADGET_lbTrace, 8, 104, Width - 16, Height - 112) ;, $4000)  ; #LBS_NOSEL == 0x4000 ; .. windows only 

;     If CreateMenu(0, WindowID(#WINDOW_Main)) 
;       MenuTitle("Menu")
;       MenuItem(1, "Item 1")
;       MenuItem(2, "Item 2")
;       MenuItem(3, "Item 3")
;     EndIf

    BindEvent(#PB_Event_SizeWindow,  @MainWindowSizeEvent(), #WINDOW_Main) 

  EndIf 
  ProcedureReturn WindowID(#WINDOW_Main) 
EndProcedure 

; ---------------------------------------------------------------------------------------------------------------------

Procedure MainWindowSizeEvent() 
  Protected ww, wndW, wndH                                    : Debug #PB_Compiler_Procedure, 9 

  wndW = WindowWidth(#WINDOW_Main) 
  wndH = WindowHeight(#WINDOW_Main) 
  Trace("Window Size == " + wndW + " x " + wndH) 

  ww = (wndW - 16) / 2 

  ResizeGadget(#GADGET_btnStart,  #PB_Ignore, #PB_Ignore, ww, #PB_Ignore) 
  ResizeGadget(#GADGET_btnStop,  ww + 10,    #PB_Ignore, ww, #PB_Ignore) 

  ResizeGadget(#GADGET_txtLoopOutput,  #PB_Ignore, #PB_Ignore, wndW - 16, #PB_Ignore) 
  ResizeGadget(#GADGET_lbTrace, #PB_Ignore, #PB_Ignore, wndW - 16, wndH - 112) 

EndProcedure 

; ---------------------------------------------------------------------------------------------------------------------

Procedure MainWindowEvents(Event)  ; returns #False (continue) or #True (quit appl.) 
  ;/ 
  ;| Use ApplicationQuit (global) 
  ;\ 

  Select Event 
    Case #PB_Event_ActivateWindow  ;{- .... 
      If GetActiveWindow() = #WINDOW_Main  ; check if it still got the focus and it is not just a delayed focus event!
      ; ........... Always give back the focus to the editor when the window gets activated
      EndIf
      ;} 
    Case #PB_Event_CloseWindow  ;{- ....  
      ApplicationQuit = #True   ; set once, cannot released by further calls 
      ;} 
    Case #PB_Event_GadgetDrop  ;{- .... 
      ; Select EventGadget()
      ;   Case #GADGET_Xxx
      ;   Case #GADGET_Yyy
      ;   Default 
      ; EndSelect 
      ;} End of #PB_Event_GadgetDrop 
    Case #PB_Event_Timer  ;{- .... 
      ; Select EventTimer() 
      ;   Case #TIMER_Xxxx 
      ;   Case #TIMER_Yyyy
      ; EndSelect
      ;} End of #PB_Event_Timer 
    Case #PB_Event_Gadget  ;{- .... 
      Select EventGadget() 
        Case #GADGET_btnStart                            : Debug "#GADGET_btnStart clicked!", 9 
          LoopRunningState = #True 
          ClearGadgetItems(#GADGET_lbTrace) 
          SetGadgetText(#GADGET_txtLoopOutput, "Loop is running") 
          Test_LongLoop() 

        Case #GADGET_btnStop                            : Debug "#GADGET_btnStop clicked!", 9 
          SetGadgetText(#GADGET_txtLoopOutput, "Loop stopped.") 
          LoopRunningState = #False 

        Case #GADGET_chkTest                            : Debug "#GADGET_chkTest clicked!", 9 
          Debug "  State == " + GetGadgetState(#GADGET_chkTest), 9 
          Trace("Gadget = #GADGET_chkTest == " + GetGadgetState(#GADGET_chkTest)) 

      ; Default   ; <--> unknown gadget .. 
      EndSelect 
      ;} End of #PB_Event_Gadget 
    Case #PB_Event_Menu  ;{- .... 
    ; ApplicationQuit = MainMenuEvent(EventMenu()) 
      ;} 
    Case #PB_Event_SizeWindow  ;{- ....  ; after sizing is done.... 
    ; ResizeMainWindow() 
      Trace("  New Window Size == " + WindowWidth(#WINDOW_Main) + " x " + WindowHeight(#WINDOW_Main)) 
      ;} 
    Case #PB_Event_MoveWindow  ;{- ....  ; after moving is done.... 
      If GetWindowState(#WINDOW_Main) = #PB_Window_Normal 
        Trace("  New Window Pos == " + WindowX(#WINDOW_Main) + ", " +WindowY(#WINDOW_Main)) 
      EndIf
      ;} 
  EndSelect ; Event 
  ProcedureReturn ApplicationQuit  ; == #False or #True (probably not needed) 
EndProcedure 


;-=== Basic Event Handler =============================================================================================

Procedure FlushEvents()  ; returns nothing 
  While DispatchEvent(WindowEvent()) : Wend  ; as long as the queue is not empty 
EndProcedure


;/---------------------------------------------------------------------------------------------------------------------
;| Processes all events.
;| moved into a procedure, so it can be called from the FlushEvents() too, so no events are lost.
;\
Procedure DispatchEvent(Event)  ; returns Event | Event == #PB_Gadget_Event, ... 

  ; shortcut for empty queue 
  ; 
  If Event = #PB_Event_None ; (0) 
    ProcedureReturn #PB_Event_None ; (0)  
  EndIf

  Select EventWindow()  ; <-- process events of all the windows 
    Case #WINDOW_Main  
      MainWindowEvents(Event) ; ApplicationQuit could be true 

  ; Case #WINDOW_Edit   ; more windows 

  EndSelect 

  ProcedureReturn Event  ; return the event still, to be able to check for 0 events (empty queue)
EndProcedure 


;-=== main ============================================================================================================

Procedure main() 

  If OpenMainWindow() 

    Repeat 
      DispatchEvent(WaitWindowEvent())  ; no check for empty queue here! 
    Until ApplicationQuit 

  EndIf 

  ProcedureReturn 0 
EndProcedure 

End main()

;-=== BoF =============================================================================================================


to 2. Using BindEvent(), BindGadgetEvent()
To compare it with the use of bindevent I did the task again.

Code: Select all

;| Advanced Event Handling -- Version 2 (BindEvent) 

EnableExplicit 
DebugLevel 9

Enumeration Window 
  #WINDOW_Main 
EndEnumeration 

Enumeration Gadget 
  #GADGET_btnStart 
  #GADGET_btnStop 
  #GADGET_chkTest
  #GADGET_txtLoopOutput 
  #GADGET_lbTrace 
EndEnumeration 

#WINDOW_Main_Flags = #PB_Window_SystemMenu | #PB_Window_ScreenCentered | #PB_Window_SizeGadget

#LOOP_MaxLoops        = 1000 
#LOOP_SlowDownDelay   =  100 

; ---------------------------------------------------------------------------------------------------------------------

Global ApplicationQuit  = #False   ; Application flag 
Global LoopRunningState = #False   ; init --> not running 


; ---------------------------------------------------------------------------------------------------------------------

Declare MainWindowSizeEvent() 
Declare OnButtonEvents() 
Declare OnCloseWindowEvent() 
; 
Declare ClearWindowMessages() 


; ---------------------------------------------------------------------------------------------------------------------

Macro Trace(MessageText) 
  AddGadgetItem(#GADGET_lbTrace, -1, #PB_Compiler_Procedure + "() :: " + MessageText) 
  SetGadgetState(#GADGET_lbTrace, CountGadgetItems(#GADGET_lbTrace) - 1) 
EndMacro 


; ---------------------------------------------------------------------------------------------------------------------

Procedure ClearWindowMessages() 
  While WindowEvent(): Wend 
EndProcedure 



;/---------------------------------------------------------------------------------------------------------------------
;| Test Function 
;| 
;|  the stupid long lasting code in one function (i.e. scanning dirs, copying files, parsing files, etc. 
;| 
;\ 
Procedure Test_LongLoop() 
  Protected index, msg$  
  
  For index = 0 To #LOOP_MaxLoops 
    Delay(#LOOP_SlowDownDelay)  ; spend some time with nothing 

    msg$ = " Item " + Str(index) + "/" + Str(#LOOP_MaxLoops) + " | Checkbox.State == " + GetGadgetState(#GADGET_chkTest) 
    SetGadgetText(#GADGET_txtLoopOutput, msg$) 

    If index % 10 
      ClearWindowMessages() 
    EndIf 

    If ApplicationQuit  ; ...................................... main window close button clicked, escape the loop 
      Debug #PB_Compiler_Procedure + "() :: ApplicationQuit == TRUE " ;,9 
      Trace("ApplicationQuit == " + ApplicationQuit) 
      Break ; .................................................. leeve the loop, now  
    EndIf 

    
    If Not LoopRunningState  
      Debug #PB_Compiler_Procedure + "() :: LoopRunningState == FALSE " ;,9 
      Trace("LoopRunningState == FALSE ") 
      Break 
    EndIf 

  Next index 

  If index >= #LOOP_MaxLoops 
    SetGadgetText(#GADGET_txtLoopOutput, "Loop finished.") 
    Trace("Loop finished (reached MaxLoops == " + index + ")") 
  EndIf 

  LoopRunningState = #False 
EndProcedure 



;-=== GUI Main Window =================================================================================================

Procedure OpenMainWindow(Width = 400, Height = 320) 
  Protected ww 

  If OpenWindow(#WINDOW_Main, 0, 0, Width, Height, "Event handling example...", #WINDOW_Main_Flags) 
    StickyWindow(#WINDOW_Main, 1)  ; show test app always above the PB_IDE 

    ww = (Width - 16) / 2 
    ButtonGadget(#GADGET_btnStart,  8, 4, ww, 32, "Start Looping") 
    ButtonGadget(#GADGET_btnStop, ww + 8, 4, ww, 32, "Stop Looping") 

    CheckBoxGadget(#GADGET_chkTest, 8, 40, Width - 16, 24, "Check me -- (see the event handling while the loop is running)") 

    TextGadget(#GADGET_txtLoopOutput, 8, 72, Width - 16, 20, "Loop is stopped.") 
    ;SetGadgetColor(#GADGET_txtLoopOutput, #PB_Gadget_BackColor, GetSysColor_(#COLOR_INFOBK)) ; windows only 

    ListViewGadget(#GADGET_lbTrace, 8, 104, Width - 16, Height - 112) ;, $4000)  ; #LBS_NOSEL == 0x4000 .. windows only 
    
;     If CreateMenu(0, WindowID(#WINDOW_Main)) 
;       MenuTitle("Menu")
;       MenuItem(1, "Item 1")
;       MenuItem(2, "Item 2")
;       MenuItem(3, "Item 3")
;     EndIf

    BindEvent(#PB_Event_SizeWindow,  @MainWindowSizeEvent(), #WINDOW_Main) 
    BindEvent(#PB_Event_CloseWindow, @OnCloseWindowEvent(),  #WINDOW_Main) 

    BindGadgetEvent(#GADGET_btnStart, @OnButtonEvents()) 
    BindGadgetEvent(#GADGET_btnStop,  @OnButtonEvents()) 
    BindGadgetEvent(#GADGET_chkTest,  @OnButtonEvents()) 

  EndIf 
  ProcedureReturn WindowID(#WINDOW_Main) 
EndProcedure 

; ---------------------------------------------------------------------------------------------------------------------

Procedure MainWindowSizeEvent() 
  Protected ww, wndW, wndH                                    : Debug #PB_Compiler_Procedure, 9 

  wndW = WindowWidth(#WINDOW_Main) 
  wndH = WindowHeight(#WINDOW_Main) 
  Trace("Window Size == " + wndW + " x " + wndH) 

  ww = (wndW - 16) / 2 

  ResizeGadget(#GADGET_btnStart,  #PB_Ignore, #PB_Ignore, ww, #PB_Ignore) 
  ResizeGadget(#GADGET_btnStop,  ww + 10,    #PB_Ignore, ww, #PB_Ignore) 

  ResizeGadget(#GADGET_txtLoopOutput,  #PB_Ignore, #PB_Ignore, wndW - 16, #PB_Ignore) 
  ResizeGadget(#GADGET_lbTrace, #PB_Ignore, #PB_Ignore, wndW - 16, wndH - 112) 

EndProcedure 

; ---------------------------------------------------------------------------------------------------------------------

Procedure OnButtonEvents() 
  Select EventGadget() 
    Case #GADGET_btnStart                                                   : Debug "#GADGET_btnStart clicked!", 9 
      LoopRunningState = #True 
      ClearGadgetItems(#GADGET_lbTrace) 
      SetGadgetText(#GADGET_txtLoopOutput, "Loop is running") 
      
    Case #GADGET_btnStop                                                    : Debug "#GADGET_btnStop clicked!", 9 
      SetGadgetText(#GADGET_txtLoopOutput, "Loop stopped.") 
      LoopRunningState = #False 

    Case #GADGET_chkTest                                                    : Debug "#GADGET_chkTest clicked!", 9 
      ; Debug "  State == " + GetGadgetState(#GADGET_chkTest), 9 
      Trace("Gadget = #GADGET_chkTest == " + GetGadgetState(#GADGET_chkTest)) 

    Default 
      Trace("Gadget = " + EventGadget()) 

  EndSelect 
EndProcedure 

; ---------------------------------------------------------------------------------------------------------------------
Procedure OnCloseWindowEvent() 
  Trace("CloseWindow .. set flag to TRUE ")                           : Debug "CloseWindow .. set flag to TRUE ", 9 
  ApplicationQuit = #True   ; set once, cannot released by further calls 
  ; 
  ; or call a procedure like ExitApplication() 
  ; 
EndProcedure 

;-=== main ============================================================================================================

Procedure main() 
  Protected event, first 

  If OpenMainWindow() 

    Repeat 
      event = WaitWindowEvent(20) 
      If event = #PB_Event_None 
        If LoopRunningState = #True 
          If first = #False 
            Test_LongLoop()   ; <-- cannot called by BindEvent() procedures 
            first = #True 
          EndIf 
        Else 
          first = #False 
        EndIf 

      ElseIf event = #PB_Event_CloseWindow 
        If EventWindow() = #WINDOW_Main 
          Debug "MainLoop Exit " 
          Break 
        EndIf 
     
      EndIf 
    Until ApplicationQuit 

  EndIf 

  ProcedureReturn 0 
EndProcedure 

End main() 

;-=== BoF =============================================================================================================
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).