What is wrong with PrintWindow? - winapi

What is wrong with the following code? Why does PrintWindow return 0?
HWND hwnd = GetDesktopWindow();
CHK(hwnd);
HDC hdc = GetWindowDC(hwnd);
CHK(hdc);
if (hdc)
{
HDC hdcMem = CreateCompatibleDC(hdc);
CHK(hdcMem);
if (hdcMem)
{
RECT rc;
CHK(GetWindowRect(hwnd, &rc));
HBITMAP hbitmap = CreateCompatibleBitmap(hdc, rc.right-rc.left, rc.bottom-rc.top);
CHK(hbitmap);
if (hbitmap)
{
SelectObject(hdcMem, hbitmap);
CHK(PrintWindow(hwnd, hdcMem, 0)); //HERE return 0
DeleteObject(hbitmap);
}
DeleteObject(hdcMem);
}
ReleaseDC(hwnd, hdc);
}

PrintWindow is a fairly thin operation. What it really does is post a WM_PRINTmessage to the queue for the window in question, in this case the desktop, and hopes that that window will respond to WM_PRINT correctly if at all (see here and here).
I repro'd your behavior but I'm not 100% sure why it's failing either. Perhaps you cannot call PrintWindow on an HWND that your process does not own, or perhaps the desktop does not respond to WM_PRINT messages.
The second link above includes a comment about using BitBlt instead:
Try getting a handle (HWND) to the
desktop window - and use BitBlt to
capture all the contents. Mind you -
you'll only capture what is visible on
the screen.
Maybe this helps.

It looks like GetDesktopWindow() returns a virtual HWND whose value is universally 0x0010010 on all Windows machines. This virtual HWND does not conform to usual PrintWindow behavior so the PrintWindow() returns FALSE, and GetLastError() reports no error code on this PrintWindow call.
To make PrintWindow() work, you can instead use the HWND from GetShellWindow(), which has the title "Program Manager" from the WinSpy++ figure below.

Replace:
HWND hwnd = GetDesktopWindow();
With:
HWND hwnd = GetDesktopWindow();
hwnd = FindWindowEx( hwnd, 0, _T("Progman"), _T("Program Manager") );
I'm not sure whether this gets what you want though. If you want to take a screenshot of the entire current desktop (including whatever top level windows are visible) then BitBlt is the route you want to take.
If you want to get the taskbar as well, you can still use this method but you'll have to take 2 screenshots and stitch the results together.

Related

Get HWND from dxgi swapchain / d3d11 device

I've got a dxgiswapchain and d3d11device, and I would like to fetch a HWND from either of the two.
void OnPresent(IDXGISwapChain *swapChain) {
ID3D11Device *device = NULL;
swapChain->GetDevice(__uuidof(ID3D11Device), (void**)&device);
}
How can I do so? I briefly remember it being possible with d3d9, so I'm not sure if the same is possible with dxgi/d3d11.
Calling IDXGISwapChain::GetDesc will give you the swap chain description, it contains the HWND to the output window:
DXGI_SWAP_CHAIN_DESC swapChainDesc;
swapChain->GetDesc(&swapChainDesc);
swapChainDesc.OutputWindow; // the hwnd

How to initialize the background color of Win32 App to something other than white to avoid flash on ShowWindow?

