1. Igor Perisic
  2. PowerBuilder
  3. Tuesday, 11 July 2023 19:01 PM UTC

Hi everyone, 

I am working on setting a custom position of a Window on startup. First, I tried adjusting the location inside the code in the open event by doing

this.X = 50 and this.Y = 100

but the window location remains unchanged. Then I tried adjusting the X and Y coordinates of my window inside the UI in the 'Other' section, but the window location still remains unchanged. 

The reason I would like a custom startup location is because I want to save the X/Y coordinates to a file when a user moves the window or closes the window. Then the next time the application starts up, the application will read these values and essentially open the window in the same location it was at when it last closed.

So, this has me wondering is it possible to adjust the position of a Window?

Thank you 

 

John Fauss Accepted Answer Pending Moderation
  1. Wednesday, 12 July 2023 14:03 PM UTC
  2. PowerBuilder
  3. # 1

Hi, Igor -

No, PB does not have a built-in way to tell you the number of monitors.

The GetSystemMetrics Windows API function can tell you this, however. I'm attaching a very small example app I had created a few years ago that reports on the number of monitors, the resolution of each and the position of each monitor within the Windows virtual screen (the minimum rectangle that encompasses all monitors). The app uses several monitor-related API functions (several of which Miguel has already informed you about). Admittedly, my little example app likely does more than what you require, but I wanted you to have a small, simple, working example that you could inspect. The app is developed in PB 2019.

Windows has an API function for enumerating each of the display monitors (called EnumDisplayMonitors), but like most enumeration methods in Windows, it utilizes a callback function argument (a pointer to a function), which PB cannot support. As an alternative, the app subdivides the virtual desktop's width and height into a series of points, then uses the MonitorFromPoint API function to obtain the handle to the nearest monitor from each point until all of the handles from every monitor (as determined by the aforementioned GetSystemMetrics API) have been obtained.

Included below are some links to Windows API documentation that I've found helpful on this topic:

    https://learn.microsoft.com/en-us/windows/win32/gdi/the-virtual-screen

    https://learn.microsoft.com/en-us/windows/win32/gdi/multiple-display-monitors-functions

    https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsystemmetrics

I hope this information helps you add the desired functionality to your application.

Best regards, John

Attachments (1)
Comment
  1. John Fauss
  2. Thursday, 13 July 2023 03:46 AM UTC
Thank you, Miguel! In this particular case, I don't believe it would be worth it, as the number of monitors on a system is typically very small, and the "trick" of calling the MonitorFromPoint API function from various spots across the virtual screen seems to be fairly efficient. At one time I created a PBNI extension that contained a method that invoked the EnumDisplayMonitors API function, which worked well, but going to that much coding effort just to obtain the Windows handle for each monitor seemed to me to be overkill. For other enumeration API functions, however, creating/using a C# DLL as you suggest might be the best (or only) alternative.
  1. Helpful 1
  1. Miguel Leeuwe
  2. Thursday, 13 July 2023 05:30 AM UTC
I think you're right about the overkill. Your code is working well enough for us.
  1. Helpful
  1. Brett Weaver
  2. Wednesday, 18 October 2023 01:14 AM UTC
This is so useful with people having multiple display configurations between home, work and business travel. Brilliant! Thanks!

  1. Helpful
There are no comments made yet.
Roland Smith Accepted Answer Pending Moderation
  1. Thursday, 13 July 2023 14:38 PM UTC
  2. PowerBuilder
  3. # 2

This example app has code that saves/restores window position from one run to the next. It also is able to tell if it is located on an inactive monitor and will shift to the main monitor.

https://www.topwizprogramming.com/freecode_pbeditor.html

The wm_wininichange event is triggered by the operating system when a monitor is inactivated or reactivated. The function gn_app.of_IsOnMonitor determines if the window is visible using the MonitorFromWindow API function.

Comment
  1. Miguel Leeuwe
  2. Thursday, 13 July 2023 17:38 PM UTC
@Roland: About Move(0, 0) in the wm_wininichange event.

One question: will this work independently whether monitor 1 or 2 is inactive? Will both have a position 0, 0 ? As far as I know, only one of them has (0,0).
  1. Helpful
  1. Roland Smith
  2. Thursday, 13 July 2023 19:55 PM UTC
The screen coordinates are such that 0,0 is the upper left corner of the primary monitor and extends to all monitors.

I just placed PBEditor to the upper left corner of monitor 2 and the coordinates saved are 8741, 480.
  1. Helpful
  1. John Fauss
  2. Thursday, 13 July 2023 20:03 PM UTC
Also, if the primary monitor is turned off, the size of the virtual screen changes and the upper-left corner of a secondary monitor then has coordinates (0,0). If the primary monitor is subsequently turned on, its upper-left corner once again becomes (0,0). So it appears that moving a "hidden" window to (0,0) is always a viable option.
  1. Helpful
