Direct2D Leak over RDP - winapi

This is a weird behaviour on which I 'd need your experience.
I have a Direct2D app which draws the screen in a typical way:
// fact,draw global factory and ID2D1RenderTarget, WM_PAINT creates them once.
case WM_PAINT:
{
PAINTSTRUCT ps;
BeginPaint(hWnd, &ps);
RECT rc = {};
GetClientRect(hWnd, &rc);
if (!fact)
D2D1CreateFactory(D2D1_FACTORY_TYPE::D2D1_FACTORY_TYPE_MULTI_THREADED, &fact);
if (!draw)
{
D2D1_HWND_RENDER_TARGET_PROPERTIES hp;
hp.hwnd = hWnd;
hp.pixelSize.width = rc.right;
hp.pixelSize.height = rc.bottom;
fact->CreateHwndRenderTarget(D2D1::RenderTargetProperties(), D2D1::HwndRenderTargetProperties(hWnd, D2D1::SizeU(rc.right - rc.left, rc.bottom - rc.top)), &draw);
}
draw->BeginDraw();
.. more paint
draw->EndDraw();
EndPaint(hWnd, &ps);
return 0;
}
This app has also a self-debugging library which displays the number of handles the app creates.
When the app is run locally, no problem, the number of handles remain static.
On running the app over Remote Desktop, I get 10 more handles per second. When the app is running for a few minutes I get 10000 handles.
Further debugging shows me that between ID2D1RenderTarget::BeginDraw() andEndDraw() the leaks occur, even if I don't draw anything.
Anyone has experienced such an issue yet? Is this a known bug?

Related

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.

Rich Edit Control paints whole application black after unminimize

SOLVED: I've posted my solution as an answer.
Here's my problem: (gif) (Sort of solved if I reload the bitmaps for painting the background image when unminimizing before any WM_PAINT message.)
It happens whenever I unminimize the application, time when the app first displays OK (for a brief split second unless you are stepping with the debugger), and suddenly turns black (or whatever color has been set as hbrBackground in the app window classes). I can prevent this behaviour by reloading the HBITMAPs used in WM_PAINT, which are global variables and initialised with their corresponding values at app startup.
The gif starts showing the app reopened after a minimize, with the debugger stepping through the parent window of the Rich Edit Control message loop, the moments just before and after the background of all windows turns black, and then stepping into the Rich Edit Control subclass message loop, into WM_PAINT.
This never happens if I'm switching between apps without the app in question never having been minimized before.
This never happens if the Rich Edit Control (RICHEDIT50W) hasn't displayed any text before, ie. the app works OK if no text is ever displayed.
This is the window tree:
Main Window
Some Child Windows
Child Window 1
Rich Edit Control
The stepping goes out of the Child Window 1 WndProc; into the WM_PAINT of the Rich Edit Control inside the WndSubclassProcWhatever callback.
Some of the things I've done before realizing that a call to LoadImage() just after unminimize could fix the background issue:
Intercept the message loop of the Rich Edit Control with a subclass, and handle (as well as in every other window) messages as: WM_COMMAND, WM_IME_NOTIFY, WM_NCPAINT, WM_WINDOWPOSCHANGED, WM_WINDOWPOSCHANGING, WM_ERASEBKGND... Mainly returning something different than the DefSubclassProc/DefWindowProcW.
Calling ValidateRect() as soon as the app is reopened...
It has happened before that instead of the whole app turning black, only the text "highlighting" or the Rich Edit Control parent turned black, with the whole app turning black after another minimize unminimize cycle.
I'm using Visual Studio Community 2019 with default settings in an updated Windows 10, and seeing this problem both in release and debug builds.
I'm now looking forward to prevent the bitmaps from "unloading", thus saving many seemingly unnecessary LoadImage() calls. SOLVED
I tried uploading a minimal version of the code, yet the behaviour turned out not to be exactly the same, so thanks for the answer given before!
This has nothing to do with Rich Edit Control , even if you delete all of the controls, this will happen.
All you have to do is add a default color to the window background when you register the window.
Here:
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_DBLCLKS;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = NULL; // Procesás WM_GETICON
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = NULL;
wcex.lpszMenuName = NULL;
wcex.lpszClassName = L"mainWindowClass";
wcex.hIconSm = NULL; // Procesás WM_GETICON
return RegisterClassExW(&wcex);
}
Click again after minimize the window will cause it to redraw with the default background color, But you set the background color to NULL here. So try to change wcex.hbrBackground = NULL to wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1)
Updated:
It sounds like you have the same problem as I have encountered before.
Here is my previous code:
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code that uses hdc here...
hdcMem = CreateCompatibleDC(hdc);
HGDIOBJ previousbit = SelectObject(hdcMem, hBmp);
AlphaBlend(hdc, 0, 0, width_1, height_1, hdcMem, 0, 0, width_1, height_1, bf);
DeleteDC(hdcMem);
EndPaint(hWnd, &ps);
}
break;
case WM_MOUSEWHEEL:
{
if (GET_WHEEL_DELTA_WPARAM(wParam) > 0 && bTrans <= 234)
{
bTrans += 20;
bf.SourceConstantAlpha = bTrans;
InvalidateRect(hWnd, NULL, TRUE);
}
if (GET_WHEEL_DELTA_WPARAM(wParam) < 0 && bTrans >= 20)
{
bTrans -= 20;
bf.SourceConstantAlpha = bTrans;
InvalidateRect(hWnd, NULL, TRUE);
}
return 0;
}
I slide the mouse wheel, it will trigger the InvalidateRect(hWnd, NULL, TRUE);
But if I delete DeleteDC(hdcMem), it will return a main window without a picture.
The debug snapshot is :
Yes, you can find previousbit == NULL.
As #Remy Lebeau said that, you are leaking the HBITMAP that SelectObject() returns, and giving the HDC permission to potentially destroy your bitmapBackgroundMainWindow behind your back.
This is the main cause.
A call to DeleteObject() fixed the issue.
This is the code from one of the bitmap-background window WM_PAINT messages fixed with the corresponding DeleteObject() call:
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
HDC temporaryDC = CreateCompatibleDC(hdc);
BITMAP bitmapToBitBlt;
HGDIOBJ hgdiobjToBitBlt = SelectObject(temporaryDC, bitmapBackgroundMainWindow);
GetObjectW(bitmapBackgroundMainWindow, sizeof(BITMAP), &bitmapToBitBlt);
BitBlt(hdc, 0, 0, bitmapToBitBlt.bmWidth, bitmapToBitBlt.bmHeight, temporaryDC, 0, 0, SRCCOPY);
DeleteObject(temporaryDC); // This fixes the app.
EndPaint(hWnd, &ps);
return 0;
As Windows Docs state, after calling CreateCompatibleDC():
When you no longer need the memory DC, call the DeleteDC function. We recommend that you call DeleteDC to delete the DC. However, you can also call DeleteObject with the HDC to delete the DC.
How does this translate to my app unexpected behaviour, I don't know, feel free to clarify in the comments!

