Window in a window with #PB_Window_WindowCentered

Post bugreports for the Windows version here
d-david
New User
New User
Posts: 3
Joined: Fri Jun 30, 2023 9:27 am

Window in a window with #PB_Window_WindowCentered

Post by d-david »

Hello,
I've noticed that the commands OpenWindow () with the flag #PB_Window_WindowCentered and "ParentWindows" aren't always correct.

When a monitor has negative start coordinates, the second window opens at X = 0, rather than in the center.

Image

Here is the example code now:

Code: Select all

Procedure win2()
    If OpenWindow(1, 0, 0, 400, 400, "PureBasic Window2", #PB_Window_WindowCentered|#PB_Window_SystemMenu | #PB_Window_MinimizeGadget | #PB_Window_MaximizeGadget ,WindowID(0))
        Repeat
            Event = WaitWindowEvent()
            If Event = #PB_Event_CloseWindow
                Quit = 1
            EndIf
        Until Quit = 1
        CloseWindow(1)
    EndIf
EndProcedure

If OpenWindow(0, 100, 200, 800, 600, "PureBasic Window", #PB_Window_SystemMenu | #PB_Window_MinimizeGadget | #PB_Window_MaximizeGadget)
    idwin = ButtonGadget(0,10,10,200,200,"Windows")
    Repeat
        Event = WaitWindowEvent()
        If Event = #PB_Event_Gadget
            Select EventGadget()
                Case 0 
                    win2()
            EndSelect
        ElseIf Event = #PB_Event_CloseWindow
            Quit = 1
        EndIf
    Until Quit = 1
EndIf
End
Thanks. And apologies if it's somewhere in the forums already.
infratec
Always Here
Always Here
Posts: 7613
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Window in a window with #PB_Window_WindowCentered

Post by infratec »

Sorry, I can not test your code, but ...

in your future programs, use only one event loop and not more.
Else you will run into trouble. Trust me.
d-david
New User
New User
Posts: 3
Joined: Fri Jun 30, 2023 9:27 am

Re: Window in a window with #PB_Window_WindowCentered

Post by d-david »

infratec wrote: Thu May 23, 2024 6:09 pm Sorry, I can not test your code, but ...

in your future programs, use only one event loop and not more.
Else you will run into trouble. Trust me.
Hello, you obviously have rights.
To demonstrate the #PB_Window_WindowCentered issues, I have only copied and slightly modified a little Windows example from Purebasic Handbook.

My primary monitor is positioned on the right, and the left monitor has a negative X-coordinate in accordance. In this way, I realized the discrepancy. In addition, I have reviewed PB - Versions 6.02. There was the same phenomenon.
Axolotl
Addict
Addict
Posts: 832
Joined: Wed Dec 31, 2008 3:36 pm

Re: Window in a window with #PB_Window_WindowCentered

Post by Axolotl »

Well, I can't help you with your multi-monitor environment, but this is what I've always used.....
I have created a small example here. Move the windows around and you'll get an idea of the function.
I'm curious if this works with multiple monitors as well.

Code: Select all

;/
;| Example of options for moving and resizing windows with Win API 
;| proudly presented by Axolotl 
;\ 

;-
;----== Low Level Procedures ==----------------------------------------------------------------------------------------

Procedure min(a, b)    ; .. calculates the minimum of two numbers 
  If a < b             ; returns:  
    ProcedureReturn a  ;   the smaller of the two numbers
  EndIf 
  ProcedureReturn b 
EndProcedure 

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

Procedure max(a, b)    ; .. calculates the maximum of two numbers 
  If a > b             ; returns:
    ProcedureReturn a  ;   the larger of the two parameter values 
  EndIf 
  ProcedureReturn b 
EndProcedure 

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

Procedure constrain(x, a, b)   ; .. constrains a number 'x' to be within a range 'a' to 'b'
  If x < a                     ; returns 
    ProcedureReturn a          ;   x: if x is between a and b
  Else                         ;   a: if x is less than a
    If x > b                   ;   b: If x is greater than b 
      ProcedureReturn b 
    EndIf 
  EndIf 
  ProcedureReturn x 
EndProcedure 


Procedure.l GetSystemMetrics(Index)   ; .l is important (works in 64-bit) .. variable can be a .i type, index == i.e. #SM_XVIRTUALSCREEN 
  ProcedureReturn GetSystemMetrics_(Index)  ; return depends on Index, i.e. #SM_XVIRTUALSCREEN 
EndProcedure 

Macro VirtualScreenX() 
  GetSystemMetrics(#SM_XVIRTUALSCREEN) 
EndMacro 

Macro VirtualScreenY() 
  GetSystemMetrics(#SM_YVIRTUALSCREEN) 
EndMacro 

Macro VirtualScreenWidth() 
  GetSystemMetrics(#SM_CXVIRTUALSCREEN) 
EndMacro 

Macro VirtualScreenHeight() 
  GetSystemMetrics(#SM_CYVIRTUALSCREEN) 
EndMacro 

Macro CountMonitors() 
  GetSystemMetrics(#SM_CMONITORS) 
EndMacro 

Macro SameDisplayFormat() 
  GetSystemMetrics(#SM_SAMEDISPLAYFORMAT) 
EndMacro 

Macro MonitorAsString()  ; returns <Number of Monitors> _ <entire width> x <entire height>  i.e. '1_1920x1080' 
  Str(GetSystemMetrics(#SM_CMONITORS)) + "_" + Str(GetSystemMetrics(#SM_CXVIRTUALSCREEN)) + "x" + Str(GetSystemMetrics(#SM_CYVIRTUALSCREEN)) 
EndMacro 


; 
;- MSDN: ClipOrCenterWindow example adapted to PureBasic :) 
; 

#MONITOR_CLIP          = $0000         ; clip rect to monitor 
#MONITOR_CENTER        = $0001         ; center rect to monitor 
#MONITOR_AREA          = $0000         ; use monitor entire area 
#MONITOR_WORKAREA      = $0002         ; use monitor work area 


Procedure SetRectToMonitor(*rc.RECT, Flags = #MONITOR_AREA | #MONITOR_CLIP)  ; returns nothing .. *rc contains the modified rectangle 
  ; 
  ; Note, do not assume that the RECT is based on the origin (0,0).
  ; 
  ; The most common problem apps have when running on a multimonitor system is that they "clip" or "pin" windows 
  ; based on the SM_CXSCREEN and SM_CYSCREEN system metrics. 
  ; Because of app compatibility reasons these system metrics return the size of the primary monitor. 
  ; This shows how you use the multi-monitor functions to do the same thing. 
  ; 
  Protected w, h, hMonitor, mi.MONITORINFO, rcMon.RECT                         :Debug #LF$+#PB_Compiler_Procedure+"()", 9  

  w = *rc\right  - *rc\left   ; calc w, h based on *rc 
  h = *rc\bottom - *rc\top    ; 

  hMonitor = MonitorFromRect_(*rc, #MONITOR_DEFAULTTONEAREST)         ; find monitor, at least the primary monitor is returned 

  mi\cbSize = SizeOf(mi)                                              ; get the work area or entire monitor rect, depends on Flags 
  If GetMonitorInfo_(hMonitor, @mi)                                   ; fill monitorinfo structure 

    If (Flags & #MONITOR_WORKAREA)
      rcMon = mi\rcWork     ; workarea rectangle without the Taskbar 
    Else 
      rcMon = mi\rcMonitor  ; the entire monitor rectangle 
    EndIf 

    ;' center or clip the passed rect to the monitor rectangle  
    If (Flags & #MONITOR_CENTER) 
      *rc\left   = rcMon\left + (rcMon\right  - rcMon\left - w) / 2 
      *rc\top    = rcMon\top  + (rcMon\bottom - rcMon\top  - h) / 2 
      *rc\right  = *rc\left + w 
      *rc\bottom = *rc\top  + h 
    Else
      *rc\left   = max(rcMon\left, min(rcMon\right - w,  *rc\left)) 
      *rc\top    = max(rcMon\top,  min(rcMon\bottom - h, *rc\top))  
      *rc\right  = *rc\left + w 
      *rc\bottom = *rc\top  + h 
    EndIf 
  EndIf 
EndProcedure 


Procedure SetWindowPosition(hWnd, hwndParent = 0)  ; move hWnd inside nearest monitor (centered to hwndParent if <> 0) 
  Protected w, h, rcClient.RECT, rcParent.RECT 

  If IsWindow_(hWnd) And GetWindowRect_(hWnd, @rcClient)  ; client window rect, keep the width and hight 
    w = rcClient\right  - rcClient\left 
    h = rcClient\bottom - rcClient\top 

    If hWndParent <> 0                                    ; use the valid parent window and center client window 
      GetWindowRect_(hwndParent, @rcParent)  
      rcClient\left = (((rcParent\right - rcParent\left) - w) / 2) + rcParent\left 
      rcClient\top  = (((rcParent\bottom - rcParent\top) - h) / 2) + rcParent\top 
      rcClient\right  = rcClient\left + w  
      rcClient\bottom = rcClient\top  + h 
    EndIf ; hWndParent <> 0 

    SetRectToMonitor(@rcClient, #MONITOR_AREA | #MONITOR_CLIP)  ; rcClient is moved inside the nearest monitor 
    SetWindowPos_(hWnd, #Null, rcClient\left, rcClient\top, 0, 0, #SWP_NOSIZE | #SWP_NOZORDER | #SWP_NOACTIVATE)  ; move the window to rcClient x, y coordinates 
  EndIf 
EndProcedure 


Procedure EnsureWindowOnMonitor(Window, Flags = #MONITOR_AREA | #MONITOR_CLIP)  ; returns nothing 
  Protected hwnd, rc.RECT 

  hwnd = WindowID(Window) 
  If GetWindowRect_(hwnd, @rc) 
    SetRectToMonitor(@rc, Flags) 
    SetWindowPos_(hwnd, #Null, rc\left, rc\top, 0, 0, #SWP_NOSIZE|#SWP_NOZORDER|#SWP_NOACTIVATE)  ; move the window to rc x, y coordinates 
  EndIf 
EndProcedure 


CompilerIf #PB_Compiler_IsMainFile ; <<< Test Application >>> 

#WINDOW_Main  = 1   ; the easy way of programming 
#WINDOW_Child = 2   ; 


Procedure main() 
  Protected hwndMain, hwndChild

  If OpenWindow(#WINDOW_Main, 0, 0, 600, 400, "Main Window", #PB_Window_SystemMenu|#PB_Window_ScreenCentered|#PB_Window_SizeGadget) 
    ; return value of OpenWindow is hwndMain as well (but not documented) 
    hwndMain = WindowID(#WINDOW_Main)  ; this is the safe way 
  EndIf 

  If OpenWindow(#WINDOW_Child, 0, 0, 300, 200, "Child Window", #PB_Window_SystemMenu|#PB_Window_ScreenCentered|#PB_Window_SizeGadget, hwndMain) 
    ; return value of OpenWindow is hwndChild as well (but not documented) 
    hwndChild = WindowID(#WINDOW_Child)  ; this is the safe way 
  EndIf 

  Repeat 
    Select WaitWindowEvent() 
      Case #PB_Event_CloseWindow 
        Select EventWindow() 
          Case #WINDOW_Main 
            Break ; bye 

          Case #WINDOW_Child 
          ; SetActiveWindow(#WINDOW_Main) 
            Break ; bye, this time .... 

        EndSelect 

      Case #PB_Event_MoveWindow   ; after finishing moving the window 
        Select EventWindow() 
          Case #WINDOW_Main 
            EnsureWindowOnMonitor(#WINDOW_Main) 
            SetWindowPosition(hwndChild, hwndMain) 
          Case #WINDOW_Child 
            SetWindowPosition(hwndChild) 
        EndSelect 

      Case #PB_Event_SizeWindow   ; after finishing sizing the window 
        Select EventWindow() 
          Case #WINDOW_Main 
            EnsureWindowOnMonitor(#WINDOW_Main) 
            SetWindowPosition(hwndChild, hwndMain) 
          Case #WINDOW_Child 
            SetWindowPosition(hwndChild) 
        EndSelect 

    EndSelect 
  ForEver 
  ProcedureReturn 0   ; default return value on windows 
EndProcedure

End main() 

CompilerEndIf 
Just because it worked doesn't mean it works.
PureBasic 6.04 (x86) and <latest stable version and current alpha/beta> (x64) on Windows 11 Home. Now started with Linux (VM: Ubuntu 22.04).
d-david
New User
New User
Posts: 3
Joined: Fri Jun 30, 2023 9:27 am

Re: Window in a window with #PB_Window_WindowCentered

Post by d-david »

Axolotl wrote: Sat May 25, 2024 1:19 pm Well, I can't help you with your multi-monitor environment, but this is what I've always used.....
I have created a small example here. Move the windows around and you'll get an idea of the function.
I'm curious if this works with multiple monitors as well.

Code: Select all

;/
;| Example of options for moving and resizing windows with Win API 
;| proudly presented by Axolotl 
;\ 

;-
;----== Low Level Procedures ==----------------------------------------------------------------------------------------

Procedure min(a, b)    ; .. calculates the minimum of two numbers 
  If a < b             ; returns:  
    ProcedureReturn a  ;   the smaller of the two numbers
  EndIf 
  ProcedureReturn b 
EndProcedure 

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

Procedure max(a, b)    ; .. calculates the maximum of two numbers 
  If a > b             ; returns:
    ProcedureReturn a  ;   the larger of the two parameter values 
  EndIf 
  ProcedureReturn b 
EndProcedure 

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

Procedure constrain(x, a, b)   ; .. constrains a number 'x' to be within a range 'a' to 'b'
  If x < a                     ; returns 
    ProcedureReturn a          ;   x: if x is between a and b
  Else                         ;   a: if x is less than a
    If x > b                   ;   b: If x is greater than b 
      ProcedureReturn b 
    EndIf 
  EndIf 
  ProcedureReturn x 
EndProcedure 


Procedure.l GetSystemMetrics(Index)   ; .l is important (works in 64-bit) .. variable can be a .i type, index == i.e. #SM_XVIRTUALSCREEN 
  ProcedureReturn GetSystemMetrics_(Index)  ; return depends on Index, i.e. #SM_XVIRTUALSCREEN 
EndProcedure 

Macro VirtualScreenX() 
  GetSystemMetrics(#SM_XVIRTUALSCREEN) 
EndMacro 

Macro VirtualScreenY() 
  GetSystemMetrics(#SM_YVIRTUALSCREEN) 
EndMacro 

Macro VirtualScreenWidth() 
  GetSystemMetrics(#SM_CXVIRTUALSCREEN) 
EndMacro 

Macro VirtualScreenHeight() 
  GetSystemMetrics(#SM_CYVIRTUALSCREEN) 
EndMacro 

Macro CountMonitors() 
  GetSystemMetrics(#SM_CMONITORS) 
EndMacro 

Macro SameDisplayFormat() 
  GetSystemMetrics(#SM_SAMEDISPLAYFORMAT) 
EndMacro 

Macro MonitorAsString()  ; returns <Number of Monitors> _ <entire width> x <entire height>  i.e. '1_1920x1080' 
  Str(GetSystemMetrics(#SM_CMONITORS)) + "_" + Str(GetSystemMetrics(#SM_CXVIRTUALSCREEN)) + "x" + Str(GetSystemMetrics(#SM_CYVIRTUALSCREEN)) 
EndMacro 


; 
;- MSDN: ClipOrCenterWindow example adapted to PureBasic :) 
; 

#MONITOR_CLIP          = $0000         ; clip rect to monitor 
#MONITOR_CENTER        = $0001         ; center rect to monitor 
#MONITOR_AREA          = $0000         ; use monitor entire area 
#MONITOR_WORKAREA      = $0002         ; use monitor work area 


Procedure SetRectToMonitor(*rc.RECT, Flags = #MONITOR_AREA | #MONITOR_CLIP)  ; returns nothing .. *rc contains the modified rectangle 
  ; 
  ; Note, do not assume that the RECT is based on the origin (0,0).
  ; 
  ; The most common problem apps have when running on a multimonitor system is that they "clip" or "pin" windows 
  ; based on the SM_CXSCREEN and SM_CYSCREEN system metrics. 
  ; Because of app compatibility reasons these system metrics return the size of the primary monitor. 
  ; This shows how you use the multi-monitor functions to do the same thing. 
  ; 
  Protected w, h, hMonitor, mi.MONITORINFO, rcMon.RECT                         :Debug #LF$+#PB_Compiler_Procedure+"()", 9  

  w = *rc\right  - *rc\left   ; calc w, h based on *rc 
  h = *rc\bottom - *rc\top    ; 

  hMonitor = MonitorFromRect_(*rc, #MONITOR_DEFAULTTONEAREST)         ; find monitor, at least the primary monitor is returned 

  mi\cbSize = SizeOf(mi)                                              ; get the work area or entire monitor rect, depends on Flags 
  If GetMonitorInfo_(hMonitor, @mi)                                   ; fill monitorinfo structure 

    If (Flags & #MONITOR_WORKAREA)
      rcMon = mi\rcWork     ; workarea rectangle without the Taskbar 
    Else 
      rcMon = mi\rcMonitor  ; the entire monitor rectangle 
    EndIf 

    ;' center or clip the passed rect to the monitor rectangle  
    If (Flags & #MONITOR_CENTER) 
      *rc\left   = rcMon\left + (rcMon\right  - rcMon\left - w) / 2 
      *rc\top    = rcMon\top  + (rcMon\bottom - rcMon\top  - h) / 2 
      *rc\right  = *rc\left + w 
      *rc\bottom = *rc\top  + h 
    Else
      *rc\left   = max(rcMon\left, min(rcMon\right - w,  *rc\left)) 
      *rc\top    = max(rcMon\top,  min(rcMon\bottom - h, *rc\top))  
      *rc\right  = *rc\left + w 
      *rc\bottom = *rc\top  + h 
    EndIf 
  EndIf 
EndProcedure 


Procedure SetWindowPosition(hWnd, hwndParent = 0)  ; move hWnd inside nearest monitor (centered to hwndParent if <> 0) 
  Protected w, h, rcClient.RECT, rcParent.RECT 

  If IsWindow_(hWnd) And GetWindowRect_(hWnd, @rcClient)  ; client window rect, keep the width and hight 
    w = rcClient\right  - rcClient\left 
    h = rcClient\bottom - rcClient\top 

    If hWndParent <> 0                                    ; use the valid parent window and center client window 
      GetWindowRect_(hwndParent, @rcParent)  
      rcClient\left = (((rcParent\right - rcParent\left) - w) / 2) + rcParent\left 
      rcClient\top  = (((rcParent\bottom - rcParent\top) - h) / 2) + rcParent\top 
      rcClient\right  = rcClient\left + w  
      rcClient\bottom = rcClient\top  + h 
    EndIf ; hWndParent <> 0 

    SetRectToMonitor(@rcClient, #MONITOR_AREA | #MONITOR_CLIP)  ; rcClient is moved inside the nearest monitor 
    SetWindowPos_(hWnd, #Null, rcClient\left, rcClient\top, 0, 0, #SWP_NOSIZE | #SWP_NOZORDER | #SWP_NOACTIVATE)  ; move the window to rcClient x, y coordinates 
  EndIf 
EndProcedure 


Procedure EnsureWindowOnMonitor(Window, Flags = #MONITOR_AREA | #MONITOR_CLIP)  ; returns nothing 
  Protected hwnd, rc.RECT 

  hwnd = WindowID(Window) 
  If GetWindowRect_(hwnd, @rc) 
    SetRectToMonitor(@rc, Flags) 
    SetWindowPos_(hwnd, #Null, rc\left, rc\top, 0, 0, #SWP_NOSIZE|#SWP_NOZORDER|#SWP_NOACTIVATE)  ; move the window to rc x, y coordinates 
  EndIf 
EndProcedure 


CompilerIf #PB_Compiler_IsMainFile ; <<< Test Application >>> 

#WINDOW_Main  = 1   ; the easy way of programming 
#WINDOW_Child = 2   ; 


Procedure main() 
  Protected hwndMain, hwndChild

  If OpenWindow(#WINDOW_Main, 0, 0, 600, 400, "Main Window", #PB_Window_SystemMenu|#PB_Window_ScreenCentered|#PB_Window_SizeGadget) 
    ; return value of OpenWindow is hwndMain as well (but not documented) 
    hwndMain = WindowID(#WINDOW_Main)  ; this is the safe way 
  EndIf 

  If OpenWindow(#WINDOW_Child, 0, 0, 300, 200, "Child Window", #PB_Window_SystemMenu|#PB_Window_ScreenCentered|#PB_Window_SizeGadget, hwndMain) 
    ; return value of OpenWindow is hwndChild as well (but not documented) 
    hwndChild = WindowID(#WINDOW_Child)  ; this is the safe way 
  EndIf 

  Repeat 
    Select WaitWindowEvent() 
      Case #PB_Event_CloseWindow 
        Select EventWindow() 
          Case #WINDOW_Main 
            Break ; bye 

          Case #WINDOW_Child 
          ; SetActiveWindow(#WINDOW_Main) 
            Break ; bye, this time .... 

        EndSelect 

      Case #PB_Event_MoveWindow   ; after finishing moving the window 
        Select EventWindow() 
          Case #WINDOW_Main 
            EnsureWindowOnMonitor(#WINDOW_Main) 
            SetWindowPosition(hwndChild, hwndMain) 
          Case #WINDOW_Child 
            SetWindowPosition(hwndChild) 
        EndSelect 

      Case #PB_Event_SizeWindow   ; after finishing sizing the window 
        Select EventWindow() 
          Case #WINDOW_Main 
            EnsureWindowOnMonitor(#WINDOW_Main) 
            SetWindowPosition(hwndChild, hwndMain) 
          Case #WINDOW_Child 
            SetWindowPosition(hwndChild) 
        EndSelect 

    EndSelect 
  ForEver 
  ProcedureReturn 0   ; default return value on windows 
EndProcedure

End main() 

CompilerEndIf 
Many thanks for your example.
It works just as it should. The child windows are always placed in the center of the window. Unfortunately, PureBasic's standard command #PB_Window_WindowCentered is incorrect.
Post Reply