WM_DPICHANGED reducing size of the window - windows

Setup:
Windows 10 Pro v1903
1st Display: 1920x1080 default DPI scaling
2nd Display: 1920x1080, 50% DPI scaling.
I am having problems implementing some DPI changes for Windows. The suggested rect (lParam of WM_DPICHANGED) is returning values of a smaller window once it gets to the scaled screen.
If I create a 1280x720 Window on 1st Display, then move it to the second display the suggested size becomes 1922x1044. The width makes sense since the resolution scaled should be 1920x1080. So I'm guessing it's factoring in the pixels for the borders. However, the height it is returning on the suggested rect, it smaller than what the Window should be if it was scaled properly. Now that it has shrunk to the new height if I go back to the original display, the new suggested size becomes 1280x694, we lose the original Window size.
I have tried manually setting the Window position to factor in a self calculated scale, (width * scale, height * scale) but it does not work properly since it does not factor borders and title bars correctly in SetWindowPos as I'm guessing it's expecting you to include those calculated sizes.
I have also tried reducing the window size to 1280x690 and moving it to the display 2 then back to display 1 and the size returns to the original size. So it's definitely related to the resolution of the window and the target display. I have tried testing other applications with DPI Scaling with a 1280x720 window (such as Microsoft Office) and they resize the window going to Display 2 and returning to Display 1 the size is the correct 1280x720 upon return, it does not experiencing the shrinking.
Am I missing something? I am using SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)
Any help would be greatly appreciated.

but it does not work properly since it does not factor borders and
title bars correctly
You could try to use GetWindowRect to get the bounding rectangle of the window. And then scale the rectangle:
static float dpi;
BOOL InitInstance(...)
{
dpi = GetDpiForWindow(hWnd);
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DPICHANGED:
{
WORD newdpi = HIWORD(wParam); //The values of X-axis and the Y-axis are identical for Windows apps
float scale = newdpi / dpi;
dpi = newdpi;
//RECT* rect = (RECT*)lParam;
RECT old;
GetWindowRect(hWnd, &old);
SetWindowPos(hWnd,
NULL,
old.left,
old.top,
(old.right - old.left) * scale,
(old.bottom - old.top) * scale,
SWP_NOZORDER | SWP_NOACTIVATE);
return 0;
}
return 0;
}

Related

How to determine horizontal and vertical extents in device units with SetViewportExtEx() and printer?

