0

I have a function which takes a rectangular region of a bitmap image, rescales it to different dimensions, and draws it at some offset inside of a window within my dialog-box application:

void DrawImage(HANDLE hImageBitmap,
               CDC* pDstDC,const CRect& dstRect,
               CDC* pSrcDC,const CRect& srcRect)
{
    pSrcDC->SelectObject(hImageBitmap);
    pDstDC->SetStretchBltMode(HALFTONE);
    pDstDC->StretchBlt
    (
        dstRect.left,dstRect.top,dstRect.Width(),dstRect.Height(),pSrcDC,
        srcRect.left,srcRect.top,srcRect.Width(),srcRect.Height(),SRCCOPY
    );
}

I create and maintain the window using a CWnd m_cImageWindow member variable.

I perform the drawing from the dialog-box's OnPaint handler as follows:

CDC* pDC = m_cImageWindow.GetDC();
CDC  cDC;
cDC.CreateCompatibleDC(pDC);

CRect srcRect = ...;
CRect dstRect = ...;
DrawImage(m_hImageBitmap,pDC,dstRect,&cDC,srcRect);

cDC.DeleteDC();
m_cImageWindow.ReleaseDC(pDC);

I have two problems:

  1. I see flickering whenever I change the drawing parameters. The standard way to solve this, from what I have read here and there, is by using a temporary DC for double-buffering. But as far as I understand, this is exactly what I am already doing.

  2. If some of the destination region falls outside the window, it is painted over other controls within the dialog box. I am able to partially solve this by calling MoveWindow or SetWindowPos for each one of these controls. But I can still see the image flickering behind them. I have tried calling SetWindowPos in various different ways, hoping in vain that it would dictate a strict Z-order of the controls.

Thank you.

goodvibration
  • 5,302
  • 1
  • 17
  • 43
  • 2
    An `OnPaint` event handler should not create its own DC to draw into. You should be using the `CPaintDC` class. – Cody Gray May 29 '17 at 13:36
  • @CodyGray: Thanks. I've tried that, but it doesn't seem to work for me. Probably because the paint-DC that I create belongs to the dialog-box itself and not to the window in which I am trying to draw. Can you please provide some more concrete evidence to why the "`OnPaint` event handler should not create its own DC"? Thank you. – goodvibration May 29 '17 at 13:42
  • 3
    Um… Why are you handling the paint event for one window (a dialog) and trying to paint on a *different* window? If you want to paint on a window, handle *that window's* paint event. – Cody Gray May 29 '17 at 13:43
  • 1
    Concrete evidence, why the `OnPaint` handler should not create its own DC: That DC never validates the invalid area, keeping a stream of `WM_PAINT` messages. (`CPaintDC` validates the invalid area in its c'tor.) – IInspectable May 29 '17 at 13:59
  • @IInspectable: I do create the paint-DC at the beginning of `OnPaint` (so as to generate the `BeginPaint` and `EndPaint` messages). I'm just not using it for drawing. – goodvibration May 29 '17 at 14:04
  • Make `m_cImageWindow` a custom control by deriving from `CWnd`, override `CWnd::OnPaint()` for this custom control to draw the bitmap in the window where it belongs to. To solve flickering issue is another topic, but first get the responsibilities for drawing right. – zett42 May 29 '17 at 14:06
  • @zett42: Thanks. I've just began doing exactly that. One problem is that the entire data-structure resides in my dialog-box class, which means I need to pass to the window child some sort of const-reference to every data entity. So I guess I need to look up for the right design pattern in order to do this neatly. – goodvibration May 29 '17 at 14:11
  • 1
    Why not just draw directly on the dialog box then? Use `WS_CLIPCHILDREN` to avoid painting over the other controls. – andlabs May 29 '17 at 16:51
  • .@andlabs: Thanks. This should be added to the style of the dialog-box? – goodvibration May 29 '17 at 17:02
  • @andlabs: Adding `WS_CLIPSIBLINGS` to the style of the window itself (not the dialog-box) seems to resolve the 2nd problem. Thank you for the this lead!!! – goodvibration May 29 '17 at 17:09
  • If you really want to use an existing control, just move all the logic to its class and change your dialog class to call methods of that control's class. Or are these just stock MFC controls? Is an image control inadequate for this program's purpose? What are you trying to draw? – andlabs May 29 '17 at 17:28

2 Answers2

1
  1. The painting of the image into the child window should be done in the WM_PAINT handler for that child window, not for the dialog. Your child window may need remember information provided by the parent dialog so that it can paint independently. By painting the window from the dialog's WM_PAINT handler, you're possibly painting more often than necessary (and possibly aren't causing a validation to occur in the image window).

  2. The dialog should probably have the WS_CLIPCHILDREN window style and your image window should probably have WS_CLIPSIBLINGS. This will prevent the dialog controls from drawing over each other, and it can reduce flicker by allowing for more minimal updates.

  3. If the image will always completely cover the entire image window, then you want to make sure there's no background erasing happening for the image window, as that can cause a flash of the background color which looks like painting. There are several ways to do this, but the easiest is probably to provide a WM_ERASEBKGND handler that just returns TRUE.

Adrian McCarthy
  • 41,073
  • 12
  • 108
  • 157
  • Thanks. The image does not cover the entire image window, so I cannot override the `OnEraseBkgnd` callback and return TRUE. – goodvibration May 31 '17 at 06:30
  • You could make your paint handler carefully erase the non-covered areas of the window and then skip the WM_ERASEBKGND. That could prevent some flash by avoiding painting pixels twice. On a modern system with desktop compositing, though, you might not even notice the flash. – Adrian McCarthy May 31 '17 at 16:03
0

I found OnEraseBkgnd to be the right place to minimize flickering of drawn bitmaps.

thomiel
  • 1,881
  • 14
  • 31