I'm writing a program which needs a DPI aware, borderless fullscreen top-level OpenGL window.
The program has only this main window for 99% of the time.
The problem I'm having is the client area of the window is erased by windows when the window becomes inactive.
This only occurs when DPI aware, fullscreen and borderless(i.e. zero non-client area).
I have found a workaround to this, by catching WM_NCACTIVATE and returning 0 which prevents windows from processing the NC redraw. It seems that the windows non-client redraw procedure erases the client area if the non-client area is zero.
But, when I create a DialogBox (child of main hWnd), it causes the main window to become inactive (obviously).
If I return 0 from WM_NCACTIVATE, mouse and keyboard events do not get passed to the dialog box proc so the program becomes locked.
Using a global flag I can deactivate the workaround before creating the dialog, then redraw my main GL screen on WM_INITDIALOG but this causes a rather ugly screen flash that I'm trying to prevent.
So my question is: How do I prevent windows from erasing my client are of main wnd, without suppressing WM_NCACTIVATE?
Alternately: How to avoid the window erase, delay, then redraw flash that occurs when creating the dialog?
FYI,
The code I'm using to create the main window, run the message loop and draw a GL scene is essentially pinched from the HeNe examples.
I know these are very dated, but I find them easy to build from.
I've changed the HeNe code to WinMain() and perform my OpenGL drawing only as the program requires (outside of the main loop and outside of WM_PAINT).
Redrawing the scene and SwapBuffers() inside of WM_PAINT and WM_NCPAINT does not fix the problem.
To make the window fullscreen, I use Raymond Chen's code:
https://devblogs.microsoft.com/oldnewthing/20100412-00/?p=14353
I have also tried c-smile's code to make fullscreen, which behaves identically to Raymond Chen's:
Windows 10, DPI scaling and fullscreen
For the record, I'm using PerMonitor V2 but it dosent seem to matter which mode I use. Ive tried setting using both API and manifest file.
VS2019 v142; SDK 10.0.18362.0
I resolved this for a Qt application on Win10. My program is DPI aware and borderless, but is not fullscreen. Nevertheless, I had the same issue where:
I would get an unpleasant flicker when activating/deactivating the window
Unconditionally returning true for WM_NCACTIVATE WndProc calls would fix the flicker but would prevent dialogs capturing input
The flicker occurs without WM_NCPAINT having been called; there is no opportunity to prevent the redraw.
To resolve this, I had to change how I remove the non-client area. Previously I was using a Qt-specific function on window creation like so:
setWindowFlag(Qt::FramelessWindowHint);
I removed that and added a handler for WM_NCCALCSIZE calls which unconditionally returns 0. That removed the non-client area and, for whatever reason, doesn't cause the redraw on activation despite not having a handler for WM_NCACTIVATE.
In short, the only thing I do to remove the non-client rect is handle WM_NCCALCSIZE. I don't call into any WINAPI functions. I don't override WM_NCACTIVATE because doing so prevents dialogs from working correctly.
Here's my WndProc in full. This is for a Qt application, but nativeEvent is just a thin wrapper around the native WndProc.
bool MainWindow::nativeEvent(const QByteArray& eventType, void* message, long* result)
{
MSG* msg = static_cast<MSG*>(message);
LRESULT hitTestResult = 0;
auto handled = DwmDefWindowProc(msg->hwnd, msg->message, msg->wParam, msg->lParam, result);
if (handled)
return true;
static bool resizing = false;
switch (msg->message)
{
case WM_NCHITTEST:
*result = HitTestNCA(msg->hwnd, msg->wParam, msg->lParam, this);
if (*result != HTNOWHERE)
return true;
return false;
case WM_NCCALCSIZE:
// Setting the result to zero removes the non-client rect. This is
// a hardcoded Microsoft thing.
*result = 0;
return true;
default:
return false;
}
// Unreachable
}
Related
This is for C++ - win32. Basically I've loaded an image (bmp) into a HBITMAP from a file and bitblitted it to the device context for the main window.
How would I call it again in case I want to change the image?
I've called InvalidateRectangle() and UpdateWindow() but that causes the window controls to flicker.
Normally you invalidate the area (e.g. via InvalidateRect) and let your WM_PAINT handler repaint it. Reasons why you would get flicker often are because you haven't overridden the WM_ERASEBKGND handler, your WM_PAINT handler isn't doing double-buffered painting, or you're invalidating (or repainting) an area larger than you need to.
This page might help: Flicker-free Drawing: Techniques to eliminate flicker from your applications
I'm working on a program that requires WM_ERASEBKGND to be disabled (to avoid flickering).
The problem comes in when my main window loses focus, and another window (another program) gains the focus.
The window that has the focus (not my program) invalidates MY program's window every time it passes over it! The result is, my window's screen turns white everywhere that another window has passed by it, leaving it almost totally blank afterward. Obviously, I cannot have a program where the screen turns white every time it loses focus.
Is there any way to continue my window's drawing operations, (continue calling wm_paint, for example) even after my window has lost focus (WM_KILLFOCUS)?
First of all, from the comments above, never send the WM_PAINT manually with SendMessage or PostMessage. Use InvalidateRect to instruct the window to be repainted.
About the WM_ERASEBKGND, the return value is used to indicate the WM_PAINT handler that the background has been erased, in case the paint procedure can be optimized. To actually prevent the background from being erased, simply do not call DefWndProc() for that message. Or even easier, set the hbrBackground to NULL in the window class.
As others mentioned the focus has nothing to do with repainting, and your window should paint normally even while in the background.
The problem that I have seems to be trivial, but I cannot find a way to solve it. Here it is. I have a window with some graphics in it.
For simplicity lets say it's a solid green rectangle which fills the entire client area of the window. I want this rectangle to be redrawn and to fill the entire window every time the window changes its size. What I did originally was this. I posted WM_PAINT message from WM_SIZE handler.
It works, but if I move mouse fast I see a bit of unpainted (white) area around the green rectangle (actually one or two sides only, close to where mouse is). My understanding of the problem is that system thread which handles user input (mouse) works faster than my handler of WM_PAINT message. It means that by the time I start drawing an updated rectangle (its size is taken from WM_SIZE), mouse actually moves a little bit and system draws a new window frame which is different from what I'm trying to fill with the green. This creates unfilled areas next to borders which move during resizing.
When I stop resizing, green eventually fills the entire window, but during resizing there is a bit of flickering happening close to borders which is annoying. In order to solve the problem I tried the following.
bool finishedPainting;
RECT windowRect;
case WM_PAINT :
// ..... painting here
finishedPainting = TRUE;
break;
case WM_SIZE :
// .... some actions
// posting WM_PAINT
InvalidateRect(hWnd, NULL, FALSE);
PostMessage(hWnd, WM_PAINT, 0, 0);
break;
case WM_SIZING :
// this supposedly should prevent the system from passing
// new window size to WM_SIZE
if (!finishedPainting) memcpy((void*)lParam, &windowRect, sizeof(windowRect));
else {
// remember current window size for later use
memcpy(&windowRect, (void*)lParam, sizeof(windowRect));
finishedPainting = FALSE;
}
return TRUE;
It doesnt' work. As a slight variation, I also tried this.
bool finishedPainting;
POINT cursorPos;
case WM_PAINT :
// ..... painting here
finishedPainting = TRUE;
break;
case WM_SIZE :
if (!finishedPainting) SetCursorPos(cursorPos.x, cursorPos.y);
else {
finishedPainting = FALSE;
GetCursorPos(&cursorPos);
// .... some actions
InvalidateRect(hWnd, NULL, FALSE);
PostMessage(hWnd, WM_PAINT, 0, 0);
}
break;
This also doesn't work. As far as I understand the solution to the problem lies in somehow slowing the mouse down so that it moves to the next position on the screen (dragging the corner or the side of the window with it) only after the painting is finished.
Any ideas how to achieve this? Or maybe there is something fundamentally wrong with the way I see the problem and solution lies somewhere else?
// ====================================================
Update
I did a few experiments and here is what I found
1) When resizing, the sequence of messages is WM_SIZING - WM_NCPAINT - WM_SIZE - WM_PAINT. This looks a bit strange to me. I would expect WM_SIZE to follow WM_SIZING without interruption by WM_NCPAINT
2) In each message handler I was checking the width of a window during resizing (for simplicity I was only changing width). Surprisingly, the width measured in WM_SIZE turned out to be different from the one in WM_SIZING, but the same as in WM_NCPAINT and WM_PAINT. This is not a problem as such, just a wierd fact.
3) I came to the conclusion that there are two major causes for flicker happening near the window borders. The first one is that WM_NCPAINT comes before WM_PAINT. Imagine that you are stretching your window. The new frame will appear first (WM_NCPAINT comes first), then WM_PAINT fills the client area. A human eye catches that short period of time when the new frame is already on the screen, but it is empty. Even if you specify that you don't want window background to be deleted before repainting, still newly added area is empty and you can see it for a split second. This reason for flicker is best demonstrated when you grab the right window edge and move it quickly to the right. The other reason for flickering effect is less obvious and best seen when you grab the left window edge and move it to the left. During this move you will see unfilled areas along the RIGHT edge. As far as I understand the effect is caused by this. When user is doing resize Windows does the following: A) it sends WM_NCPAINT to draw the new frame, B) it copies the content of the old client area into the new top left window corner (in our case it moved to the left), C) it sends WM_PAINT to fill the new client area. However during stage B for some reason Windows produces those unfilled areas along the right edge, although it seems like it shouldn't because the old content should just stay where it is until it gets repainted over during WM_PAINT.
Ok, the question remains - how to get rid of those artefacts during resizing. As far as I can see now it is impossible to do using standard techniques and functions, because they are caused by the sequence of steps Windows performs during resizing. Swapping WM_NCPAINT and WM_PAINT would probably help, but this seems to be beyond our control (unless there is a simple way to do that which I just don't know about).
You shouldn't post or send WM_PAINT messages yourself. Rather, use ::InvalidateRect to invalidate parts of your window and let Windows decide when to send the WM_PAINT messages.
Windows works this way on purpose. It's generally considered more important to be responsive to the user (i.e. the mouse) than to have a fully up-to-date painted window.
If you're always painting the entire window in your WM_PAINT handler, you can eliminate a lot of flicker by overriding the WM_ERASEBKGND handler and return without doing anything.
If you really insist on preferring window updates over mouse responsiveness, replace the InvalidateRect call with a RedrawWindow call using the RDW_UPDATENOW flag.
Edit: Your observations make sense. WM_SIZING comes before the window is resized, to give you a chance to modify the size and/or position. You might try painting the client area in the WM_NCPAINT handler, even before the border is drawn. After you paint it you can validate the client area to keep it from drawing again.
It's a bad idea to manually post WM_PAINT or WM_SIZE. One weird hacky crazy thing you can do though, is to record the new resized co-ordinates in a RECT in WM_SIZE, Use MoveWindow to change the size of the window back to it's previous one, then manually resize it again using MoveWindow in the WM_PAINT message after you've done the painting.
Another possible solution is to constantly fill your window with the color regardless of whether the screen is resized or not. i.e
// in the WinMain function
if (GetMessage(&msg,NULL,NULL,0))
{
TranslateMessage(&msg,NULL,NULL);
DispatchMessage(&msg,NULL,NULL);
}
else
{
// fill your window with the color in here
}
Or, of course, you can just set the background color of your window to green, instead of doing all the painting yourself:
// Before RegisterClass or RegisterClassEx
// wincl is a WNDCLASS or WNDCLASSEX
wincl.hbrBackground = CreateSolidBrush(RGB(50, 238, 50));
Attempting to simulate client area based window dragging by returning HTCAPTION under a WM_NCHITTEST (excluding HTCLIENT & appropriate areas) works flawlessly when used with a parent window - however presence of child windows such as tabs placeholder windows, even when set to the extended style WS_EX_TRANSPARENT, cause clicks to fail to pass WM_NCHITTEST messages to the parent window (and attempting to process local WM_NCHITEST messages in a similar fashion produces the expected effect of dragging the child window around the parent rather than the parent itself).
Given that every area in that tab child window appears to be considered to be client area, processing WM_LBUTTONDOWN instead appears to produce the desired effect (see below):
case WM_LBUTTONDOWN: {
SendMessage(mainWnd.hWnd, WM_NCLBUTTONDOWN, HTCAPTION, lParam);
break;
}
Where mainWnd.hWnd is the parent window handle (hWnd is a member of a designed window properties helper class)
Although this produces the desired effect, I'm confused at whether WS_EX_TRANSPARENT is actually meant to allow clicks to pass through to underlying windows, and whether there is a more appropriate solution?
Have you tried returning HTTRANSPARENT from WM_NCHITTEST for the tab control? I think that should propagate the message to the parent window.
WS_EX_TRANSPARENT has to do with how the window is painted afaik.
I have a window which can be resized, but there are some situations when resizing is not possible because of the application state. Is there a way to prevent resizing the window temporarily?
I want to disable resizing by all means available to the users, which include window menu, dragging edges by mouse, user initiated window tiling performed by OS - and perhaps some other I am not aware of?
To retain the look of the window border and still prevent re-size (and cursor change), catch WM_NCHITTEST, pass it to DefWindowProc, if the returned code is one of the size constants, change the real return to something else, HTCLIENT for example
One way is to use GetWindowLong() with GWL_STYLE flag to get the window style and
reset/remove any styles you need, ie the WS_THICKFRAME style so that the window can't be resized.
You apply the new style with SetWindowLong.
Another possibility is to handle the WM_GETMINMAXINFO message and set the MINMAXINFO struct so that both min and max size of the window is the current size. Then the user can't resize the window either.
Following code in the window procedure seems to handle the case of user dragging the window edge/corner:
case WM_SIZING:
RECT &rc = *(LPRECT) lParam;
RECT windowRect;
GetWindowRect(hwnd, &windowRect);
rc = windowRect;
return 0;
I did not find anything yet to prevent the system from resizing the window when tiling/cascading windows. I hoped following might do the trick, but it seems it does not:
case WM_SIZE:
return TRUE;
I guess I can find similar measure for other cases, but at least I would need to know the exhaustive list of messages which can result in a window changing its size.
Also, while this really prevents the window from resizing, I would rather prevent the user from even initiating the resize, than apparently letting him to resize and then refusing to do so.