Page 1 of 1

Additional flag for MessageRequester to center requester on app window

Posted: Sun Dec 12, 2021 3:17 am
by Axeman
Hi, I'd like to request an additional flag for the MessageRequester command that tells it to center the requester window on the calling app's window. Similar to what the #PB_Window_WindowCentered flag does with OpenWindow.

I'll probably make a custom requester with this capability for my projects, as the #PB_Gadget_RequiredSize flag for GadgetHeight seems to return useful values for multi-line text gadgets now ( https://www.purebasic.fr/english/viewtopic.php?t=65761 ). This is a request for the standard MessageRequester command (adding this before anyone tells me to just make my own requester).

Re: Additional flag for MessageRequester to center requester on app window

Posted: Sun Dec 12, 2021 3:46 am
by BarryG
On Windows, the MessageRequester() just calls MessageBox_() API behind the scenes, and a center flag isn't supported by that, so your request can never be done.

But see here -> https://www.purebasic.fr/english/viewtopic.php?t=6680

And there's other posts with code if you search for "requester position".

Re: Additional flag for MessageRequester to center requester on app window

Posted: Sun Dec 12, 2021 7:17 am
by Axeman
Fair point. I'll probably just start using a custom requester, as that gives me more control.

Re: Additional flag for MessageRequester to center requester on app window

Posted: Sun Dec 12, 2021 8:52 am
by chi
Axeman wrote: Sun Dec 12, 2021 7:17 am Fair point. I'll probably just start using a custom requester, as that gives me more control.
A custom requester with all available flags is probably more work than this...

Code: Select all

#PB_MessageRequester_WinCenter = 128