I was looking into why when running my Windows App, it has a short flash of white background before rendering the actual app (i.e., before WM_ERASEBKGND and WM_PAINT is received).
Now, I just noticed that this problem is also present in the default sample app created by Visual Studio. At least this is the case for me when running under Windows 10,21H1 (in VS2008 & VS2013).
The only thing you have to do, after creating a "new Win32 Project", is change the background color of the window class, e.g., to the color red:
//wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.hbrBackground = (HBRUSH) CreateSolidBrush(RGB(255, 0, 0));
And then add a WM_ERASEBKGND with a Sleep to the WndProc:
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...
EndPaint(hWnd, &ps);
break;
case WM_ERASEBKGND:
Sleep(1000);
return DefWindowProc(hWnd, message, wParam, lParam);
The Sleep exaggerates the problem, causing the white background to show for at least a second.
After that the red background is drawn as expected.
I'm including a short video when running the App with those changes.
For any app, it looks quite unprofessional that the window flashes white before rendering,
especially if the interface is dark.
So my question is: What is causing this behavior?
The background color is set through RegisterClassEx and passed to CreateWindow, before calling ShowWindow(..) So Windows should know that the background color is red. So why does it render it white? Am I missing something?
Ideally, I would like to change this initial background color to something other than white, such as black. But how? I've tried drawing to the window before calling ShowWindow, without luck.
This indeed seems to be a Windows bug as demonstrated by the excellent research by the OP.
The bug is even affecting applications developed by Microsoft.
The question is what is best workaround, especially for products that need to support backwards compatibility even after a fix is released in a specific version of Windows 11 (or Windows 10).
The main problem is that it is the act of making the window visible that makes Windows paint it with the white brush prior to correctly applying the background brush, regardless of what was painted into its DC beforehand. Therefore tricks such as painting into the DC prior to showing the window are unsatisfying, as the white background will still be shown, even if only for a few frames.
One method that seems to work well is to make the window visible, but fully transparent, paint the background, and then make the window opaque. We also need to animate the activation of the window, so it doesn't just pop in. For example, we can hijack WM_SHOWWINDOW for this:
case WM_SHOWWINDOW:
{
if (!GetLayeredWindowAttributes(hWnd, NULL, NULL, NULL))
{
SetLayeredWindowAttributes(hWnd, 0, 0, LWA_ALPHA);
DefWindowProc(hWnd, WM_ERASEBKGND, (WPARAM)GetDC(hWnd), lParam);
SetLayeredWindowAttributes(hWnd, 0, 255, LWA_ALPHA);
AnimateWindow(hWnd, 200, AW_ACTIVATE|AW_BLEND);
return 0;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
Full sample code:
#include "framework.h"
#include "WindowsProject1.h"
#define MAX_LOADSTRING 100
HINSTANCE hInst;
WCHAR szTitle[MAX_LOADSTRING];
WCHAR szWindowClass[MAX_LOADSTRING];
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
HINSTANCE mInstance;
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
mInstance = hInstance;
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadStringW(hInstance, IDC_WINDOWSPROJECT1, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WINDOWSPROJECT1));
MSG msg;
while (GetMessage(&msg, nullptr, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int) msg.wParam;
}
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW | CS_CLASSDC;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINDOWSPROJECT1));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = CreateSolidBrush(RGB(255, 0, 0));
wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_WINDOWSPROJECT1);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassExW(&wcex);
}
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance;
HWND hWnd = CreateWindowExW(WS_EX_LAYERED, szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_SHOWWINDOW:
{
if (!GetLayeredWindowAttributes(hWnd, NULL, NULL, NULL))
{
SetLayeredWindowAttributes(hWnd, 0, 0, LWA_ALPHA);
DefWindowProc(hWnd, WM_ERASEBKGND, (WPARAM)GetDC(hWnd), lParam);
SetLayeredWindowAttributes(hWnd, 0, 255, LWA_ALPHA);
AnimateWindow(hWnd, 200, AW_ACTIVATE|AW_BLEND);
return 0;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
ReleaseDC(hWnd, hdc);
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}
A more controversial answer could be that this is simply a bug in Windows.
For reference, (aside from the existing GIFs from Windows 10 that I already posted) here are recordings of the sample app running with and without background erase in Windows XP, Windows 7 and Windows 11.
Windows XP:
Windows XP: Without WM_ERASEBKGND/WM_PAINT: OK (no white background)
Windows XP: With WM_ERASEBKGND: OK (no white background)
Windows 7:
Windows 7: Without WM_ERASEBKGND/WM_PAINT: NOT OK (white background)
Windows 7: With WM_ERASEBKGND: NOT OK (white background)
Windows 7: With WM_ERASEBKGND + Sleep: NOT OK (white background)
Windows 7 with Aero disabled:
Windows 7 with Aero disabled: Without WM_ERASEBKGND/WM_PAINT: OK (no white background)
Windows 7 with Aero disabled: With WM_ERASEBKGND: OK (no white background)
Windows 7 with Aero disabled: With WM_ERASEBKGND + Sleep: OK (no white background)
Windows 11 (with Animation disabled):
Windows 11: Without WM_ERASEBKGND/WM_PAINT: NOT OK (white background)
Windows 11: With WM_ERASEBKGND: OK (no white background)
Windows 11: With WM_ERASEBKGND + Sleep: NOT OK (white background)
I've added Sleep to tests where it was hard to see the issue.
To sum up:
Windows XP: No issue. Everything seems to work as expected.
Windows 7: Issue occurs when Aero is enabled (Windows 7
theme), but not when it is disabled (Classic theme).
Windows 10: Issue occurs for all tests.
Windows 11: Issue occurs, but works without Sleep added.
Most likely since this was running on a faster machine.
So although I cannot conclude anything solid from these tests, it does look like this behavior was introduced in Windows 7 with Aero.
If someone can debunk this claim, please comment below.
I've hit this issue recently. I tried mnistic's solution using layered windows and transparency but it caused problems with rendering pane captions in the MFC app I'm working on. However, I've found a simple solution which appears to work nicely without any need for animation, changing window styles, etc.:
The Desktop Window Manager API enables a window to be "cloaked", so that it isn't shown on the screen but is still internally composited, i.e. still accumulates the results of drawing operations. You can turn "cloaking" on by:
BOOL cloak = TRUE;
DwmSetWindowAttribute(hwnd, DWMWA_CLOAK, &cloak, sizeof(cloak));
To avoid the white flash on first showing the window, do the above before calling ShowWindow(). Then do the initial UpdateWindow() to get correct content drawn. Finally, turn "cloaking" off using:
BOOL cloak = FALSE;
DwmSetWindowAttribute(hwnd, DWMWA_CLOAK, &cloak, sizeof(cloak));
to get the final window content displayed.
This should work on all Windows versions which have a Desktop Windows Manager, so Windows Vista and up.
Did some more poking, so here is a different potential answer.
I realized that even if I completely discard WM_PAINT and WM_ERASEBKGND (i.e., return 0 in WM_PAINT and return TRUE in WM_ERASEBKGND), I can still get the app to draw the red background by manually resizing the window!
Here is a clip to illustrate:
This means that Windows does indeed know and respect hbrBackground, which is great! For some odd reason it just doesn't clear it to that, but to white instead.
(Incidentally, I went through all the system colors in the registry (HKEY_CURRENT_USER\Control Panel\Colors HKEY_CURRENT_USER\Control Panel\Desktop\Colors) with a "255 255 255" setting and forcibly changed them to see if that would change the initial white background. But no luck. This makes me conclude that the white background is not a system color.)
Anyway, the above lead me to try to programmatically resize the window after ShowWindow. But since I don't want it to flicker on open, do the ShowWindow off-screen.
So here is the code that would replace regular ShowWindow(..):
int x0 = GetSystemMetrics(SM_XVIRTUALSCREEN);
int x1 = GetSystemMetrics(SM_CXVIRTUALSCREEN);
RECT rect;
GetWindowRect(hWnd, &rect);
// resize and move off-screen
SetWindowPos(hWnd, NULL, x1-x0, 0, 0, 0, SWP_NOREDRAW );
// show window
ShowWindow(hWnd,nCmdShow);
// restore and redraw
SetWindowPos(hWnd, NULL, rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top, 0 );
Now, I would call this a hack. Yet, it does not rely on WM_ERASEBKGND nor WM_PAINT, so there should be less of a timing issue.
Also, the window shows up exactly like a regular ShowWindow(...) would, just with the correct hbrBackground, which is what I wanted.
Here is what it looks like # 25Hz:
Notice that there is no flash of white background.
Please note that I've tried to write the code to cater for virtual desktop/multi-monitor, but haven't actually tested that.
But unfortunately everything is not fine and dandy. As I was writing this answer, I did quite a few trial runs with OBSStudio recording # 60Hz, and went through the footage. There I found one that simply shows trash inside the window frame on open (apparently from Chrome), for just one frame. Here is a slowed-down replay:
I'm stumped. Perhaps that is the real issue ?
I did some more testing, and want to post a potential answer to this question.
Now, this is mainly based on the suggestion by #JonathanPotter, so full credit to him. And while it doesn't really fix the problem, it does alleviate it quite a bit.
Now, ideally, it would be great if Windows would simply render the window with the correct initial background color, but no matter how hard I've tried, I can only get it to update the background color by utilizing WM_ERASEBKGND or WM_PAINT.
So it seems that the time delay between showing the window (i.e. using ShowWindow), and the actual clearing of the background (WM_ERASEBKGND) is the crux of the problem. Hence, it makes sense to profile it. I've done so by recording the time difference between calling ShowWindow and reaching WM_ERASEBKGND using QueryPerformanceCounter.
So on an i7-4960HQ CPU # 2.60GHz running Window 10, the time between ShowWindow and WM_ERASEBKGND is between 100 - 317ms. It fluctuates quite a bit. This is with a vanilla Win32 Sample App, built in Release without any Sleeps or anything like that, but using a red hbrBackground to show the issue. This means that the white background is clearly visible for a few frames before the red background is drawn. Here is an animated gif captured # 25Hz: The white background is visible for 3 frames in that animation.
Now the potential fix is to use a combination of SetWindowPos and RedrawWindow before showing the window.
For my tests I simply added these two lines before calling ShowWindow(..):
SetWindowPos(hWnd, NULL, 0,0,0,0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOREDRAW);
RedrawWindow(hWnd, NULL, 0, RDW_INVALIDATE | RDW_ERASE);
Although RedrawWindow does not seem to make any difference.
Profiling again, the time between ShowWindow and WM_ERASEBKGND is now 10 - 23ms. A 10x speed-up!
Again, an animated gif captured (with SetWindowPos) # 25Hz:
This clearly shows that the flash of white background is gone, and thus the problem is fixed. It's like night and day.
Now, I would argue that this is not a fix, but rather a workaround. Since the underlying problem of Windows using a white background color is still there.
And since this is a timing issue, I can easily imagine that the white background could show up again, say if the system was sluggish or busy doing other stuff.
Similarly, having a faster system means you that you are less likely to see this in the first place, effectively hiding the issue.
But simply setting a breakpoint in WM_ERASEBKGND will still show you a white window.
Also, I have no explanation for the speed-up. I tracked the number of messages in the message pump, and they are the same in both scenarios.
Now, I'm still hoping for a better fix. I find it hard to believe that the Microsoft engineers found it cool to fill all freshly created Windows with a hardcoded 0xFFFFFF, so I'm hoping that this color is actually read from somewhere, and thus possible to change, so the initial background matches the hbrBackground.
Please feel free to post alternative answers, questions, or suggestions.
I will of course update this thread if I figure out anything else.

Incorrect WM_DPICHANGED triggered by SetWindowPlacement

I'm saving/restoring my window position using GetWindowPlacement/SetWindowPlacement on Windows 10. My application is DPI aware. The issue occurs when SetWindowPlacement is both sizing and moving the window from monitor #1 with one DPI to monitor #2 with a different DPI. The coordinates have been saved as the correct size for monitor #2 in the WINDOWPLACEMENT structure.
The window is first resized during SetWindowPlacement while it's still on monitor #1. Then window is moved to monitor #2, which causes a WM_DPICHANGED message to fire, saying the window size should be changed. The suggested size is incorrect since it's changing the size of the window which was already the correct size for monitor #2.
What is the correct way to solve this? Should I be setting a flag before SetWindowPlacement to ignore WM_DPICHANGED messages until that call is done? Are there cases where that will cause me to miss a message I shouldn't be ignoring?
Thanks
Edit: Attached repro for #SongZhu-MSFT.
In this test case I'm using a Surface Studio 2 as my primary monitor, running at 4500x3000, using 175% scaling. On the right of that monitor, aligned to the bottom is a 1920x1080 monitor set to 100% scaling. This code attempts to open the monitor on the right monitor with a set size, however a DPICHANGE message comes through during the SetWindowPlacement() call which causes the size to be adjusted incorrectly, unless I manually avoid it. Sample code is edited from:
https://learn.microsoft.com/en-us/windows/win32/learnwin32/windows-hello-world-sample
#ifndef UNICODE
#define UNICODE
#endif
#include <windows.h>
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow)
{
// Register the window class.
const wchar_t CLASS_NAME[] = L"Sample Window Class";
WNDCLASS wc = { };
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
RegisterClass(&wc);
// Create the window.
HWND hwnd = CreateWindowEx(
0, // Optional window styles.
CLASS_NAME, // Window class
L"Learn to Program Windows", // Window text
WS_OVERLAPPEDWINDOW, // Window style
// Size and position
CW_USEDEFAULT, CW_USEDEFAULT, 1280, 720,
NULL, // Parent window
NULL, // Menu
hInstance, // Instance handle
NULL // Additional application data
);
if (hwnd == NULL)
{
return 0;
}
WINDOWPLACEMENT wp = {};
wp.length = sizeof(wp);
wp.showCmd = 1;
wp.ptMaxPosition.x = -1;
wp.ptMaxPosition.y = -1;
wp.ptMinPosition.x = -1;
wp.ptMinPosition.y = -1;
wp.rcNormalPosition.left = 4510;
wp.rcNormalPosition.top = 2320;
wp.rcNormalPosition.right = wp.rcNormalPosition.left + 1850;
wp.rcNormalPosition.bottom = 2909;
::SetWindowPlacement((HWND)hwnd, &wp);
ShowWindow(hwnd, nCmdShow);
// Run the message loop.
MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_DPICHANGED:
{
int dpi = HIWORD(wParam);
{
RECT* const prcNewWindow = (RECT*)lParam;
SetWindowPos(hwnd,
NULL,
prcNewWindow->left,
prcNewWindow->top,
prcNewWindow->right - prcNewWindow->left,
prcNewWindow->bottom - prcNewWindow->top,
SWP_NOZORDER | SWP_NOACTIVATE);
}
return 0;
}
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
// All painting occurs here, between BeginPaint and EndPaint.
FillRect(hdc, &ps.rcPaint, (HBRUSH) (COLOR_WINDOW+1));
EndPaint(hwnd, &ps);
}
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
And the .manifest I'm using.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2,permonitor</dpiAwareness>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
</windowsSettings>
</application>
</assembly>
I encountered the same issue in a slightly different context: saving and restoring the positions of other applications' windows (i.e. when un-docking a laptop all windows move to the primary display, but when re-docking we'd like them to go back where they were).
Since I don't control the third-party application windows, neither of #Sunius' suggestions work: I cannot make them ignore WM_DPICHANGED, nor can I know whether they will indeed react to WM_DPICHANGED -- if they aren't actually DPI-aware applications then pre-adjusting the window size is unnecessary and counterproductive.
My solution so far is a little clumsy, but simple and effective: when moving windows between different-DPI monitors, simply call SetWindowPlacement() twice. The first call will put it on the right monitor, but possibly with the wrong size, but the second call will immediately fix the size since it's already in the correct position.
The only catch here is that GetDpiForWindow() appeared to be unreliable for some applications, especially if the window remained minimized (I think Windows internally doesn't update the window's DPI setting for its new monitor if it's minimized). So instead I had to use MonitorFromWindow() and then GetDpiForMonitor() to detect when a window is going to change DPI, in order to trigger the second SetWindowPlacement().
There are two ways to solve this:
Before calling SetWindowPlacement, set a flag like s_IsInsideWindowMove to true, and if it's set when WM_DPICHANGED fires, do not follow the suggestion to resize the window. Once SetWindowPlacement returns, set the flag back to false;
Calculate the size passed to SetWindowPlacement as if you were placing it on the monitor with the same DPI as the monitor the window is currently on. For instance, if you're moving the window from DPI 144 to DPI 192 monitor and you want the end result size to be 800x600, ask SetWindowPlacement for a size of 600x450.
We use the first option as it's just easier to reason about and implement.

Find all windows beneath a point

I want to find all the top-level windows (children of the desktop) beneath a given point on the desktop. I can't find an API for this.
My scenario is that I'm dragging a window across the screen and want to drop it into another (known) window. I can hit test the bounds of the target window ok, but that doesn't tell me whether it's occluded by another (unknown) window. Using WindowFromPoint and friends won't work, because the window being dragged is necessarily directly under the mouse. So I'm wondering if I can obtain all windows at the mouse position, and review them to see whether one of the windows I'm tracking is directly beneath the window I'm dragging.
Is there a way to do this without resorting to EnumDesktopWindows/GetWindowRect on every mouse drag? Or perhaps there's another solution I'm missing.
If you ask kindly, WindowFromPoint will ignore your window (the one currently being dragged) and return the next window. This is what Internet Explorer does when you drag a tab.
To do that:
Handle WM_NCHITTEST in window being dragged
Return HTTRANSPARENT during dragging. Call default window proc otherwise.
WindowFromPoint will ignore HTTRANSPARENT windows, but only those belonging to the calling thread. This shouldn't be a problem for you, because you should be calling WindowFromPoint from the window owner thread anyway.
Make sure there're no child windows at point passed to WindowFromPoint, or handle WM_NCHITTEST for these child windows as well.
Troubleshooting (if you still get your window from WindowFromPoint)
Test GetCurrentThreadID() == GetWindowThreadProcessId(WindowFromPoint(), 0) to ensure you're calling from correct thread
In WM_NCHITTEST, test that hwnd parameter equals what you get from WindowFromPoint()
Example (the area within rectangle returns the underlying window from WindowFromPoint):
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static const RECT s_TransparentRect = {100, 100, 200, 200};
switch (message)
{
case WM_NCCREATE:
SetTimer(hWnd, 1, 100, 0);
break;
case WM_TIMER:
{
POINT cursorPos;
GetCursorPos(&cursorPos);
TCHAR buffer[256];
_snwprintf_s(buffer, _countof(buffer), _TRUNCATE, _T("WindowFromPoint: %08X\n"), (int)WindowFromPoint(cursorPos));
SetWindowText(hWnd, buffer);
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
BeginPaint(hWnd, &ps);
Rectangle(ps.hdc, s_TransparentRect.left, s_TransparentRect.top, s_TransparentRect.right, s_TransparentRect.bottom);
EndPaint(hWnd, &ps);
}
break;
case WM_NCHITTEST:
{
POINT cursorPos;
GetCursorPos(&cursorPos);
MapWindowPoints(HWND_DESKTOP, hWnd, &cursorPos, 1);
if (PtInRect(&s_TransparentRect, cursorPos))
return HTTRANSPARENT;
}
break;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
Right, you already know what WindowFromPoint() is going to return, should be the one you are dragging. Then use GetWindow() with uCmd = GW_HWNDNEXT to get the one below it in the Z-order. GetWindowRect() to get its bounds, IntersectRect() to compute the overlap.
Keep calling GetWindow() to find more windows that might be overlapped. Until it returns NULL or the overlap is good enough. If not then you'll normally favor the one that has the largest result rectangle from IntersectRect().

Win32: Does a window have the same HDC for its entire lifetime?

Am i allowed to use a DC outside of a paint cycle?
Is my window's DC guaranteed to be valid forever?
i'm trying to figure out how long my control's Device Context (DC) is valid.
i know that i can call:
GetDC(hWnd);
to get the device context of my control's window, but is that allowed?
When Windows sends me a WM_PAINT message, i am supposed to call BeginPaint/EndPaint to properly acknowledge that i've painted it, and to internally clear the invalid region:
BeginPaint(hWnd, {out}paintStruct);
try
//Do my painting
finally
EndPaint(hWnd, paintStruct);
end;
But calling BeginPaint also returns me a DC inside the PAINTSTRUCT structure. This is the DC that i should be painting on.
i cannot find anything in the documentation that says that the DC returned by BeginPaint() is the same DC that i would get from GetDC().
Especially now, in the days of Desktop Composition, is it valid to paint on a DC that i obtain outside of BeginPaint?
There seem to be 2 ways i can get a DC to paint on during a paint cycle:
dc = GetDC(hWnd);
BeginPaint(&paintStruct);
There is a 3rd way, but it seems to be a bug with the Borland Delphi that i develop with.
During WM_PAINT processing, Delphi believes that the wParam is a DC, and proceeds to paint on it. Whereas the MSDN says that the wParam of a WM_PAINT message is unused.
The Why
My real goal is to try to keep a persistent GDI+ Graphics object against an HDC, so that i can use some better performing features of GDI+ that depend on having a persistent DC.
During the WM_PAINT message handling i want to draw a GDI+ image to the canvas. The following nieve version is very slow:
WM_PAINT:
{
PAINTSTRUCT ps;
BeginPaint(m_hwnd, ps);
Graphics g = new Graphics(ps.hdc);
g.DrawImage(m_someBitmap, 0, 0);
g.Destroy();
EndPaint(h_hwnd, ps);
}
GDI contains a faster performing bitmap, a CachedBitmap. But using it without thinking gives no performance benefit:
WM_PAINT:
{
PAINTSTRUCT ps;
BeginPaint(m_hwnd, ps);
Graphics g = new Graphics(ps.hdc);
CachedBitmap bm = new CachedBitmap(m_someBitmap, g);
g.DrawCachedBitmap(m_bm, 0, 0);
bm.Destroy();
g.Destroy();
EndPaint(h_hwnd, ps);
}
The performance gain comes from creating the CachedBitmap once, so on program initialization:
m_graphics = new Graphics(GetDC(m_hwnd));
m_cachedBitmap = new CachedBitmap(b_someBitmap, m_graphcis);
And now on the paint cycle:
WM_PAINT:
{
PAINTSTRUCT ps;
BeginPaint(m_hwnd, ps);
m_graphics.DrawCachedBitmap(m_cachedBitmap, 0, 0);
EndPaint(h_hwnd, ps);
}
Except now i'm trusting that the DC i obtained after program initializtion will be the same DC for my window as long as the application is running. This means that it survives through:
fast user switches
composition enabled/disabled
theme switching
theme disabling
i find nothing in MSDN that guarantees that the same DC will be used for a particular window for as long as the window exists.
Note: i am not using double-buffering, because i want to be a good developer, and do the right thing. *
Sometimes that means you double-buffering is bad.
There are exceptions, but in general, you may get a different DC each time you call GetDC or BeginPaint. Thus you shouldn't try to save state in the DC. (If you must do this for performance, there are special DCs you can create for a class of windows or a particular window instance, but it doesn't sound like that's what you really need or want.)
Most of the time, however, those DCs will be compatible. They will represent the same graphics mode, so your compatible bitmap should work, even if you get a different DC.
There are Windows messages that tell you when the graphics mode changes, like WM_DISPLAYCHANGE and WM_PALETTECHANGED. You can listen for these, and recreate your cached bitmap. Since those are rare events, you won't have to worry about the performance impact of recreating your cached bitmap at that point.
You can also get notifications for things like theme changes. Those don't change the graphics mode--they're a higher level concept--so your cached bitmap should still be compatible with any DC you get. But if you want to change bitmap when the theme changes, you can listen for WM_THEMECHANGED as well.
The only way I know of that may (or may not) do what you are looking for is to create the window with the CS_OWNDC class style.
What that does is allocates a unique device context for each window in the class.
Edit
From the linked MSDN article:
A device context is a special set of
values that applications use for
drawing in the client area of their
windows. The system requires a device
context for each window on the display
but allows some flexibility in how the
system stores and treats that device
context.
If no device-context style is
explicitly given, the system assumes
each window uses a device context
retrieved from a pool of contexts
maintained by the system. In such
cases, each window must retrieve and
initialize the device context before
painting and free it after painting.
To avoid retrieving a device context
each time it needs to paint inside a
window, an application can specify the
CS_OWNDC style for the window class.
This class style directs the system to
create a private device context — that
is, to allocate a unique device
context for each window in the class.
The application need only retrieve the
context once and then use it for all
subsequent painting.
Windows 95/98/Me: Although the
CS_OWNDC style is convenient, use it
carefully, because each device context
uses a significant portion of 64K GDI
heap.
Perhaps this example will illustrate the use of CS_OWNDC better:
#include <windows.h>
static TCHAR ClassName[] = TEXT("BitmapWindow");
static TCHAR WindowTitle[] = TEXT("Bitmap Window");
HDC m_hDC;
HWND m_hWnd;
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
static PAINTSTRUCT ps;
switch (msg)
{
case WM_PAINT:
{
BeginPaint(hWnd, &ps);
if (ps.hdc == m_hDC)
MessageBox(NULL, L"ps.hdc == m_hDC", WindowTitle, MB_OK);
else
MessageBox(NULL, L"ps.hdc != m_hDC", WindowTitle, MB_OK);
if (ps.hdc == GetDC(hWnd))
MessageBox(NULL, L"ps.hdc == GetDC(hWnd)", WindowTitle, MB_OK);
else
MessageBox(NULL, L"ps.hdc != GetDC(hWnd)", WindowTitle, MB_OK);
RECT r;
SetRect(&r, 10, 10, 50, 50);
FillRect(m_hDC, &r, (HBRUSH) GetStockObject( BLACK_BRUSH ));
EndPaint(hWnd, &ps);
return 0;
}
case WM_DESTROY:
{
PostQuitMessage(0);
return 0;
}
}
return DefWindowProc(hWnd, msg, wParam, lParam);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
WNDCLASSEX wcex;
wcex.cbClsExtra = 0;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.cbWndExtra = 0;
wcex.hbrBackground = (HBRUSH) GetStockObject( WHITE_BRUSH );
wcex.hCursor = LoadCursor( NULL, IDC_ARROW );
wcex.hIcon = LoadIcon( NULL, IDI_APPLICATION );
wcex.hIconSm = NULL;
wcex.hInstance = hInstance;
wcex.lpfnWndProc = WndProc;
wcex.lpszClassName = ClassName;
wcex.lpszMenuName = NULL;
wcex.style = CS_OWNDC;
if (!RegisterClassEx(&wcex))
return 0;
DWORD dwExStyle = 0;
DWORD dwStyle = WS_OVERLAPPEDWINDOW | WS_VISIBLE;
m_hWnd = CreateWindowEx(dwExStyle, ClassName, WindowTitle, dwStyle, 0, 0, 300, 300, NULL, NULL, hInstance, NULL);
if (!m_hWnd)
return 0;
m_hDC = GetDC(m_hWnd);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
The CS_OWNDC flag is not to be confused with the CS_CLASSDC flag which:
Allocates one device context to be shared by all windows in the class. Because window classes are process specific, it is possible for multiple threads of an application to create a window of the same class. It is also possible for the threads to attempt to use the device context simultaneously. When this happens, the system allows only one thread to successfully finish its drawing operation.
If all else fails just reconstruct the CachedBitmap.
When you construct a CachedBitmap object, you must pass the address of a Graphics object to the constructor. If the screen associated with that Graphics object has its bit depth changed after the cached bitmap is constructed, then the DrawCachedBitmap method will fail, and you should reconstruct the cached bitmap. Alternatively, you can hook the display change notification message and reconstruct the cached bitmap at that time.
I'm not saying that CS_OWNDC is the perfect solution, but it is one step towards a better solution.
Edit
The sample program seemed to retain the same DC during screen resolution / bit depth change testing with the CS_OWNDC flag, however, when that flag was removed, the DC's were different (Window 7 64-bit Ultimate)(should work the same over differn OS versions... although it wouldn't hurt to test).
Edit2
This example doesn't call GetUpdateRect to check if the window needs to be painted during the WM_PAINT. That is an error.
You can draw onto whichever window dc pleases you. They're both valid. A window does not have just one dc that can represent it at a time. So each time you call GetDC - and BeginPaint internally does so, you will get a new, unique dc, that nonetheless represents the same display area.
Just ReleaseDC (or EndPaint) when you're done with them. In the days of Windows 3.1 device contexts were a limited, or very expensive system resource, so applications were encouraged to never hold onto them, but to retrieve them from the GetDC cache. nowadays its perfectly acceptable to create a dc at window creation, and cache it for the life of the window.
The only "problem" is, when handling WM_PAINT, the dc returned by BeginPaint will be clipped to the invalid rect, and the saved one will not.
I don't however understand what you are attempting to achieve with gdiplus. Usually, if an object is ... selected into a dc for a long period of time, that dc is a memory dc, not a window dc.
Each time GetDC is called you WILL get a new HDC representing a distinct device context with its own state. So, objects, background colors, text modes etc. set on one DC will NOT effect that state of another DC retrieved by a different call to GetDC or BeginPaint.
The system cannot randomly invalidate HDCs retrieved by the client, and actually does a lot of work in the background to ensure that HDCs retrieved before a display mode switch, continue to function. Even changing the bit depth, that technically makes the dc's incompatible, will not, in any way, prevent an application from continuing to use an hdc to blit.
That said, it is wise to watch at LEAST for WM_DISPLAYCHANGE, release any cached DCs and device bitmaps, and recreate them.

Resources