Enabled
Disabled
When I disable a button ( Created with BS_BITMAP style flag ) it changes its look (Please see the above images), same thing happens with edit controls.
How do I make the controls not change when disabled ?
I can do that by subclassing the control, but is there an easier way ?
I don't want to subclass the controls just for that, if possible.
You do not need to subclass the control in order to do this, although I'd say it would be much cleaner. The alternative to set the BS_OWNERDRAW style and handle the WM_DRAWITEM message. That means you're taking over all drawing, but that's okay since you don't want it to look like a normal button anyway.
I could not agree more with Jonathan Potter's observation that it is extremely bad UI design to fail to indicate to the user which buttons are enabled and which ones are not. There are multiple ways to do this, but not doing it is not a viable option. Fortunately, it is easy to do with WM_DRAWITEM, since it tells you the button's current state.
So make the WM_DRAWITEM message handler look like this (in the parent window's window procedure):
case WM_DRAWITEM:
{
const DRAWITEMSTRUCT* pDIS = reinterpret_cast<DRAWITEMSTRUCT*>(lParam);
// See if this is the button we want to paint.
// You can either check the control ID, like I've done here,
// or check against the window handle (pDIS->hwndItem).
if (pDIS->CtlID == 1)
{
// Load the bitmap.
const HBITMAP hBmp = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP1));
// Draw the bitmap to the button.
bool isEnabled = (pDIS->itemState & ODS_DISABLED) == 0;
DrawState(pDIS->hDC,
nullptr,
nullptr,
reinterpret_cast<LPARAM>(hBmp),
0, 0, 0, 0, 0,
DST_BITMAP | (isEnabled ? DSS_NORMAL : DSS_DISABLED));
// Delete the bitmap.
DeleteObject(hBmp);
// Draw the focus rectangle, if applicable.
if ((pDIS->itemState & ODS_FOCUS) && ((pDIS->itemState & ODS_NOFOCUSRECT) == 0))
{
DrawFocusRect(pDIS->hDC, &pDIS->rcItem);
}
// Indicate that we handled this message.
return TRUE;
}
break;
}
Naturally, you could optimize this code further by loading the bitmap a single time and caching it in a global object, rather than loading and destroying it each time the button needs painting.
Note that I've used the DrawState function, which can draw bitmaps either in a "normal" (DSS_NORMAL) or "disabled" (DSS_DISABLED) state. That simplifies the code considerably, and allows us to easily handle the disabled state, but unfortunately the result looks a little bit ugly. That's because the DrawState function converts the bitmap to monochrome before applying any effects other than normal.
You probably don't like that effect, so you'll need to do something else. For example, use two separate images, one for the enabled state and the other for the disabled state, and draw the appropriate one. Or convert your normal color image into grayscale, then draw that for the disabled state.
And if the custom-drawing code runs too slowly, you can optimize it even further by checking the value of pDIS->itemAction and only re-drawing the necessary portions.
Then, once you think you've got everything all polished and efficient, the inevitable bug reports will start to roll in. For example, keyboard accelerators are not supported. Then, once you add support for these, you'll need to indicate that in the UI. That will be difficult with a bitmap that already contains the text; the only way to draw a letter underlined is to draw the text yourself. This all proves that owner-draw is way too much work. Just let Windows draw the controls the normal way, don't break everything for your users just because some designer thinks it "looks cool".
Related
INTRODUCTION AND RELEVANT INFORMATION:
I have implemented complex painting of the main window’s background and its child static controls.
The picture below shows how it looks.
Static controls have SS_NOTIFY style, which is important to mention, as certain things happen when user clicks on them.
At this point, actions activated when clicking on them, are not relevant.
Both main window, and static controls, have gradient backgrounds, which were made through usage of GradientFill(...) API.
Top banner of the main window is created with gray brush, and the grid lines were created with LineTo(...) and MoveTo(...) API.
Map on the orange static control, and the top left logo are EMF files, top right logo is PNG file, and other pictures are bitmaps.
Orange static control has 4 child static controls which are owner drawn and also have SS_NOTIFY style.
It was the only way I could have thought of, which enabled me to draw the control the way it was asked of me ( if I can improve on this, please suggest it, I will accept any reasonable suggestion ).
In order to paint the orange static control, I have decided to paint its background in WM_CTLCOLORSTATIC handler, and to owner draw child static controls in a subclass procedure.
Notifications received from child static controls are also handled in the orange static controls subclass procedure, since I didn’t know how to forward them to the parent window, but are omitted since they are also irrelevant at this moment.
I have decided to provide link to the demo project, instead of making this post quite lengthy with code snippets.
I have tried to submit demo application as small and simple as it is possible.
I did not skimp on the commentaries, so I believe everything is well covered and explained in source code.
If there are still questions please leave a comment and I will reply as soon as possible ( usually immediately, or in the same day, at least ).
Here is the link to the demo project:http://www.filedropper.com/geotermistgrafika_1
Important update:
/==========================================================/
Text bellow in square brackets was the original part of the question, but is now omitted since the project had memory leaks.The above link links to an improved version.
[ Updated in response to member xMRi's comment: This link should be fine: http://www.filedropper.com/geotermistgrafika ]
/==========================================================/
I work on Windows XP, using MS Visual Studio C++ and pure Win32 API.
One note: since Express edition of VS has no resource editor, resource file and resource header were created using ResEdit from here: http://www.resedit.net/.
PROBLEM:
When I resize my window, static controls slightly flicker.
MY EFFORTS TO SOLVE PROBLEM:
I believe that my code has no memory leaks-therefore I doubt this is the problem, but being inexperienced, I would highly appreciate if my assumption can be somehow confirmed.
I think that I have properly handled WM_ERASEBKGND, and I have excluded styles CS_VREDRAW and CS_HREDRAW from my window class-therefore flickering should not be caused because of this.
I have forgot to mention, that my window has WS_CLIPCHILDREN style, so I am mentioning that now, in response to the comment bellow made by member Roger Rowland.
I have implemented double buffering for both handlers, in order to avoid flickering.
QUESTIONS:
How can I modify code in demo project to get rid of flickering?
I need advice on how to optimize both WM_PAINT and WM_CTLCOLORSTATIC handlers, so my painting code gets more efficient and faster.
A small note for second question:
I was thinking to improve my code by drawing the entire picture on the main window’s background, and to put transparent static controls on top of the part of the picture that corresponds that static controls background.
That way, I would only return NULL_BRUSH in my WM_CTLCOLORSTATIC handler, and do all the work in the WM_PAINT.
Am I on the right track with this idea? Could this work ?
Thank you.
Regards.
Firstly, your App is leaky as hell. Haven't looked for leaks, but most of them should be in WM_CTLCOLORSTATIC as you forget to delete HBITMAP's(use this neat freeware http://www.nirsoft.net/utils/gdi_handles.html to look for gdi leaks).
Secondly, your code is way to big. I noticed that you didn't use functions, maybe because you don't know what they are capable of. For example I would use:
void DrawBackground(HDC &hDC, SOMEINFOSTRUCT GradientInfo, LPCTSTR Text);
to simplify your code a lot.
Anyway enough of lecturing, let's go back to your problem. In WM_CTLCOLORSTATIC you must return brush, you want to paint background with. What you're doing now is painting background manually using Bitblt(), then return NULL brush and program paints it on your already painted background. Instead of painting it yourself, let the brush do the job.
Simply instead of the last Bitblt() use CreatePatternBrush(), but then you need to take care of this Brush and here is what you should do:
HBRUSH TempBrush = NULL; //Create global brush
//Some Code....
case WM_CTLCOLORSTATIC:
{
if (TempBrush != NULL)
{
DeleteObject(TempBrush);
TempBrush = NULL;
}
//Let's skip to the end....
GradientFill( MemDC, vertex3, 3, &gTriangle, 1,
GRADIENT_FILL_TRIANGLE );
TempBrush = CreatePatternBrush(bmp);// these 3 line should be at the
//end of every if
DeleteDC(MemDC); // or put them once outside if's
DeleteObject(bmp); // also if you delete HDC first, you don't need to
//unselect hbitmap
}
return (LRESULT)TempBrush;
}
break;
case WM_CLOSE:
{
if (TempBrush != NULL)
{
DeleteObject(TempBrush);
TempBrush = NULL;
}
//.......
I am trying to get my dialog box to match. I have been all through google, random testing, etc, even read some places it cant be done.
What I have been able to do is to use one of the messages to set font and colors, but nowhere about drawing itself.
I would think it has to be able to do...
Does anyone have any ideas? Or know anything about this?
http://imageshack.com/a/img832/5955/91m.png
It looks like edit controls don't support owner draw, but you can still solve your direct problem. According to the MSDN page for EDITTEXT, by default edit controls in a resource file have the WS_BORDER style set. Looks like you can get rid of it with something like this:
EDITTEXT IDC_EDIT1,17,51,136,14,ES_AUTOHSCROLL | NOT WS_BORDER
For the status bar, you might try using a static control with customized colors instead of a real status bar. Or you could roll your own, specify the window class name in the resource file, and make sure you register the class before displaying the dialog.
UPDATED: Wow, the documentation for status bar is terrible. You can owner draw one, though. Follow these steps:
// where hStatus is the HWND of a status bar...
// You must set simple mode to false, because simple mode doesn't
// support owner draw.
SendMessage(hStatus, SB_SIMPLE, FALSE, 0);
// I'm assuming 1 status bar part for demonstration. Setting the right edge
// for the 1 part to -1 make it take up the whole status bar.
int partWidths[] = { -1 };
SendMessage(hStatus, SB_PARTS, 1, reinterpret_cast<LPARAM>(partWidths));
// There is background stuff that stays behind even with owner draw,
// so you have to set the background color to black, too, to get rid of
// any appearance of borders.
SendMessage(hStatus, SB_SETBKCOLOR, 0, RGB(0, 0, 0));
// There is still a slim border that stays behind, so you need to set
// SBT_NOBORDERS in addition to SBT_OWNERDRAW. The 0 is the index of the
// status bar part. It could be anything between 0 and 255.
SendMessage(
hStatus,
SB_SETTEXT,
SBT_NOBORDERS | SBT_OWNERDRAW | 0,
reinterpret_cast<LPARAM>(_T("Status")));
From there, you must also handle the WM_DRAWITEM for the status bar. Now, as to why I say the documentation for status bar is terrible...
Docs for SB_SETTEXT say the high byte of the low order word of the WPARAM can be one of the values that follows. There are two problems with this:
You can combine them, and you must for this to work. MFC does it, too. I checked.
You might be tempted to write MAKEWPARAM(MAKEWORD(0, SBT_OWNERDRAW), 0). This will not work. By appearances, the SBT_ styles are defined so that they will automatically appear in the high byte of the low word if you just OR them with your index value.
That I had to look at the MFC source code to figure out how to use SB_SETTEXT correctly is telling.
Edit controls do not have an owner-draw mode, however you can subclass an Edit control and process messages like WM_ERASEBKGND, WM_NCPAINT, WM_PAINT, etc, as well as the WM_CTLCOLOREDIT message sent to the edit's parent window.
The answer for part 2, vertical aligning text in an edit:
RECT rect;
GetClientRect(GetDlgItem(hwnd, IDC_TIMEINPUT),&rect);
Rectangle(hdcEdit, rect.left, rect.top, rect.right, rect.bottom);
rect.left+=5; rect.top+=5; rect.right+=5; //rect.bottom+=5;
SendMessage(GetDlgItem(hwnd, IDC_TIMEINPUT), EM_SETRECTNP, 0, (LPARAM)&rect);
Has to be multi-line, and you really do have to play around with different numbers to keep it single lined, and maintain the vertical align. The EMS_SETRECTNP allows you to specify where you want the text to be, allowing the Edit to have a larger height.
I have a mode less dialog that I have changed the shape into a roundrect using SetWindowRgn(). I would like to draw a colored border around it using FrameRgn. Here is the code I am using
BOOL CMyDlg::OnInitDialog()
{
CDialog::OnInitDialog(); m_Brush.CreateSolidBrush(RGB(255,255,255));
CRect rcDialog;
GetClientRect(rcDialog);
// This Creates area assigned to Dialog: This goes directly below the above in OnInitDialog
m_rgnShape.CreateRoundRectRgn(rcDialog.TopLeft().x, rcDialog.TopLeft().y,rcDialog.BottomRight().x,rcDialog.BottomRight().y, rcDialog.Width()/8, rcDialog.Height()/8);
::SetWindowRgn(GetSafeHwnd(), (HRGN)m_rgnShape, TRUE);
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
void CMyDlg::OnPaint()
{
CPaintDC dc(this); // device context for painting
CBrush brush;
brush.CreateSolidBrush(RGB(255,0,0));
dc.FrameRgn(&m_rgnShape, &brush, 2, 2);
}
Can anyone explain why the FrameRgn is not working, and maybe provide some sample code here
that will make it work.
As shown in the CWnd::SetWindowRgn documentation:
After a successful call to SetWindowRgn, the operating system owns the
region specified by the region handle hRgn. The operating system does
not make a copy of the region, so do not make any further function
calls with this region handle, and do not close this region handle.
What this basically means is that you can't then go and use the region for another purpose, and you also can't "lose" the region. As it's a member variable, this last issue isn't a problem you need to worry about. But regarding the "do not use it" part, you will notice that the FrameRgn(...) call most likely returned zero, indicating the failure when trying to draw.
What you can do is to detach the region handle from the CRgn object and use that to set the window region, then you can recreate a new one as before:
m_rgnShape.CreateRoundRectRgn(...);
HGDIOBJ hRgn = m_rgnShape.Detach();
::SetWindowRgn(GetSafeHwnd(), (HRGN)hRgn, TRUE);
m_rgnShape.CreateRoundRectRgn(...);
For a better description, have a look at this article which covers Setting a Window Region, to make it look like a cat.
Edit: Your comment mentions that now, the framed region is effectively offset by an amount. The amount is likely to be the size of the border of your window.
When you call GetClientRect, it returns the size of the client area of the window - the part you can easily draw on, and the part that is "described" by the device context when you do CPaintDC dc(this); in your OnPaint() method.
The reason for the offset is that your window has a border, which you don't normally draw on (there are ways, but we'll ignore those for now). So the device context describes an area that's offset from your window.
The simplest solution to this in your case is likely to be to modify the dialog template to specify no borders. This will of course limit resizing the window, but as you've already set a region, I'm assuming resizing isn't an option either.
I'm updating an MFC dialog with a number of buttons on it.
At present, the dialog has a Picture control covering the whole dialog providing a patterned background. On top of that, each button is a CBitmapButton using (opaque) images carefully generated to match the area of background they cover.
It would obviously be much easier if the images could be created as mostly transparent, so the background shows through automatically. However, I can't work out how to get MFC to render transparent images correctly in this case.
I understand that I might want a different class to CBitmapButton, or need to write a custom subclass; that's fine, but I don't know where to start. It would be nice to support 32-bit BMP or PNG with alpha channel, but I'd settle for the "specified colour should be transparent" type.
It may not be the best way to do it, but what I'd do is create a custom CButton derived class (assuming that you're actually using the rest of the CButton functionality), then override the DrawItem function to put your custom draw code in.
For the image itself I'd use a Bitmap GDI+ object (which will allow you to load either BMPs or PNGs with alpha channels) then use the regular DrawImage function to draw the bitmap.
If you're going to put PNGs into your resource file then you need to put them in as a "PNG" type. Make sure when you look in the resource file code that the entry looks like
IDB_PNG1 PNG "C:\temp\test.png"
and doesn't try to treat it as a BITMAP resource otherwise you'll have problems loading them.
Edit
Putting my response here so I can post code. Yes, I meant to derive a custom class from CButton, then add a Gdiplus::Bitmap member variable. Here is roughly what you'll need to do to get it to work, though I haven't checked that the code actually compiles and works, but hopefully you'll get the idea. It's not the most efficient way to do it, but if you've not done much custom drawing before then it does have the advantage of being simple!
void CMyButton::LoadImage(const int resourceID)
{
m_pBitmap = Gdiplus::Bitmap::FromResource(NULL, MAKEINTRESOURCE(resourceID));
ASSERT(m_pBitmap);
}
void CMyButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
ASSERT(lpDrawItemStruct->CtlType == ODT_BUTTON);
CRect rcClient;
GetClientRect(&rcClient);
if (lpDrawItemStruct->itemState & ODS_SELECTED)
{
// If you want to do anything special when the button is pressed, do it here
// Maybe offset the rect to give the impression of the button being pressed?
rcClient.OffsetRect(1,1);
}
Graphics gr(lpDrawItemStruct->hDC);
gr.DrawImage(m_pBitmap, rcClient.left, rcClient.top);
}
When the user grabs a corner of a resizable window, and then moves it, windows first moves the contents of the window around, then issues a WM_SIZE to the window being resized.
Thus, in a dialog where I want to control the movement of various child controls, and I want to eliminate flickering, the user first sees what windows OS thinks the window will look like (because, AFAICT, the OS uses a bitblt approach to moving things around inside the window before sending the WM_SIZE) - and only then does my dialog get to handle moving its child controls around, or resize them, etc., after which it must force things to repaint, which now causes flicker (at the very least).
My main question is: Is there a way to force windows NOT to do this stupid bitblt thing? Its definitely going to be wrong in the case of a window with controls that move as the window is resized, or that resize themselves as their parent is resized. Either way, having the OS do a pre-paint just screws the works.
I thought for a time that it might be related to CS_HREDRAW and CSVREDRAW class flags. However, the reality is that I don't want the OS to ask me to erase the window - I just want to do the repainting myself without the OS first changing the contents of my window (i.e. I want the display to be what it was before the user started resizing - without any bitblit'ing from the OS). And I don't want the OS to tell every control that it needs to be redrawn either (unless it happened to be one that was in fact obscured or revealed by the resize.
What I really want:
To move & resize child controls before anything gets updated onscreen.
Draw all of the moved or resized child controls completely so that they appear without artifacts at their new size & location.
Draw the spaces inbetween the child controls without impacting the child controls themselves.
NOTE: Steps 2 and 3 could be reversed.
The above three things appear to happen correctly when I use DeferSetWindowPos() in combination with the dialog resource marked as WS_CLIPCHILDREN.
I'd get an additional small benefit if I could do the above to a memory DC, and then only do a single bitblt at the end of the WM_SIZE handler.
I have played with this for a while now, and I cannot escape two things:
I still am unable to suppress Windows from doing a 'predictive bitblt'. Answer: See below for a solution that overrides WM_NCCALCSIZE to disable this behavior.
I cannot see how one can build a dialog where its child controls draw to a double buffer. Answer: See John's answer (marked as answer) below for how to ask Windows OS to double buffer your dialog (note: this disallows any GetDC() in-between paint operations, according to the docs).
My Final Solution (Thank you everyone who contributed, esp. John K.):
After much sweat and tears, I have found that the following technique works flawlessly, both in Aero and in XP or with Aero disabled. Flicking is non-existent(1).
Hook the dialog proc.
Override WM_NCCALCSIZE to force Windows to validate the entire client area, and not bitblt anything.
Override WM_SIZE to do all of your moves & resizes using BeginDeferWindowPos/DeferWindowPos/EndDeferWindowPos for all visible windows.
Ensure that the dialog window has the WS_CLIPCHILDREN style.
Do NOT use CS_HREDRAW|CS_VREDRAW (dialogs don't, so generally not an issue).
The layout code is up to you - its easy enough to find examples on CodeGuru or CodeProject of layout managers, or to roll your own.
Here are some code excerpts that should get you most of the way:
LRESULT ResizeManager::WinProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
switch (msg)
{
case WM_ENTERSIZEMOVE:
m_bResizeOrMove = true;
break;
case WM_NCCALCSIZE:
// The WM_NCCALCSIZE idea was given to me by John Knoeller:
// see: http://stackoverflow.com/questions/2165759/how-do-i-force-windows-not-to-redraw-anything-in-my-dialog-when-the-user-is-resiz
//
// The default implementation is to simply return zero (0).
//
// The MSDN docs indicate that this causes Windows to automatically move all of the child controls to follow the client's origin
// and experience shows that it bitblts the window's contents before we get a WM_SIZE.
// Hence, our child controls have been moved, everything has been painted at its new position, then we get a WM_SIZE.
//
// Instead, we calculate the correct client rect for our new size or position, and simply tell windows to preserve this (don't repaint it)
// and then we execute a new layout of our child controls during the WM_SIZE handler, using DeferWindowPos to ensure that everything
// is moved, sized, and drawn in one go, minimizing any potential flicker (it has to be drawn once, over the top at its new layout, at a minimum).
//
// It is important to note that we must move all controls. We short-circuit the normal Windows logic that moves our child controls for us.
//
// Other notes:
// Simply zeroing out the source and destination client rectangles (rgrc[1] and rgrc[2]) simply causes Windows
// to invalidate the entire client area, exacerbating the flicker problem.
//
// If we return anything but zero (0), we absolutely must have set up rgrc[0] to be the correct client rect for the new size / location
// otherwise Windows sees our client rect as being equal to our proposed window rect, and from that point forward we're missing our non-client frame
// only override this if we're handling a resize or move (I am currently unaware of how to distinguish between them)
// though it may be adequate to test for wparam != 0, as we are
if (bool bCalcValidRects = wparam && m_bResizeOrMove)
{
NCCALCSIZE_PARAMS * nccs_params = (NCCALCSIZE_PARAMS *)lparam;
// ask the base implementation to compute the client coordinates from the window coordinates (destination rect)
m_ResizeHook.BaseProc(hwnd, msg, FALSE, (LPARAM)&nccs_params->rgrc[0]);
// make the source & target the same (don't bitblt anything)
// NOTE: we need the target to be the entire new client rectangle, because we want windows to perceive it as being valid (not in need of painting)
nccs_params->rgrc[1] = nccs_params->rgrc[2];
// we need to ensure that we tell windows to preserve the client area we specified
// if I read the docs correctly, then no bitblt should occur (at the very least, its a benign bitblt since it is from/to the same place)
return WVR_ALIGNLEFT|WVR_ALIGNTOP;
}
break;
case WM_SIZE:
ASSERT(m_bResizeOrMove);
Resize(hwnd, LOWORD(lparam), HIWORD(lparam));
break;
case WM_EXITSIZEMOVE:
m_bResizeOrMove = false;
break;
}
return m_ResizeHook.BaseProc(hwnd, msg, wparam, lparam);
}
The resizing is really done by the Resize() member, like so:
// execute the resizing of all controls
void ResizeManager::Resize(HWND hwnd, long cx, long cy)
{
// defer the moves & resizes for all visible controls
HDWP hdwp = BeginDeferWindowPos(m_resizables.size());
ASSERT(hdwp);
// reposition everything without doing any drawing!
for (ResizeAgentVector::const_iterator it = m_resizables.begin(), end = m_resizables.end(); it != end; ++it)
VERIFY(hdwp == it->Reposition(hdwp, cx, cy));
// now, do all of the moves & resizes at once
VERIFY(EndDeferWindowPos(hdwp));
}
And perhaps the final tricky bit can be seen in the ResizeAgent's Reposition() handler:
HDWP ResizeManager::ResizeAgent::Reposition(HDWP hdwp, long cx, long cy) const
{
// can't very well move things that no longer exist
if (!IsWindow(hwndControl))
return hdwp;
// calculate our new rect
const long left = IsFloatLeft() ? cx - offset.left : offset.left;
const long right = IsFloatRight() ? cx - offset.right : offset.right;
const long top = IsFloatTop() ? cy - offset.top : offset.top;
const long bottom = IsFloatBottom() ? cy - offset.bottom : offset.bottom;
// compute height & width
const long width = right - left;
const long height = bottom - top;
// we can defer it only if it is visible
if (IsWindowVisible(hwndControl))
return ::DeferWindowPos(hdwp, hwndControl, NULL, left, top, width, height, SWP_NOZORDER|SWP_NOACTIVATE);
// do it immediately for an invisible window
MoveWindow(hwndControl, left, top, width, height, FALSE);
// indicate that the defer operation should still be valid
return hdwp;
}
The 'tricky' being that we avoid trying to mess with any windows that have been destroyed, and we don't try to defer a SetWindowPos against a window that is not visible (as this is documented as "will fail".
I've tested the above in a real project that hides some controls, and makes use of fairly complex layouts with excellent success. There is zero flickering(1) even without Aero, even when you resize using the upper left corner of the dialog window (most resizable windows will show the most flickering and problems when you grab that handle - IE, FireFox, etc.).
If there is interest enough, I could be persuaded to edit my findings with a real example implementation for CodeProject.com or somewhere similar. Message me.
(1) Please note that it is impossible to avoid one draw over the top of whatever used to be there. For every part of the dialog that has not changed, the user can see nothing (no flicker whatsoever). But where things have changed, there is a change visible to the user - this is impossible to avoid, and is a 100% solution.
You can't prevent painting during resizing, but you can (with care) prevent repainting which is where flicker comes from. first, the bitblt.
There a two ways to stop the bitblt thing.
If you own the class of the top level window, then just register it with the CS_HREDRAW | CS_VREDRAW styles. This will cause a resize of your window to invalidate the entire client area, rather than trying to guess which bits are not going to change and bitblting.
If you don't own the class, but do have the ability to control message handling (true for most dialog boxes). The default processing of WM_NCCALCSIZE is where the class styles CS_HREDRAW and CS_VREDRAW are handled, The default behavior is to return WVR_HREDRAW | WVR_VREDRAW from processing WM_NCCALCSIZE when the class has CS_HREDRAW | CS_VREDRAW.
So if you can intercept WM_NCCALCSIZE, you can force the return of these values after calling DefWindowProc to do the other normal processing.
You can listen to WM_ENTERSIZEMOVE and WM_EXITSIZEMOVE to know when resizing of your window starts and stops, and use that to temporarily disable or modify the way your drawing and/or layout code works to minimize the flashing. What exactly you want to do to modify this code will depend on what your normal code normally does in WM_SIZE WM_PAINT and WM_ERASEBKGND.
When you paint the background of your dialog box, you need to not paint behind any of the child windows. making sure that the dialog has WS_CLIPCHILDREN solves this, so you have this handled already.
When you do move the child windows, Make sure that you use BeginDeferWindowPos / EndDefwindowPos so that all of the repainting happens at once. Otherwise you will get a bunch of flashing as each window redraws their nonclient area on each SetWindowPos call.
If I understood the question properly, it's exactly the question Raymond addressed today.
Here's a 2018 update, since I just ran through the very same gauntlet as you.
The "final solution" in your question, and the related answers, that mention tricks with WM_NCCALCSIZE and CS_HREDRAW|CS_VREDRAW are good for preventing Windows XP/Vista/7 from doing the BitBlt that molests your client area during resizing. It might even be useful to mention a similar trick: you can intercept WM_WINDOWPOSCHANGING (first passing it onto DefWindowProc) and set WINDOWPOS.flags |= SWP_NOCOPYBITS, which disables the BitBlt inside the internal call to SetWindowPos() that Windows makes during window resizing. This has the same eventual effect of skipping the BitBlt.
And some people mentioned that your WM_NCCALCSIZE trick no longer works in Windows 10. I think that might be because the code you wrote returns WVR_ALIGNLEFT|WVR_ALIGNTOP when it should be returning WVR_VALIDRECTS in order for the two rectangles you constructed (nccs_params->rgrc[1] and nccs_params->rgrc[2]) to be used by Windows, at least according to the very skimpy dox in the MSDN pages for WM_NCCALCSIZE and NCCALCSIZE_PARAMS. It's possible that Windows 10 is more strict about that return value; I would try it out.
However, even if we assume that we can convince Windows 10 not to do BitBlt inside SetWindowPos(), it turns out there's a new problem...
Windows 10 (and possibly also Windows 8) adds another layer of client area molestation on top of the old legacy molestation from XP/Vista/7.
Under Windows 10, apps do not draw directly to the framebuffer, but instead draw into offscreen buffers that the Aero Window manager (DWM.exe) composites.
It turns out that DWM will sometimes decide to "help" you by drawing its own content over your client area (sort of like a BitBlt but even more perverse and even further out of your control).
So in order to be free of client area molestation, we still need to get WM_NCCALCSIZE under control but we also need to prevent DWM from messing with your pixels.
I was fighting with exactly the same problem and created a roundup Question/Answer which brings together 10 years of posts on this topic and offers some new insights (too long to paste the content here in this question). The BitBlt mentioned above is no longer the only problem, as of Windows Vista. Enjoy:
How to smooth ugly jitter/flicker/jumping when resizing windows, especially dragging left/top border (Win 7-10; bg, bitblt and DWM)?
For some controls, you can use WM_PRINT message to make the control draw into a DC. But that doesn't really solve your primary problem, which is that you want Windows to NOT draw anything during resize, but to let you do it all.
And the answer is that you just can't do what you want as long as you have child windows.
The way I ended up solving this eventually in my own code is to switch to using Windowless Controls. Since they have no window of their own, they always draw at the same time (and into the same DC) as their parent window. This allows me to use simple double buffering to completely remove flicker. I can even trivially suppress painting of the children when I need to just by not calling their draw routine inside the parent's draw routine.
This is the only way I know of to completely get rid of flicker and tearing during resize operations.
If you can find a place to plug it in, CWnd::LockWindowUpdates() will prevent any drawing from occuring until after you unlock the updates.
But keep in mind this is a hack, and a fairly ugly one at that. Your window will look terrible during resizes. If the problem you are having is flickering during resizes, then the best thing to do is diagnose the flickering, rather than hiding the flickering by blocking paints.
One thing to look for are redraw commands that get called too often during the resize. If you r window's controls are calling RedrawWindow() with the RDW_UPDATENOW flag specified, it is going to repaint then and there. But you can strip out that flag and specify RDW_INVALIDATE instead, which tells the control to invalidate the window without repainting. It will repaint at idle time, keeping the display fresh without spazzing out.
There are various approaches, but I found the only one that can be used generally is double buffering: draw to an offscreen buffer, then blit the entire buffer to screen.
That comes for free in Vista Aero and above, so your pain might be shortlived.
I am not aware of a general double-buffering implementation for windows and system controls under XP, However, here are some things to explore:
Keith Rule's CMemDC for double-buffering anything you draw yourself with GDI
WS_EX_COMPOSITED Window style (see the remarks section, and something here on stackoverflow)
there is only one way to effectively diagnose repainting problems - remote debugging.
Get a 2nd PC. Install MSVSMON on it. Add a post build step or utility project that copies your build products to the remote PC.
Now you should be able to place breakpoints in WM_PAINT handlers, WM_SIZE handlers and so on and actually trace through your dialog code as it performs the size and redraw. If you download symbols from the MS symbol servers you will be able to see full call stacks.
Some well placed breakpoints - in your WM_PAINT, WM_ERAGEBKGND handlers and you should have a good idea of why your window is being synchronously repainted early during the WM_SIZE cycle.
There are a LOT of windows in the system that consist of a parent window with layered child controls - explorer windows are massivly complicated with listviews, treeviews preview panels etc. Explorer does not have a flicker problem on resizing, so It is celarly possible to get flicker free resizing of parent windows :- what you need to do is catch the repaints, figure out what caused them, and, well, ensure that the cause is removed.
What appears to work:
Use the WS_CLIPCHILDREN on the parent dialog (can be set in WM_INITDIALOG)
During WM_SIZE, loop through the child controls moving and resizing them using DeferSetWindowPos().
This is very close to perfect, in my testing under Windows 7 with Aero.