I am wanting to handle WM_NCPAINT messages to draw my own window frame. I wrote some simple code to draw just a rectangle, which should give a black border around it. Here was the code:
LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam){
switch(msg){
case WM_NCPAINT:
{
RECT rc;
GetWindowRect(hWnd, &rc);
HDC hDC = GetWindowDC(hWnd);
Rectangle(hDC, 0, 0, rc.right - rc.left, rc.bottom - rc.top);
ReleaseDC(hWnd, hDC);
return TRUE;
}
case WM_DESTROY:
{
PostQuitMessage(0);
break;
}
}
return DefWindowProc(hWnd, msg, wParam, lparam);
}
However, there were a couple issues with this. When resizing the window on the left or top edge, it causes the window to flicker heavily on the bottom and right edges. The second problem is that on the upper corners of the window, there are rounded corners, where my painting does not seem to take place. (This is Windows 10).
To my understanding, there should not be any flicker, as I am painting the window immediately after receiving a WM_NCPAINT message, which does not seem to be the case.
Can someone tell me what I am doing wrong and how to avoid these problems?
By the way, this is what the rounded corners look like, on the top-left and top-right corners.
After some more searching, I found the solution. I was a bit skeptical at first. I added the following code to my WM_NCCREATE:
HMODULE uxtheme = LoadLibrary("uxtheme.dll");
HRESULT __stdcall (* SetWindowTheme) (HWND, LPWSTR, LPWSTR) = GetProcAddress("SetWindowTheme");
SetWindowTheme(uxtheme, L" ", L" ");
FreeLibrary(uxtheme);
This fixes both the flickering and the annoying rounded corners.
Related
Note, below is about Windows 10. There is same functionality for Windows 7, but code a bit different
If to apply undocumented blurring just WM_POPUP|WM_VISIBLE window's background feature, as shown below:
void BlurBehind(HWND hwnd)
{
struct ACCENTPOLICY
{
int na;
int nf;
int nc;
int nA;
};
struct WINCOMPATTRDATA
{
int na;
PVOID pd;
ULONG ul;
};
int error = 0;
const HINSTANCE hm = LoadLibrary(L"user32.dll");
if (hm)
{
typedef BOOL(WINAPI* pSetWindowCompositionAttribute)(HWND, WINCOMPATTRDATA*);
const pSetWindowCompositionAttribute SetWindowCompositionAttribute = (pSetWindowCompositionAttribute)GetProcAddress(hm, "SetWindowCompositionAttribute");
if (SetWindowCompositionAttribute)
{
ACCENTPOLICY policy = { 4, 0, 155, 0 };
WINCOMPATTRDATA data = { 19, &policy,sizeof(ACCENTPOLICY) };
SetWindowCompositionAttribute(hwnd, &data);
error = (GetLastError());
}
FreeLibrary(hm);
}
}
int WINAPI WinMain(HINSTANCE hin, HINSTANCE, LPSTR, int)
{
WNDCLASS c = {NULL};
c.lpszClassName = L"Example";
c.lpfnWndProc = WndProc;
c.hInstance = hin;
c.style = CS_VREDRAW | CS_HREDRAW;
c.hCursor = LoadCursor(NULL, IDC_ARROW);
c.hbrBackground = CreateSolidBrush(RGB(255, 255, 255));
RegisterClass(&c);
HWND hwnd = CreateWindowEx(NULL, L"Example", L"Example", WS_POPUP | WS_VISIBLE, 0, 0, 1000, 600, NULL, NULL, hin, 0);
MSG msg;
while(GetMessage(&msg, NULL, NULL, NULL))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
And, handling WM_NCHITTEST message like this (again, window doesn't have default bar):
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp)
{
switch (message)
{
case WM_NCHITTEST:
{
RECT r;
GetWindowRect(hwnd, &r);
auto cp = MAKEPOINTS(lp);
int dyn = 61;
const LRESULT result = DefWindowProc(hwnd, message, wp, lp);
if (result == HTCLIENT)
{
if (cp.x <= r.right - dyn && cp.y <= r.top + 31)
return HTCAPTION;
else
{
if (r.right - cp.x < 20 && r.bottom - cp.y < 20)
return HTBOTTOMRIGHT;
else if (r.right - cp.x < 20)
return HTRIGHT;
else if (cp.x - r.left < 20 && r.bottom - cp.y < 20)
return HTBOTTOMLEFT;
else if (r.bottom - cp.y < 20)
return HTBOTTOM;
else if (cp.x - r.left < 20)
return HTLEFT;
else
{
SetClassLong(hwnd, GCL_HCURSOR, (LONG)LoadCursor(NULL, IDC_ARROW));
return result;
}
}
else
{
SetClassLong(hwnd, GCL_HCURSOR, (LONG)LoadCursor(NULL, IDC_ARROW));
return result;
}
}
else
return result;
}
break;
default:
return DefWindowProc(hwnd, message, wp, lp);
}
return 1;
}
Then HT_ events will be slow, laggy. Window will not respond as fast, as cursor moves, so you can, trying to move window, shake your mouse for a while, release it, and window still will be follow the path. So there is some delay.
If not to apply blur, all events, like moving, resizing occurs initially, instantly following mouse cursor.
Perhaps, someone, who used this undocumented features also faced this issue, and know the solution?
I found this trick (didn't tested yet) for dragging window, but I also need to resize it.
Update
So, updating my question with minimal example, I explored, that lags disappear, if to remove completely WM_PAINT case from WndProc. In WM_PAINT currently draws some direct2d stuff, but problem was with GDI+ also earlier. Probably, laggs appears, because message handling in Windows is synchronous, so it waits till stuff is drawn. But why there is such a big difference between switching blur, I don't know.
Also, I found, that I call render function twice: in WM_PAINT and in WM_SIZE (because in WM_SIZE I recreate direct2d render target).
Currently I removed render call in WM_PAINT at all, left only ValidateRect call, and things seems to be better, though during resize it still a bit laggy.
Update 2
Well, I just did not do anything but now it laggs again, even with empty WM_PAINT and WM_SIZE. If I leave WM_NCHITTEST alone at all in WndProc, it still laggs during moving or resizing window.
Perhaps, earlier, when I wrote that there are no laggs, I disabled blur and forget about that.
The example, I provided is representative, so can anyone check, do they have same behaviour (at least if You don't have core i9 and 3080 gpu, probably there will not be difference)?
Update 3
Seems, that earlier lags disappeared not because I accidently turned off blur in code, but because of the battery on my Surface Go was low, and Windows enabled charge economy mode. When that mode is enabled, Windows turns off Transparency effects switcher in Settings->Colors
If manually switch off Transparency effects in settings lags also disappears.
But the most interesting thing is that "Transparency effect", specifically blur is not gone for custom Window, although for other uwp apps, start menu, etc. is.
I am currently using Visual Studio 2022 (C++) to develop a simple desktop application.
I have included #define UNICODE in my code.
However, I have tried ExtTextOutW(), TextOutW() and DrawText() but neither of them showed what I had expected. For example, I used the following code with ExtTextOutW():
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
// ... omitted
case WM_PAINT:
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1));
RECT rec;
SetRect(&rec, 10, 10, 100, 100);
ExtTextOutW(hdc, 0, 0, NULL, &rec, L"中文字", 3, nullptr);
EndPaint(hwnd, &ps);
return 0;
}
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
The outcome (in the window) was , instead of 中文字. Similarly, if I use L"\u210E" or L"\U0001D465" (or other unicode characters in the block Mathematical Alphanumeric Symbols) the outcome is always (two characters give two |, so on and on). From my observation, ascii characters like L"abc" worked, though.
May I ask what may possibly solve this problem? Any help will be appreciated. Thank you!
I try to move a rect, but I'm puzzled. I just creat a rect, but after I move it 2 steps, the result is it have two copies. I used to wrote similar code, but this time something goes wrong.
just like this
begin:
b
move two steps:
b
b
b
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc ;
static int x_left,y_top,x_right,y_bottom;
PAINTSTRUCT ps ;
switch (message)
{
case WM_CREATE:
hdc = GetDC (hwnd) ;
x_left=100;
x_right=300;
y_top=50;
y_bottom=200;
Rectangle(hdc,x_left,y_top,x_right,y_bottom);
ReleaseDC (hwnd, hdc);
return 0;
case WM_KEYDOWN:
switch(wParam){
case VK_DOWN:
y_top+=160;
y_bottom+=160;
break;
default:break;
}
InvalidateRect(hwnd,NULL, FALSE);
break;
case WM_PAINT:
hdc=BeginPaint(hwnd,&ps);
Rectangle(hdc,x_left,y_top,x_right,y_bottom);
EndPaint(hwnd,&ps);
return 0;
case WM_DESTROY :
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
Replace the InvalidateRect line with:
InvalidateRect(hwnd,NULL, TRUE);
Your WM_PAINT doesn't explicitly paint the background. When you call InvalidateRect, you're not telling the function to paint the background with the window color. Ergo, the drawing (the rectangle) that was already there remains drawn.
Also, don't draw in WM_CREATE. The window isn't visible yet. The moment it appears, WM_PAINT will fire. You can draw with GetDC/ReleaseDC in the WM_KEYDOWN handler if you wish, but the InvalidateRect/WM_PAINT sequence won't be noticeably slower, and the code is cleaner that way.
The Win32 API function CreateFont() documentation has a confusing example:
As you can see below, they made 3 calls to CreateFont() using the same HANDLE (hFont).. and at the end they have called DeleteObject() for the last one only... but before EndPaint() (while the font is being select to the DC).
I've found this example so misleading about the way DeleteObject() must be used.
I hope if someone could confirm or justify this code.
The example:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
case WM_PAINT:
{
RECT rect;
HBRUSH hBrush;
HFONT hFont;
hdc = BeginPaint(hWnd, &ps);
//Logical units are device dependent pixels, so this will create a handle to a logical font that is 48 pixels in height.
//The width, when set to 0, will cause the font mapper to choose the closest matching value.
//The font face name will be Impact.
hFont = CreateFont(48,0,0,0,FW_DONTCARE,FALSE,TRUE,FALSE,DEFAULT_CHARSET,OUT_OUTLINE_PRECIS,
CLIP_DEFAULT_PRECIS,CLEARTYPE_QUALITY, VARIABLE_PITCH,TEXT("Impact"));
SelectObject(hdc, hFont);
//Sets the coordinates for the rectangle in which the text is to be formatted.
SetRect(&rect, 100,100,700,200);
SetTextColor(hdc, RGB(255,0,0));
DrawText(hdc, TEXT("Drawing Text with Impact"), -1,&rect, DT_NOCLIP);
//Logical units are device dependent pixels, so this will create a handle to a logical font that is 36 pixels in height.
//The width, when set to 20, will cause the font mapper to choose a font which, in this case, is stretched.
//The font face name will be Times New Roman. This time nEscapement is at -300 tenths of a degree (-30 degrees)
hFont = CreateFont(36,20,-300,0,FW_DONTCARE,FALSE,TRUE,FALSE,DEFAULT_CHARSET,OUT_OUTLINE_PRECIS,
CLIP_DEFAULT_PRECIS,CLEARTYPE_QUALITY, VARIABLE_PITCH,TEXT("Times New Roman"));
SelectObject(hdc,hFont);
//Sets the coordinates for the rectangle in which the text is to be formatted.
SetRect(&rect, 100, 200, 900, 800);
SetTextColor(hdc, RGB(0,128,0));
DrawText(hdc, TEXT("Drawing Text with Times New Roman"), -1,&rect, DT_NOCLIP);
//Logical units are device dependent pixels, so this will create a handle to a logical font that is 36 pixels in height.
//The width, when set to 10, will cause the font mapper to choose a font which, in this case, is compressed.
//The font face name will be Arial. This time nEscapement is at 250 tenths of a degree (25 degrees)
hFont = CreateFont(36,10,250,0,FW_DONTCARE,FALSE,TRUE,FALSE,DEFAULT_CHARSET,OUT_OUTLINE_PRECIS,
CLIP_DEFAULT_PRECIS,ANTIALIASED_QUALITY, VARIABLE_PITCH,TEXT("Arial"));
SelectObject(hdc,hFont);
//Sets the coordinates for the rectangle in which the text is to be formatted.
SetRect(&rect, 500, 200, 1400, 600);
SetTextColor(hdc, RGB(0,0,255));
DrawText(hdc, TEXT("Drawing Text with Arial"), -1,&rect, DT_NOCLIP);
DeleteObject(hFont);
EndPaint(hWnd, &ps);
break;
}
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
I want to calculate the full size of a window before opening it. I'm using AdjustWindowRectEx() to achieve this. My code looks like this for a window with a client size of 640x480:
wrect.left = 0;
wrect.top = 0;
wrect.right = 640;
wrect.bottom = 480;
AdjustWindowRectEx(&wrect, WS_CAPTION|WS_SYSMENU|WS_MINIMIZEBOX, FALSE, 0);
This returns the following values:
left: -3
top: -22
right: 643
bottom: 483
However, when opening the window using CreateWindowEx() and passing
wrect.right - wrect.left
wrect.bottom - wrect.top
as the window size, the window's size physical size actually turns out to be 656x515 pixels. Still, GetWindowRect() returns 646x505, i.e. the same dimensions as returned by AdjustWindowRectEx() but as I said, when I take a screenshot of the desktop and measure the window's size using a paint program, its physical size is actually 656x515 pixels. Does anybody have an explanation for this?
The client size is alright, it's 640x480 but it looks like the border size is calculated wrongly because the border uses more pixels than calculated by AdjustWindowRectEx() and GetWindowRect().
I'm on Windows 7.
EDIT:
Is this downvoted because the title of the question is misleading? As stated in the MSDN autodocs, WS_OVERLAPPED is not supported by AdjustWindowRectEx(). So is there any other way to calculate the dimensions of a WS_OVERLAPPED window? Using WS_OVERLAPPEDWINDOW instead is not a solution because it sets the WS_THICKFRAME and WS_MAXIMIZEBOX which I don't want.
Here is some test code now which shows the problem. You can see that the client size is alright but the window's physical size is larger than what is returned by GetWindowRect().
#include <stdio.h>
#include <windows.h>
#define CLASSNAME "Test"
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wcx;
RECT wrect;
HWND hWnd;
char tmpstr[256];
memset(&wcx, 0, sizeof(WNDCLASSEX));
wcx.cbSize = sizeof(WNDCLASSEX);
wcx.style = CS_HREDRAW|CS_VREDRAW;
wcx.lpfnWndProc = WindowProc;
wcx.hInstance = hInstance;
wcx.hCursor = LoadCursor(NULL, IDC_ARROW);
wcx.hbrBackground = GetStockObject(BLACK_BRUSH); // important! otherwise a borderless window resize will not be drawn correctly
wcx.lpszClassName = CLASSNAME;
RegisterClassEx(&wcx);
wrect.left = 0;
wrect.top = 0;
wrect.right = 640;
wrect.bottom = 480;
AdjustWindowRectEx(&wrect, WS_CAPTION|WS_SYSMENU|WS_MINIMIZEBOX, FALSE, 0);
hWnd = CreateWindowEx(0, CLASSNAME, "Test", WS_CAPTION|WS_SYSMENU|WS_MINIMIZEBOX|WS_OVERLAPPED, 0, 0, wrect.right - wrect.left, wrect.bottom - wrect.top, NULL, NULL, hInstance, NULL);
ShowWindow(hWnd, SW_SHOWNORMAL);
GetWindowRect(hWnd, &wrect);
sprintf(tmpstr, "%d %d %d %d\n", wrect.left, wrect.top, wrect.right, wrect.bottom);
AllocConsole();
WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), tmpstr, strlen(tmpstr), NULL, NULL);
GetClientRect(hWnd, &wrect);
sprintf(tmpstr, "%d %d %d %d\n", wrect.left, wrect.top, wrect.right, wrect.bottom);
WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), tmpstr, strlen(tmpstr), NULL, NULL);
Sleep(10000);
DestroyWindow(hWnd);
UnregisterClass(CLASSNAME, hInstance);
return 0;
}
GetWindowRect api doesn't give the correct size:
Due to compatability requirements, certain metrics are reported in such a way that they're not consistent with what is actually drawn on the screen when Aero Glass (more accurately, "Windows Vista Aero") is enabled. This sort of approach is needed in order to change the look of the system for the vast majority of apps for which this isn't an issue.However, there's been a recent change in the system which will be coming out in Vista RC1 that will return the correct rendered value from GetWindowRect() for executables that are linked with "winver = 6.0". This allows new and newly-linked applications to get the "correct" values from GetWindowRect().
GetWindowRect on non-resizable windows under Aero Glass
Use instead DwmGetWindowAttribute to get the correct size:
DwmGetWindowAttribute(hWnd, DWMWA_EXTENDED_FRAME_BOUNDS, &wrect, sizeof(wrect));
If AdjustWindowRect doesn't work, just try to create the Window and adjust the size after the window is created.
// Create the window with a nearly correct size
RECT rect;
rect.left = 0;
rect.top = 0;
rect.right = 640;
rect.bottom = 480;
AdjustWindowRectEx(&rect, WS_CAPTION|WS_SYSMENU|WS_MINIMIZEBOX, FALSE, 0);
// try
hWnd = CreateWindowEx(0, CLASSNAME, "Test", WS_CAPTION|WS_SYSMENU|WS_MINIMIZEBOX|WS_OVERLAPPED, 0, 0, rect.right - rect.left, rect.bottom - rect.top, NULL, NULL, hInstance, NULL);
// Get the size that is really required and adjust
RECT rectCreated;
GetWindowRect(hWnd, &rectCreated);
rect.right += rectCreated.right-rectcreated.left;
rect.bottom += rectCreated.bottom-rectcreated.top;
// Resize to let the window fir the inner client aea
SetWindowPos(hWnd,NULL,0,0,rect.right-rect.left,rect.bottom-rect.top,SWP_NOMOVE|SWP_NOZORDER);
// Show it.
ShowWindow(hWnd, SW_SHOWNORMAL);
Code is not tested. Hopefully I have no bugs or syntax errors in it.