Windows - GDI - Scaling a screen DC to a printer DC without modifying the draw functions

I'm writing a Windows application showing a document to the user. The content is painted using the GDI functions, and all appears as expected on the screen.
Now I want to print this document. I get a printer device context, and I do the exact same drawing as I do on the screen. Of course the printed content appears tiny on the top of the printed page. The reason of this behavior is clear for me, and is fully explained here:
https://www.codeproject.com/Articles/764057/GDI-Drawing-and-Printing
So I need to add a scaled viewport on my printer DC, and there are several functions to achieve that in the GDI. However I'm a little puzzled about HOW to configure these functions. I tried various examples found on the internet, but none of them worked for me.
My screen resolution is 1920x1080 pixels, and I'm trying to print on an A4 portrait page. I tested various configurations, and I found that the best approximation to fit on my printed page is the following:
::SetMapMode(hDC, MM_ISOTROPIC);
::SetWindowExtEx(hDC, 1, 1, NULL);
::SetViewportExtEx(hDC, 5, 5, NULL);
::SetViewportOrgEx(hDC, -10200, 0, NULL);
As the screen and print configurations may, of course, change on other PC, I need to know how the above values may be calculated, but I cannot find a formula that works in my case. Especially I don't know why I need to scale my canvas origin using the SetViewportOrgEx() function, nobody mentioned that on the documents I read.
So what is the correct manner to calculate my print DC viewport, considering that:
The exactly same painting functions will be used for both the screen and printer drawing, and I will NEVER write different functions to print on the screen and the printer
The screen and printer devices may be entirely configured by the user, but the printed result should always fit the document on both the screen and the printer
And as an additional question, it would be better to use a metafile to do this kind of job?
In order to map the screen coordinates to paper coordinates, we need the width and length of the paper. This information is available in GetDeviceCaps(hdc, PHYSICALWIDTH) and GetDeviceCaps(hdc, PHYSICALHEIGHT), where hdc is printer's device context. We already have the screen coordinates somewhere.
The printer cannot print on the edges of the paper. We can get that information from PHYSICALOFFSETX and PHYSICALOFFSETY.
The example below uses uses a common function paint which does all the painting. print doesn't do any painting, it calls paint instead.
This assumes that rc.left and rc.right is (0,0) in screen coordinates.
void paint(HDC hdc, RECT rc)
{
HBRUSH brush = GetSysColorBrush(COLOR_WINDOWTEXT);
InflateRect(&rc, -10, -10);
FrameRect(hdc, &rc, brush);
DrawText(hdc, L"hello world", -1, &rc, 0);
}
void print(HWND hWnd, RECT rc)
{
PRINTDLG pd = { sizeof(pd) };
pd.hwndOwner = hWnd;
pd.Flags = PD_RETURNDC;
if(!PrintDlg(&pd))
return;
HDC hdc = pd.hDC;
DOCINFO doc = { sizeof(doc) };
StartDoc(hdc, &doc);
StartPage(hdc);
SetMapMode(hdc, MM_ISOTROPIC);
SetWindowExtEx(hdc, rc.right, rc.bottom, NULL);
SetViewportExtEx(hdc,
GetDeviceCaps(hdc, PHYSICALWIDTH), GetDeviceCaps(hdc, PHYSICALHEIGHT), NULL);
SetViewportOrgEx(hdc,
-GetDeviceCaps(hdc, PHYSICALOFFSETX), -GetDeviceCaps(hdc, PHYSICALOFFSETY), NULL);
paint(hdc, rc);
EndPage(hdc);
EndDoc(hdc);
DeleteObject(hdc);
}
Testing:
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
RECT rc;
GetClientRect(hwnd, &rc);
paint(hdc, rc);
EndPaint(hwnd, &ps);
break;
}
case WM_LBUTTONDOWN:
{
RECT rc;
GetClientRect(hwnd, &rc);
print(hwnd, rc);
break;
}

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