有没有可能消除写得不好的 child 控件中的重绘闪烁?

Is it possible to eliminate redraw flicker in a badly written child control?

简短版本: 我正在使用 WinForms 控件,它在经常重绘时会闪烁。我发现 None 现有 "Double-buffer enable" 类型的解决方案是有效的,因为此控件在重绘时直接对其绘图表面执行两个不同的绘制操作。此控件需要是交互式的(因此位于窗体的最顶部)。鉴于我 可以 在该控件开始重新绘制之前和完成重新绘制之后立即执行指令,parent 容器或包含的 Form 是否可以执行任何操作来消除该控件的闪烁?

完整解释: 我正在做一个用户控件。一个ListView作为children之一就可以了。我正在使用 ListView 在我的控件上设置列以及 ListView 的列提供的所有功能(因此,实际上只有 ListView 的 header 列部分在我的控件上可见)。我需要在 ListView 的列 header 所在的位置绘制一些附加信息(我们称之为 "the overlay"),但不想更改列 header 的 'look' 否则.我显然不能以任何方式从我的用户控件中绘制叠加层(ListView 是我控件上的 child 并覆盖了我控件的绘图表面)。我无法在 ListView 顶部设置一个控件来充当叠加层的绘图表面 - 当然我可以在其上很好地绘制 ListView 甚至通过弄乱 WM_NCHITTEST 消息来保持交互性,但现在 ListView 没有重新绘制,我坚持在 any 鼠标事件上重新绘制整个 ListView,或者根据我的覆盖控件收到的事件手动跟踪 ListView 的哪些区域需要重新绘制(太麻烦了)

所以显而易见的解决方案是覆盖 ListView 的列 header 的 WndProc(就像 ObjectListView 那样)并在那里添加我的叠加绘图代码。所以我有:

protected override void WndProc(ref Message m) {
  switch (m.Msg) {
    case WM_PAINT:
      RECT r = new RECT();
      WinAPI.GetUpdateRect(Handle, ref r, false);       //user32.dll - bool GetUpdateRect(IntPtr hWnd, ref RECT rect, bool erase)
      Rectangle rc = new Rectangle(r.Left, r.Top, r.Right - r.Left, r.Bottom - r.Top);
         /* marker 1 */
      base.WndProc(ref m);                      //draw the column headers
         /* marker 2 */
      MyPaint(rc, Graphics.FromHwnd(Handle));   //draw overlay  (I'm aware of graphics needing to be disposed)
         /* marker 3 */
      return;
    }
  base.WndProc(ref m);
}

看起来很棒!但是当经常重绘时,有时我会在屏幕上看到只绘制到 marker 2 的结果。当然,这是有道理的,因为我现在在 ListView 的列 header 的绘图表面上有两个不同的绘图操作。不过,我可以在 marker 1marker 3 中设置我想要的任何状态,所以必须有某种方法可以让我以某种方式暂停某些绘图,对吧?好吧,我这辈子都想不出办法做到这一点。 所以我的问题是 - 这可能吗?

基本上,无论我尝试以某种方式暂停重绘的任何解决方案,显然都会在 'un-suspend' 上触发重绘,从而导致无休止的 WM_PAINTs。我真的试图避免只是摆脱 base.WndProc(ref m) 并自己绘制列 headers,以及为保留列 headers 而必须附带的任何样式绘制内容' 原生 'look'。但是在摆弄 WndProcs 几天后,我开始认为从头开始绘制列 headers 实际上是最不痛苦的解决方案...

好吧,我刚刚意识到我一直以来都错过了一个相当简单的解决方案 - 只需将基本 ListView 绘制到不同的表面即可!所以现在我有:

case WM_Paint:
  PAINTSTRUCT ps;
  WinAPI.BeginPaint(Handle, out ps);            //user32 native calls
  Rectangle rc = new Rectangle(ps.rcPaint.Left, ps.rcPaint.Top, ps.rcPaint.Right - ps.rcPaint.Left, ps.rcPaint.Bottom - ps.rcPaint.Top);
  listview.DrawToBitmap(bmp, listview.ClientRectangle); //send yourself a WM_PRINT, pretty much
  buf.Graphics.SetClip(rc);                     //now just draw everything to a BufferedGraphicsContext buffer
  buf.Graphics.DrawImageUnscaled(bmp, 0, 0);    //draw base ListView column header
  MyPaint(rc, buf.Graphics);                    //draw overlay
  buf.Render(Graphics.FromHwnd(Handle));        //flush buffer
  WinAPI.EndPaint(Handle, ref ps);              //validate drawing rectangle
  return;

不再闪烁!最好的部分是,这从一般意义上回答了原始问题,因为这种技术可用于 'fix' 任何绘制不当的控件(或向其中添加额外的绘图代码),只要该控件正确处理 WM_PRINT 条消息。