There are no comments made yet.
Miguel Leeuwe Accepted Answer Pending Moderation
  1. Tuesday, 11 July 2023 21:05 PM UTC
  2. PowerBuilder
  3. # 3

 

To avoid windows showing on a deactived monitor:

External Function declarations (there might be more declarations than you need):

FUNCTION Longptr MonitorFromWindow ( &
   Longptr hWnd, &
   UnsignedLong dwFlags &
   ) LIBRARY "user32.dll"

FUNCTION Boolean GetMonitorInfo ( &
   Longptr hMonitor , &
   REF str_monitorinfo lstr_MonitorInfo ) &
   LIBRARY "user32.dll" ALIAS FOR "GetMonitorInfoW"

str_rect object:

global type str_rect from structure
	long		l_left		descriptor "comment" = "X-coordinate of upper-left corner of rectangle (can be negative)"
	long		l_top		descriptor "comment" = "Y-coordinate of upper-left corner of rectangle (can be negative)"
	long		l_right		descriptor "comment" = "X-coordinate of lower-right corner of rectangle (can be negative)"
	long		l_bottom		descriptor "comment" = "Y-coordinate of lower-right corner of rectangle (can be negative)"
end type

 

str_monitorinfo structure object:

global type str_monitorinfo from structure
	unsignedlong		ul_structsize		descriptor "comment" = "The size of this structure, in bytes (always 40)"
	str_rect		str_monitor		descriptor "comment" = "The display monitor rectangle, in virtual screen coordinates"
	str_rect		str_workarea		descriptor "comment" = "The work area rectangle of the display monitor, in virtual screen coordinates"
	unsignedlong		ul_flags		descriptor "comment" = "Bit flags. Bit 1 (xxxx xxx1) = 1 when this is the primary monitor"
end type

 

wf_workspace_full function:

// boolean wf_workspace_full(ref long al_width, ref long al_height, ref long al_left, ref long al_right, ref long al_top, ref long al_bottom, readonly alptr_alptr_handle_frame)
str_monitorinfo  lstruc_mon_info

setnull(al_width)
setnull(al_height)

lstruc_mon_info.ul_structsize = 40
if NOT GetMonitorInfo(alptr_handle_frame, ref lstruc_mon_info ) then
	return FALSE
end if

al_width  = lstruc_mon_info.str_workarea.l_right - lstruc_mon_info.str_workarea.l_left 
al_height = lstruc_mon_info.str_workarea.l_bottom - lstruc_mon_info.str_workarea.l_top
al_left = lstruc_mon_info.str_workarea.l_left
al_right = lstruc_mon_info.str_workarea.l_right
al_top = lstruc_mon_info.str_workarea.l_top
al_bottom = lstruc_mon_info.str_workarea.l_bottom

al_width  = PixelsToUnits(al_width, XPixelsToUnits!) 
al_height = PixelsToUnits(al_height, YPixelsToUnits!)
al_left = PixelsToUnits(al_left, XPixelsToUnits!)
al_right = PixelsToUnits(al_right, XPixelsToUnits!)
al_top = PixelsToUnits(al_top, YPixelsToUnits!)
al_bottom = PixelsToUnits(al_bottom, YPixelsToUnits!)

if isnull(al_width) or isnull(al_height) then
	return FALSE
end if
return TRUE

 

Code in an event that has been POSTED from the Open() event:

"gnv_app" is pfc classes related. Let me know if any of them need explanation.

// v2, mjl, 04/06/20: avoid response window always showing on primary window
// we want the x and y to always be within the boundaries of the frame window.

string ls_pfc_ancestor_window

// v2, mjl, 12/08/20: if the frame is not valid, do nothing (happens with w_logon and w_change_password):
if not isvalid(gnv_app.of_getframe()) then
	return 0
end if

ls_pfc_ancestor_window = of_pfc_ancestor_window()
choose case ls_pfc_ancestor_window
	case 'w_response', 'w_popup', 'w_child', 'w_main'
		// continue
	case else
		// not for w_sheet nor w_frame as these cannot appear on deactivated monitor.
		RETURN 0
end choose

Environment lEnv
	
//	// for debug run on secondary monitor and uncomment these 2 lines:
//	this.x -= 2500
//	yield()

longptr lptr_handle, lptr_monitor_frame, lptr_monitor_this
long ll_frame_monitor_left, ll_frame_monitor_right, ll_frame_monitor_top, ll_frame_monitor_bottom
long ll_frame_monitor_width, ll_frame_monitor_height
long ll_this_x, ll_this_y, ll_this_width, ll_this_height

// frame dimensions:
lptr_monitor_frame = handle(gnv_app.of_getframe())
lptr_monitor_frame = MonitorFromWindow (lptr_monitor_frame, 0)

lptr_monitor_this = handle(this)
lptr_monitor_this = MonitorFromWindow (lptr_monitor_this, 0)

