Changed an MFC app to per-monitor DPI aware. On the WM_DPICHANGED message I wanted to changed the fonts of the CMFCMenuBar to be scaled correctly. So I added:
LOGFONT logfont;
GetGlobalData()->fontRegular.GetLogFont(&logfont);
logfont.lfHeight=g_DPIHelper.ScaleNonClientMetricsFont(logfont.lfHeight);
if (!m_wndMenuBar.SetMenuFont(&logfont)) {
TRACE0("Unable to set menu font\n");
}
I confirmed that logfont.lfHeight went from -11 to -17 in the test case. I confirmed the SetMenuFont() call returned success. Yet the CMFCMenuBar font shown on the menu bar is the same size as it was before. What am I missing?
TIA!!
The problem is that after WM_DPICHANGED a WM_SETTINGCHANGED is sent and MFC then calls AFX_GLOBAL_DATA::OnSettingsChange() which calls UpdateFonts() which uses GetNonClientMetrics() which resets it back to the wrong size because the size is based on when the application started.
On WM_DPICHANGED calculate your scaling factor. Then do the actual font change in WM_SETTINGCHANGED which allows the font change size to take affect when you change the DPI of the screen (not sure if WM_SETTINGCHANGED called if moved to another window, if not the change can be done in WM_DPICHANGED. Here's one method:
void CMainFrame::OnSettingChange(UINT uFlags, LPCTSTR lpszSection)
{
CFrameWndEx::OnSettingChange(uFlags, lpszSection);
LOGFONT logfont;
GetGlobalData()->fontRegular.GetLogFont(&logfont);
logfont.lfHeight=g_DPIHelper.Scale(-11);
if (!m_wndMenuBar.SetMenuFont(&logfont)) {
TRACE0("Unable to set menu fonts\n");
}
}
Related
I want to create a window with the following properties:
Thick border ("double border" as described in MSDN)
Not resizable
Icon in the title
Close button in the title
It sounds so simple and we can find this kind of windows everywhere in Windows. But as soon as I disable ability to resize my window I also lose the thick border. Here is the illustration of my problem:
The height of my test window's caption bar is slightly less than that of a Shell property dialog, but I would like it to be the same height.
I have tried to use WinSpy and copy all styles from Explorer's window to mine, but this did not help.
I don't know what's going on under the hood, but I found two solutions:
Switch to the newer Platform Toolset (which means drop support of Windows XP)
Disable resizing by writing custom WM_NCHITTEST handler
The second option is quite simple and I do not want drop Windows XP support for a silly reason. Job is done:
LRESULT CListEditorDlg::OnNcHitTest(CPoint point)
{
LRESULT retVal = CDialog::OnNcHitTest(point);
switch (retVal)
{
case HTBOTTOM:
case HTBOTTOMLEFT:
case HTBOTTOMRIGHT:
case HTLEFT:
case HTRIGHT:
case HTTOP:
case HTTOPLEFT:
case HTTOPRIGHT:
retVal = HTBORDER;
}
return retVal;
}
When adding a system tray icon from in Windows there are two versions of API that we can pass to Shell_NotifyIcon() via NOTIFYICONDATA structure. There are subtle differences between the two API, and these are not listed anywhere on MSDN. It took me some effort to figure out some of the differences, which I am going to share now. Improvements/additions to the answer are always welcome.
PS: This question is purely for sharing what I have learnt over last few days experimenting with windows DPI scaling.
uVersion member of the NOTIFYICONDATA structure can have 3 possible values, representing the version of the API being used to create the taskbar icon.
0 Use this value for applications designed for Windows versions prior to Windows 2000.
NOTIFYICON_VERSION Use the Windows 2000 behavior. Use this value for applications designed for Windows 2000 and later.
NOTIFYICON_VERSION_4 Use the current behavior. Use this value for applications designed for Windows Vista and later.
When it comes to message handler for the tray icon, the wParam, and uParam have the differences as illustrated in the following image.
Notice that in NOTIFYICON_VERSION_4 the wParam gives X, and Y coordinates of various events, but there is no provision for getting the coordinates in NOTIFYICON_VERSION. This gives rise to an interesting behaviour (which was a cause of a BUG I was trying to solve). If you use NOTIFYICON_VERSION, and then invoke the context menu of the tray icon, then the mouse cursor, wherever it may be while you are invoking the menu, gets placed right at the center of the tray icon. Even if you use keyboard (WINDOWS+B) for invoking context menu of the icon, the mouse cursor still moves to the icon.
This may not be of particular interest to you until you look at this particular BUG I am trying to solve in Pico torrent application.
Here is the scenario.
OS : Windows 10
Application isn't per-monitor DPI aware, but is system level DPI aware.
There is an initial value of Desktop scaling set, say 150%, when the user logs in.
Pico torrent is running.
DPI scaling value is changed to, say 125%
Pico torrent's context menu is invoked
The context menu will not be displayed at its proper place, and will be displaced a little, showing a deviance.
See the following images to understand what's happening.
The problem is that although MSDN says that GET_X_LPARAM(wParam), and GET_Y_LPARAM(wParam) should give correct values in the handler of tray icon, but it doesn't, in the presence of DPI scaling (i.e. for a change in DPI scaling without doing a sign out and sign in). On the other hand the API GetCursorPos() returns the correct value of mouse cursor coordinates. Note that NOTIFYICON_VERSION_4 along with GetCursorPos() will not work, since the context menu can be invoked using keyboard, at which the mouse cursor can be anywhere on the screen(s).
So, how do you combine all the knowledge just learnt to display the tray icon's context menu correctly when DPI scaling is done in the manner above, without making you application per-monitor DPI aware (for per-monitor DPI aware applications GET_X_LPARAM(wParam), and GET_Y_LPARAM(wParam) always return correct value)?
Use NOTIFYICON_VERSION instead of NOTIFYICON_VERSION_4, this will position the mouse cursor at the tray icon when context menu is invoked, and then use GetCursorPos() to get mouse cursor's position. Display the context menu using TrackPopupMenu() with the coordinates.
PS: In the example above the DPI scaling value is changed from 150% to 125%. The context menu deviance is more pronounced when DPI scaling is done from a bigger value to a smaller value, when your tray icon area is on lower right of the screen. This is because when DPI scaling is done, and windows magnifies UI elements which are not per-monitor aware, using DPI virtualization, then things move right-wards, and down-wards. eg. if in an application a windows rectangle is (0,0,100,100) (screen coordinates), then after magnification to 150%, it may become (0,0,150,150). Now for tray icon's menu, if you specify coordinates which lie beyond bottom-right of the screen, then the OS will still display is at a bottom right position which lies inside the screen, and which ensures that menu is displayed properly. eg. if a screen is 1920x1080, and TrackPopupMenu()is given (10000,10000) for menu, the menu will still be displayed inside the 1920x1080 screen rectangle. Thus increasing DPI scaling will not move the context menu any further, if it has already reached the right-bottom most position.
#sahil-singh you are right about it, and I agree with everyone else that your application should be DPI aware, but when this is not a point here.
I had a similar issue where my application is (still) not DPI aware and GET_X_LPARAM(wParam) will return non-virtual coordinates. After passing this value to the TrackPopupMenu() I get wrong position on the screen.
The best way is to use GetMessagePos() instead of wParam. In this case, Windows will give you new DWORD with virtual coordinates and then use GET_X_LPARAM/GET_Y_LPARAM to get a values that you can pass to the TrackPopupMenu().
This seems to (2 years later in 2019) be documented on MSDN:
NOTIFYICONDATAA structure
uCallbackMessage
Type: UINT
When the uVersion member is either 0 or NOTIFYICON_VERSION, the wParam parameter of the message contains the identifier of the taskbar icon in which the event occurred. This identifier can be 32 bits in length. The lParam parameter holds the mouse or keyboard message associated with the event. For example, when the pointer moves over a taskbar icon, lParam is set to WM_MOUSEMOVE.
When the uVersion member is NOTIFYICON_VERSION_4, applications continue to receive notification events in the form of application-defined messages through the uCallbackMessage member, but the interpretation of the lParam and wParam parameters of that message is changed as follows:
LOWORD(lParam) contains notification events, such as NIN_BALLOONSHOW, NIN_POPUPOPEN, or WM_CONTEXTMENU.
HIWORD(lParam) contains the icon ID. Icon IDs are restricted to a length of 16 bits.
GET_X_LPARAM(wParam) returns the X anchor coordinate for notification events NIN_POPUPOPEN, NIN_SELECT, NIN_KEYSELECT, and all mouse messages between WM_MOUSEFIRST and WM_MOUSELAST. If any of those messages are generated by the keyboard, wParam is set to the upper-left corner of the target icon. For all other messages, wParam is undefined.
GET_Y_LPARAM(wParam) returns the Y anchor coordinate for notification events and messages as defined for the X anchor.
My purpose is to size a window to a width/height greater than the size of my physical screen programmatically under Win32. How can I do this?
On my systems it seems the maximum size of a given window is bound by the size of my screen whether programmatically or whether sizing manually by dragging the sizing cursor.
I have tried programmatically with SetWindowPos() and MoveWindow() and both cap the size of the target window. Oddly I know some people do not have this 'cap' so I wonder whether this is perhaps due to some OS setting (registry). Does anyone know something about this? Or perhaps some way to workaround it?
// Edit: new developments
I am testing on Windows XP and Windows 7. The graphics cards I'm using are a NVIDIA Quadro NVS 290 (256MB) and a Geforce 9800GT (1GB). After further investigation it looks like Windows is intercepting the message and fiddling with the parameters. For example, if you call SetWindowPos to make a target 2000x2000 it will only receive a WM_SIZE for the capped x/y.
Implement a message handler for WM_GETMINMAXINFO to stop Windows from applying the sane default behavior:
case WM_GETMINMAXINFO: {
DefWindowProc(hWnd, message, wParam, lParam);
MINMAXINFO* pmmi = (MINMAXINFO*)lParam;
pmmi->ptMaxTrackSize.x = 2000;
pmmi->ptMaxTrackSize.y = 2000;
return 0;
}
Windows with a thick frame (to allow user resize) are restricted from growing larger than the desktop.
Try SetWindowLong() clearing the THICKFRAME (0x40000) flag.
The following should allow programatic sizing, but the user will lose the ability to resize. If you add the Thickframe back after sizing, the user can resize, but when he does so the window will immediately shrink back to the desktop limited size.
The following is from some csharp code that also removes all borders, caption, etc:
WS style = (WS)GetWindowLong(ptr, GWL_STYLE);
style = style & ~WS.BORDER & ~WS.ThickFrame & ~WS.SYSMENU & ~WS.CAPTION | WS.POPUP;
SetWindowLong(ptr, GWL_STYLE, (int)style);
A good tool to play with window settings is uuSpy.
It's like Microsoft Spy++, but allows you to modify settings like THICKFRAME.
Yes, windows can be larger than the screen (or even the sum of all your monitors). Windows can also be positioned off-screen (which some applications do as a hack to hide while remaining active).
Perhaps the Windows 7 desktop manager is kicking in and trying to "dock" those windows to the edges of your screen for you.
You might try using the slightly lower-level API SetWindowPos, which gives you control over notifications, z-order, and other stuff.
You can get a window to be larger in resolution (and even way way larger) than your screen, using the 'Infinte Screen" software:
http://ynea.futureware.at/cgi-bin/infinite_screen.pl
Here's how to use it:
Download it, run it.
In the Oversize tab, choose the Windows you want to enlarge.
Give it the Width and Height you want. Done!
Just in case you need a large screenshot (that's how I ended up here):
If you want to get a screenshot of the window, you've got a screenshot option in the same Oversize tab. (Because screenshots are normally no bigger than the screen size, even if the window is larger). Another (and better) way to screenshot the window is using Greenshot, as you can save them in .tiff and directly watching the window.
I've got a dialog box where I need to display the standard Information icon. Here's my RC code:
ICON "",IDC_ICON_INFORMATION,18,70,21,20
I process the WM_INITDIALOG message as follows:
HICON aIcn = LoadIcon(NULL, IDI_INFORMATION);
SendDlgItemMessage(m_hWnd, IDC_ICON_INFORMATION, STM_SETICON, (WPARAM) aIcn, 0);
Everything works great under 96 DPI: the static control displays a 32x32-pixel icon.
However, when I switch to higher DPI's (through right-clicking on the Desktop, choosing Screen resolution, and clicking Make or other items larger or smaller) the icon does not scale! Since everything else scales nicely, the icon looks visually much smaller than the neighboring text. I would expect that on 144 DPI (150%) the icon dimensions will be 48x48 pixels. I did declare my application as DPI-aware through an XML manifest.
The damnedest thing is that when I use my own custom-made icon (also coming from the RC file), everything scales perfectly. Furthermore, the MessageBox function called with the MB_ICONINFORMATION flag does display a scaled version of the icon, as well.
Given these thoughts, I assume of the following:
The static control with the SS_ICON style can display scaled versions of icons.
The icon resource that contains the standard Information icon has a scaled version of the icon (48x48).
What am I doing wrong then?
Use LoadImage() instead of LoadIcon(), and specify the cxDesired and cyDesired params with the values you get from GetSystemMetrics(SM_CYICON) and GetSystemMetrics(SM_CXICON).
Or maybe just declaring your app as DPI aware could be enough? You can try that easily by simple creating a text file making it a manifest file.
See the example in the remarks section for the SetProcessDPIAware API
I've got an application window in which I'm adding the WS_THICKFRAME style and I have removed the WS_CAPTION style. When the window maximizes, I want to hide the WS_THICKFRAME, but retain the Aero-Snap feature, so I have altered my handler for WS_NCCALCSIZE to return an inflated rect with respect to the size of the window borders.
That is, the important part of the WS_NCCLIENTSIZE handler code looks like this:
...
CRect rc( lpncsp->rgrc[0] );
if (IsZoomed())
{
int borderSize = GetSystemMetrics(SM_CYSIZEFRAME);
rc.InflateRect(borderSize,topOff+borderSize,borderSize,borderSize);
}
else
rc.InflateRect(0,topOff+0,0,0);
lpncsp->rgrc[0] = rc;
...
That code effectively makes the WS_THICKFRAME hidden.
Only problem is that when the window loses focus or regains focus (while maximized) the WS_THICKFRAME gets drawn within the boundary. Is there a message in which I can return the Inflated rect back or at least re-adjust the window size to hide the WS_THICKFRAME again when the window focus is set/unset?
Yeah, that won't work. Implement a message handler for WM_GETMINMAXINFO to allow the borders of the window to fall off the screen. Beware that if you didn't set the linker's /SUBSYSTEM option to say that your program is made for Vista or Win7 (version 6,0) then Aero will lie to you when you use GetWindowRect(). The value you get back is based on thin (legacy) borders.