1. Daniel Vivier
  2. PowerBuilder
  3. Friday, 29 November 2019 02:17 AM UTC

I just figured out that if you do GetEnvironment(env) and then examine env.ScreenHeight and env.ScreenWidth, they do NOT show you current screen dimensions, unless you have the text etc. scaling set to 100%. If you have it set to 125%, for instance, the values are divided by 1.25.

So for instance my Display Settings in Windows 10 are scaling 125%, with 1920 x 1080 resolution. But env.ScreenHeight is 1536 (1920 / 1.25) and env.ScreenWidth is 864 (1080 / 1.25). This is definitely NOT made clear in the Help on the Environment object!

Our program is trying to determine on startup whether the screen dimensions are big enough to display our largest fixed-size window, and I have mostly been depending on just the env.ScreenHeight and env.ScreenWidth. But now I know that is inaccurate.

Any suggestions on what we should be doing instead? The Environment object doesn't seem to have a scale factor property, though I'm sure one could get it with an API call. But I'm then still not really sure what to do with that info, because I'm not sure what my test should be for "are the display dimensions big enough for our program".

Thanks.

Miguel Leeuwe Accepted Answer Pending Moderation
  1. Friday, 29 November 2019 03:48 AM UTC
  2. PowerBuilder
  3. # 1

Hi Dan,

Not a real solution here but maybe this link can have some information on which API's to use? https://community.appeon.com/index.php/qna/q-a/monitor-scale-factor?limitstart=0#reply-16814