Procedure MonitorEnumProc(hMonitor, hdcMonitor, *lprcMonitor.RECT, dwData)
  Protected rgn = CreateRectRgnIndirect_(*lprcMonitor)
  CombineRgn_(dwData, dwData, rgn, #RGN_OR)
  DeleteObject_(rgn)
  ProcedureReturn #True
EndProcedure

Procedure CBTProc(nCode, wParam, lParam)
  If nCode < 0 : Goto rethook : EndIf
  If nCode = #HCBT_CREATEWND
    Protected *cw.CBT_CREATEWND = lParam
    Protected *cs.CREATESTRUCT = *cw\lpcs
    If *cs\lpszClass = 32770
      Protected pRect.RECT, rgn, x, y
      rgn = CreateRectRgn_(0, 0, 0, 0)
      EnumDisplayMonitors_(0, 0, @MonitorEnumProc(), rgn)
      GetWindowRect_(*cs\hWndParent, pRect)
      x = pRect\left + (pRect\right - pRect\left - *cs\cx)/2
      y = pRect\top  + (pRect\bottom - pRect\top - *cs\cy)/2
      If PtInRegion_(rgn, x, y) And PtInRegion_(rgn, x + *cs\cx, y) And PtInRegion_(rgn, x, y + *cs\cy) And PtInRegion_(rgn, x + *cs\cx, y + *cs\cy)
        *cs\x = x
        *cs\y = y
      EndIf
      DeleteObject_(rgn)
    EndIf
  EndIf
  rethook:
  ProcedureReturn CallNextHookEx_(0, nCode, wParam, lParam)
EndProcedure

Procedure _MessageRequester(title$, text$, flags=0)
  If flags & #PB_MessageRequester_WinCenter
    Protected hook = SetWindowsHookEx_(#WH_CBT, @CBTProc(), 0, GetCurrentThreadId_())
  EndIf
  Protected ret = MessageRequester(title$, text$, flags&~#PB_MessageRequester_WinCenter)
  If hook : UnhookWindowsHookEx_(hook) : EndIf
  ProcedureReturn ret
EndProcedure

Macro MessageRequester(title, text, flags)
  _MessageRequester(title, text, flags)
EndMacro

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

CompilerIf #PB_Compiler_IsMainFile
  
  OpenWindow(0, 0, 0, 640, 400, "MessageRequester_WinCenter", #PB_Window_SystemMenu|#PB_Window_MaximizeGadget|#PB_Window_SizeGadget|#PB_Window_ScreenCentered|#PB_Window_Invisible)
  ButtonGadget(0, 10, 10, 100, 30, "Show")
  HideWindow(0, #False)
  
  Repeat
    Define event = WaitWindowEvent()
    Select event
      Case #PB_Event_Gadget
        Select EventGadget()
          Case 0
            MessageRequester("Info", ~"This MessageRequester is always CENTERED on the parent window,\nbut only if the client area of the MR is fully visible on the desktop(s).", #PB_MessageRequester_Info|#PB_MessageRequester_WinCenter)
        EndSelect
    EndSelect
  Until event = #PB_Event_CloseWindow
  
CompilerEndIf
Image

Re: Additional flag for MessageRequester to center requester on app window

Posted: Sun Dec 12, 2021 8:58 am
by BarryG
Nice, Chi! Just what the doctor ordered. Thanks!

Re: Additional flag for MessageRequester to center requester on app window

Posted: Sun Dec 12, 2021 8:31 pm
by chi
BarryG wrote: Sun Dec 12, 2021 8:58 am Nice, Chi! Just what the doctor ordered. Thanks!
Hehe np. I have changed the code slightly to account for offscreen MessageRequesters.

Re: Additional flag for MessageRequester to center requester on app window

Posted: Mon Dec 13, 2021 9:20 am
by AZJIO
I calculated the place of appearance of the child window in such a way that it was not centered, but as close as possible to the edge for which the parent window was shifted.

Re: Additional flag for MessageRequester to center requester on app window

Posted: Mon Dec 13, 2021 4:53 pm
by Axolotl
+1
I created a similar solution to chi's for my own apps. Especially on an 4k monitor it is really enoying if your tool shows at an edge and the message requestor appears screen centered.

Re: Additional flag for MessageRequester to center requester on app window

Posted: Wed Dec 15, 2021 1:50 am
by chi
I have adjusted the code to consider all 4 corner points (instead of just TL & RB). Now it works regardless of the multi-screen setup.

Re: Additional flag for MessageRequester to center requester on app window

Posted: Wed Dec 15, 2021 3:37 am
by BarryG
Axolotl wrote: Mon Dec 13, 2021 4:53 pmEspecially on an 4k monitor it is really enoying if your tool shows at an edge and the message requestor appears screen centered.
So many apps do that! Very annoying and unprofessional.

@Chi: Why the check for "If nCode<0" and Goto? Because if nCode<>#HCBT_CREATEWND (3) then it's going to skip the If/EndIf block anyway.

Code: Select all

If nCode < 0 : Goto rethook : EndIf
If nCode = #HCBT_CREATEWND
  ; Blah
EndIf
rethook:
ProcedureReturn CallNextHookEx_(0, nCode, wParam, lParam)

Re: Additional flag for MessageRequester to center requester on app window

Posted: Wed Dec 15, 2021 5:49 am
by chi
BarryG wrote: Wed Dec 15, 2021 3:37 am @Chi: Why the check for "If nCode<0" and Goto? Because if nCode<>#HCBT_CREATEWND (3) then it's going to skip the If/EndIf block anyway.
From CBTProc callback function:
If nCode is less than zero, the hook procedure must pass the message to the CallNextHookEx function without further processing and should return the value returned by CallNextHookEx.
I think it is somewhat time sensitive, especially when more nCode checks are involved. I used a DLL without it for many years and never had any problems tho. It's up to you, but better safe than sorry ;)

Re: Additional flag for MessageRequester to center requester on app window

Posted: Wed Dec 15, 2021 8:17 am
by AZJIO

Code: Select all

EnableExplicit

Enumeration Window
	#Window_Main
	#Window_Child
EndEnumeration

Enumeration Gadget
	#a1
	#b1
	#a2
	#b2
	#yzit
EndEnumeration

Enumeration Gadget
	#ok
	#txt
	#chb
EndEnumeration

Structure MXYWH
	x.l
	y.l
	w.l
	h.l
	m.l
EndStructure

Global WinRect.MXYWH
Global WWE

Declare _MsgBox(txt$, w = 270, h = 180, t = 0)
Declare _ChildCoor(nWin, w, h, c=0, d=0)

If OpenWindow(#Window_Main, 0, 0, 420, 250, "Example...", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
	ButtonGadget (#a1, 5, 5, 50, 30, "corner")
	ButtonGadget (#b1, 5, 45, 50, 30, "center")
	
	ButtonGadget (#a2, 360, 170, 50, 30, "corner")
	ButtonGadget (#b2, 360, 210, 50, 30, "center")
	
	ButtonGadget (#yzit, 170, 110, 70, 30, "reduce")
	
	Repeat
		WWE = WaitWindowEvent()
		
		Select EventWindow()
			Case #Window_Main
				Select WWE
					Case #PB_Event_Gadget
						Select EventGadget()
							Case #a1, #a2
								_MsgBox("Close the program window?", 0, 0, 1)
							Case #b1, #b2
								_MsgBox("Close the program window?")
							Case #yzit
								ResizeWindow(#Window_Main, #PB_Ignore, #PB_Ignore, 200, 70)
								ResizeGadget(#a1, 5, 3, #PB_Ignore, #PB_Ignore)
								ResizeGadget(#b1, 5, 35, #PB_Ignore, #PB_Ignore)
								ResizeGadget(#a2, 140, 3, #PB_Ignore, #PB_Ignore)
								ResizeGadget(#b2, 140, 35, #PB_Ignore, #PB_Ignore)
								ResizeGadget(#yzit, 60, 30, #PB_Ignore, #PB_Ignore)
						EndSelect
					Case #PB_Event_CloseWindow
						CloseWindow(#Window_Main)
						End
				EndSelect
				
			Case #Window_Child
				Select WWE
					Case #PB_Event_Gadget
						Select EventGadget()
							Case #ok
								CloseWindow(#Window_Child)
								DisableWindow(#Window_Main, #False)
						EndSelect
					Case #PB_Event_CloseWindow
						CloseWindow(#Window_Child)
						DisableWindow(#Window_Main, #False)
				EndSelect
		EndSelect
	ForEver
EndIf

Procedure _MsgBox(txt$, w = 270, h = 180, t = 0)
	If Not w
		w = 270
	EndIf
	If Not h
		h = 180
	EndIf
	_ChildCoor(#Window_Main, w, h, t, 30)
	With WinRect
		OpenWindow(#Window_Child, \x, \y, \w, \h, "Message", #PB_Window_SystemMenu)
	EndWith
	SetWindowLongPtr_(WindowID(#Window_Child), #GWL_HWNDPARENT, WindowID(#Window_Main))
	TextGadget(#txt, 10, 10, 250, 60, txt$)
	CheckBoxGadget(#chb, 20, h-80, 240, 20, "Don't ask again")
	ButtonGadget (#ok, (w-50)/2, h - 40, 50, 30, "Ок")
	DisableWindow(#Window_Main, #True)
EndProcedure

Procedure _ChildCoor(nWin, w, h, c=0, d=0)
	Protected DX, DY
	With WinRect
		\h = WindowHeight(nWin)
		\w = WindowWidth(nWin)
		\x = WindowX(nWin)
		\y = WindowY(nWin)
		
		ExamineDesktops()
		DX=DesktopWidth(0)
		DY=DesktopHeight(0)
		If c = 0
			\x+(\w-w)/2
			\y+(\h-h)/2
		EndIf
		If \x+w+d>DX
			\x=DX-w-10-d
		EndIf
		If \y+h+60+d>DY
			\y=DY-h-70-d
		EndIf
		If \x<=d
			\x=d
		EndIf
		If \y<=d
			\y=d
		EndIf
		\w=w
		\h=h
	EndWith
EndProcedure

Re: Additional flag for MessageRequester to center requester on app window

Posted: Wed Dec 15, 2021 8:42 am
by BarryG
@Chi: Okay, I see what you mean. I've amended it to use short-circuit evaluation and lose the Goto; so the timing should be the same because anything less than 0 will immediately exit the procedure as required, along with passing the less-than-zero nCode value with it.

Code: Select all

If nCode > -1 And nCode = #HCBT_CREATEWND
  ; Blah
EndIf
ProcedureReturn CallNextHookEx_(0, nCode, wParam, lParam)