I am experimenting with using the Windows GDI API for printing and have been doing a few experiments to attempt to understand the translation and how window and viewport extents work.
Examples I have found are using GetDeviceCaps() to get the HORZRES and VERTRES dimensions (despite the known fact they can be unreliable and inaccurate) and then using these values with SetViewportExtEx() however they divide the values returned by GetDeviceCaps() by two.
Why is the cxpage and the cypage values halved and how can I predict the values to use and the effect on the printed output? Is this due to using MM_ISOTROPIC as the mapping mode?
Examples use something like the following code:
int cxpage = GetDeviceCaps (hDC, HORZRES);
int cypage = GetDeviceCaps (hDC, VERTRES);
SetMapMode (hDC, MM_ISOTROPIC);
SetWindowExtEx(hDC, 1500, 1500, NULL);
SetViewportExtEx(hDC, cxpage/2, cypage/2, NULL);
SetViewportOrgEx(hDC, 0, 0, NULL);
In my actual test program I have the following function to print a page when my main Windows message handler sees the message IDM_PRINT generated when the user selects Print from the File menu of the test application. The handler uses PrintDlg() to get a handle to a Device Context (hDC) then calls this function to exercise the printing.
int PrintMyPages (HDC hDC)
{
int cxpage = GetDeviceCaps (hDC, HORZRES);
int cypage = GetDeviceCaps (hDC, VERTRES);
// When MM_ISOTROPIC mode is set, an application must call the
// SetWindowExtEx function before it calls SetViewportExtEx. Note that
// for the MM_ISOTROPIC mode certain portions of a nonsquare screen may
// not be available for display because the logical units on both axes
// represent equal physical distances.
SetMapMode (hDC, MM_ISOTROPIC);
// Since mapping mode is MM_ISOTROPIC we need to specify the extents of the
// window and the viewport we are using to see the window in order to establish
// the proper translation between window and viewport coordinates.
SetWindowExtEx(hDC, 1500, 1500, NULL);
SetViewportExtEx(hDC, cxpage/2, cypage/2, NULL);
SetViewportOrgEx(hDC, 0, 0, NULL);
// figure out the page size in logical units for the loop that is printing
// out the pages of output. we must do this after setting up our window and
// viewport extents so Windows will calculate the DPtoLP() for the specified
// translation correctly.
RECT pageRect = {0};
pageRect.right = GetDeviceCaps (hDC, HORZRES);
pageRect.bottom = GetDeviceCaps (hDC, VERTRES);
DPtoLP(hDC, (LPPOINT)&pageRect, 2);
// create my font for drawing the text to be printed and select it into the DC for printing.
HFONT DisplayFont = CreateFont (166, 0, 0, 0, FW_DONTCARE, false, false, false, DEFAULT_CHARSET,
OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
DEFAULT_PITCH | FF_DONTCARE, _T("Arial Rounded MT Bold"));
HGDIOBJ hSave = SelectObject (hDC, DisplayFont);
POINT ptLine = {300, 200}; // our printer line cursor for where printing should start.
static DOCINFO di = { sizeof (DOCINFO), TEXT ("INVOICE TABLE : Printing...")};
StartDoc (hDC, &di);
StartPage (hDC);
for (int i = 1; i < 30; i++) {
TCHAR xBuff[256] = {0};
swprintf (xBuff, 255, _T("This is line %d of my text."), i);
TextOut (hDC, ptLine.x, ptLine.y, xBuff, _tcslen(xBuff));
// get the dimensions of the text string in logical units so we can bump cursor to next line.
SIZE lineSize = {0};
GetTextExtentPoint32(hDC, xBuff, _tcslen(xBuff), &lineSize);
ptLine.y += lineSize.cy; // bump the cursor down to the next line of the printer. X coordinate stays the same.
if (ptLine.y + lineSize.cy > pageRect.bottom) {
// reached the end of this page so lets start another.
EndPage (hDC);
StartPage (hDC);
ptLine.y = 200;
}
}
// end the final page and then end the document so that physical printing will start.
EndPage (hDC);
EndDoc (hDC);
// Release the font object that we no longer need.
SelectObject (hDC, hSave);
DeleteObject (DisplayFont);
return 1;
}
When I modify the call of SetViewportExtEx() from SetViewportExtEx(hDC, cxpage/2, cypage/2, NULL); (output on the right in the image below) to SetViewportExtEx(hDC, cxpage, cypage, NULL); (output on the left in the image below) the printed text seems almost double in height and width.
Additional Notes on Extents and Mapping Modes
Charles Petzold Programming Windows 5th Edition (Chapter 5 - Basic Drawing, page 180) writes:
The formulas also include two points that specify "extents": the point
(xWinExt, yWinExt) is the window extent in logical coordinates;
(xViewExt, yViewExt) is the viewpoort extent in device
coordinates. In most mapping modes, the extents are implied by the
mapping mode and cannot be changed. Each extent means nothing by
itself, but the ratio of the viewport extent to the window extent is a
scaling factor for converting logical units to device units.
For example, when you set the MM_LOENGLISH mapping mode, Windows sets
xViewExt to be a certain number of pixels and xWinExt to be the length in hundredths of an inch occupied by xViewExt pixels. The
ratio gives you pixels per hundredths of an inch. The scaling factors
are expressed as ratios of integers rather than floating point values
for performance reasons.
Petzold then goes on to discuss MM_ISOTROPIC and MM_ANISOTROPIC on page 187.
The two remaining mapping modes are named MM_ISOTROPIC and
MM_ANISOTROPIC. These are the only two mapping modes for which
Windows lets you change the viewport and window extents, which means
that you can change the scaling factor that Windows uses to translate
logical and device coordinates. The word isotropic means "equal in
all directions"; anisotropic is the opposite - "not equal." Like the
metric mapping modes shown earlier, MM_ISOTROPIC uses equally scaled
axes. Logical units on the x-axis have the same physical dimensions as
logical units on the y-axis. This helps when you need to create images
that retain the correct aspect ratio regardless of the aspect ratio of
the display device.
The difference between MM_ISOTROPIC and the metric mapping modes is
that with MM_ISOTROPIC you can control the physical size of the
logical unit. If you want, you can adjust the size of the logical unit
based on the client area. This lets you draw images that are always
contained within the client area, shrinking and expanding
appropriately. The two clock programs in Chapter 8 have isotropic
images. As you size the window, the clocks are resized appropriately.
A Windows program can handle the resizing of an image entirely through
adjusting the window and viewport extents. The program can then use
the same logical units in the drawing functions regardless of the size
of the window.
... why do so many examples use SetViewportExtEx(hDC, cxpage/2, cypage/2, NULL); where cxpage and cypage are GetDeviceCaps(hDC, HORZRES) and GetDeviceCaps(hDC, VERTRES) respectively[?]
I suspect MM_ISOTROPIC is often used for plotting graphs where the origin would be in the center of the page rather than the corner. If we took your code and tweaked it to move the origin to the center of the printable region, like this:
SetWindowExtEx(hDC, 1500, 1500, NULL);
SetViewportExtEx(hDC, cxpage/2, cypage/2, NULL);
SetViewportOrgEx(hDC, cxpage/2, cypage/2, NULL);
Then you could plot using logical coordinates that range from -1500 to +1500. (You might also want to flip the sign of one of the y-extents to get positive "up".)
For textual output, I don't see any advantage to halving the viewport extents and I would keep the origin in the upper left.

