Drawing in window while resizing leaves Unpainted border - c++

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));

Related

Flickering on window when resizing from left side

It seems that my window is flickering whenever I move and window and resize at the same time. This usually occurs when sizing is done from the left side of the window.
Why does this flickering happen? In other words, what is the OS doing when you reposition a window?
Note: I do not experience flickering when resizing from the right side which means the window is not necessarily moving its origin X and Y.
Resizing a window under Windows involves several messages sent between the OS and the window's handler (the lpfnWndProc member of WNDCLASSEX structure used to register the window's class). You can discover them by yourself using some message monitoring tool. Spy++ that comes with Visual Studio is one such tool.
One interesting message is WM_NCCALCSIZE: this message, called during window resizing, can generate two rectangles (when WVR_VALIDRECTS flag is set): source and target specifying what content of the old window's client area can be "reused" at the new window's position. By default it's assumed that the top-left corner is a pivot:
resizing the left or top border causes the old window's content to get copied to preserve the pivot;
resizing the right ot bottom border copies nothing because the top-left corner of the window did not move.
This default copying can cause flicker, if it does not correspond to the way you position visuals during repaint. For example, everything that is displayed relative to the right or bottom border will be misplaced after resize from the left or top border: these objects will get moved unnecessarily leaving strange mix of old and new things after such resize, because only non-copied pixels will be repainted. If you try to cure the mess with InvalidateRect during, say, WM_SIZE you will get the flicker (the time interval where things are misplaced is very short but it still exists).
The easiest way to disable this behavior is by setting the CS_HREDRAW and CS_VREDRAW Class Styles for your window.
A 2018 update.
The WM_NCCALCSIZE WVR_VALIDRECTS trick is still a good way from preventing Windows XP/Vista/7 SetWindowPos from doing a needless BitBlt that causes the flickering.
However, Microsoft did it again and on Windows 8/10, the DWM window manager adds another layer of BitBlt on top of the legacy SetWindowPos BitBlt which can cause the same problem and is harder to work around.
For an explanation of why the unwanted BitBlt causes flickering, as well as sample code of the WM_NCCALCSIZE WVR_VALIDRECTS trick and some code ideas for how to prevent Windows 8/10 Aero from doing the same, please see:
How to smooth ugly jitter/flicker/jumping when resizing windows, especially dragging left/top border (Win 7-10; bg, bitblt and DWM)?

How to update the screen once it has lost focus

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.

c++ gdi drawing ellipse problem

ok i can draw ellipse the problem is this, im trying to draw one ellipse but change its x value to different one. like this i draw one ellipse and the x value is 1 after ten seconds i want it the x value to be 10 but it seems that im creating new ellipse with x value 10. here is my code
while(sd==1)//sd equal 1
{
sf++;//sf equals 1
onPaint(hdc);
InvalidateRect(hWnd,0,true);
}
//on paint function
VOID onPaint(HDC hdc)
{
Graphics graphics(hdc);
Pen pen(Color(255, 0, 0, 255));
graphics.DrawEllipse(&pen,sf , 0, 50, 50);
}
well i thought that invalidate rect will clear everything have been painted and repaint it but it didn't work
If you want to make an animation you are better setting a timer.
Using InvalidateRect as a way to generate WM_PAINTs seems overkill, it will do much more thant that. Instead you can draw directly in OnTimer call, since it is outside a WM_PAINT you will need to get a device context with GetDC.
For example if you can have the function DrawFrame(HDC hDC). OnTimer will update the current position and call DrawFrame, OnPaint will call DrawFrame but will not update the position (that way if you want to stop the animation you will have the last frame draw).
The DrawFrame will clear the background (probably with a FillRect), and draw the circle in the new position. If you have a large area this will flicker, to avoid it as Tom suggested you may use a memory DC and a HBITMAP for the double buffer.
You shouldn't try to draw multiple frames of an animation in one shot.
Save your variable sf somewhere, and in OnPaint(), increment sf, draw a single ellipse, and call Invalidate()
The Invalidate will trigger OnPaint() to be called again.
This should work, but will be very flickery :) You can fix the flicker by double-buffering.
InvalidateRect marks the window as "invalid", but that doesn't cause an erase and repaint to happen right away. The erasing and painting happen only when your message pump is running (e.g., the loop with GetMessage and DispatchMessage). When the message queue runs dry, GetMessage will synthesize WM_ERASEBKGND and WM_PAINT messages for the invalid windows. When those messages are dispatched to the window procedure, the window gets a chance to draw.
Your onPaint function only draws, it doesn't erase. And since your loop never exits, the message pump never gets to run.
For simple animations, the solution is to SetTimer. In your handler for the WM_TIMER messages, update your variables for single frame, call InvalidateRect, and return (which lets the message pump keep running). The erasing and painting message will happen, then the timer will fire again, and you'll get the next frame.

Constraining window position to desktop working area

I want to allow a user to drag my Win32 window around only inside the working area of the desktop. In other words, they shouldn't be able to have any part of the window extend outside the monitor(s) nor should the window overlap the taskbar.
I'd like to do it in a way that does cause any stuttering. Handling WM_MOVE messages and calling MoveWindow() to reposition the window if it goes off works, but I don't like the flickering effect that's caused by MoveWindow().
I also tried handling WM_MOVING which prevents the need to call MoveWindow() by altering the destination rectangle before the move actually happens. This resolves the flickering problem, but another issue I run into is that the cursor some times gets aways from the window when a drag occurs allowing the user to drag the window around while the cursor is not even inside the window.
How do I constrain my window without running into these issues?
Windows are, ultimately, positioned via the SetWindowPos API.
SetWindowPos starts by validating its parameters by sending the window being sized or moved a WM_WINDOWPOSCHANGING message, and then a WM_WINDOWPOSCHANGED message notifying the window proc of the changed size and/or position.
DefWindowProc handling of these messages is to, in turn, send WM_GETMINMAXINFO and then WM_SIZE or WM_MOVE messages.
Anyway, handle WM_WINDOWPOSCHANGING to filter both user, and code, based attempts to position a window out of bounds.
Keep in mind that users with multi-monitor setups may have a desktop that extends into negative x- and y-coordinates, or that is not rectangular. Also, some users use alternative window managers such as LiteStep, which implement virtual desktops by moving them off-screen; if you try to fight this, your application will break for these users.
You can do this by handling the WM_MOVING message and changing the RECT pointed to by the lParam.
lParam: Pointer to a RECT structure with the current position of the window, in screen coordinates. To change the position of the drag rectangle, an application must change the members of this structure.
you may also want to handle WM_ENTERSIZEMOVE to know when the window is beginning to move, and WM_EXITSIZEMOVE
WM_GETMINMAXINFO is what you seem to be looking for.

Can I change the thickness of the border of a window with MFC?

Normally, the thickness of a window is 4 pixels, which can be retrieved by GetSystemMetrics method. Can I change its value, for example 2 pixels?
Thank you very much!
Simple answer: No. Not for a specific window.
Complicated answer: The border is drawn as part of the "non-client" region of the window. This is all handled (under the hood) by the default processing (i.e. DefWindowProc), along with the caption, minimize, maximize buttons, etc. You can override this by handling the WM_NCPAINT message. You'll then be responsible for drawing the entire non-client area of your window. You'll also want to handle the WM_NCCALCSIZE message, so that Windows knows how much of the remaining space to give to your client area.
Alternatively, you can set the border style of your window to none. This will allow Windows to draw the caption for you, although it'll probably look slightly different. Unfortunately, by doing this, you lose the drag-to-resize functionality. For that, you'll need to handle the WM_NCHITTEST message.