Page 3 of 3

Re: Child window message loop

Posted: Mon May 28, 2018 8:31 pm
by the.weavster
Blue wrote:
TI-994A wrote:[...]
Nevertheless, it should be noted that even windows from other processes would be able to dismiss this child window if they were to automatically pop-up or receive focus.
Oops ! That’s an unexpected curve. But now that you point it out, it does seem obvious.
I guess you could move the event handling from #PB_Event_DeactivateWindow to #PB_Event_ActivateWindow so when a window is activated it closes any other open windows you want rid of. You'll just have to cycle over the windows you want to display that behaviour, test each one with IsWindow() and if so close it.

Re: Child window message loop

Posted: Tue May 29, 2018 5:22 am
by TI-994A
Blue wrote:...try the latest updated code provided by the.weavster, but with the 2 modified procedures I submitted. Can you think of an independent process that would interfere unexpectedly with that child window ?
I've added a small routine that would trigger-launch NotePad five seconds after the child window is opened. Although there are no clicks whatsoever, the child window would be closed once NotePad launches and receives focus.

Code: Select all

Enumeration windows
  #frmMain
  #frmChild
EndEnumeration

Enumeration gadgets
  #frmMain_btnChild 
  #frmChild_btnOK
  #frmMain_txtClock
  #frmChild_txtClock
EndEnumeration

Enumeration timers
  #frmMain_tmrClock
  #frmChild_tmrClock
EndEnumeration