TEXTMETRIC is giving wrong height when resizing text with mouse wheel

I'm calculating the number of lines in a rich edit control.
Currently I'm using next code
TEXTMETRIC tm; {
HDC hdc = GetDC(hwndRichEdit);
GetTextMetrics(hdc, &tm);
ReleaseDC(hwndRichEdit, hdc);
}
RECT editRect;
GetClientRect(hwndRichEdit, &editRect);
long int countLines = (editRect.bottom - editRect.top) / (tm.tmHeight + tm.tmExternalLeading);
The code yields out the right number of lines until I start to change the size of the text via mouse wheel + ctr.
Is it possible to get the right text height even if the text is resized with mouse wheel?
N.B. I'm recalculating the number of lines with EN_UPDATE notification.
You can send an EM_GETZOOM message to the control to retrieve the current zoom ratio. Dividing the countLines value by the zoom ratio should yield the correct line count. Use the MulDiv API call to implement the division.

Are the coordinates in WINDOWPOS on WM_WINDOWPOSCHANGED in parent coordinates or screen coordinates?

Quick and simple one this time. I have a subclassed tab control that handles WM_WINDOWPOSCHANGED to, when not SWP_NOSIZE, resize its content. It passes the WINDOWPOS lParam's cx and cy fields to TCM_ADJUSTRECT to get the content rect size.
I need to do this on command as well (after changing tabs, say). However, I can't just do a dummy resize to the same size; even with an explicit SetWindowPos(), real Windows seems to add SWP_NOSIZE itself if the size doesn't change. So I want to write the analogous code to my WM_WINDOWPOSCHANGED handler.
My question is: what coordinate system are the coordinates given to WM_WINDOWPOSCHANGED given in, parent coordinates or screen coordinates? GetWindowRect() returns screen coordinates, so I'd like to know if I need to convert the coordinates to get the same values that I would get in WM_WINDOWPOSCHANGED. The documentation for WM_WINDOWPOSCHANGED doesn't say; neither does the documentation for WINDOWPOS.
Thanks.
WINDOWPOS, GetWindowRect, GetCursorPos, etc. give screen coordinates. When you use SetWindowPos you have to supply coordinates in relation to parent. This is straight forward for main window and popup windows which use screen coordinates. For moving child windows, you can use ScreenToClient and ClientToScreen for conversion.
For example, this will find the coordinates of OK button in relation to top-left corner of dialog box:
RECT rcOK;
HWND hitem = ::GetDlgItem(m_hWnd, IDOK);
GetWndRect(rcOK, hitem, m_hWnd);
void GetWndRect(RECT &rect, HWND item, HWND parent)
{
::GetWindowRect(item, &rect);//screen coordinates of OK button
POINT offset{ 0 };
ClientToScreen(parent, &offset); //Top-left (0,0) of client area of dialog -> screen coordinates
rect.left -= offset.x;
rect.right -= offset.x;
rect.top -= offset.y;
rect.bottom -= offset.y;
//client coordinates of OK button in relation to Dialog's Top-Right
}
Now we can move up the OK button by 10px:
rc.top -= 10;
rc.bottom -= 10;
::SetWindowPos(hitem, 0, rc.left, rc.top, 0, 0, SWP_NOSIZE);