// If the window is visible on a monitor, check the boundaries
if lptr_monitor_frame > 0 then
	if NOT wf_workspace_full(ll_frame_monitor_width, ll_frame_monitor_height, ll_frame_monitor_left, ll_frame_monitor_right, &
									 ll_frame_monitor_top, ll_frame_monitor_bottom, lptr_monitor_frame) then
		return 0
	end if
end if

ll_this_x = this.x
ll_this_y = this.y
ll_this_width = this.width
ll_this_height = this.height

long ll_x, ll_y
if NOT (ll_this_x > ll_frame_monitor_left AND ll_this_x < ll_frame_monitor_right) &
or lptr_monitor_frame <> lptr_monitor_this then
	ll_x = ll_frame_monitor_left + round( (ll_frame_monitor_width - ll_this_width) / 2, 0)
	if ll_x < ll_frame_monitor_left then
		ll_x = ll_frame_monitor_left
	end if
else
	ll_x = this.x
end if
if NOT (ll_this_y > ll_frame_monitor_top AND ll_this_y < ll_frame_monitor_bottom) &
or lptr_monitor_frame <> lptr_monitor_this then
	ll_y = ll_frame_monitor_top + round( (ll_frame_monitor_height - ll_this_height) / 2, 0)
	if ll_y < ll_frame_monitor_top then
		ll_y = ll_frame_monitor_top
	end if
else
	ll_y = this.y
end if
if ll_x <> this.x or ll_y <> this.y then

	// you can't move a maximised or minimised window so first check on state:
	boolean lb_reset_maximized, lb_resize
	if this.windowstate = maximized! then
		lb_reset_maximized = true
	else
		// When the window's size was saved on a bigger monitor, make sure the size is not bigger than the frame-monitor's resolution:
		if ll_this_width > ll_frame_monitor_width then
			lb_resize = true
			ll_this_width = ll_frame_monitor_width
		end if
		if ll_this_height > ll_frame_monitor_height then
			lb_resize = true
			ll_this_height = ll_frame_monitor_height
		end if
	end if
	
	f_redraw(this, false)
	post f_redraw_window(this)
	
	// set to normal!
	if this.windowstate <> normal! then
		// set to normal when maximized or minimized:
		this.windowstate = normal!
	end if
	
	if lb_resize then
		// resize so it fits on the monitor
		this.Resize(ll_this_width, ll_this_height)
	end if
	
	this.Move(ll_x, ll_y)

	// v2, mjl, 07/08/20: save the correct "normal" positions to avoid being restored wrongly in the future:
//	if isvalid(gnv_app) and isvalid(inv_preference) then
//		gnv_app.inv_display_props.of_saveWinProps(this, this.inv_preference) 
//	end if
	
	if lb_reset_maximized then
		this.windowstate = maximized!
		// v2, mjl, 07/08/20: save the correct "maximized" positions to avoid being restored wrongly in the future:
//		if isvalid(gnv_app) and isvalid(inv_preference) then
//			gnv_app.inv_display_props.of_saveWinProps(this, this.inv_preference) 
//		end if
	end if

end if

return 0

 

I got this code, standing on the shoulders of other great people in this forum. Maybe I can find which posts were involved, but this is the final product I made of that information.

Have fun!

Comment
There are no comments made yet.
Chris Pollach @Appeon Accepted Answer Pending Moderation
  1. Tuesday, 11 July 2023 19:35 PM UTC
  2. PowerBuilder
  3. # 4

Hi Igor;

  That will not work in many cases as the Window class being opened has not yet completed its full instantiation processing. Thus, your X & Y values are being overwritten after you set them in the Open Event.

  The fix / workaround (for example) would be to ...

  1. Create a User Event - say for example "oe_PostOpen"
  2. Post the UE from the real Open event
  3. Set the X & Y in the UE.

HTH

Regards ... Chris

 

Comment
  1. Miguel Leeuwe
  2. Tuesday, 11 July 2023 20:22 PM UTC
Now that I think of it, this would also work:

Open event:

---------------

This.Hide() // make the window invisible

This.POST Move(50,100) // move to position after the Open() has finished

This.POST Show() // Make the window visible after the Move() has finished.

  1. Helpful
  1. Igor Perisic
  2. Tuesday, 11 July 2023 20:30 PM UTC
That worked for me, thank you! I do have a follow up question to this though. Does powerBuilder have an option to check the count of monitors and also check the resolution of each monitor? I've figured out how to check the resolution of my main monitor but I'd like to add the functionality if possible so that if someone is switching between a multi monitor setup and a single monitor setup the window won't be displayed somewhere out of bounds.



Thanks
  1. Helpful
  1. Miguel Leeuwe
  2. Tuesday, 11 July 2023 20:31 PM UTC
Nope, but you can do it using Windows API functions. This is exactly what we are doing in our own applicaitons. I'll try to check my code and get back to you. For starters, if you are opening up a Sheet window, you never have to worry, since it can only open up within the limits of your frame window. The other window types might be a problem though.
  1. Helpful
There are no comments made yet.
  • Page :
  • 1


There are no replies made for this question yet.
However, you are not allowed to reply to this question.