;; modified by Blue
Procedure frmChild_Open()
  If OpenWindow(#frmChild,#PB_Ignore,#PB_Ignore,250,150,"child",#PB_Window_SystemMenu)
    ButtonGadget(#frmChild_btnOK,10,10,150,30,"Message Box")                    ; <<< changed
    TextGadget(#frmChild_txtClock,20, 110, 210, 30,
               FormatDate("%hh:%ii", Date()), #PB_Text_Right)
    AddWindowTimer(#frmChild, #frmChild_tmrClock, 1000)
    ;#############################
    SetWindowData(#frmChild,#True) ; deactivating the window will close it      ; <<< changed
    ResizeWindow(#frmChild,WindowX(#frmMain)+80,WindowY(#frmMain)+80,#PB_Ignore,#PB_Ignore) ;so we can always see it
    SetWindowTitle(#frmChild,"Closes automatically")                            ; <<< changed
    ;#############################
    
    Global t = ElapsedMilliseconds()
    
  EndIf
EndProcedure

Procedure frmChild_onGadget(nGadget,nEventType,nX,nY)
  Select nGadget
    Case #frmChild_btnOK
      ;##############################
      SetWindowData(#frmChild,#False)
      SetWindowTitle(#frmChild,"Remains active")
      nResponse = MessageRequester("Message","something important... or not !",#PB_MessageRequester_Ok)
      SetWindowData(#frmChild,#True)
      SetWindowTitle(#frmChild,"Closes automatically")                          ; <<< changed
      ;##############################      
  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")
    TextGadget(#frmMain_txtClock,280, 20, 100, 30,
               FormatDate("%hh:%ii:%ss", Date()), #PB_Text_Right)
    AddWindowTimer(#frmMain, #frmMain_tmrClock, 1000)
  EndIf 
EndProcedure

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

Procedure Events_Timer()
  Static minuteUpdatedTime.s, c
  nTimer = EventTimer()
  Select nTimer
    Case #frmMain_tmrClock
      ;updates every second
      SetGadgetText(#frmMain_txtClock,
                    FormatDate("%hh:%ii:%ss", Date()))
    Case #frmChild_tmrClock
      ;updates every minute
      
      If ElapsedMilliseconds() - t > 1000
        SetGadgetText(#frmChild_txtClock, "I'm going bye-bye in " + Str(5 - c) + "...")
        t = 0 : c + 1
        If c > 5
          c = 0
          RunProgram("notepad.exe")  
        EndIf
      EndIf
            
      currentTime.s = FormatDate("%hh:%ii", Date())
      If minuteUpdatedTime <> currentTime
        minuteUpdatedTime = currentTime
        SetGadgetText(#frmChild_txtClock, currentTime)
      EndIf     
  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

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

Procedure Events_DeactivateWindow()
  nWin = EventWindow()
  Select nWin     
    Case #frmChild ; list all windows you want this behaviour for
      If GetWindowData(nWin) = #True
        CloseWindow(nWin)
      EndIf
  EndSelect   
EndProcedure
;###############################

BindEvent(#PB_Event_Gadget,@Events_Gadget())
BindEvent(#PB_Event_Timer,@Events_Timer())
BindEvent(#PB_Event_CloseWindow,@Events_CloseWindow())
;###############################
;point this to the new handler
BindEvent(#PB_Event_DeactivateWindow,@Events_DeactivateWindow())
;###############################

frmMain_Open()
Repeat
  nWait = WaitWindowEvent()
ForEver
I believe that there are no native contingencies for that.

Re: Child window message loop

Posted: Tue May 29, 2018 5:37 am
by TI-994A
the.weavster wrote:
Fred wrote:When starting with PB, it's probably a very good advice to use only one event loop.
To my mind there is only one event loop in my code. Taking gadget events as an example, Events_Gadget() is the only callback bound to gadget events and that simply routes to other procedures based on the associated window id, gadget id and event type. Would you agree with that, Fred?
I don't believe that he's referring to your example. IMHO, the object-based callbacks are very clean and structured. It's a good model. :)

Re: Child window message loop

Posted: Tue May 29, 2018 5:42 am
by TI-994A
User_Russian wrote:...If you call BindEvent several times with identical parameters, a memory leak occurs.

Code: Select all

Procedure Child()
  If OpenWindow(1, #PB_Ignore, #PB_Ignore, 200, 150, "child", #PB_Window_SystemMenu, WindowID(0))
    BindEvent(#PB_Event_CloseWindow, @Child_Event(), 1)
    For i=1 To 1000000
      BindEvent(#PB_Event_CloseWindow, @Child_Event(), 1)
    Next i
  EndIf
EndProcedure
You're right; neither the UnbindEvent() nor the CloseWindow() functions seem to release the memory captured by the BindEvent() function. The memory footprint simply keeps growing with every call.

Re: Child window message loop

Posted: Tue May 29, 2018 4:27 pm
by Blue
TI-994A wrote:...
I've added a small routine that would trigger-launch NotePad five seconds after the child window is opened. Although there are no clicks whatsoever, the child window would be closed once NotePad launches and receives focus.
...
Clever demonstration, TI-994A. Much appreciated.


@ the.weavster
As TI-994A pointed out, your way of coding is very good. It uses a single events loops, which is obvious just from reading the code.
Fred's comment could only be directed at my admission that I keep using dedicated events loops for my child windows.

Re: Child window message loop

Posted: Tue May 29, 2018 5:01 pm
by Blue
TI-994A wrote:
User_Russian wrote:...If you call BindEvent several times with identical parameters, a memory leak occurs.
[...]
You're right; neither the UnbindEvent() nor the CloseWindow() functions seem to release the memory captured by the BindEvent() function. The memory footprint simply keeps growing with every call.
@ User_Russian :
In light of TI-994A's confirmation of your observation concerning the memory leak, you really should post that as a bug, regardless of what Fred had to say about it.

Re: Child window message loop

Posted: Tue May 29, 2018 7:23 pm
by DontTalkToMe
Blue wrote: In light of TI-994A's confirmation of your observation concerning the memory leak, you really should post that as a bug, regardless of what Fred had to say about it.
I don't think there is a memory leak, or at least I don't think what shown in the task manager can be used as supporting evidence.
When you free resources, this is not necessary reflected in the task manager in real time.
Some pages of memory results as allocated in the task manager until the memory manager trims pages from the working set to create more available memory. It does that when it thinks it's the right moment to do so.
If you manually force the trim of those pages using the appropriate API call, the task manager reflects that immediately and there is no longer an apparent memory leak.

Re: Child window message loop

Posted: Wed May 30, 2018 2:28 am
by TI-994A
DontTalkToMe wrote:... When you free resources, this is not necessary reflected in the task manager in real time.
Some pages of memory results as allocated in the task manager until the memory manager trims pages...
That might be right. Not sure if there are any utilities to force this cache-trimming, but simply starting up some gobblers, like PhotoShop, might also do the trick. The memory usage, as reported by Task Manager, shrinks exponentially.

Good info. Thank you. :)

Re: Child window message loop

Posted: Wed May 30, 2018 3:06 pm
by DontTalkToMe
TI, you can use EmptyWorkingSet to force the trimming of the pages not actually in use.
https://msdn.microsoft.com/it-it/library/windows/desktop/ms682606(v=vs.85).aspx

A tool you can use is VMMap.
https://docs.microsoft.com/en-us/sysinternals/downloads/vmmap
You can force the trimming using an option under Tools.

About the bind/unbind commands, they seem to work correctly when the same event is bound to different callbacks.
If you bind a single event to the same callback multiple times (why ?) and then try to unbind it, it does so only once, so n-1 bindings are still active.
Preferably this should not be permitted or at least treated appropriately, I think.

Re: Child window message loop

Posted: Wed May 30, 2018 3:13 pm
by User_Russian
This will move memory to swap file, but will not free.

Re: Child window message loop

Posted: Thu May 31, 2018 7:24 am
by Blue
Continuing on the subject of a child window closing when it loses focus
(running PB 5.62 (X64) on Windows 10 Pro x64)

I'm still using the.weavster's sample code, heavily modified by Ti-994A and some more by me. What follows is its latest incarnation, with the addition of a few debug statements to track what happens under certain circumstances :

Code: Select all


;- :: ************************************
;- ::    may 2018                               
;- ::    source : the.weavster  
;- ::    modified by TI-994A, Blue        
;- ::    child window 3                              
; https://www.purebasic.fr/english/viewtopic.php?p=522679#p522679
; demo : how to open an interactive child window WITHOUT using a secondary dedicated events loop
;- :: ************************************

EnableExplicit
; :: constantes            
Enumeration windows
  #main_WINDOW
  #child_WINDOW
EndEnumeration

Enumeration gadgets
  #main_btn 
  #main_clock
  #child_btn
  #child_clock
EndEnumeration

Enumeration timers
  #main_timer
  #child_timer
EndEnumeration
;.

Procedure Child_Open()
  If OpenWindow(#child_WINDOW,#PB_Ignore,#PB_Ignore,250,150,"child",#PB_Window_SystemMenu)
    ButtonGadget(#child_btn,10,10,150,30,"Message Box")
    TextGadget(#child_clock,20, 110, 210, 30,
               FormatDate("%hh:%ii", Date()), #PB_Text_Right)
    AddWindowTimer(#child_WINDOW, #child_timer, 1000)

    SetWindowData(#child_WINDOW,#True) ; signals that, when losing focus, the child window must close
    ResizeWindow(#child_WINDOW,WindowX(#main_WINDOW)+80,WindowY(#main_WINDOW)+80,#PB_Ignore,#PB_Ignore) ;so it remains visible
    SetWindowTitle(#child_WINDOW,"Closes spontaneously !")    ; show action in title 

    #timerTime = 20               ; timer duration 
    Global timer_t = #timerTime   ; timer countdown 
  EndIf
EndProcedure

Procedure Child_gadgets(gadget, eventType)
  Select gadget
    Case #child_btn
      ;##############################
      SetWindowData(#child_WINDOW,#False)   ; child window must NOT close when losing focus to message box
      SetWindowTitle(#child_WINDOW,"Remains active")
      MessageRequester("Message", "something real important... or not !",$40)
      ; reset the window to its auto-close state
      SetWindowData(#child_WINDOW,#True)    ; will close when losing focus
      SetWindowTitle(#child_WINDOW,"Closes spontaneously !")
      ;##############################      
  EndSelect
EndProcedure

; app's main window
Procedure Main_opened()
  If OpenWindow(#main_WINDOW,#PB_Ignore,#PB_Ignore,400,300,"Main window")
    ButtonGadget(#main_btn,10,10,150,30,"Show Child")
    TextGadget(#main_clock,280, 20, 100, 30,
               FormatDate("%hh:%ii:%ss", Date()), #PB_Text_Right)
    AddWindowTimer(#main_WINDOW, #main_timer, 1000)
    ProcedureReturn 1
  EndIf
  ProcedureReturn 0
EndProcedure

Procedure Main_gadgets(gadget, eventType)
  Select gadget
    Case #main_btn
      Child_Open()
  EndSelect
EndProcedure

;- ************************************
Procedure Events_Timer()
  Static currentMinute
  
  Select EventTimer()
    Case #main_timer
      ;updates every second
      SetGadgetText(#main_clock,
                    FormatDate("%hh:%ii:%ss", Date()))
    Case #child_timer
      ;updates every minute
      Define thisMinute = Minute(Date())
      If thisMinute <> currentMinute 
        currentMinute = thisMinute
        SetGadgetText(#child_clock, FormatDate("%hh:%ii", Date()))
      EndIf

      ; timer_t previously defined as global
      If timer_t <= 10
        SetGadgetText(#child_clock, "NotePad opens in " + timer_t + "...")
      EndIf
      timer_t - 1
      Debug "timer_t: "+ timer_t
      If timer_t = 0
        RunProgram("notepad.exe")
        timer_t = #timerTime                                        ; reset timer countdown to original value
        SetGadgetText(#child_clock, FormatDate("%hh:%ii", Date()))  ; re-display the clock
      EndIf
  EndSelect 
EndProcedure
Procedure Events_Gadget()
  Define gadget, eventType
  gadget = EventGadget()
  eventType = EventType()
  Select EventWindow()
    Case #main_WINDOW
      Main_gadgets(gadget,eventType)
    Case #child_WINDOW
      Child_gadgets(gadget,eventType)
  EndSelect
EndProcedure

Procedure Events_window_closed()
  ; closing main window quits app
  Debug "closing " + EventWindow() 
  If #main_WINDOW = EventWindow()
    Debug "  >>> " + #main_WINDOW + " is #main_WINDOW"
    Debug "all done !" 
    End
    Debug "never displayed ???" 
  EndIf
  
  Debug "  >>> " + #child_WINDOW + " is #child_WINDOW"
  CloseWindow(#child_WINDOW)
EndProcedure

Procedure Events_window_deactivated()
  Protected window = EventWindow()
  Select window
    Case #child_WINDOW
      If GetWindowData(window) = #True
        CloseWindow(window)

        ;SetActiveWindow(#main_WINDOW)       ; nothing happens !
        Debug "  >>> has #main_WINDOW changed to activated appearance ???"
      EndIf
  EndSelect
EndProcedure

Procedure Events_window_activated()
  Protected window = EventWindow()
  Debug "ActivateWindow " + EventWindow() 
  Debug "  >>> has window changed to activated appearance ???"
EndProcedure

BindEvent(#PB_Event_Gadget,@Events_gadget())
BindEvent(#PB_Event_Timer,@Events_timer())
BindEvent(#PB_Event_CloseWindow,@Events_window_closed())

BindEvent(#PB_Event_ActivateWindow,@Events_window_activated())
BindEvent(#PB_Event_DeactivateWindow,@Events_window_deactivated())

If Main_opened()
  Repeat
    WaitWindowEvent()
  ForEver
EndIf

End
The code works really well, but something is missing :
Launch the app, minimize PB's IDE, just leaving the debug output window opened. When you select PB's debug output window, it takes on its activated look, while the demo changes to its deactivated look. When you return to the demo, the reverse happens : its window takes on its activated look, while the debug window looks deactivated. All normal. That's the behaviour you expect and it happens correctly with any opened window on the desktop, and with the desktop itself.

However, If you now open a child window in the demo app, and then click into the parent window, the child window closes (as designed by code), but the parent window does not take on its activated look ! Furthermore, at line 144 of the code, the instruction SetActiveWindow(#main_WINDOW), intended to provoke a change of appearance, produces nothing; the debug statements also clearly show that the #PB_Event_ActivateWindow event never fires, even though it should (I think...). The parent window is expected to change its appearance to its activated colors.

Is this a PB bug ?

Re: Child window message loop

Posted: Thu May 31, 2018 9:36 am
by TI-994A
Blue wrote:... If you now open a child window in the demo app, and then click into the parent window, the child window closes (as designed by code), but the parent window does not take on its activated look !
Remove the deactivation callback altogether, and replace the activation procedure with this one:

Code: Select all

Procedure Events_window_deactivated()

EndProcedure

Procedure Events_window_activated()
  Debug "ActivateWindow " + EventWindow() 
  If EventWindow() = #main_WINDOW 
    If IsWindow(#child_WINDOW) And 
       GetWindowData(#child_WINDOW) = #True
      CloseWindow(#child_WINDOW)
    EndIf
  EndIf
EndProcedure

Re: Child window message loop

Posted: Thu May 31, 2018 1:58 pm
by Blue
TI-994A wrote:[...]
Remove the deactivation callback altogether, and replace the activation procedure with this one:

Code: Select all

[,,,]
Thanks, TI-994A; what you propose does the job, no question. But it is exactly, word for word, what I already had in place as a workaround. (well, you know the saying about great minds thinking alike etc. :D )
So that's not really the question here. I'm not looking for a workaround. I already have that.
The point i want to raise is two-fold :
(1) in the original demo code, PB should have generated a #PB_Event_ActivateWindow event when a click in the parent window returns focus to it . Put another way, shouldn't PB be doing, with the demo code as is, exactly what the workaround code does ?
(2) on line 144 of the demo code, the SetActiveWindow(#main_WINDOW) instruction produces nothing . I certainly was expecting that instruction to force the parent window to show its activated colors. Weren't you ?

That's what I mean when I write that something appears to be missing in PB's output code.
And since PB fails on both points, isn't that a bug ? I don't see how the current behaviour can be the correct one.

Re: Child window message loop

Posted: Sat Jun 02, 2018 12:39 pm
by mk-soft
I only ever have one event loop and
in which nothing more is programmed than to leave the program.
Each gadget gets its own event procedure and each window gets its own event procedures for each event.

Most of the time I use my EventDesigner to create all necessary event procedures.
But with the Runtime-Library I found a new interesting method to bind the event procedures automatically.

Here is the link to the RuntimeEventManager with complete GUI event diagnosis.
Link: viewtopic.php?f=12&t=70119

Translated with www.DeepL.com/Translator