5

I'm currently building a control derived from System.Windows.Forms.ContainerControl that has a border area I need to paint myself. Since there's no OnPaintNonClientArea to override, I built it myself like this (handling of other messages like WM_NCCALCSIZE, WM_NCHITTEST etc. removed for brevity):

protected override void WndProc(ref Message m)
{
  switch (m.Msg)
  {
    case WM_NCPAINT:
      IntPtr hDC = NativeApi.Methods.GetWindowDC(m.HWnd);
      if (hDC != IntPtr.Zero)
      {
        using (Graphics canvas = Graphics.FromHdc(hDC))
        {
          if (Width > 0 && Height > 0)
            using (PaintEventArgs e = new PaintEventArgs(canvas, new Rectangle(0, 0, Width, Height)))
            {
              OnPaintNonClientArea(e);
            }
        }
        NativeApi.Methods.ReleaseDC(m.HWnd, hDC);
      }
      m.Result = IntPtr.Zero;
      break;
  }
  base.WndProc(ref m);
}

Within OnPaintNonClientArea, I did:

private void OnPaintNonClientArea(PaintEventArgs e)
{
  if (_ncBuffer == null)
  {
    _ncBuffer = new Bitmap(Width, Height);
  }

  using (Graphics g = Graphics.FromImage(_ncBuffer))
  {
    // painting occurs here ...
  }
  // this causes flickering
  e.Graphics.DrawImageUnscaled(_ncBuffer, 0, 0, Width, Height);
}

Leaving OnPaintNonClientArea untouched, this removes the flicker:

protected override void WndProc(ref Message m)
{
  switch (m.Msg)
  {
    case WM_NCPAINT:
      using(Bitmap ncBitmap = new Bitmap(Width, Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb))
      {
        using(Graphics ncGraphics = Graphics.FromImage(ncBitmap))
        {
          using (PaintEventArgs e = new PaintEventArgs(ncGraphics, new Rectangle(0, 0, Width, Height)))
          {
            OnPaintNonClientArea(e);
            IntPtr hDCWin = NativeApi.Methods.GetWindowDC(m.HWnd);
            IntPtr hDCImg = ncGraphics.GetHdc();
            IntPtr hBmp = ncBitmap.GetHbitmap();
            IntPtr hBmpOld = NativeApi.Methods.SelectObject(hDCImg, hBmp);
            Padding p = GetNonClientArea();
            NativeApi.Methods.ExcludeClipRect(hDCWin, p.Left, p.Top,Width- p.Right, Height-p.Bottom);
            NativeApi.Methods.BitBlt(hDCWin, 0, 0, Width, Height, hDCImg, 0, 0,NativeApi.TernaryRasterOperations.SRCCOPY);
            NativeApi.Methods.SelectObject(hDCImg, hBmpOld);
            NativeApi.Methods.DeleteObject(hBmp);
            ncGraphics.ReleaseHdc(hDCImg);
            NativeApi.Methods.ReleaseDC(m.HWnd, hDCWin);
          }
        }
      }
      m.Result = IntPtr.Zero;
      break;
  }
  base.WndProc(ref m);
}

So, why does DrawImageUnscaled cause this flickering? It seems to erase the area it works on with a white brush before drawing the buffer. I didn't find anything in the docs that clarified this issue. It wouldn't matter too much if it were just a small border around the control, but there will be text displayed within the NC area, so the area is clearly visible and therefore the flickering is really visible and annoying.

Related questions: Am I doing the native GDI stuff right, or are there potential problems I don't see right now? Also, when creating the ncBitmap, I'm using the control width and height, but GDI+ is resolution-independant, can there be any problems there?

takrl
  • 6,356
  • 3
  • 60
  • 69
  • Have you tried using Double Buffering in the form? Double Buffering is supposed to deal with graphics issues such as this. Also, are there SuspendLayout and PerformLayout equivalents you could use to stop the control from updating before you've loaded the graphics object? – David T. Macknet Jun 15 '11 at 09:39
  • Thanks for this. I fiddled with this stuff for quite a while and tried all sorts of stuff, at one point I did doublebuffer the form which didn't make a difference. I don't think this applies here, since I effectively do doublebuffering already - I'm painting all of the stuff using a `Graphics` object and then by using `DrawImageUnscaled` I try to paint the buffer onto the Graphics object created by `Graphics.FromHdc(hDC)`. It's DrawImageUnscaled itself that first erases the area to be drawn using a white brush and then draws the content of the Graphics object. Not sure if this can be fixed. – takrl Jun 15 '11 at 11:38

1 Answers1

2

To avoid flickering in UserControl's, I've had better luck with the BufferedGraphics class.

MSDN

Is that an option?

John66NY
  • 121
  • 1
  • 6
  • 1
    +1 for an approach I didn't know about. But for painting the non-client area, this caused just as much flicker as the first approach I tried. – takrl Jun 17 '11 at 21:19
  • New link: https://learn.microsoft.com/en-us/dotnet/api/system.drawing.bufferedgraphics?view=dotnet-plat-ext-6.0. This was a lifesaver. It-Just-Works. Flickering gone! – skavan May 13 '22 at 17:02