Child window message loop

Just starting out? Need help? Post your questions and find answers here.
Cezary
Enthusiast
Enthusiast
Posts: 108
Joined: Sun Feb 12, 2017 2:31 pm

Child window message loop

Post by Cezary »

Dear users,

I have a problem with child window message loop:

Code: Select all

Procedure Child()
  
  If OpenWindow(1, #PB_Ignore, #PB_Ignore, 200, 150, "child", #PB_Window_SystemMenu, WindowID(0))
    
    Repeat
      Select WaitWindowEvent()
        Case #PB_Event_CloseWindow
          ChildQuit = #True
      EndSelect
    Until ChildQuit
  EndIf
  
EndProcedure

If OpenWindow(0, #PB_Ignore, #PB_Ignore, 400, 300, "parent")
  
  ButtonGadget(2, 100, 100, 100, 25, "click")
  
  Repeat
    Select WaitWindowEvent()
      Case #PB_Event_Gadget
        If EventGadget() = 2
          Child()
        EndIf
      Case #PB_Event_CloseWindow
        ParentQuit = #True
    EndSelect
  Until ParentQuit
EndIf
 
In this example closing child window closes parent too.
What is wrong?
Best regards
infratec
Always Here
Always Here
Posts: 7604
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Child window message loop

Post by infratec »

2 event loops are wrong.
infratec
Always Here
Always Here
Posts: 7604
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Child window message loop

Post by infratec »

Code: Select all

Procedure Child()
 
  If OpenWindow(1, #PB_Ignore, #PB_Ignore, 200, 150, "child", #PB_Window_SystemMenu, WindowID(0))
   
    
    
  EndIf
 
EndProcedure

If OpenWindow(0, #PB_Ignore, #PB_Ignore, 400, 300, "parent")
 
  ButtonGadget(2, 100, 100, 100, 25, "click")
 
  Repeat
    Event = WaitWindowEvent()
    Select EventWindow()
      Case 0
        Select Event
          Case #PB_Event_Gadget
            If EventGadget() = 2
              Child()
            EndIf
          Case #PB_Event_CloseWindow
            ParentQuit = #True
        EndSelect
      Case 1
        Select Event
            Case #PB_Event_CloseWindow
              CloseWindow(1)
      EndSelect
    EndSelect
  Until ParentQuit
EndIf
Cezary
Enthusiast
Enthusiast
Posts: 108
Joined: Sun Feb 12, 2017 2:31 pm

Re: Child window message loop

Post by Cezary »

infratec,
thank you very much.
My mistake is I've tried to do it in the same manner, as I did it in Win32 API.
Thanks
User avatar
TI-994A
Addict
Addict
Posts: 2740
Joined: Sat Feb 19, 2011 3:47 am
Location: Singapore
Contact:

Re: Child window message loop

Post by TI-994A »

Cezary wrote:...closing child window closes parent too.
With all due respect to Bernd, multiple event loops are not wrong, per se; just not recommended.

The reason is, when another loop overrides the main one, the events for the main application are not being processed. This poses an issue with thread and timer events, which would be suspended until the main event loop regains control.

This slightly modified version of your snippet works as expected, with each event loop handling their respective windows.

Code: Select all

Procedure Child()
  If OpenWindow(1, 550, 400, 200, 150, "child", #PB_Window_SystemMenu, WindowID(0))
    Repeat
      Select WaitWindowEvent()
        Case #PB_Event_CloseWindow
          If EventWindow() = 1
            ChildQuit = #True
            CloseWindow(1)
          EndIf
      EndSelect
    Until ChildQuit
  EndIf
EndProcedure

If OpenWindow(0, 300, 300, 300, 200, "parent")
  ButtonGadget(0, 100, 100, 100, 25, "click")
  TextGadget(1, 0, 40, 300, 50, "Clock Display", #PB_Text_Center)
  AddWindowTimer(0, 0, 1000)
  Repeat
    Select WaitWindowEvent()
      Case #PB_Event_Gadget
        If EventGadget() = 0
          Child()
        EndIf
      Case #PB_Event_Timer
        If EventTimer() = 0
          SetGadgetText(1, FormatDate("%hh:%ii:%ss", Date()))
        EndIf        
      Case #PB_Event_CloseWindow
        If EventWindow() = 0
          ParentQuit = #True
        EndIf
    EndSelect
  Until ParentQuit
EndIf
The two windows function correctly as parent and child, with the parent disabled when the child is active. However, notice how the clock display stops updating when the child window is opened, and resumes when it is closed.

It's easy to integrate all event processing into one main loop, but for those modular-critical models, this approach serves the purpose just as well.

The many flexibilities of PureBasic. :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
walbus
Addict
Addict
Posts: 929
Joined: Sat Mar 02, 2013 9:17 am

Re: Child window message loop

Post by walbus »

Yep, its so TI-994A say
The one bite not the other
User avatar
skywalk
Addict
Addict
Posts: 4215
Joined: Wed Dec 23, 2009 10:14 pm
Location: Boston, MA

Re: Child window message loop

Post by skywalk »

TI-994A wrote:The reason is, when another loop overrides the main one, the events for the main application are not being processed. This poses an issue with thread and timer events, which would be suspended until the main event loop regains control.
This is not entirely true. Yes, the paused window will appear dead, but any threads that were started from the main loop will continue to crank along. :wink:
The nice thing about standards is there are so many to choose from. ~ Andrew Tanenbaum
User avatar
the.weavster
Addict
Addict
Posts: 1577
Joined: Thu Jul 03, 2003 6:53 pm
Location: England

Re: Child window message loop

Post by the.weavster »

If you prefer the handlers on a window by window basis you can do something like this:

Code: Select all

Enumeration windows
  #frmMain
  #frmChild
EndEnumeration

Enumeration gadgets
  #frmMain_btnChild
  #frmChild_btnOK
EndEnumeration

; child window - definition and handlers could be in separate file(s)
Procedure frmChild_Open()
  If OpenWindow(#frmChild,#PB_Ignore,#PB_Ignore,200,150,"child",#PB_Window_SystemMenu)
    ButtonGadget(#frmChild_btnOK,10,10,150,30,"Show Message")
  EndIf
EndProcedure

Procedure frmChild_onGadget(nGadget,nEventType,nX,nY)
  Select nGadget
    Case #frmChild_btnOK
      MessageRequester("","Hello, World!",0)
  EndSelect
EndProcedure

; app's main window
Procedure frmMain_Open()
  If OpenWindow(#frmMain,#PB_Ignore,#PB_Ignore,400,300,"parent")
    ButtonGadget(#frmMain_btnChild,10,10,150,30,"Show Child")
  EndIf  
EndProcedure

Procedure frmMain_onGadget(nGadget,nEventType,nX,nY)
  Select nGadget
    Case #frmMain_btnChild
      frmChild_Open()
  EndSelect
EndProcedure

Procedure Events_Gadget()
  nGadget = EventGadget()
  nEventType = EventType()
  nX = 0 : nY = 0
  Select GadgetType(nGadget)
    Case #PB_GadgetType_Canvas
      nX = GetGadgetAttribute(nGadget,#PB_Canvas_MouseX)
      nY = GetGadgetAttribute(nGadget,#PB_Canvas_MouseY)
    Case #PB_GadgetType_ListIcon
      nY = GetGadgetState(nGadget)
    ; etc...
  EndSelect
  Select EventWindow()
    Case #frmMain
      frmMain_onGadget(nGadget,nEventType,nX,nY)
    Case #frmChild
      frmChild_onGadget(nGadget,nEventType,nX,nY)
  EndSelect
EndProcedure

Procedure Events_CloseWindow()
  nWin = EventWindow()
  Select nWin
    Case #frmMain
      End ; closing main window quits app
    Default
      CloseWindow(nWin)
  EndSelect  
EndProcedure

BindEvent(#PB_Event_Gadget,@Events_Gadget())
BindEvent(#PB_Event_CloseWindow,@Events_CloseWindow())

frmMain_Open()
Repeat
  nWait = WaitWindowEvent()
ForEver

User avatar
TI-994A
Addict
Addict
Posts: 2740
Joined: Sat Feb 19, 2011 3:47 am
Location: Singapore
Contact:

Re: Child window message loop

Post by TI-994A »

skywalk wrote:
TI-994A wrote:...the events for the main application are not being processed. This poses an issue with thread and timer events, which would be suspended until the main event loop regains control.
...threads that were started from the main loop will continue to crank along.
You've clearly misread. :lol:
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
skywalk
Addict
Addict
Posts: 4215
Joined: Wed Dec 23, 2009 10:14 pm
Location: Boston, MA

Re: Child window message loop

Post by skywalk »

TI-994A wrote:
skywalk wrote:
TI-994A wrote:...the events for the main application are not being processed. This poses an issue with thread and timer events, which would be suspended until the main event loop regains control.
...threads that were started from the main loop will continue to crank along.
You've clearly misread. :lol:
Sorry, I am still confused? Any threads started in the main will continue. Only the main event loop is disabled. You wrote that threads AND timers are affected.

Code: Select all

EnableExplicit
Global.i ParentQuit, ChildQuit, i
Procedure Do1(*thr1)
  Repeat
    i + 1
    Debug "Do1() - " + Str(i)
    Delay(500)
  Until ParentQuit
EndProcedure
Procedure Child()
  If OpenWindow(1, 550, 400, 200, 150, "child", #PB_Window_SystemMenu, WindowID(0))
    Repeat
      Select WaitWindowEvent()
      Case #PB_Event_CloseWindow
        If EventWindow() = 1
          ChildQuit = #True
          CloseWindow(1)
        EndIf
      EndSelect
    Until ChildQuit
  EndIf
EndProcedure
If OpenWindow(0, 300, 300, 300, 200, "parent")
  ButtonGadget(0, 100, 100, 100, 25, "click")
  TextGadget(1, 0, 40, 300, 50, "Clock Display", #PB_Text_Center)
  AddWindowTimer(0, 0, 1000)
  CreateThread(@Do1(), 0)
  Repeat
    Select WaitWindowEvent()
    Case #PB_Event_Gadget
      If EventGadget() = 0
        Child()
      EndIf
    Case #PB_Event_Timer
      If EventTimer() = 0
        Debug "evTimer(0) - " + Str(i)
        SetGadgetText(1, FormatDate("%hh:%ii:%ss", Date()))
      EndIf
    Case #PB_Event_CloseWindow
      If EventWindow() = 0
        ParentQuit = #True
      EndIf
    EndSelect
  Until ParentQuit
EndIf
The nice thing about standards is there are so many to choose from. ~ Andrew Tanenbaum
User avatar
TI-994A
Addict
Addict
Posts: 2740
Joined: Sat Feb 19, 2011 3:47 am
Location: Singapore
Contact:

Re: Child window message loop

Post by TI-994A »

skywalk wrote:...threads started in the main will continue. Only the main event loop is disabled. You wrote that threads AND timers are affected.
Actually, I wrote that thread and timer events would be suspended; not the threads and timers themselves. :D

In the example, the clock display stopped updating as the timer events weren't being processed when the second event loop took over. Similarly, any events posted by running threads would also not be processed.
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
Demivec
Addict
Addict
Posts: 4267
Joined: Mon Jul 25, 2005 3:51 pm
Location: Utah, USA

Re: Child window message loop

Post by Demivec »

TI-994A wrote:
skywalk wrote:...threads started in the main will continue. Only the main event loop is disabled. You wrote that threads AND timers are affected.
Actually, I wrote that thread and timer events would be suspended; not the threads and timers themselves. :D

In the example, the clock display stopped updating as the timer events weren't being processed when the second event loop took over. Similarly, any events posted by running threads would also not be processed.
Because there isn't a way to retrieve the events later wouldn't it be more accurate to say the events are lost or dropped instead of saying that the events are suspended?

Isn't it also true that if you handle events entirely by binding the events to procedures to handle them (as the.weavster did) then different event loops could also coexist because each event would be either dispatched or dropped every time it is retrieved according to your wishes?
User avatar
TI-994A
Addict
Addict
Posts: 2740
Joined: Sat Feb 19, 2011 3:47 am
Location: Singapore
Contact:

Re: Child window message loop

Post by TI-994A »

Demivec wrote:...wouldn't it be more accurate to say the events are lost or dropped instead of saying that the events are suspended?
Contextually speaking, no. Because the preceding statement already established that.
TI-994A wrote:...when another loop overrides the main one, the events for the main application are not being processed. This poses an issue with thread and timer events, which would be suspended until the main event loop regains control.
Here, the word suspended continues with the earlier-mentioned premise that such events are not being processed. And if events are not processed, they are implicitly lost or dropped.
Demivec wrote:...if you handle events entirely by binding the events to procedures to handle them (as the.weavster did) then different event loops could also coexist...
Of course, although that's beside the point. Another story for another day. :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
Cezary
Enthusiast
Enthusiast
Posts: 108
Joined: Sun Feb 12, 2017 2:31 pm

Re: Child window message loop

Post by Cezary »

Very interesting disscussion.
Thanks to all!
But regardless, I do not have a good idea how to achieve what I need.
The main purpose is to create a procedure with an input dialog (similar to the InputRequester), but with two separate input strings. The particular need is that StringGadgets must be subclassed to filter the input characters, because they are designed to enter floating point numbers. This is the reason to create a separate message pump loop, eclosed in a procedure that should return two strings (pointers to strings, given as procedure parameters). The main window uses timers that unfortunately stops when the described dialog box is active. Is there any solution without using global variables to achieve this? Maybe modeless dialog?
Best regards.
User avatar
TI-994A
Addict
Addict
Posts: 2740
Joined: Sat Feb 19, 2011 3:47 am
Location: Singapore
Contact:

Re: Child window message loop

Post by TI-994A »

Cezary wrote:...to create a procedure with an input dialog (similar to the InputRequester), but with two separate input strings. The particular need is that StringGadgets must be subclassed to filter the input characters, because they are designed to enter floating point numbers...
Perhaps something like this:

Code: Select all

;====================================================================
;   NumericInputBox() - custom input requester for decimal digits
;
;   cross-platform masking routines by Keya & Shardik - Thank you!
;   original posted code available in the following forum thread:
;
;====================================================================
; http://forums.purebasic.com/english/viewtopic.php?t=67986&p=503517
;====================================================================
;   
;   tested & working on Windows 10 running PureBasic v5.62 (x64)
;
;   by TI-994A - free to use, improve, share...
;
;   10th May 2018
;====================================================================


;==================
; masking routines 
;==================

CompilerIf #PB_Compiler_OS = #PB_OS_MacOS
  CompilerIf #PB_Compiler_Version < 470 Or (#PB_Compiler_Version >= 500 And Subsystem("Carbon"))
    ImportC ""
      GetControlData(ControlRef.L, ControlPartCode.L, TagName.L, BufferSize.L, *Buffer, *ActualSize)
      SetControlData(ControlRef.L, ControlPartCode.L, TagName.L, BufferSize.L, *Buffer)
    EndImport   
    #kControlEditTextPart = 5
    #kControlEditTextSelectionTag = $73656C65 ;'sele'   
    Structure ControlEditTextSelectionRec
      SelStart.W
      SelEnd.W
    EndStructure
  CompilerElse
    Global NSRangeZero.NSRange
    Procedure.i TextEditor(Gadget.i)
      Protected TextField.i = GadgetID(Gadget)
      Protected Window.i = CocoaMessage(0, TextField, "window")
      ProcedureReturn CocoaMessage(0, Window, "fieldEditor:", #YES, "forObject:", TextField)
    EndProcedure
  CompilerEndIf
CompilerEndIf

Procedure SetStringGadgetSelection(GadgetID.i, Start.i, Length.i=0)
  SetActiveGadget(gadgetid)
  CompilerSelect #PB_Compiler_OS
    CompilerCase #PB_OS_Windows
      SendMessage_(GadgetID(GadgetID), #EM_SETSEL, Start-1, Start-1)
    CompilerCase #PB_OS_Linux
      gtk_editable_select_region_(GadgetID(gadgetid), Start-1, Start-1)
    CompilerCase #PB_OS_MacOS
      CompilerIf #PB_Compiler_Version < 470 Or (#PB_Compiler_Version >= 500 And Subsystem("Carbon"))
        Protected TextSelection.ControlEditTextSelectionRec
        TextSelection\selStart = Start - 1
        TextSelection\selEnd = Start -1 
        SetControlData(GadgetID(GadgetID), #kControlEditTextPart, 
                       #kControlEditTextSelectionTag, SizeOf(ControlEditTextSelectionRec), @TextSelection)
      CompilerElse
        Protected Range.NSRange\location = Start - 1 : Range\length = 0
        CocoaMessage(0, TextEditor(GadgetID), "setSelectedRange:@", @Range)
      CompilerEndIf
  CompilerEndSelect
EndProcedure

Procedure.i GetCaretPosition(GadgetID.i)
  Protected Start.i = 0
  Protected Stop.i = 0
  CompilerSelect #PB_Compiler_OS
    CompilerCase #PB_OS_Windows
      SendMessage_(GadgetID(GadgetID.i), #EM_GETSEL, @Start.i, @Stop.i)
      ProcedureReturn Stop + 1
    CompilerCase #PB_OS_Linux
      Stop = gtk_editable_get_position_(GadgetID(gadgetid))
      ProcedureReturn Stop + 1
    CompilerCase #PB_OS_MacOS
      CompilerIf #PB_Compiler_Version < 470 Or (#PB_Compiler_Version >= 500 And Subsystem("Carbon"))
        Protected TextSelection.ControlEditTextSelectionRec
        GetControlData(GadgetID(GadgetID), #kControlEditTextPart, #kControlEditTextSelectionTag, 
                       SizeOf(ControlEditTextSelectionRec), @TextSelection.ControlEditTextSelectionRec, 0)
        ProcedureReturn TextSelection\End + 1
      CompilerElse
        Protected Range.NSRange
        CocoaMessage(@Range, TextEditor(GadgetID), "selectedRange")        
        ProcedureReturn Range\location + Range\length + 1
      CompilerEndIf
  CompilerEndSelect
EndProcedure

Procedure RestrictChars(*pchr.Ascii, slen)
  If slen
    *pout.Ascii = *pchr
    For i = 1 To slen
      Select *pchr\a
        Case '0' To '9'
          *pchr+SizeOf(Character): *pout+SizeOf(Character)
        Case '.':
          dotcnt+1
          If dotcnt=1
            *pchr+SizeOf(Character): *pout+SizeOf(Character)
          Else
            Goto BadChar
          EndIf
        Default:
          BadChar:
          *pchr+SizeOf(Character): changed=1
      EndSelect 
      *pout\a = *pchr\a
    Next
  EndIf
  ProcedureReturn changed
EndProcedure

Procedure StringFilter(gadget)
  Static Changing
  If Not Changing
    Changing=1
    sTxt$ = GetGadgetText(gadget)
    lastcaretpos = GetCaretPosition(gadget)
    If RestrictChars(@sTxt$, Len(sTxt$))
      SetGadgetText(gadget, sTxt$)
      SetStringGadgetSelection(gadget, lastcaretpos-1)
    EndIf
    Changing=0
  EndIf 
EndProcedure


;====================
; input box routines
;====================

Procedure InputBoxHandler()
  Select Event()
    Case #PB_Event_CloseWindow
      w = EventWindow()
      DisableWindow(GetWindowData(w), #False)
      CloseWindow(w)  
    Case #PB_Event_Gadget      
      g = EventGadget()      
      w = GetGadgetData(g)
      If GadgetType(g) = #PB_GadgetType_Button 
        DisableWindow(GetWindowData(w), #False)
        CloseWindow(w)          
      Else
        StringFilter(g)
        SetGadgetText(w, GetGadgetText(g))        
      EndIf      
  EndSelect  
EndProcedure

Procedure InputBox(parentWindow, parentInput1, parentInput2, x = 0, y = 0, width = 300, height = 160)    
  inputBoxWindow = OpenWindow(#PB_Any, x, y, width, height, "Numeric InputBox", 
                              #PB_Window_SystemMenu | #PB_Window_WindowCentered, 
                              WindowID(parentWindow))
  inputBoxLabel = TextGadget(#PB_Any, 50, 15, 200, 30, "Allowed chars: 0-9 and one '.'")
  inputBoxNumeric1 = StringGadget(#PB_Any, 50, 40, 200, 24, "")
  inputBoxNumeric2 = StringGadget(#PB_Any, 50, 70, 200, 24, "")
  inputBoxButton = ButtonGadget(#PB_Any, 50, 100, 200, 30, "OK")
  SetWindowData(inputBoxWindow, parentWindow)  
  SetGadgetData(inputBoxNumeric1, parentInput1)  
  SetGadgetData(inputBoxNumeric2, parentInput2)  
  SetGadgetData(inputBoxButton, inputBoxWindow)  
  BindGadgetEvent(inputBoxButton, @InputBoxHandler())
  BindGadgetEvent(inputBoxNumeric1, @InputBoxHandler(), #PB_EventType_Change)
  BindGadgetEvent(inputBoxNumeric2, @InputBoxHandler(), #PB_EventType_Change)
  BindEvent(#PB_Event_CloseWindow, @InputBoxHandler(), inputBoxWindow)  
  DisableWindow(parentWindow, #True)
EndProcedure


;===============
; usage example
;===============

tFlags = #PB_Text_Center | #PB_Text_Border
wFlags = #PB_Window_SystemMenu | #PB_Window_ScreenCentered
mainWindow = OpenWindow(#PB_Any, 0, 0, 400, 300, "Custom Numeric InputBox", wFlags)
clockDisplay = TextGadget(#PB_Any, 0, 20, 380, 20, "Clock Display", #PB_Text_Right)
numericValue1 = TextGadget(#PB_Any, 90, 70, 220, 30, "Float Value 1", tFlags)
numericValue2 = TextGadget(#PB_Any, 90, 110, 220, 30, "Float Value 2", tFlags)
popInputBox = ButtonGadget(#PB_Any, 90, 160, 220, 30, "INPUT REQUIRED VALUES")
SetGadgetColor(numericValue1, #PB_Gadget_BackColor, RGB(255, 150, 250))
SetGadgetColor(numericValue2, #PB_Gadget_BackColor, RGB(150, 250, 255))
AddWindowTimer(mainWindow, clock, 1000)

Repeat
  Select WaitWindowEvent()
      
    Case #PB_Event_CloseWindow
      Select EventWindow()
        Case mainWindow
          appQuit = 1          
      EndSelect
      
    Case #PB_Event_Timer
      Select EventTimer()
        Case clock
          SetGadgetText(clockDisplay, FormatDate("%hh:%ii:%ss", Date()))
      EndSelect  
      
    Case #PB_Event_Gadget
      Select EventGadget()  
        Case popInputBox
          InputBox(mainWindow, numericValue1, numericValue2)
      EndSelect
      
  EndSelect  
Until appQuit
The masking and input box routines are fully portable and could be extracted and included as separate .pbi files for use in any project. The input box routines could also work independently if the decimal-digit sub-classing is not required. The code is easily modifiable to accommodate any number of inputs, as required.

To invoke the custom input requester, simply call its function as follows:

Code: Select all

InputBox(#parentWindow, #value1placeHolder, #value2placeHolder)
The input values would automatically be inserted into the respective placeholders.

There's a customisable version of this code posted in the Tricks 'n' Tips section, which accepts multiple text and decimal-digit inputs.
> InputRequester() with Multiple Inputs & Decimal-Digit Mask

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