WM_DPICHANGED event suggesting bad new position for window - c++

I have two monitors connected to my desktop. A 3840x2160 main monitor on the left set to 150% scaling (144 DPI) and a 1920x1080 monitor on the right set to 100% scaling (96 DPI). The are aligned on the bottom edge. This is on Windows 8.1, but the same issue occurs on Windows 10.
My application is C++ using native Win32 windows. It is set at per-monitor DPI aware.
I have a dialog window that is a borderless (not title bar either) window with a custom title bar inside the client area. The user can drag this custom title bar to move the window, and internally it gets moved using SetWindowPos().
When this window is dragged from the High-DPI left monitor over to the regular monitor on the right, I get a WM_DPICHANGED message. The current RECT for the window is:
curRect = {LeftTop(3527, 1099) RightBottom(4157, 2098) WidthHeight[630 x 999]}
The new suggested RECT that the WM_DPICHANGED message gives me is:
newRect = {LeftTop(3527, 1099) RightBottom(3947, 1765) WidthHeight[420 x 666]}
If I pass the new RECT to SetWindowPos as the documentation for WM_DPICHANGED suggests, then the window is now considered back on my high-DPI monitor, and in the SetWindowPos() I get another WM_DPICHANGED message telling me to change the window pos/size again. This occurs before my original WM_DPICHANGED event has returned.
This makes sense based on the new suggested RECT since the left edge doesn't get moved, but the width is getting reduced.
The WM_DPICHANGED message for the same window doesn't have this issue if I'm using an actual non-client titlebar. The left edge is moved in the new suggested RECT.
Any suggestions on how to handle this? Any way to minimally change the new suggest RECT such that it ensures the window w/h is as suggested, but it's positioned so it doesn't fire another DPICHANGED event (stays on the new monitor).
Thanks!

Related

Flickering on window when resizing from left side

It seems that my window is flickering whenever I move and window and resize at the same time. This usually occurs when sizing is done from the left side of the window.
Why does this flickering happen? In other words, what is the OS doing when you reposition a window?
Note: I do not experience flickering when resizing from the right side which means the window is not necessarily moving its origin X and Y.
Resizing a window under Windows involves several messages sent between the OS and the window's handler (the lpfnWndProc member of WNDCLASSEX structure used to register the window's class). You can discover them by yourself using some message monitoring tool. Spy++ that comes with Visual Studio is one such tool.
One interesting message is WM_NCCALCSIZE: this message, called during window resizing, can generate two rectangles (when WVR_VALIDRECTS flag is set): source and target specifying what content of the old window's client area can be "reused" at the new window's position. By default it's assumed that the top-left corner is a pivot:
resizing the left or top border causes the old window's content to get copied to preserve the pivot;
resizing the right ot bottom border copies nothing because the top-left corner of the window did not move.
This default copying can cause flicker, if it does not correspond to the way you position visuals during repaint. For example, everything that is displayed relative to the right or bottom border will be misplaced after resize from the left or top border: these objects will get moved unnecessarily leaving strange mix of old and new things after such resize, because only non-copied pixels will be repainted. If you try to cure the mess with InvalidateRect during, say, WM_SIZE you will get the flicker (the time interval where things are misplaced is very short but it still exists).
The easiest way to disable this behavior is by setting the CS_HREDRAW and CS_VREDRAW Class Styles for your window.
A 2018 update.
The WM_NCCALCSIZE WVR_VALIDRECTS trick is still a good way from preventing Windows XP/Vista/7 SetWindowPos from doing a needless BitBlt that causes the flickering.
However, Microsoft did it again and on Windows 8/10, the DWM window manager adds another layer of BitBlt on top of the legacy SetWindowPos BitBlt which can cause the same problem and is harder to work around.
For an explanation of why the unwanted BitBlt causes flickering, as well as sample code of the WM_NCCALCSIZE WVR_VALIDRECTS trick and some code ideas for how to prevent Windows 8/10 Aero from doing the same, please see:
How to smooth ugly jitter/flicker/jumping when resizing windows, especially dragging left/top border (Win 7-10; bg, bitblt and DWM)?

How To Capture Mouse Movement Over Caption Bar In C++ Win32?

