WM_NCHITTEST not changing mouse cursor - winapi

I have a Layered Window (WS_EX_LAYERED) that implements a custom NCHITTEST and NCCALCSIZE to make the client rect of my window the same as the window rect. My window sizes and paints correctly; I can return HTBOTTOM from WM_NCHITTEST when the cursor is near the bottom edge of my window to cause a vertical-resize-window-from-the-bottom type action. However, I am not getting the vertical resize cursor. Is there way a do fix this without having to implement WM_SETCURSOR and test the pointer's position vs the edges of the windows?
Here's a snippet of my code:
case WM_NCCALCSIZE:
// Bypass DefWindowProc, so the Window Rect == Client Rect
return 0;
case WM_NCHITTEST: {
RECT w;
::GetWindowRect(hwnd, &w);
// Compare the mouse X/Y vs the rect of the window to detect
// resizing from the bottom edges
int r = HTCLIENT;
int x = GET_X_LPARAM(lParam);
int y = GET_Y_LPARAM(lParam);
if (w.bottom - y < 10) {
// If I was not using NCHITTEST, I should get a verticle resize pointer here
if (x - w.left < 10)
r = HTBOTTOMLEFT;
else if (w.right - x < 10)
r = HTBOTTOMRIGHT;
else
r = HTBOTTOM;
}
return r;
}

You need to handle the WM_SETCURSOR message - the low-order word of lParam specifies the hit-test code.
For instance,
case WM_SETCURSOR:
switch (LOWORD(lParam))
{
case HTBOTTOM:
SetCursor(LoadCursor(0, IDC_SIZENS));
return 0;
}
}
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);

Related

Undocumented BlurBehind makes WM_NCHITTEST slow

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.

WinApi, disabling all controls in a rect area

Is there an api to disable all controls in a rect area?
I am trying to write something like that:
GetClientRect(hWnd, rect);
DisableControls(rect);
GetClientRect gets the client area (left and top are always 0), you need to use GetWindowRect to figure out where a child window is.
All child controls are in the client area and you can just disable the parent window and all children will also stop accepting input.
It does seem a little contrived to disable based on a rectangle instead of a list of known controls but I suppose it might be useful in some cases.
static BOOL CALLBACK DisableChildrenInRectProc(HWND hWnd, LPARAM Param)
{
RECT *pParentRect = (RECT*) Param, r, ir;
if (GetWindowRect(hWnd, &r) && IntersectRect(&ir, &r, pParentRect))
{
EnableWindow(hWnd, FALSE);
}
return TRUE;
}
HWND hWnd = ...
RECT r;
GetWindowRect(hWnd, &r);
r.bottom = r.top + (r.bottom - r.top) / 2; // In this example, only disable controls in the top half.
EnumChildWindows(hWnd, DisableChildrenInRectProc, (LPARAM) &r);
I don't believe there is one. Use EnumChildWindows, GetWindowRect and some coordinate mapping via ScreenToClient. For overlap testing you could use IntersectRect.

Handling Window Movement after setting GWL_STYLE to 0

i want to have a border and title less window so i do SetWindowLongPtrW( window_handle, GWL_STYLE, 0 );
After that i can't move my Window so in my WndProc i do
if( message == WM_NCHITTEST ) {
RECT rc;
GetClientRect( hwnd, &rc );
MapWindowPoints( hwnd, GetParent( hwnd ), (LPPOINT)&rc, 2 );
int mouseX = LOWORD( lParam ) - rc.left;
int mouseY = HIWORD( lParam ) - rc.top;
POINT p;
p.x = mouseX;
p.y = mouseY;
return PtInRect( &rc, p ) ? HTCAPTION : DefWindowProc( hwnd, message, wParam, lParam );
}
It works, the first time i move the window. After i once stop clicking with the mouse it won't move again :/
SetWindowLongPtrW( window_handle, GWL_STYLE, 0 ); will hide the window, assuming it doesn't cause more serious problems. Use GetWindowLongPtr and combine that with valid window styles, or hide the window using ShowWindow
The error you have described is unrelated. You are attempting to find the screen coordinates of the window using GetClientRect and MapWindowPoints. The result will not be exact because the window may have borders and title bar.
Use GetWindowRect instead. This will give you screen coordinates of the window.
You can compare that with the mouse position LOWORD(lParam) and HIWORD(lParam). This is already screen coordinates. This code will move the screen every where the mouse lands in window:
RECT rc;
GetWindowRect(hwnd, &rc);
int mouseX = LOWORD(lparam);
int mouseY = HIWORD(lparam);
Don't subtract rc.left and rc.top from mouse position. That would convert the coordinates in client coordinates (roughly). Your code may work when window is on top-left corner of the screen, but it won't work later when window is moved.
Use ScreenToClient if you wish to work in client window coordinates.

Drawing (too slow) in WM_PAINT causes flicker?