(Myself I'm using some routines to check if a window has been opened on a second monitor (stored positions from DB) and now the monitor might be off. You just made me realize I might have to revise that).

 

Comment
There are no comments made yet.
René Ullrich Accepted Answer Pending Moderation
  1. Friday, 29 November 2019 06:24 AM UTC
  2. PowerBuilder
  3. # 2

Hi Dan,

check Windows API function GetSystemMetrics.

HTH,

René

Comment
There are no comments made yet.
Michael Kramer Accepted Answer Pending Moderation
  1. Friday, 29 November 2019 08:11 AM UTC
  2. PowerBuilder
  3. # 3

Hey Dan, I'm not sure what the right screen dimensions to report are - if  I had to choose just one set. Reason:

  • GetEnvironment uses legacy Windows APIs (it used to, and I expect it continues to do so)
  • Windows' compatibility mode can "lie" to the app process about a whole bunch of stuff. E.g.:
    • OS version
    • Screen size (where monitor scaling makes a difference)
    • Color depth
    • CPU and memory

Just like running in a VM emulates an environment different from the physical hardware underneath. So question is when you query for some metric => What do you want in return? The emulated environment or the underlying environment? There you may have multiple virtual layers of "truth".

I can see benefit in expanding the documentation to highlight that GetEnvironment returns the emulated environment which may differ from the physical environment. Then each of us can decide if we need to call other APIs to get info on a "lower layer of reality."

HTH /Michael

Comment
There are no comments made yet.
Daniel Vivier Accepted Answer Pending Moderation
  1. Friday, 29 November 2019 15:11 PM UTC
  2. PowerBuilder
  3. # 4

OK, thanks all. I think I have a function now that returns the info I want. Some of the code is based on heuristics and experience, but I think it's overall correct.

You first need the external function declaration:

// Get screen dimensions etc.
Function long GetSystemMetrics(long nIndex) Library "User32.dll"

and the constants:

// Arguments to GetSystemMetrics
constant int SM_CXSCREEN = 0
constant int SM_CYSCREEN = 1

Then this is the function:

public subroutine uf_screen_info (ref long statedwidth, ref long statedheight, ref long scalingpercent, ref long logicalwidth, ref long logicalheight);

// Return a bunch of details about the main/default screen.
// The stated dimensions are what the user sees them being set to in Control Panel's Display Settings, like 1920 x 1080.
// The scalePercent is the DPI scaling, usually 100, 125 or 150.
// The logical dimensions are the effective usable pixels based on that scaling - and what env.ScreenWidth and env.ScreenHeight
// return. So for instance at 1920 x 1080, with 125% scaling, the logical dimensions are 1536 x 864.
// Note: Calculations done with the UnitsToPixels and PixelsToUnits functions are also in logical dimensions.

ulong ll_dpi = 96 // default if no results
string ls_dpi
Environment env

GetEnvironment(env)

// GET THE DPI

// the following setting works on at least Windows 10 - only changed, if you change the scaling, after a reboot or log off and on
if RegistryGet("HKEY_CURRENT_USER\Control Panel\Desktop\WindowMetrics", "AppliedDPI", ReguLong!, &
ll_dpi) <> 1 &
then
// the following setting works on Windows NT and 2000 (though it may not be present if the
// machine has never been switched from small to large fonts, but that's OK)
if RegistryGet("HKEY_CURRENT_CONFIG\Software\Fonts", "LogPixels", ReguLong!, &
ll_dpi) <> 1 &
then
// the following setting works on Windows 95 and 98
if RegistryGet("HKEY_CURRENT_CONFIG\Display\Settings", "DPILogicalX", RegString!, &
ls_dpi) = 1 &
then
ll_dpi = Long(ls_dpi)
end if
end if
end if

scalingPercent = Round(ll_dpi * 100 / 96.0, 0)

/* A user with dual monitors gets the incorrect (too small) results from env.ScreenWidth and env.ScreenHight,
* try double-checking the primary monitor's dimensions directly with the Windows API,
* and then use the max answer, from that, or from GetEnvironment's settings.
*/
logicalWidth = GetSystemMetrics(SM_CXSCREEN)
logicalHeight = GetSystemMetrics(SM_CYSCREEN)
if logicalWidth <> env.ScreenWidth or logicalHeight <> env.ScreenHeight then
logicalWidth = Max(logicalWidth, env.ScreenWidth)
logicalHeight = Max(logicalHeight, env.ScreenHeight)
end if

statedWidth = Round(logicalWidth * scalingPercent / 100.0, 0)
statedHeight = Round(logicalHeight * scalingPercent / 100.0, 0)

end subroutine

So with this function, if I think all of my windows will fit on a monitor with an effective size of 1024 x 768, I compare that to the returned logicalWidth and logicalHeight. Then if I want to give messaging to the user (because their screen may be too small for our windows), I can tell them that their screen dimensions are statedWidth by statedHeight, with a scaling factor of scalingPercent, but their effective screen dimensions are thus logicalWidth and logicalHeight, which some of the program's windows may not fit within.

 

Comment
  1. Michael Kramer
  2. Friday, 29 November 2019 15:28 PM UTC
You immediately have my vote. What is the OLDEST O/S version your users run? Some Win APIs require fairly recent version - or disappeared recently.
  1. Helpful
  1. Daniel Vivier
  2. Friday, 29 November 2019 15:43 PM UTC
We stopped allowing the installer to install on Windows XP several months ago. (We sell to small to mid-sized churches and charities, at least some of which keep their PCs way longer than one might think would be ideal, to save money!) So the minimum is Vista. The only API we are actually using there is GetSystemMetrics which I'm sure goes back at least that far. But I just realized that there's code in what I posted for reading Registry values for Windows 95 and 98 - that should definitely be removed! So that section changes to:



// the following setting works on at least Windows 10 - only changed, if you change the scaling, after a reboot or log off and on

if RegistryGet("HKEY_CURRENT_USER\Control Panel\Desktop\WindowMetrics", "AppliedDPI", ReguLong!, ll_dpi) <> 1 &

then

// the following setting works on earlier versions of windows (at least since NT), though it may not be present if the

// machine has never been switched from small to large fonts, but that's OK

RegistryGet("HKEY_CURRENT_CONFIG\Software\Fonts", "LogPixels", ReguLong!, ll_dpi)

end if

  1. Helpful
  1. Miguel Leeuwe
  2. Saturday, 30 November 2019 01:38 AM UTC
Thanks for sharing Dan,

regards
  1. Helpful
There are no comments made yet.
Vladimir K. Accepted Answer Pending Moderation
  1. Monday, 2 December 2019 18:48 PM UTC
  2. PowerBuilder
  3. # 5

Hi Dan.

Look at here:

https://community.appeon.com/index.php/qna/q-a/monitor-scale-factor

Guys already help me solve similar problem.

I need to check user display resolution for some some screens in our app, i am doing on the screen level.

First, global functions need to be declared, i did at the application level:

FUNCTION Longptr GetDC ( Longptr hWnd ) LIBRARY "user32.dll"
FUNCTION Longptr GetDesktopWindow ( ) LIBRARY "user32.dll"
FUNCTION UnsignedLong GetDeviceCaps ( Longptr hDC, UnsignedLong nIndex ) LIBRARY "gdi32.dll"
FUNCTION Long ReleaseDC ( Longptr hWnd, Longptr hDC ) LIBRARY "user32.dll"

Second, i create functions:

global type f_check_display_settings from function_object
end type

forward prototypes
global function boolean f_check_display_settings (long ar_l_screen_width, long ar_l_screen_height, string ar_s_window_title)
end prototypes

global function boolean f_check_display_settings (long ar_l_screen_width, long ar_l_screen_height, string ar_s_window_title);environment env
int i_rc
long l_screenwidth, l_screenheight
string s_width, s_height

Constant ULong LOGPIXELSX = 88, LOGPIXELSY = 90
Long ll_rc, ll_percent
ULong lul_dpi_x, lul_dpi_y
Longptr llptr_hwnd, llptr_hdc

i_rc = GetEnvironment(env)

IF i_rc <> 1 THEN
messagebox('System Error', 'Cannot verify monitor screen size.~nPlease contact system administrator.~n~nGetEnvironment()=' + string(i_rc), exclamation!)
return false
end if

l_screenwidth = env.ScreenWidth
l_screenheight = env.ScreenHeight

if isnull(l_screenwidth) then
s_width = 'Null'
else
s_width = string(l_screenwidth)
end if

if isnull(l_screenheight) then
s_height = 'Null'
else
s_height = string(l_screenheight)
end if

IF isnull(l_screenwidth + l_screenheight) = true or l_screenwidth + l_screenheight < 1 THEN
messagebox('System Error', 'Cannot verify monitor screen size.~nPlease contact system administrator.~n~nScreenWidth=' + s_width + ', ScreenHeight=' + s_height, exclamation!)
return false
end if

if env.osmajorrevision < 10 then
// Script begin ...
/*
Global external functions need to be declared in the application object:
FUNCTION Longptr GetDC ( Longptr hWnd ) LIBRARY "user32.dll"
FUNCTION Longptr GetDesktopWindow ( ) LIBRARY "user32.dll"
FUNCTION UnsignedLong GetDeviceCaps ( Longptr hDC, UnsignedLong nIndex ) LIBRARY "gdi32.dll"
FUNCTION Long ReleaseDC ( Longptr hWnd, Longptr hDC ) LIBRARY "user32.dll"
*/

llptr_hwnd = GetDesktopWindow() // Get handle to desktop window
llptr_hdc = GetDC(llptr_hwnd) // Get handle to desktop's device context

// 96 dots/inch is normal (100%), 120 is 125%, 144 is 150%
lul_dpi_x = GetDeviceCaps(llptr_hdc, LOGPIXELSX)
lul_dpi_y = GetDeviceCaps(llptr_hdc, LOGPIXELSY)

ll_percent = (lul_dpi_x * 100) / 96

ll_rc = ReleaseDC(llptr_hwnd, llptr_hdc) // Release the device context
// Script end ... BIG THANKYOU to John Fauss: https://community.appeon.com/index.php/qna/q-a/profile/2184-john-fauss
else
ll_percent = 100
end if

choose case true
case l_screenwidth >= ar_l_screen_width and l_screenheight >= ar_l_screen_height and ll_percent = 100
return true
case else
MessageBox(ar_s_window_title, 'Minimum resolution to run this screen is ' + string(ar_l_screen_width) + ' X ' + string(ar_l_screen_height) + '.~n~nYour monitor display settings are: ' + s_width + ' X ' + s_Height + &
'~n~nPlease readjust you monitor:~n Display Settings/Scale Layout to run this screen.', Exclamation!)
return false
end choose

end function

 

Third, just before opening window, in our case in the w_frame, i am passing minimum width (1920) and height (1080) to functions, so i can check if user can run this window:

 

if not f_check_display_settings(1920,1080,'My window title') then
return

else

// open window ...
end if

 

Hope it will help you at least for a start.

Comment
  1. Daniel Vivier
  2. Monday, 2 December 2019 19:18 PM UTC
Thanks, though I really think this gets more technical than needed, given that my solution seems to be accurate, using env.ScreenHeight and env.ScreenWidth along with a Registry setting (varying somewhat with the version of Windows) that lets you figure out the scaling factor.
  1. Helpful
  1. Miguel Leeuwe
  2. Tuesday, 3 December 2019 03:10 AM UTC
Not an expert, but

Constant ULong LOGPIXELSX = 88, LOGPIXELSY = 90

I seem to remember that this has to do with pixel sizes?

If so, then I also remember that these values might be different for different monitors?

  1. Helpful
There are no comments made yet.
Miguel Leeuwe Accepted Answer Pending Moderation
  1. Tuesday, 3 December 2019 03:06 AM UTC
  2. PowerBuilder
  3. # 6

FYI, for the ones who haven't seen it yet:

There's an added problem when you have dddw close to the bottom of your screen:

https://www.appeon.com/standardsupport/search/view?id=3713

I guess we'll just have to tell our users to run our powerbuilder applications with compatibility settings for DPI to 100% system ( I tried "enhanced" but that didn't work out as well, can't remember why).

 

Or ...

Tell our users to use 100% scaling in windows. Which for now is the best, but also less user friendly solution.

regards,

MiguelL

Comment
  1. Michael Kramer
  2. Tuesday, 3 December 2019 13:32 PM UTC
Thanks for pointing this out. It's an important gotcha.

Scaling reset to 100% is a workaround but it has limited viability because it significantly reduces accessibility - and that itself can be issue.



Also, a 4K screen on 13.5 inch laptop with touch screen => Direct interaction on screen becomes a challenge when touch targets are just millimeter size and located next to each other. Again, accessibility suffers due to scaling issues.
  1. Helpful
  1. Miguel Leeuwe
  2. Tuesday, 3 December 2019 13:34 PM UTC
Totally agree.
  1. Helpful
  1. Miguel Leeuwe
  2. Tuesday, 3 December 2019 13:35 PM UTC
Is that even working correctly with Powerbuilder, touch screens?
  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.