Keep Windows Size & Position Between Run (Win)

Share your advanced PureBasic knowledge/code with the community.
User avatar
ChrisR
Addict
Addict
Posts: 1466
Joined: Sun Jan 08, 2017 10:27 pm
Location: France

Keep Windows Size & Position Between Run (Win)

Post by ChrisR »

A module to keep Windows position, size and state (Normal, Minimize, Maximize) between Run with multi-screen support.
Based on MS: Positioning Objects on a Multiple Display Setup
It's not new, there are probably other codes around...

Code: Select all

;-Top
; -----------------------------------------------------------------------------
;             Title: Keep Window position, size and state (Normal, Minimize, Maximize) between run
;       Description: Save the Window position, size and state (Normal, Minimize, Maximize) on closing. To then initialize the window size and position
;                    on the same monitor, at the same size, state and position (optionally, clip to the edge, default), the next time the program run.
;          Based on: https://learn.microsoft.com/en-us/windows/win32/gdi/positioning-objects-on-a-multiple-display-setup 
;       Source Name: KeepWindowSize.pbi
;            Author: ChrisR
;     Creation Date: 2024-04-05
; Modification Date: 2024-10-15 - Use GetWindowPlacement to save Window position & size
;           Version: 1.1.0
;        PB-Version: 6.0 or other
;                OS: Windows Only
;             Forum: https://www.purebasic.fr/english/viewtopic.php?t=83983
; -----------------------------------------------------------------------------
;
; (*) How tu use:
;     Add: XIncludeFile "KeepWindowSize.pbi"
;     And optionaly UseModule KeepWindowSize
; Then use the 2 Functions:
;     InitWindowSize(#Window[, Flag]) ; Optional Flag parameter: #MONITOR_CENTER | #MONITOR_CLIP | #MONITOR_WORKAREA | #MONITOR_AREA | #MONITOR_NOMOVE
;       - Call it after opening the window to initialize its size and position with the previous values saved in the function below.
;     SaveWindowSize(#Window)
;       - Call it when you close the window to save its size and position. Note that saved values are in pixel, without scaling.
;       - The name of the preference file saved is the same as the executable file name without extension +".pref", next to it, in the same path.
;         And the section name (group) is "Size_Window_" + Str(#Window), if a Window constant is used (for multi-window support), else for variables only "Size_Window"
;
; -----------------------------------------------------------------------------

CompilerIf (#PB_Compiler_IsMainFile)
  EnableExplicit
CompilerEndIf
;
;------------------------------------------------------------------------------
;- Declare Module KeepWindowSize
;------------------------------------------------------------------------------
;
DeclareModule KeepWindowSize
  
  #MONITOR_CENTER   = 1        ; Center window to monitor 
  #MONITOR_CLIP     = 0        ; Clip window to monitor 
  #MONITOR_WORKAREA = 2        ; Use monitor work area (without the taskbar)
  #MONITOR_AREA     = 0        ; Use monitor entire area
  #MONITOR_NOMOVE   = 4        ; Keep saved window position
  
  Declare InitWindowSize(Window, Flags = #MONITOR_CLIP | #MONITOR_WORKAREA)
  Declare SaveWindowSize(Window)
  
EndDeclareModule
;
;------------------------------------------------------------------------------
;- Module KeepWindowSize
;------------------------------------------------------------------------------
;
Module KeepWindowSize
  EnableExplicit
  
  Structure MONITORINFOEX2 Align #PB_Structure_AlignC
    cbSize.l
    rcMonitor.RECT
    rcWork.RECT
    dwFlags.l
    szDevice.c[#CCHDEVICENAME]
  EndStructure
  
  Declare Min(ValueA, ValueB)
  Declare Max(ValueA, ValueB)
  
  Procedure Min(ValueA, ValueB)
    If ValueA < ValueB
      ProcedureReturn ValueA
    EndIf
    ProcedureReturn ValueB
  EndProcedure
  
  Procedure Max(ValueA, ValueB)
    If ValueA > ValueB
      ProcedureReturn ValueA
    EndIf
    ProcedureReturn ValueB
  EndProcedure
  
  Procedure InitWindowSize(Window, Flags = #MONITOR_CLIP | #MONITOR_WORKAREA)
    Protected PrefFile.s = GetPathPart(ProgramFilename()) + GetFilePart(ProgramFilename(), #PB_FileSystem_NoExtension) + ".pref"
    Protected X, Y, Width, Height, State
    Protected GroupName.s, hMonitor, WinRect.RECT, MonitorRect.RECT, Mi.MONITORINFOEX2
    
    If OpenPreferences(PrefFile, #PB_Preference_GroupSeparator)
      If Window < 10000   ; Use the constante Window number in the Group section name to differentiate them in multi-windows mode
        GroupName = "Size_Window_" + Str(Window)
      Else
        GroupName = "Size_Window"
      EndIf
      If PreferenceGroup(GroupName)
        X      = ReadPreferenceLong("X",      0)
        Y      = ReadPreferenceLong("Y",      0)
        Width  = ReadPreferenceLong("Width",  0)
        Height = ReadPreferenceLong("Height", 0)
        State  = ReadPreferenceLong("State",  #PB_Window_Normal)
        ClosePreferences()
        
        ;Debug "Window pref . . : " +#TAB$+ X + "," + Y + "," + Width + "," + Height
        SetWindowPos_(WindowID(Window), #Null, X, Y, Width, Height, #SWP_NOZORDER | #SWP_NOACTIVATE)
        
        If Not (Flags & #MONITOR_NOMOVE)
          GetWindowRect_(WindowID(Window), @WinRect)
          ; Correct width and height are below the minimum size for any reason, X and Y do not change: X = WinRect\left : Y = WinRect\top
          Width  = WinRect\right  - WinRect\left
          Height = WinRect\bottom - WinRect\top
          
          hMonitor = MonitorFromRect_(WinRect, #MONITOR_DEFAULTTONEAREST)
          If hMonitor
            Mi\cbSize = SizeOf(Mi)
            If GetMonitorInfo_(hMonitor, @Mi)
              If Flags & #MONITOR_WORKAREA
                MonitorRect = Mi\rcWork
              Else
                MonitorRect = Mi\rcMonitor
              EndIf
              ;Debug "Monitor nearest : " +#TAB$+ MonitorRect\left + "," + MonitorRect\top + "," + Str(MonitorRect\right-MonitorRect\left) + "," + Str(MonitorRect\bottom-MonitorRect\top)
              ; Center or Clip the window rect to the monitor rect 
              If Flags & #MONITOR_CENTER
                X = MonitorRect\left + (MonitorRect\right  - MonitorRect\left - Width) / 2
                Y = MonitorRect\top  + (MonitorRect\bottom - MonitorRect\top  - Height) / 2
              Else
                X = Max(MonitorRect\left, Min(MonitorRect\right - Width,  X))
                Y = Max(MonitorRect\top,  Min(MonitorRect\bottom - Height, Y))
              EndIf
              
              SetWindowPos_(WindowID(Window), #Null, X, Y, 0, 0, #SWP_NOSIZE | #SWP_NOZORDER | #SWP_NOACTIVATE)
              ;Debug "Window Pos  . . : " +#TAB$+ X + "," + Y + "," + Width + "," + Height
            EndIf
          EndIf
        EndIf
        
        SetWindowState(Window, State)
      EndIf
    EndIf
  EndProcedure
  
  Procedure SaveWindowSize(Window)
    Protected PrefFile.s = GetPathPart(ProgramFilename()) + GetFilePart(ProgramFilename(), #PB_FileSystem_NoExtension) + ".pref"
    Protected GroupName.s, lpwndpl.WINDOWPLACEMENT
    
    If OpenPreferences(PrefFile, #PB_Preference_GroupSeparator) = 0
      If CreatePreferences(PrefFile, #PB_Preference_GroupSeparator)
        ClosePreferences()
      EndIf
    EndIf
    
    If OpenPreferences(PrefFile, #PB_Preference_GroupSeparator)
      If Window < 10000   ; Use the constante Window number in the Group section name to differentiate them in multi-windows mode
        GroupName = "Size_Window_" + Str(Window)
      Else
        GroupName = "Size_Window"
      EndIf
      If PreferenceGroup(GroupName)
        GetWindowPlacement_(WindowID(Window), @lpwndpl)
        WritePreferenceLong("X", lpwndpl\rcNormalPosition\left)
        WritePreferenceLong("Y", lpwndpl\rcNormalPosition\top)
        WritePreferenceLong("Width", lpwndpl\rcNormalPosition\right - lpwndpl\rcNormalPosition\left)
        WritePreferenceLong("Height", lpwndpl\rcNormalPosition\bottom - lpwndpl\rcNormalPosition\top)
        WritePreferenceLong("State", GetWindowState(Window))
        ClosePreferences()
      EndIf
    EndIf
  EndProcedure
  
EndModule
;
;------------------------------------------------------------------------------
;- Demo
;------------------------------------------------------------------------------
;
CompilerIf (#PB_Compiler_IsMainFile)
  UseModule KeepWindowSize
  
  Enumeration Window
    #MainWindow
  EndEnumeration
  
  Enumeration Gadgets
    #Button
  EndEnumeration
  
  ; Required to receive the #PB_Event_CloseWindow event, when the window is minimized and closed from the taskbar (right-click). Not required, fixed in PB6.10
  CompilerIf #PB_Compiler_Version < 610
    Procedure Callback_MainWindow(hWnd, uMsg, wParam, lParam)
      If uMsg = #WM_CLOSE
        PostEvent(#PB_Event_Gadget, GetDlgCtrlID_(hWnd), 0, #PB_Event_CloseWindow)
      EndIf
      ProcedureReturn #PB_ProcessPureBasicEvents
    EndProcedure
  CompilerEndIf
  
  Procedure Resize_MainWindow()
    Protected MainWindow_WidthIni = 340, MainWindow_HeightIni = 180
    Protected ScaleX.f, ScaleY.f
    
    ScaleX = WindowWidth(#MainWindow) / MainWindow_WidthIni : ScaleY = WindowHeight(#MainWindow) / MainWindow_HeightIni
    ResizeGadget(#Button, ScaleX * 40, ScaleY * 40, ScaleX * 260, ScaleY * 100)
  EndProcedure
  
  Procedure Open_MainWindow(X = 0, Y = 0, Width = 340, Height = 180)
    If OpenWindow(#MainWindow, X, Y, Width, Height, "Keep position & Size between Run", #PB_Window_SystemMenu | #PB_Window_MinimizeGadget | #PB_Window_MaximizeGadget | #PB_Window_SizeGadget | #PB_Window_ScreenCentered)
      ButtonGadget(#Button, 40, 40, 260, 100, "Proportional Button")
      CompilerIf #PB_Compiler_Version < 610 : SetWindowCallback(@Callback_MainWindow(), #MainWindow) : CompilerEndIf
      
      BindEvent(#PB_Event_SizeWindow, @Resize_MainWindow(), #MainWindow)
      PostEvent(#PB_Event_SizeWindow, #MainWindow, 0)
      ProcedureReturn #True
    EndIf
  EndProcedure
  
  If Open_MainWindow()
    InitWindowSize(#MainWindow)  ; Optional Flag parameter: #MONITOR_CENTER | #MONITOR_CLIP | #MONITOR_WORKAREA | #MONITOR_AREA | #MONITOR_NOMOVE. Default value = #MONITOR_CLIP | #MONITOR_WORKAREA
    Repeat : Until WaitWindowEvent() = #PB_Event_CloseWindow
    SaveWindowSize(#MainWindow)
  EndIf
  
CompilerEndIf
Edit 2024/10/15: Use GetWindowPlacement to save Window position & size
Last edited by ChrisR on Tue Oct 15, 2024 8:52 pm, edited 2 times in total.
User avatar
jacdelad
Addict
Addict
Posts: 1991
Joined: Wed Feb 03, 2021 12:46 pm
Location: Riesa

Re: Keep Windows Size & Position Between Run (Win)

Post by jacdelad »

Aw man, I wanted to write something very the same and publish it here. After the third project I realized that I recreate my own code for this each time I start a new program.

Thanks very much, that's really useful.
Good morning, that's a nice tnetennba!

PureBasic 6.21/Windows 11 x64/Ryzen 7900X/32GB RAM/3TB SSD
Synology DS1821+/DX517, 130.9TB+50.8TB+2TB SSD
User avatar
ChrisR
Addict
Addict
Posts: 1466
Joined: Sun Jan 08, 2017 10:27 pm
Location: France

Re: Keep Windows Size & Position Between Run (Win)

Post by ChrisR »

In reference to Window position and size just before maximize,
the code has been updated to use GetWindowPlacement API to save the window's position & size.
Post Reply