Trying to change dimensions of my FMX form to emulate a 'FullScreen' mode, but using the Screen size backfires when a user has its settings with some bigger scaling, since the whole form becomes bigger than the screen.
How can I retrieve the scaling value so I can Size the form accordingly?
EDIT:
That's a little snippet that shows what was my intention with the question and how it was solved. Thank you for you time and help.
procedure TMyForm.ApplyFullScreen;
var
tmpEscale: Extended;
begin
BorderStyle := TFmxFormBorderStyle.None;
Left := 0;
Top := 0;
tmpEscala := USER_DEFAULT_SCREEN_DPI / GetDeviceCaps(GetDC(0), LOGPIXELSX);
Height := Round(Screen.Height * tmpEscala);
Width := Round(Screen.Width * tmpEscala);
end;
When you call EnumDisplaySettings the resulting DEVMODE structure contains the DPI setting in the dmYResolution field. Beware that passing NULL as the device name to EnumDisplaySettings will get the information only for one screen, on a multimonitor system you should enumerate all the display devices.
You can also call GetDeviceCaps on a device context, and query for LOGPIXELSX and LOGPIXELSY.
The DPI corresponds to font scaling as follows:
100% = 96 dpi
125% = 120 dpi
150% = 144 dpi
200% = 192 dpi
For more information, it would be good to refer to the MSDN article on DPI-related APIs
Related
I would like to get the actual screen dpi/ppi, not the dpi setting used for font in C++.
I tried with the following codes:
Version 1, reports 72 dpi, which is wrong.
SetProcessDPIAware(); //true
HDC screen = GetDC(NULL);
double hSize = GetDeviceCaps(screen, HORZSIZE);
double vSize = GetDeviceCaps(screen, VERTSIZE);
double hRes = GetDeviceCaps(screen, HORZRES);
double vRes = GetDeviceCaps(screen, VERTRES);
double hPixelsPerInch = hRes / hSize * 25.4;
double vPixelsPerInch = vRes / vSize * 25.4;
ReleaseDC(NULL, screen);
return (hPixelsPerInch + vPixelsPerInch) * 0.5;
Version 2, reports 96 dpi, which is the Windows dpi setting for font, but not the actual screen dpi.
SetProcessDPIAware(); //true
HDC screen = GetDC(NULL);
double hPixelsPerInch = GetDeviceCaps(screen,LOGPIXELSX);
double vPixelsPerInch = GetDeviceCaps(screen,LOGPIXELSY);
ReleaseDC(NULL, screen);
return (hPixelsPerInch + vPixelsPerInch) * 0.5;
I'm honestly confused by the answers here.
Microsoft has a GetDpiForMonitor method:
https://msdn.microsoft.com/en-us/library/windows/desktop/dn280510(v=vs.85).aspx
And monitors DO expose their physical dimensions to tools. You can read your monitors width and height, in centimeters, using the HWiNFO64 tool. So if they're getting it (DDI?), it stands to reason that you can access that information yourself.
Even a different Stack Overflow post mentions using WmiMonitorBasicDisplayParams to get the data.
How to get monitor size
So the top post is flat-out, 100%, wrong.
What you're asking for is, unfortunately, not possible in the general case.
Windows doesn't know the physical screen size. Windows might know that your screen has 1024x768 pixels, but it doesn't know how big the screen actually is. You might pull the cable out of your old 13" screen and connect it to a 19" monitor without changing the resolution. The DPI would be different, but Windows won't notice that you changed monitors.
You can get the true physical dimensions and DPI for a printer (assuming the driver isn't lying), but not for a screen. At least not reliably.
UPDATED
As others have pointed out, there are standards for two-way communication between newer monitors and the OS (EDID), that might make this information available for some devices. But I haven't yet found a monitor that provides this information.
Even if EDID were universally available, it's still not solvable in the general case, as the display could be a video projector, where the DPI would depend on the zoom, the focus, the lens type, and the throw distance. A projector is extremely unlikely to know the throw distance, so there's no way for it to report the actual DPI.
Getting DPI information is found to produce exact value using the below method.
ID2D1Factory* m_pDirect2dFactory;
D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pDirect2dFactory);
FLOAT dpiX, dpiY;
m_pDirect2dFactory->GetDesktopDpi( &dpiX, &dpiY );
I think what you're after is:
GetDeviceCaps(hdcScreen, LOGPIXELSX);
GetDeviceCaps(hdcScreen, LOGPIXELSY);
For the fist time I have some problems creating a "complex" UI with delphi. I use a 4K monitor for development & testing and I have some scaling issues.
My app use a 2 column design, think of a diff view under a source control where you have a left pane and a right pane that mirror themselves component wise but the content (text) of these components are different.
To give you an idea I do this on the form resize:
edtFixExifLeftFile.Width := Floor((pnlFixExifTop.ClientWidth - (bbpFixExifBrowseLeft.Width * 2 + 42)) / 2);
bbpFixExifBrowseLeft.Left := edtFixExifLeftFile.Left + edtFixExifLeftFile.Width + 5;
edtFixExifRightFile.Left := bbpFixExifBrowseLeft.Left + bbpFixExifBrowseLeft.Width + 16;
edtFixExifRightFile.Width := edtFixExifLeftFile.Width;
bbpFixExifBrowseRight.Left := edtFixExifRightFile.Left + edtFixExifRightFile.Width + 5;
This way both columns take 50% of the space. This is pixel perfect when used in a HD environment (1080p) but in a 4K environment the scaling (my TForm.Scaled = True) completely "eats" one of the margins (I use 8px margins and the scaling take about 9px more than expected). This make the look wrong in 4K (the right TEdit overflow the TForm a bit).
How can I create a pixel perfect UI in HD and 4K environments? What is the right way to code my component resize?
I use the latest version of Delphi (10.3) and I don't use fancy components (only "stock" VCL).
After reading a good tutorial by Žarko Gajić I ended doing this:
Pad := MulDiv(8, Monitor.PixelsPerInch, 96);
grdpnlCover.Left := pnlCoverImage.Left + pnlCoverImage.Width + Pad;
Where 8 is the unscaled value and 96 is the DPI the form was designed under.
In Windows 10, Delphi XE7, when font size is set to > 100%, Delphi's screen[0].workarea.rect returns dimensions that reflect the font scaling. E.G., with the graphics card for screen 0 set to 1920x1080, screen[0].workarea.rect.width returns 1280 (assuming there is no vertical task bar, etc.). However, I can't find a way to programatically determine that the width is being returned as 1280 due to the font scaling vs. 1280 being the actual graphics card setting. Can anyone tell me how to do 1 or more of the following -- any 1 of them would give me the info I need to differentiate among the possibilities: 1) determine what the font percentage is; 2) determine what the hardware resolution is.
GetDeviceCaps function returns a parameter, defined by index, of chosen device context.
Works in Windows 7, gives true font size value for my desktop (96 dpi is default value "small font 100%")
dc := GetDC(0);
Memo1.Lines.Add(Format('hor res %d', [GetDeviceCaps(dc, HORZRES)]));
lp := GetDeviceCaps(dc, LOGPIXELSX);
Memo1.Lines.Add(Format('logx %d percents %d%%', [lp, 100 * lp div 96]));
hor res 1280
logx 120 percents 125%
I am having trouble with some of my owner drawn listboxes on High DPI monitors on Windows 10 in a dialog box. The text is chopped off at the bottom. We saw the problem on Windows 7 and were able to fix it. It is not necessarily High DPI, but when the user sets a different text scaling. I solved the problem, so I thought (!), by using a CClientDC (wrapper around GetDC()) and calling GetTextMetrics() to determine the text height. Previously, our icons had always been taller than our text so it was not a problem. With larger DPI monitors we saw some customers reporting problems when they scaled the text.
Now we are getting new reports under Windows 10. The former problem is fine under Windows 7--but Windows 7 only scales to 100, 125, and 150 percent. Windows 10 (and maybe 8? -- but no customer reports) allows user defined scaling.
So, I tracked down the problem somewhat... I knew what the font height was when I called GetTextMetrics() during WM_MEASUREITEM. I went and put some code in to debug what GetTextMetrics() was during my WM_DRAWITEM. Well, they were different--20 pixels high during WM_MEASUREITEM, and 25 pixels high during WM_DRAWITEM. Obviously, that is a problem. I want the GetTextMetrics() to have the same results in both places.
My thought was that the only real difference I could think of was that during WM_MEASUREITEM I am calling GetDC() via CClientDC constructor, and that during WM_DRAWITEM I am using an already constructed HDC (which probably was from a return of GetPaint() inside GDI32.dll or another system DLL).
I thought maybe the BeginPaint() does something like select the windows HFONT into the HDC...
So, inside my WM_MEASUREITEM after getting the DC, I select the font of the listbox into the HDC, and then I call GetTextMetrics(). Lo and behold, the numbers match now in WM_MEASUREITEM and WM_DRAWITEM.
However, I don't know if I just got lucky. It's all just guesswork at this point.
Does BeginPaint() select the window font into the DC whereas GetDC() does not? Does the default handler of WM_PAINT for an owner drawn LISTBOX or COMBOBOX do something like select the window font into the paint DC?
BOOL DpiAwareMeasureGraphItem(LPMEASUREITEMSTRUCT lpM, CWnd* pWnd)
{
int iItemHeight = INTERG_BITMAP_HEIGHT + 4;
if (pWnd)
{
CClientDC dc(pWnd);
if (dc.GetSafeHdc())
{
CFont* pOldFont = dc.SelectObject(pWnd->GetFont()); // seems to fix it on Windows 10, but is it luck?
TEXTMETRIC tm;
memset(&tm, 0, sizeof(tm));
dc.GetTextMetrics(&tm);
LONG tmHeight = tm.tmHeight + 4; //pad
iItemHeight = max(iItemHeight, tmHeight);
dc.SelectObject(pOldFont);
}
}
lpM->itemHeight = iItemHeight;
return (TRUE);
}
Neither GetDC() or BeginPaint() initialise the DC they return with anything other than the default system font. But WM_DRAWITEM is different - it gives you an already-initialised DC to draw into.
The method you stumbled across is the right one. WM_MEASUREITEM doesn't supply a DC at all, so if you need one for size calculations you're responsible for obtaining it and setting it up with the appropriate font.
The rich text in an NSTextView displays on my screen (27" Mac) a lot smaller than the font size would imply, although it prints correctly, and is the correct size if pasted in to another app (e.g. OpenOffice). TextEdit shows the same behaviour.
The following lines in awakeFromNib fixes this more or less exactly.
[myTextView scaleUnitSquareToSize:NSMakeSize(96.0/72, 96.0/72)];
myTextView.layoutManager.usesScreenFonts = NO;
So it looks as if the screen is using 96 points per inch. If I don't have the 2nd line, the text is slightly squashed up, and monotext mangled. Obviously I shouldn't hard code the scale factor, but where can I find the factor to put there? From [NSScreen mainScreen].deviceDescription ( a dictionary) I get NSDeviceResolution = {72, 72}", so it seems that's not what's being used.
I'm not sure this is wise, but you can get the "real" DPI from the display mode:
CGDirectDisplayID displayID = [window.screen.deviceDescription[#"NSScreenNumber"] unsignedIntValue];
CGSize size = CGDisplayScreenSize(displayID);
CGDisplayModeRef mode = CGDisplayCopyDisplayMode(displayID);
NSSize dpi;
dpi.width = CGDisplayModeGetWidth(mode) * 25.4 / size.width;
dpi.height = CGDisplayModeGetHeight(mode) * 25.4 / size.height;
CGDisplayModeRelease(mode);
[myTextView scaleUnitSquareToSize:NSMakeSize(dpi.width/72, dpi.height/72)];
myTextView.layoutManager.usesScreenFonts = NO;
Some display modes are letterboxed. These will presumably never be used as the general mode for the Mac GUI; they'd only be used by full-screen games. However, if you want your app to handle such a mode, it should account for the fact that the mode size does not correspond to the full screen size in one dimension and the DPI calculation above will be off. I don't think there's a direct way to figure that out. You have to examine all available display modes to see if there's one that's the same in relevant properties (size, pixel encoding, etc.) as the current mode but whose IOKit flags indicate that it's stretched (CGDisplayModeGetIOFlags(mode) & kDisplayModeStretchedFlag != 0) while the current mode is not. In that case, you probably want to assume the pixels are square and pick the smaller of dpi.width and dpi.height to use for both.