PureBasic Event Binding: A Quick Tutorial

Share your advanced PureBasic knowledge/code with the community.
User avatar
TI-994A
Addict
Addict
Posts: 2740
Joined: Sat Feb 19, 2011 3:47 am
Location: Singapore
Contact:

PureBasic Event Binding: A Quick Tutorial

Post by TI-994A »

A Quick Summary About System Messages
Computer programs typically run on some form of operating system, and the operating system relays all the inputs and outputs between the user and the application itself. The operating system notifies each application when there are inputs intended for the application so that the inputs can be consumed and processed accordingly. This notification data is referred to as messages, and most applications run a dedicated loop to continuously await such messages. This dedicated loop is also known as the message loop, message pump, or the message dispatcher. It dispatches each relevant message that is received to the corresponding handler.

PureBasic's Event Loop
In PureBasic, system messages are intercepted by two native functions; WindowEvent() and WaitWindowEvent(). They are typically called in a loop known as the main event loop, which looks something like this:

Code: Select all

OpenWindow(0, 0, 0, 300, 400,
           "The Windows Message Loop", 
           #PB_Window_SystemMenu | 
           #PB_Window_SizeGadget | 
           #PB_Window_ScreenCentered)
ListViewGadget(0, 10, 10, 280, 380)   ; for status output only

; - this is the conventional event loop, also known as
;   the message loop, message pump, or message dispatcher
; - it acts like a post office receiving event messages for
;   its application and dispatching them to relevant handlers
; - here is a cross sampling of some of the event messages that the
;   application expects and how they will be dispatched and processed

Repeat 
  event = WaitWindowEvent()
  Select event
      
    Case #PB_Event_SizeWindow  
      ; to handle window resizing events
      AddGadgetItem(0, 0, "window resized...")
      
    Case #PB_Event_MoveWindow  
      ; to handle window move events
      AddGadgetItem(0, 0, "window moving...")
      
    Case #PB_Event_CloseWindow
      ; to handle the window close event
      AddGadgetItem(0, 0, "window closing...")
      
    Case #PB_Event_LeftClick
      ; to handle a left mouse click on the window
      AddGadgetItem(0, 0, "left mouse click on window...")
      
    Case #PB_Event_RightClick
      ; to handle a right mouse click on the window   
      AddGadgetItem(0, 0, "right mouse click on window...")
      
    Case #PB_Event_Gadget
      ; to handle all gadget events
      AddGadgetItem(0, 0, "unspecified gadget event...")
      
    Case #PB_Event_Timer
      ; to handle all timer events
      AddGadgetItem(0, 0, "left mouse click on window...")
      
    Case #PB_Event_SysTray
      ; to handle all system tray events
      AddGadgetItem(0, 0, "click on the app system tray icon...")
      
  EndSelect
ForEver

PureBasic's Event Binding
PureBasic also offers alternative methods to this conventional event loop, in the form of event binding. Technically, events and objects can be bound to dedicated handlers which would automatically be called when the bound events or objects are triggered. For larger applications, this approach provides more modularity to code design, as opposed to stacking all the event conditions into one huge loop. Once bound, these events and objects do not need to be processed in the main event loop any longer. This approach could conceivably replace the main event loop altogether, but it could still exist side-by-side with bound events, processing other events if required.

1a. BindEvent()
The main binding method is the BindEvent() function, which has the following basic syntax:

Code: Select all

; when specificEvent is triggered > relay it to specificHandler()
BindEvent(specificEvent, @specificHandler())

The events natively supported by PureBasic include the following:
PureBasic Manual wrote: #PB_Event_Menu : a menu has been selected
#PB_Event_Gadget : a gadget has been pushed
#PB_Event_SysTray : an icon in the systray zone was clicked
#PB_Event_Timer : a timer has reached its timeout
#PB_Event_CloseWindow : the window close gadget has been pushed
#PB_Event_Repaint : the window content has been destroyed and must be repainted
#PB_Event_SizeWindow : the window has been resized
#PB_Event_MoveWindow : the window has been moved
#PB_Event_MinimizeWindow : the window has been minimized
#PB_Event_MaximizeWindow : the window has been maximized
#PB_Event_RestoreWindow : the window has been restored to normal size
#PB_Event_ActivateWindow : the window has been activated (got the focus)
#PB_Event_DeactivateWindow : the window has been deactivated (lost the focus)
#PB_Event_WindowDrop : a drag & drop operation was finished on a window
#PB_Event_GadgetDrop : a drag & drop operation was finished on a gadget
#PB_Event_RightClick : a right mouse button click has occurred on the window
#PB_Event_LeftClick : a left mouse button click has occurred on the window
#PB_Event_LeftDoubleClick : a left mouse button double-click has occurred on the window

Technically, these events could be bound to specific handlers to be processed. Here's an example:

Code: Select all

Procedure closeWindowHandler()
  Shared window
  MessageRequester("PureBasic BindEvent() Function",
                   "Received the close-window event")
  CloseWindow(window) 
  End
EndProcedure

window = OpenWindow(#PB_Any, 0, 0, 300, 400, "PureBasic BindEvent() Function", 
                    #PB_Window_SystemMenu | #PB_Window_ScreenCentered)

; binds the close window event to closeWindowHandler()
BindEvent(#PB_Event_CloseWindow, @closeWindowHandler())

Repeat : WaitWindowEvent() : ForEver

In the above example, the #PB_Event_CloseWindow event is bound to the closeWindowHandler() procedure to exclusively process the window-close event, and nothing else. Here, the handler simply displays a message box before ending the program; but if more logic were required prior to closing the window, it could all be consolidated into this one single procedure. Of course, the main event loop could also simply call a dedicated procedure in the same manner; but with this approach, there are some improved design and performance considerations, and it provides the option to forgo the main event loop altogether, if so desired.

It should also be noted that in the context of the above example, if there is more than one active window, the close window-events for all of them will be bound to the closeWindowHandler() procedure. The next example demonstrates this:

Code: Select all

Procedure closeWindowHandler()
  Shared window1, window2, appQuit
  
  Select EventWindow()   ; determine the event window
  
    Case window1
      If MessageRequester("PureBasic BindEvent() Function", "Close the main window?",
                          #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes
        CloseWindow(window1)
        End
      EndIf      
      
    Case window2
      If MessageRequester("PureBasic BindEvent() Function", "Close the child window?",
                          #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes
        CloseWindow(window2)
      EndIf
      
  EndSelect
EndProcedure

window1 = OpenWindow(#PB_Any, 0, 0, 600, 400, "PureBasic BindEvent() Function", 
                     #PB_Window_SystemMenu | #PB_Window_ScreenCentered)

window2 = OpenWindow(#PB_Any, 0, 0, 300, 200, "Child window...", 
                     #PB_Window_SystemMenu | #PB_Window_WindowCentered, 
                     WindowID(window1))

; binds the close window event for all windows to closeWindowHandler()
BindEvent(#PB_Event_CloseWindow, @closeWindowHandler())

Repeat : WaitWindowEvent() : ForEver

1b. BindEvent()
Alternatively, the BindEvent() function could also be configured to bind events based on specific windows.

This is done with the third parameter of the function call:

Code: Select all

; when specificEvent is triggered by specificWindow > relay it to specificHandler()
BindEvent(specificEvent, @specificHandler(), specificWindow)

With the window parameter of the BindEvent() function set, only events from the specific window will trigger its corresponding handler, like this:

Code: Select all

Procedure closeWindowHandler1()  

  If MessageRequester("PureBasic BindEvent() Function", "Close the main window?",
                      #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes    
    CloseWindow(EventWindow())
    End    
  EndIf      
  
EndProcedure

Procedure closeWindowHandler2()
    
  If MessageRequester("PureBasic BindEvent() Function", "Close the child window?",
                      #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes
    CloseWindow(EventWindow())    
  EndIf      
  
EndProcedure

window1 = OpenWindow(#PB_Any, 0, 0, 600, 400, "PureBasic BindEvent() Function", 
                     #PB_Window_SystemMenu | #PB_Window_ScreenCentered)

window2 = OpenWindow(#PB_Any, 0, 0, 300, 200, "Child window...", 
                     #PB_Window_SystemMenu | #PB_Window_WindowCentered, 
                     WindowID(window1))

; binds the close window event of only window1 to closeWindowHandler1()
BindEvent(#PB_Event_CloseWindow, @closeWindowHandler1(), window1)

; binds the close window event of only window2 to closeWindowHandler2()
BindEvent(#PB_Event_CloseWindow, @closeWindowHandler2(), window2)

Repeat : WaitWindowEvent() : ForEver

Another event that would require handling of multiple objects is the #PB_Event_Gadget event, which could be triggered by any of the active gadgets in an application. The handling would be quite similar to the handling of multiple windows, as demonstrated here:

Code: Select all

Procedure closeWindowHandler()
  If MessageRequester("PureBasic BindEvent() Function", "Close the main window?",
                      #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes
    CloseWindow(EventWindow())
    End
  EndIf      
EndProcedure

Procedure gadgetHandler()
  ; to handle all the gadget events
  Shared button, editor1, editor2
  Define event.s, editor = editor1
  
  Select EventGadget()   ; determine the event gadget
      
    Case editor1   ; contains three associated event types
      Select EventType()   ; determine the event type
        Case #PB_EventType_Focus
          event = "Editor 1 received focus!"              
        Case #PB_EventType_LostFocus
          event = "Editor 1 lost focus!"                        
        Case #PB_EventType_Change      
          event = "Editor 1 contents changed!" 
      EndSelect
      
    Case editor2   ; contains three associated event types
      editor = editor2
      Select EventType()   ; determine the event type
        Case #PB_EventType_Focus
          event = "Editor 2 received focus!"              
        Case #PB_EventType_LostFocus
          event = "Editor 2 lost focus!"                        
        Case #PB_EventType_Change      
          event = "Editor 2 contents changed!" 
      EndSelect
      
    Case button   ; no associated event types
      event = "Button clicked!" 
      
  EndSelect   
  
  AddGadgetItem(editor, 0, event)     
EndProcedure

window = OpenWindow(#PB_Any, 100, 100, 600, 450, "PureBasic BindEvent() Function", 
                    #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
editor1 = EditorGadget(#PB_Any, 10, 10, 580, 180)
editor2 = EditorGadget(#PB_Any, 10, 200, 580, 180)
button = ButtonGadget(#PB_Any, 200, 390, 200, 40, "Button")

; binds the close window event to closeWindowHandler()
; only 1 window - so no window parameter required
BindEvent(#PB_Event_CloseWindow, @closeWindowHandler())

; binds all gadget events to gadgetHandler()
; only 1 window - so no window parameter required
BindEvent(#PB_Event_Gadget, @gadgetHandler())   

Repeat : WaitWindowEvent() : ForEver

In the above example, the gadget handler must process all the events for all the gadgets. It does this by determining the gadget that triggered the event using the EventGadget() function. However, notice that some events here are further processed for an event type. Unlike window objects, some gadgets have event types associated with them. For example, button gadgets have no event types associated with them, so they require no further event-processing. Editor gadgets, however, have three associated event types, and thus, they are subject to another level of conditional checks.


1c. BindEvent()
For greater streamlining and modular control, the BindEvent() function could be further configured to bind events based on specific objects and their associated event types as well.

To bind an event to a specific object, the object must be specified in the fourth parameter of the BindEvent() function call:

Code: Select all

; when specificEvent is triggered by specificObject in specificWindow > relay it to specificHandler()
BindEvent(specificEvent, @specificHandler(), specificWindow, specificObject)

In PureBasic, depending on the event being bound, objects could be gadgets, timers, system tray items, or menu items. The next example demonstrates this object binding:

Code: Select all

Procedure closeWindowHandler()
  If MessageRequester("PureBasic BindEvent() Function", "Close the main window?",
                      #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes
    CloseWindow(EventWindow())
    End
  EndIf      
EndProcedure

Procedure editorHandler()
  ; to handle all the events of editor1 only
  Shared editor1
  Define.s event.s
  
  Select EventType()   ; determine the event type
    Case #PB_EventType_Focus
      event = "received focus!"              
    Case #PB_EventType_LostFocus
      event = "lost focus!"                        
    Case #PB_EventType_Change      
      event = "contents changed!" 
  EndSelect   
  
  AddGadgetItem(editor1, 0, "Editor 1 " + event)     
EndProcedure

window = OpenWindow(#PB_Any, 100, 100, 600, 400, "PureBasic BindEvent() Function", 
                    #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
editor1 = EditorGadget(#PB_Any, 10, 10, 580, 180)
editor2 = EditorGadget(#PB_Any, 10, 200, 580, 180)

; binds the close window event to closeWindowHandler()
BindEvent(#PB_Event_CloseWindow, @closeWindowHandler(), window)

; binds all the events of only editor1 to editorHandler()
BindEvent(#PB_Event_Gadget, @editorHandler(), window, editor1)

Repeat : WaitWindowEvent() : ForEver

In the example above, the #PB_Event_Gadget event was bound to the editorHandler() procedure to exclusively handle all the events triggered by only one of the editor gadgets. Notice that the events of the second editor gadget are not being processed, because the handler was expressly bound only to the events of the first editor gadget - editor1.


1d. BindEvent()
Notice also that the handler is processing all the event types triggered by the editor gadget; namely the focus, lost-focus, and the change event types. As mentioned earlier, the BindEvent() function could be configured another level further to filter out these event types as well.

This is done with the fifth parameter:

Code: Select all

; when specificEvent and specificEventType are triggered by specificObject in specificWindow > relay it to specificHandler()
BindEvent(specificEvent, @specificHandler(), specificWindow, specificObject, specificEventType)

These are some of the possible event types triggered by PureBasic gadgets:
PureBasic Manual: wrote: #PB_EventType_LeftClick : left mouse button click
#PB_EventType_RightClick : right mouse button click
#PB_EventType_LeftDoubleClick : left mouse button double click
#PB_EventType_RightDoubleClick : right mouse button double click
#PB_EventType_Focus : get the focus
#PB_EventType_LostFocus : lose the focus
#PB_EventType_Change : content change
#PB_EventType_DragStart : the user tries to start a drag & drop operation

Here's the above example modified to individually bind each of the event types of the editor gadget:

Code: Select all

Procedure closeWindowHandler()
  If MessageRequester("PureBasic BindEvent() Function", "Close the main window?",
                      #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes
    CloseWindow(EventWindow())
    End
  EndIf      
EndProcedure

Procedure editorFocusHandler()
  ; to handle only the focus event of editor1
  Shared editor1  
  AddGadgetItem(editor1, 0, "Editor 1 received focus!")     
EndProcedure

Procedure editorLostFocusHandler()
  ; to handle only the lost-focus event of editor1
  Shared editor1  
  AddGadgetItem(editor1, 0, "Editor 1 lost focus!")     
EndProcedure

Procedure editorChangeHandler()
  ; to handle only the change event of editor1
  Shared editor1  
  AddGadgetItem(editor1, 0, "Editor 1 contents changed!")     
EndProcedure

window = OpenWindow(#PB_Any, 100, 100, 600, 400, "PureBasic BindEvent() Function", 
                    #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
editor1 = EditorGadget(#PB_Any, 10, 10, 580, 180)
editor2 = EditorGadget(#PB_Any, 10, 200, 580, 180)

; binds the close window event to closeWindowHandler()
BindEvent(#PB_Event_CloseWindow, @closeWindowHandler(), window)

; binds only the focus event of editor1 to editorFocusHandler()
BindEvent(#PB_Event_Gadget, @editorFocusHandler(), window, editor1, #PB_EventType_Focus)

; binds only the lost-focus event of editor1 to editorLostFocusHandler()
BindEvent(#PB_Event_Gadget, @editorLostFocusHandler(), window, editor1, #PB_EventType_LostFocus)

; binds only the change event of editor1 to editorChangeHandler()
BindEvent(#PB_Event_Gadget, @editorChangeHandler(), window, editor1, #PB_EventType_Change)

Repeat : WaitWindowEvent() : ForEver

Now, let's implement them altogether to see how they synergise:

Code: Select all

Global appQuit
Global window1, w1Editor, w1Button1, w1Button2
Global window2, w2Editor, w2Button1, w2Button2

Procedure closeWindowHandler1()
  If MessageRequester("PureBasic BindEvent() Function", "Close the main window?",
                      #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes
    appQuit = #True
  EndIf      
EndProcedure

Procedure closeWindowHandler2()
  If MessageRequester("PureBasic BindEvent() Function", "Close the child window?",
                      #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes
    CloseWindow(EventWindow())    
  EndIf      
EndProcedure

Procedure commonGadgetHandler()
  ; to handle all gadget events from all windows  
  Define.s window, event
  
  Select EventWindow()   ; determine the window
      
    Case window1
      window = "Window 1 "
      output = w1Editor
      Select EventGadget()   ; determine the gadget
        Case w1Button2
          event = "Button 2 clicked!"
        Case w1Editor            
          Select EventType()   ; determine the event type
            Case #PB_EventType_Focus
              event = "Editor received focus!"          
            Case #PB_EventType_LostFocus
              event = "Editor lost focus!"
            Case #PB_EventType_Change      
              event = "Editor contents changed!" 
          EndSelect
        Default
          event = "Unspecified gadget event!"
      EndSelect
      
    Case window2
      window = "Window 2 "
      output = w2Editor
      Select EventGadget()   ; determine the gadget
        Case w2Button2
          event = "Button 2 clicked"
        Case w2Editor            
          Select EventType()   ; determine the event type
            Case #PB_EventType_Focus
              event = "Editor received focus!"          
            Case #PB_EventType_LostFocus
              event = "Editor lost focus!"
            Case #PB_EventType_Change      
              event = "Editor contents changed!" 
          EndSelect
        Default
          event = "Unspecified gadget event!"
      EndSelect
      
  EndSelect   
  
  If EventType() <> #PB_EventType_Change And event <> ""
    AddGadgetItem(output, 0, "[common handler] " + window + event)  
  EndIf
  
EndProcedure

Procedure win1Button1Handler()
  ; to handle only button1 from window1  
  AddGadgetItem(w1Editor, 0, "[win1_but1 handler] Window 1 Button 1 clicked!")     
EndProcedure

Procedure win2EditorHandler()
  ; to handle only the change-event-type of the editor in window2 
  AddGadgetItem(w2Editor, 0, "[win2_edit handler] Window 2 Editor contents changed!")     
EndProcedure

window1 = OpenWindow(#PB_Any, 100, 100, 600, 400, 
                     "PureBasic BindEvent() Function", 
                     #PB_Window_SystemMenu)
w1Editor = EditorGadget(#PB_Any, 10, 10, 580, 330)
w1Button1 = ButtonGadget(#PB_Any, 10, 350, 200, 40, "Button 1")
w1Button2 = ButtonGadget(#PB_Any, 390, 350, 200, 40, "Button 2")

window2 = OpenWindow(#PB_Any, 600, 400, 500, 300, "Child window...", 
                     #PB_Window_SystemMenu, WindowID(window1))
w2Editor = EditorGadget(#PB_Any, 10, 10, 480, 230)
w2Button1 = ButtonGadget(#PB_Any, 10, 250, 150, 40, "Button 1")
w2Button2 = ButtonGadget(#PB_Any, 340, 250, 150, 40, "Button 2")

; binds the close window events of each window to corresponding handlers
BindEvent(#PB_Event_CloseWindow, @closeWindowHandler1(), window1)
BindEvent(#PB_Event_CloseWindow, @closeWindowHandler2(), window2)

; binds all gadget events to commonGadgetHandler()
BindEvent(#PB_Event_Gadget, @commonGadgetHandler())

; bind only button1 from window1 to win1Button1Handler()
BindEvent(#PB_Event_Gadget, @win1Button1Handler(), window1, w1Button1)

; bind only the change-event-type of the editor from window2 to win2EditorHandler()
BindEvent(#PB_Event_Gadget, @win2EditorHandler(), window2, w2Editor, #PB_EventType_Change)

Repeat : WaitWindowEvent() : Until appQuit

To demonstrate the versatility of the binding function, this expanded example above mixes up the bindings with different configurations. The code contains two windows, each with three gadgets. Each window has its own dedicated window-close event handlers, but the gadgets are handled in a very unique manner:
1. all gadget events from both windows are bound to commonGadgetHandler()
2. only the editor gadgets and second buttons from both windows are handled by commonGadgetHandler()
3. the first buttons from either window are not exclusively bound and are not handled by commonGadgetHandler()
4. the first buttons from both windows will still trigger the default response from commonGadgetHandler()
5. the first button from the first window is also exclusively bound to win1Button1Handler() to handle its clicks
6. the editor from the second window is also exclusively bound to win2EditorHandler() to handle only its change events
7. the first button from the first window and the editor change-event from the second window will both trigger twice, once by its own dedicated handlers, and once by commonGadgetHandler() as default, unhandled events.

The commonGadgetHandler() could easily ignore the gadgets and events that it is not handling, but a default handler is added in the example to better demonstrate the mechanics of multiple bindings.

This covers the basics of the BindEvent() function. It is clearly a useful and powerful feature towards cleaner and more efficient code design, especially since it could work side-by-side with duplicate binding calls and with the conventional event handling processes as well.


2. BindGadgetEvent()
It should be noted that PureBasic has another, more specialised binding function, geared exclusively for its gadgets. It is the BindGadgetEvent() function, which works in exactly the same way as the BindEvent() function, but exclusively for the #PB_Event_Gadget event. It could be viewed as an abridged version of the BindEvent() function, not requiring the first Event parameter, nor the third Window parameter.


The BindGadgetEvent() function has the following syntax, with the third parameter as optional:

Code: Select all

; when specificGadget is triggered > relay it to specificHandler()
; or
; when specificEventType of specificGadget is triggered > relay it to specificHandler()
BindGadgetEvent(specificGadget, @specificHandler() [,specificEventType])

Here's an example of the function being implemented without the optional event type parameter (third parameter):

Code: Select all

Procedure closeWindowHandler()
  If MessageRequester("PureBasic BindGadgetEvent() Function", "Close the main window?",
                      #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes
    CloseWindow(EventWindow())
    End
  EndIf      
EndProcedure

Procedure editor1Handler()
  ; to handle all editor1 events
  Shared editor1
  Define event.s
  
  Select EventType()   ; determine the event type
    Case #PB_EventType_Focus
      event = "Editor 1 received focus!"              
    Case #PB_EventType_LostFocus
      event = "Editor 1 lost focus!"                        
    Case #PB_EventType_Change      
      event = "Editor 1 contents changed!" 
  EndSelect
  
  AddGadgetItem(editor1, 0, event)     
EndProcedure

Procedure editor2Handler()
  ; to handle all editor2 events
  Shared editor2
  Define event.s
  
  Select EventType()   ; determine the event type
    Case #PB_EventType_Focus
      event = "Editor 2 received focus!"              
    Case #PB_EventType_LostFocus
      event = "Editor 2 lost focus!"                        
    Case #PB_EventType_Change      
      event = "Editor 2 contents changed!" 
  EndSelect
  
  AddGadgetItem(editor2, 0, event)     
EndProcedure

window = OpenWindow(#PB_Any, 100, 100, 600, 450, "PureBasic BindGadgetEvent() Function", 
                    #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
editor1 = EditorGadget(#PB_Any, 10, 10, 580, 180)
editor2 = EditorGadget(#PB_Any, 10, 200, 580, 180)

; binds the close window event to closeWindowHandler()
BindEvent(#PB_Event_CloseWindow, @closeWindowHandler())

; binds editor1 events to editor1Handler()
BindGadgetEvent(editor1, @editor1Handler())   

; binds editor2 events to editor2Handler()
BindGadgetEvent(editor2, @editor2Handler())   

Repeat : WaitWindowEvent() : ForEver

The syntax is clearly more succinct when compared to the BindEvent() function. It's even simpler when the event type (third parameter) is specified:

Code: Select all

Procedure closeWindowHandler()
  If MessageRequester("PureBasic BindGadgetEvent() Function", "Close the main window?",
                      #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes
    CloseWindow(EventWindow())
    End
  EndIf      
EndProcedure

Procedure editorFocusHandler()
  ; to handle only the focus event of editor1
  Shared editor1  
  AddGadgetItem(editor1, 0, "Editor 1 received focus!")     
EndProcedure

Procedure editorLostFocusHandler()
  ; to handle only the lost-focus event of editor1
  Shared editor1  
  AddGadgetItem(editor1, 0, "Editor 1 lost focus!")     
EndProcedure

Procedure editorChangeHandler()
  ; to handle only the change event of editor1
  Shared editor1  
  AddGadgetItem(editor1, 0, "Editor 1 contents changed!")     
EndProcedure

window = OpenWindow(#PB_Any, 100, 100, 600, 400, "PureBasic BindGadgetEvent() Function", 
                    #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
editor1 = EditorGadget(#PB_Any, 10, 10, 580, 180)
editor2 = EditorGadget(#PB_Any, 10, 200, 580, 180)

; binds the close window event to closeWindowHandler()
BindEvent(#PB_Event_CloseWindow, @closeWindowHandler(), window)

; binds only the focus event of editor1 to editorFocusHandler()
BindGadgetEvent(editor1, @editorFocusHandler(), #PB_EventType_Focus)

; binds only the lost-focus event of editor1 to editorLostFocusHandler()
BindGadgetEvent(editor1, @editorLostFocusHandler(), #PB_EventType_LostFocus)

; binds only the change event of editor1 to editorChangeHandler()
BindGadgetEvent(editor1, @editorChangeHandler(), #PB_EventType_Change)

Repeat : WaitWindowEvent() : ForEver

The above example individually binds the events of the first editor gadget and deliberately leaves the second editor gadget unhandled. Technically, this function works in exactly the same way as the BindEvent() function, but exclusively for the #PB_Event_Gadget event.


3. BindMenuEvent()
Last but not least, there's the BindMenuEvent() function, which also works in exactly the same way as the BindEvent() function, but exclusively for the #PB_Event_Menu event.

The BindMenuEvent() function has the following basic syntax:

Code: Select all

; when specificMenuItem of specificMenu is triggered > relay it to specificHandler()
BindMenuEvent(specificMenu, specificMenuItem, @specificHandler())

Here's a short example:

Code: Select all

Enumeration 
  #fileMenuItem1 = 1
  #fileMenuItem2
  #editMenuItem1
  #editMenuItem2
  #helpMenuItem1
  #helpMenuItem2
EndEnumeration

Procedure closeWindowHandler()
  If MessageRequester("PureBasic BindMenuEvent() Function", "Close the main window?",
                      #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes
    CloseWindow(EventWindow())
    End
  EndIf      
EndProcedure

Procedure menuHandler()
  ; to handle all the menu items
  Shared editor
  Define menuEvent.s
  
  Select EventMenu()   ; determine the menu item
    Case #fileMenuItem1
      menuEvent = "File menu item 1 selected!"
    Case #fileMenuItem2
      menuEvent = "File menu item 2 selected!"      
    Case #editMenuItem1
      menuEvent = "Edit menu item 1 selected!"
    Case #editMenuItem2
      menuEvent = "Edit menu item 2 selected!"
    Case #helpMenuItem1
      menuEvent = "Help menu item 1 selected!"
    Case #helpMenuItem2
      menuEvent = "Help menu item 2 selected!"
  EndSelect
    
  AddGadgetItem(editor, 0, menuEvent)     
EndProcedure

window = OpenWindow(#PB_Any, 100, 100, 600, 400, "PureBasic BindMenuEvent() Function", 
                    #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
editor = EditorGadget(#PB_Any, 10, 10, 580, 380)
menu = CreateMenu(#PB_Any, WindowID(window))
MenuTitle("File")
MenuItem(1, "File Item 1")
MenuItem(2, "File Item 2")
MenuTitle("Edit")
MenuItem(3, "Edit Item 1")
MenuItem(4, "Edit Item 2")
MenuTitle("Help")
MenuItem(5, "Help Item 1")
MenuItem(6, "Help Item 2")

; binds the close window event to closeWindowHandler()
BindEvent(#PB_Event_CloseWindow, @closeWindowHandler(), window)

; binds all the menu items to menuHandler()
For i = 1 To 6
  BindMenuEvent(menu, i, @menuHandler())
Next i

Repeat : WaitWindowEvent() : ForEver

Similarly, each menu item could have its own handler, like this:

Code: Select all

Enumeration 
  #fileMenuItem1 = 1
  #fileMenuItem2
  #fileMenuQuit
  #editMenuItem1
  #editMenuItem2
  #helpMenuItem1
  #helpMenuItem2
EndEnumeration

Procedure closeWindowHandler()
  If MessageRequester("PureBasic BindMenuEvent() Function", "Close the main window?",
                      #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes
    CloseWindow(EventWindow())
    End
  EndIf      
EndProcedure

Procedure fileMenuHandler()
  ; to handle the file menu items
  Shared editor
  Define menuEvent.s
  
  Select EventMenu()   ; determine the menu item
    Case #fileMenuItem1
      menuEvent = "File menu item 1 selected!"
    Case #fileMenuItem2
      menuEvent = "File menu item 2 selected!"      
  EndSelect
    
  AddGadgetItem(editor, 0, menuEvent)     
EndProcedure

Procedure editMenuHandler()
  ; to handle the edit menu items
  Shared editor
  Define menuEvent.s
  
  Select EventMenu()   ; determine the menu item
    Case #editMenuItem1
      menuEvent = "Edit menu item 1 selected!"
    Case #editMenuItem2
      menuEvent = "Edit menu item 2 selected!"
  EndSelect
    
  AddGadgetItem(editor, 0, menuEvent)     
EndProcedure

Procedure helpMenuHandler1()
  ; to exclusively handle the help menu item 1
  Shared editor
  AddGadgetItem(editor, 0, "Help menu item 1 selected!")     
EndProcedure

Procedure helpMenuHandler2()
  ; to exclusively handle the help menu item 2
  Shared editor
  AddGadgetItem(editor, 0, "Help menu item 2 selected!")     
EndProcedure

window = OpenWindow(#PB_Any, 100, 100, 600, 400, "PureBasic BindMenuEvent() Function", 
                    #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
editor = EditorGadget(#PB_Any, 10, 10, 580, 380)
menu = CreateMenu(#PB_Any, WindowID(window))
MenuTitle("File")
MenuItem(1, "File Item 1")
MenuBar()
MenuItem(2, "File Item 2")
MenuBar()
MenuItem(3, "Quit")
MenuTitle("Edit")
MenuItem(4, "Edit Item 1")
MenuBar()
MenuItem(5, "Edit Item 2")
MenuTitle("Help")
MenuItem(6, "Help Item 1")
MenuBar()
MenuItem(7, "Help Item 2")

; binds the close window event to closeWindowHandler()
BindEvent(#PB_Event_CloseWindow, @closeWindowHandler(), window)

; binds the file menu > quit item to closeWindowHandler()
BindEvent(#PB_Event_Menu, @closeWindowHandler(), window, #fileMenuQuit)

; binds the file menu items to fileMenuHandler()
BindMenuEvent(menu, #fileMenuItem1, @fileMenuHandler())
BindMenuEvent(menu, #fileMenuItem2, @fileMenuHandler())

; binds the edit menu items to editMenuHandler()
BindMenuEvent(menu, #editMenuItem1, @editMenuHandler())
BindMenuEvent(menu, #editMenuItem2, @editMenuHandler())

; binds the help menu > item 1 to helpMenuHandler1()
BindMenuEvent(menu, #helpMenuItem1, @helpMenuHandler1())

; binds the help menu > item 2 to helpMenuHandler2()
BindMenuEvent(menu, #helpMenuItem2, @helpMenuHandler2())

Repeat : WaitWindowEvent() : ForEver

For demonstration purposes, the above example has the following arbitrary scenarios:
1. both the file menu items are bound to a single handler, fileMenuHandler()
2. both the edit menu items are bound to a single handler, editMenuHandler()
3. the first help menu item is exclusively bound to its own handler, helpMenuHandler1()
4. the second help menu item is exclusively bound to its own handler, helpMenuHandler2()
5. the third file menu item (Quit) is bound to closeWindowHandler() with BindEvent() instead of BindMenuEvent()


And that covers about all of it! Quite simple and straightforward.

While the conventional event loop is still quite efficient and sufficient, an overly large stacked loop could sometimes prove to be a little overwhelming. These binding functions provide good alternatives for modular code design, promoting better readability and maintainability.

I hope that this short tutorial has been helpful in explaining the various implementations of these different binding functions and how they could operate together and alongside the conventional event loop as well.

As always, your feedback and comments are most welcome. :D


* edited for typos
Last edited by TI-994A on Wed Aug 27, 2025 7:24 pm, edited 4 times in total.
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
User avatar
Caronte3D
Addict
Addict
Posts: 1361
Joined: Fri Jan 22, 2016 5:33 pm
Location: Some Universe

Re: PureBasic Event Binding: A Quick Tutorial

Post by Caronte3D »

WoW! :shock:
Very nicely explained!
Thanks!
Quin
Addict
Addict
Posts: 1132
Joined: Thu Mar 31, 2022 7:03 pm
Location: Colorado, United States
Contact:

Re: PureBasic Event Binding: A Quick Tutorial

Post by Quin »

Very well written indeed, thanks! You just enspired me to go convert my current project to BindEvent before it gets too large... :mrgreen:
User avatar
TI-994A
Addict
Addict
Posts: 2740
Joined: Sat Feb 19, 2011 3:47 am
Location: Singapore
Contact:

Re: PureBasic Event Binding: A Quick Tutorial

Post by TI-994A »

Thanks for your kind words, @Caronte3D and @Quin. Always appreciated.

And thank you to the ADMIN who stickied the topic. :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
Fred
Administrator
Administrator
Posts: 18199
Joined: Fri May 17, 2002 4:39 pm
Location: France
Contact:

Re: PureBasic Event Binding: A Quick Tutorial

Post by Fred »

Nice work as usual ! About this sentence: "Once bound, these events and objects do not need to be processed by the WaitWindowEvent() function in the main event loop any longer", I know what you mean but we can also understand than WaitWindowEvent() insn't needed anymore, which is indeed wrong. The binded events are called when WaitWindowEvent()/WindowEvent() is called, as a callback.
User avatar
TI-994A
Addict
Addict
Posts: 2740
Joined: Sat Feb 19, 2011 3:47 am
Location: Singapore
Contact:

Re: PureBasic Event Binding: A Quick Tutorial

Post by TI-994A »

Fred wrote: Mon Sep 02, 2024 9:13 am...but we can also understand than WaitWindowEvent() insn't needed anymore, which is indeed wrong. The binded events are called when WaitWindowEvent()/WindowEvent() is called, as a callback.
Hi Fred. Thank you for your kind words, as always. I'm glad that it's helpful. :D

You're right about the wording; it could come across a little ambiguously. I've amended it as follows:
PureBasic's Event Binding
PureBasic also offers alternative methods to this conventional event loop, in the form of event binding. Technically, events and objects can be bound to dedicated handlers which would automatically be called when the bound events or objects are triggered. For larger applications, this approach provides more modularity to code design, as opposed to stacking all the event conditions into one huge loop. Once bound, these events and objects do not need to be processed in the main event loop any longer. This approach could conceivably replace the main event loop altogether, but it could still exist side-by-side with bound events, processing other events if required.
I've removed the reference to the WaitWindowEvent() function altogether to avoid this possible confusion. I hope it's alright now.
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
BarryG
Addict
Addict
Posts: 4168
Joined: Thu Apr 18, 2019 8:17 am

Re: PureBasic Event Binding: A Quick Tutorial

Post by BarryG »

@Fred: Please don't ever remove the normal non-bind event approach, as I prefer that to BindEvent() for several reasons. Thanks.
Fred
Administrator
Administrator
Posts: 18199
Joined: Fri May 17, 2002 4:39 pm
Location: France
Contact:

Re: PureBasic Event Binding: A Quick Tutorial

Post by Fred »

There is no plan for that, it would break 100% of the PB programs
User avatar
Piero
Addict
Addict
Posts: 914
Joined: Sat Apr 29, 2023 6:04 pm
Location: Italy

Re: PureBasic Event Binding: A Quick Tutorial

Post by Piero »

I bet that looking at GREAT """event based""" programming (smalltalk, applescript)
would inspire many great programmers here…
It's being lost… sigh… :(
That's what OOP was for…
juror
Enthusiast
Enthusiast
Posts: 232
Joined: Mon Jul 09, 2007 4:47 pm
Location: Courthouse

Re: PureBasic Event Binding: A Quick Tutorial

Post by juror »

Clicking button 1 or 2 on main window in the main example results in a noncancelable loop. Maybe not a complete example?

Code: Select all

Global appQuit
Global window1, w1Editor, w1Button1, w1Button2
Global window2, w2Editor, w2Button1, w2Button2

Procedure closeWindowHandler1()
  If MessageRequester("PureBasic BindEvent() Function", "Close the main window?",
                      #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes
    appQuit = #True
  EndIf      
EndProcedure

Procedure closeWindowHandler2()
  If MessageRequester("PureBasic BindEvent() Function", "Close the child window?",
                      #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes
    CloseWindow(EventWindow())    
  EndIf      
EndProcedure

Procedure commonGadgetHandler()
  ; to handle all gadget events from all windows  
  Define.s window, event
  
  Select EventWindow()   ; determine the window
      
    Case window1
      window = "Window 1 "
      output = w1Editor
      Select EventGadget()   ; determine the gadget
        Case w1Button2
          event = "Button 2 clicked!"
        Case w1Editor            
          Select EventType()   ; determine the event type
            Case #PB_EventType_Focus
              event = "Editor received focus!"          
            Case #PB_EventType_LostFocus
              event = "Editor lost focus!"
            Case #PB_EventType_Change      
              event = "Editor contents changed!" 
          EndSelect
        Default
          event = "Unspecified gadget event!"
      EndSelect
      
    Case window2
      window = "Window 2 "
      output = w2Editor
      Select EventGadget()   ; determine the gadget
        Case w2Button2
          event = "Button 2 clicked"
        Case w2Editor            
          Select EventType()   ; determine the event type
            Case #PB_EventType_Focus
              event = "Editor received focus!"          
            Case #PB_EventType_LostFocus
              event = "Editor lost focus!"
            Case #PB_EventType_Change      
              event = "Editor contents changed!" 
          EndSelect
        Default
          event = "Unspecified gadget event!"
      EndSelect
      
  EndSelect   
  
  AddGadgetItem(output, 0, "[common handler] " + window + event)  
  
EndProcedure

Procedure win1Button1Handler()
  ; to handle only button1 from window1  
  AddGadgetItem(w1Editor, 0, "[win1_but1 handler] Window 1 Button 1 clicked!")     
EndProcedure

Procedure win2EditorHandler()
  ; to handle only the change-event-type of the editor in window2 
  AddGadgetItem(w2Editor, 0, "[win2_edit handler] Window 2 Editor contents changed!")     
EndProcedure

window1 = OpenWindow(#PB_Any, 100, 100, 600, 400, 
                     "PureBasic BindEvent() Function", 
                     #PB_Window_SystemMenu)
w1Editor = EditorGadget(#PB_Any, 10, 10, 580, 330)
w1Button1 = ButtonGadget(#PB_Any, 10, 350, 200, 40, "Button 1")
w1Button2 = ButtonGadget(#PB_Any, 390, 350, 200, 40, "Button 2")

window2 = OpenWindow(#PB_Any, 600, 400, 500, 300, "Child window...", 
                     #PB_Window_SystemMenu, WindowID(window1))
w2Editor = EditorGadget(#PB_Any, 10, 10, 480, 230)
w2Button1 = ButtonGadget(#PB_Any, 10, 250, 150, 40, "Button 1")
w2Button2 = ButtonGadget(#PB_Any, 340, 250, 150, 40, "Button 2")

; binds the close window events of each window to corresponding handlers
BindEvent(#PB_Event_CloseWindow, @closeWindowHandler1(), window1)
BindEvent(#PB_Event_CloseWindow, @closeWindowHandler2(), window2)

; binds all gadget events to commonGadgetHandler()
BindEvent(#PB_Event_Gadget, @commonGadgetHandler())

; bind only button1 from window1 to win1Button1Handler()
BindEvent(#PB_Event_Gadget, @win1Button1Handler(), window1, w1Button1)

; bind only the change-event-type of the editor from window2 to win2EditorHandler()
BindEvent(#PB_Event_Gadget, @win2EditorHandler(), window2, w2Editor, #PB_EventType_Change)

Repeat : WaitWindowEvent() : Until appQuit
User avatar
TI-994A
Addict
Addict
Posts: 2740
Joined: Sat Feb 19, 2011 3:47 am
Location: Singapore
Contact:

Re: PureBasic Event Binding: A Quick Tutorial

Post by TI-994A »

juror wrote: Mon Aug 25, 2025 9:34 pmClicking button 1 or 2 on main window in the main example results in a noncancelable loop. Maybe not a complete example?

Sorry, but I don't quite follow. :?
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
juror
Enthusiast
Enthusiast
Posts: 232
Joined: Mon Jul 09, 2007 4:47 pm
Location: Courthouse

Re: PureBasic Event Binding: A Quick Tutorial

Post by juror »

Guess that's the way it should work, since the editorgadget in that snippit is constantly receiving messages.
Randy Walker
Addict
Addict
Posts: 1058
Joined: Sun Jul 25, 2004 4:21 pm
Location: USoA

Re: PureBasic Event Binding: A Quick Tutorial

Post by Randy Walker »

Maybe you could explain why I don't get anything but "unspecified gadget event..." when I left or right click the window when I run code from your OP. In fact left click returns nothing until I right click. Tested in 5.40 and 6.20
- - - - - - - - - - - - - - - -
Randy
I *never* claimed to be a programmer.
juror
Enthusiast
Enthusiast
Posts: 232
Joined: Mon Jul 09, 2007 4:47 pm
Location: Courthouse

Re: PureBasic Event Binding: A Quick Tutorial

Post by juror »

I get an uninterruptible scrolling editorGadget in the "PureBasic BindEvent Function" window. The only way to interrupt the program is to kill it in the IDE. PB545,612, 620 and 621. Must just be me/my environment - you do need to cause an event. Click a button or in the editorGadget. Maybe there's an event it needs to handle or ignore, since it's a loop.

BTW I did not write the code in my OP it was copied from above, so I can not really explain anything about it, since I just ran the example. It is copied from the "Now, let's implement them altogether to see how they synergise: " post above. I will investigate further, however right now I am looking for a reliable event handler. So it's back to the "original" PB method, which works for me and I am familiar with. :wink:

It does not produce the behavior you mentioned when I run it. Guess I need to d/l a 64 bit PB and try that.
http://ublt.com/bindevent.jpg

edit - also continual loop in 611 LTS x64
User avatar
TI-994A
Addict
Addict
Posts: 2740
Joined: Sat Feb 19, 2011 3:47 am
Location: Singapore
Contact:

Re: PureBasic Event Binding: A Quick Tutorial

Post by TI-994A »

Randy Walker wrote: Wed Aug 27, 2025 2:45 am...I don't get anything but "unspecified gadget event..." when I left or right click the window when I run code from your OP. In fact left click returns nothing until I right click.

PureBasic supports mouse button events only on selected gadgets, which does not include windows, editors, or buttons. So, in the example, besides the button event or editor focus/change events, left and right mouse clicks should not trigger any events in the message queue.

In the example, the only gadgets that would trigger an "unspecified gadget" message, would be Button 1. :wink:
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