GUI Button for all screen resolutions

I am using OnGUI Buttons on a car game and attached forward and backward buttons on it. But they keep on adjusting on all screens badly.
I have tried various online solutions, but failed. I am using this:
GUI.RepeatButton (new Rect (Screen.width - 780, Screen.height - 130, 120, 120), LeftBtnTexture, TransparentStyle)
I know this is wrong, what will be alternative width and height setting for all screens. :(
Take a look at Screen.dpi - http://docs.unity3d.com/ScriptReference/Screen-dpi.html
It will allow you to specify button size in inches. But be aware that dpi could be unavailable (equals zero) or maybe even incorrect, therefore you should do some sanity check. For example:
public float sizeInInches = 0.5;
float dpi = Screen.dpi;
if (dpi < 25 || dpi > 800) {
dpi = 150;
}
float sizeInPx = sizeInInches * dpi;
As an alternative for some cases, you may want to set GUI element size in percent of screen dimensions, like sizeInPx = Screen.width * 0.1;

How to get the title bar height and width for aero and basic design

I am using GetSystemMetrics(SM_CXSIZEFRAME) for the width of the border, it works with the basic design, but not with aero, how can I improve it, so it works with aero?
I am using GetSystemMetrics(SM_CYCAPTION) for the height of the title bar but the value is too small for both, basic and aero design, what am I doing wrong here?
In unthemed Windows, GetSystemMetrics(SM_CYCAPTION) is the height of the text in the title bar; you need to add in the size of the frame and border padding (GetSystemMetrics(SM_CYSIZEFRAME) + GetSystemMetrics(SM_CYEDGE) * 2).
For themed Windows (which is the default these days), GetThemeSysSize is most likely the function you're looking for; in particular, GetThemeSysSize(SM_CXBORDER) for the border width, and GetThemeSysSize(SM_CYSIZE) + GetThemeSysSize(SM_CXPADDEDBORDER) * 2 for the title bar.
I had a similar problem, where I needed to subtract the title bar height from the mouse Y to render a cursor in the window.
After looking around, and looking at functions like GetSystemMetrics, I ended up just using GetWindowRect and ClientToScreen.
I got the screen position of the client area using ClientToScreen with point 0,0.
Then subtracted that from the window top retrieved through GetWindowRect. Result is the distance from the top of the window to the inside of the window. Or the titlebar height.
int getWindowHeadSize()
{
RECT Rect;
GetWindowRect(hWnd, &Rect);
POINT point = { 0, 0 };
ClientToScreen(hWnd, &point);
return point.y - Rect.top + GetSystemMetrics(SM_CYSIZEFRAME) + GetSystemMetrics(SM_CYEDGE) * 2;
}

Resources