I want to draw a lot of lines in the WM_PAINT message handler with the following code.
//DrawLine with double buffering
LRESULT CALLBACK CMyDoc::OnPaint(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
std::vector<Gdiplus::Point> points;
std::vector<Gdiplus::Point>::iterator iter1, iter2;
HDC hdc, hdcMem;
HBITMAP hbmScreen, hbmOldBitmap;
PAINTSTRUCT ps;
RECT rect;
hdc = BeginPaint(hWnd, &ps);
//Create memory dc
hdcMem = CreateCompatibleDC(hdc);
GetClientRect(hWnd, &rect);
hbmScreen = CreateCompatibleBitmap(hdc, rect.right, rect.bottom);
hbmOldBitmap = (HBITMAP)SelectObject(hdcMem, hbmScreen);
//Fill the rect with white
FillRect(hdcMem, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH));
//Draw the lines
Gdiplus::Graphics graphics(hdcMem);
Gdiplus::Pen blackPen(Gdiplus::Color(255, 0, 0));
points = m_pPolyLine->GetPoints();
for (iter1 = points.begin(); iter1 != points.end(); iter1++) {
for (iter2 = iter1 + 1; iter2 != points.end(); iter2++)
graphics.DrawLine(&blackPen, *iter1, *iter2);
}
//Copy the bitmap from memory dc to the real dc
BitBlt(hdc, 0, 0, rect.right, rect.bottom, hdcMem, 0, 0, SRCCOPY);
//Clean up
SelectObject(hdcMem, hbmOldBitmap);
DeleteObject(hbmScreen);
DeleteDC(hdcMem);
EndPaint(hWnd, &ps);
return 0;
}
However, if the size of points exceed 20, the client rect just flicker. I think the reason is that Gdiplus::DrawLines is too slow.
Is there any method to solve the flicker problem?
Thanks.
The flickering may be caused by slow painting as well as by other things. In general, try/ensure the following:
Try to not rely on WM_ERASEBKGND message, i.e. return non-zero, or specify NULL in WNDCLASS::hbrBackground if possible. Often the paint method paints all the background of the dirty region, then there is no need to do the erasing.
If you need the erasing, it can often be optimized so that WM_ERASEBKGND returns non-zero, and the paint method then ensures the "erasing" by also painting areas not covered by the regular painted contents, if PAINTSTRUCT::fErase is set.
If reasonably possible, write the paint method so that it does not repaint the same pixels in one call. E.g. to make blue rect with red border, do not FillRect(red), then repainting inner part of it with FillRect(blue). Try to paint each pixel once as much as reasonably possible.
For complex controls/windows, the paint method may often be optimized to easily skip a lot of painting outside the dirty rect (PAINTSTRUCT::rcPaint) by proper organizing the control data.
When changing the control state, invalidate only the minimal required region of the control.
If it is not top-level window, consider using CS_PARENTDC. If your paint method does not rely on system setting clipping rectangle to the client rect of the control, this class style will lead to a somewhat better performance.
If you see the flickering on control/window resizing, consider to not using CS_HREDRAW and CS_VREDRAW. Instead invalidate the relevant parts of the control in WM_SIZE manually. This often allows to invalidate only smaller parts of the control.
If you see the flickering on control scrolling, do not invalidate whole client, but use ScrollWindow() and invalidate only small area which exposes the new (scrolled-in) content.
If everything above fails, then use double buffering.
Use double buffer. It is an offen problem with Win32 C++ application and specifically with OnPaint function and DC facilities.
Here is a few links to help you check if everything is fine with YOUR implementation of double buffer: Flicker Free Drawing In MFC and SO question "Reduce flicker with GDI+ and C++"
If your lines happen to extend outside the bounds of the DC (Graphics), Win32/GDI+ is painfully slow at clipping. Like, up to two orders of magnitude slower than rolling your own clipping function. Here is some C# code that implements Liang/Barsky - I scrounged this up from an old library that was originally in C++ 20 years ago. Should be easy enough to port back.
If your lines can extend beyond the client rectangle, call ClipLine(rect, ...) on your points before handing them off to Graphics::DrawLine.
private static bool clipTest(double dp, double dq, ref double du1, ref double du2)
{
double dr;
if (dp < 0.0)
{
dr = dq / dp;
if (dr > du2)
{
return false;
}
else if (dr > du1)
{
du1 = dr;
}
}
else
{
if (dp > 0.0)
{
dr = dq / dp;
if (dr < du1)
{
return false;
}
else if (dr < du2)
{
du2 = dr;
}
}
else
{
if (dq < 0.0)
{
return false;
}
}
}
return true;
}
public static bool ClipLine(Rectangle clipRect, ref int x1, ref int y1, ref int x2, ref int y2)
{
double dx1 = (double)x1;
double dx2 = (double)x2;
double dy1 = (double)y1;
double dy2 = (double)y2;
double du1 = 0;
double du2 = 1;
double deltaX = dx2 - dx1;
double deltaY;
if (clipTest(-deltaX, dx1 - clipRect.Left, ref du1, ref du2))
{
if (clipTest(deltaX, clipRect.Right - dx1, ref du1, ref du2))
{
deltaY = dy2 - dy1;
if (clipTest(-deltaY, dy1 - clipRect.Top, ref du1, ref du2))
{
if (clipTest(deltaY, clipRect.Bottom - dy1, ref du1, ref du2))
{
if (du2 < 1.0)
{
x2 = DoubleRoundToInt(dx1 + du2 * deltaX);
y2 = DoubleRoundToInt(dy1 + du2 * deltaY);
}
if (du1 > 0.0)
{
x1 = DoubleRoundToInt(dx1 + du1 * deltaX);
y1 = DoubleRoundToInt(dy1 + du1 * deltaY);
}
return x1 != x2 || y1 != y2;
}
}
}
}
return false;
}
The problem is I have not handle WM_ERASEBKGND message by myself.

GetPixel always returns CLR_INVALID

I'm trying to read the pixel color of a given window. The window is not mine. The window may not have focus or be at foreground. I don't know if it matters, does it? I have the window handle, so I do:
HDC hdc = GetDC(m_window);
if (hdc)
{
COLORREF color = GetPixel(hdc,x,y);
if(color == CLR_INVALID)
{
wxLogDebug("COLOR DATA INVALID");
}
else
{
wxLogDebug("COLOR DATA 0x%x", color);
}
x and y are screen coordinates that lie inside the window. Don't know why this is not working. Any ideas?
You should convert x, y into client coordinates with ScreenToClient Function.

Resources