I have my own custom painted caption bar for a window. I have also painted a bitmap on this bar which I want to know when someone hovers over it with the mouse cursor. I have handled the WM_NCMOUSEMOVE message but this seems to only be triggered over the borders and not my caption area itself.
How can I detect when the mouse is over a certain area of the caption bar?
This is with Visual C++ 2010.
Update:
I have worked out the problem to an extent. I (deliberately) do not have the WS_CAPTION style set on the window and instead I handle the WM_NCCALCSIZE and manually adjust the size of the non-client area - shifting it down by 20 pixels in this case. This means that the area that I am "forcing" as a non-client area does not get WM_NCMOUSEMOVE messages but neither does it get WM_MOUSEMOVE messages either. One way round it is to force the WM_CAPTION style, get rid of my custom code on WM_NCCALCSIZE but this isn't ideal. Is there a way round it?
Update2:
It is the same problem with WM_NCHITTEST messages. The area does not get these triggered unless I have WS_CAPTION style set which I don't want. Also it is somehow affecting the client area in that it no longer gets WM_LBUTTONDOWN messages.
I am not quite positive what you mean, but I think you are saying you want to check for a certain section of the caption bar being moused over. if so, then maybe try tracking window size and position, and then check to see if, relative to your window position and size, that section of the bar would be moused over (in pixels). This is the only way I can think of that doesn't use OS specific functions, which I try my best to avoid.

Child Windows that does not occupy client area

In the Win32 API (pure win32), The Menu bar does not occupy any area from the client area of the window. Which means the origin coordinates of the client area is right under the menu bar to the left.
When we create child window controls using CreateWindow (or any other method), that window takes some area of the client-area.
eg:- Creating a button which is at (xPos = 0, yPos = 0) and (width=width_of_client_area, height=20).
After creating the button if you'll use a GDI function like this, it'll be drew below the button:
Rectangle(hdc, 0,0, 200, 200);
But when creating a menu bar, it doesn't occupy client area. (GDI will not be drew under menu).
FINAL QUESTION:
How can i create a control on my parent window like the menu bar ?
The menu is rendered in the non-client area of the window, which is driven by a completely different set of window messages. Keep in mind that you don't actually create child windows for these types of controls. You will need to provide all the rendering and event handling for the customization you want to add. This means that if you want to add a button or checkbox you will need to implement it yourself. You can get your started with a handful of steps but there may be other things that need to be done depending on your requirements.
The first step is to process the WM_NCCALCSIZE message. This will allow you to adjust the size of the non-client area so that you have more space to draw the custom control. Basically you will pass this message on to the default window proc then modify the location and dimensions (just drop the top down X pixels) before returning.
The next step is to handle WM_NCPAINT message. Pass the message on to the default window proc then draw your custom visuals in the area you have reserved. You will be working with a window DC so you can draw to the entire window. It's important to keep this in mind so you don't destroy anything in the client area.
The last item that needs to be done is to handle mouse messages like WM_NCMOUSEMOVE. If the mouse event occurs outside the area where your control is located pass the message to the default window proc, otherwise handle the message yourself and skip the default proc. you will need to manage some state data for keeping track of when a button is down or not so as not to cause conflicts with the default window proc.
Keep in mind that when you want to draw directly to the non-client area you need to call GetWindowDC() instead of GetDC() to acquire a device context.
[Note: A good Google dance will be something like "WinAPI non-client area"]

How to make a window that's full screen but still shows the task bar

I'd like to make a captionless window that covers the entire desktop, but still shows the task bar. What is the best way to do this?
I can detect where the taskbar is and just resize my window to exclude it, but then I need to know when the user changes the size / position of the task bar.
Or, is there a combination of window styles or something else that will make sure my window is always behind the task bar?
Set your window placement using the return value from SystemParametersInfo, passing SPI_GETWORKAREA as a parameter.
Retrieves the size of the work area on the primary display monitor.
The work area is the portion of the screen not obscured by the system
taskbar or by application desktop toolbars. The pvParam parameter must
point to a RECT structure that receives the coordinates of the work
area, expressed in virtual screen coordinates.
Maximise a window with no caption/border.

How to intercept mouse events of a transparent window?

I have a transparent window (created with WS_EX_LAYERED) and I'd like to receive mouse events of the zero-alpha regions.
As far as I know, I could:
1) Use mouse hook
2) Paint the background with almost completely transparent color (that has an opacity of 1)
However, the first solution is time consuming and the 2nd one will slow my rendering time as my window is stretched almost all over the desktop and most of the pixels are completely transparent at the moment.
Is there another way receiving those mouse events?
According to MSDN:
Hit testing of a layered window is
based on the shape and transparency of
the window. This means that the areas
of the window that are color-keyed or
whose alpha value is zero will let the
mouse messages through. However, if
the layered window has the
WS_EX_TRANSPARENT extended window
style, the shape of the layered window
will be ignored and the mouse events
will be passed to other windows
underneath the layered window.
However, in a new thread you could get continuously the coordinates of the mouse with GetCursorPos and if the position is inside one of your icons (regardless, that it's over a zero alpha pixel inside the icon) you handle it. Not too much better than the hook