为啥从 WM_NCPAINT 使用 DrawImageUnscaled 会导致闪烁?

Posted

技术标签:

【中文标题】为啥从 WM_NCPAINT 使用 DrawImageUnscaled 会导致闪烁?【英文标题】:Why does DrawImageUnscaled cause flickering when used from WM_NCPAINT?为什么从 WM_NCPAINT 使用 DrawImageUnscaled 会导致闪烁? 【发布时间】:2011-09-09 02:47:55 【问题描述】:

我目前正在构建一个从System.Windows.Forms.ContainerControl 派生的控件,它有一个我需要自己绘制的边界区域。由于没有要覆盖的OnPaintNonClientArea,所以我自己构建了它(为简洁起见,删除了WM_NCCALCSIZEWM_NCHITTEST 等其他消息的处理):

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

OnPaintNonClientArea,我做到了:

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

保持OnPaintNonClientArea 不变,这将消除闪烁:

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

那么,为什么DrawImageUnscaled 会导致这种闪烁?在绘制缓冲区之前,它似乎用白色刷子擦除了它工作的区域。我在文档中没有找到任何澄清这个问题的东西。如果只是控件周围的一个小边框也没关系,但是NC区域内会显示文本,因此该区域清晰可见,因此闪烁确实可见且烦人。

相关问题:我是否正确地处理了原生 GDI 内容,或者是否存在我现在看不到的潜在问题?另外,在创建ncBitmap时,我使用的是控件的宽度和高度,但是GDI+是与分辨率无关的,会有什么问题吗?

【问题讨论】:

您是否尝试过在表单中​​使用双缓冲?双缓冲应该处理诸如此类的图形问题。此外,是否有 SuspendLayout 和 PerformLayout 等效项可用于在加载图形对象之前阻止控件更新? 谢谢。我摆弄这些东西很长一段时间并尝试了各种各样的东西,有一次我确实对没有任何影响的表单进行了双缓冲。我认为这不适用于这里,因为我已经有效地进行了双缓冲 - 我正在使用 Graphics 对象绘制所有内容,然后使用 DrawImageUnscaled 我尝试将缓冲区绘制到由 @ 创建的 Graphics 对象上987654334@。它是 DrawImageUnscaled 本身,它首先使用白色画笔擦除要绘制的区域,然后绘制 Graphics 对象的内容。不确定这是否可以解决。 【参考方案1】:

为避免在 UserControl 中闪烁,我在 BufferedGraphics 类中获得了更好的运气。

MSDN

这是一个选项吗?

【讨论】:

+1 表示我不知道的一种方法。但是对于绘制非客户区,这会导致与我尝试的第一种方法一样多的闪烁。

以上是关于为啥从 WM_NCPAINT 使用 DrawImageUnscaled 会导致闪烁?的主要内容,如果未能解决你的问题,请参考以下文章

窗口绘制有关的消息整理 WM_PAINT, WM_NCPAINT, WM_ERASEBKGND

自绘LISTVIEW的滚动条(Delphi实现)

绘制非客户区的问题 - Win32

Delphi如何在Form的标题栏绘制自定义文字

C# WinForm 重绘标题栏的问题

如何绘制圆角窗口的边框?