是否有可能在写得不好的子控件中消除重绘闪烁?

时间:2018-05-07 19:49:53

标签: c# winforms listview flicker wndproc

简短版: 我使用WinForms控件,经常重绘时闪烁。我发现的现有" Double-buffer enable" -type解决方案都没有效果,因为这个控件在重绘时直接对它的绘图表面进行两次不同的绘制操作。这种控制需要是交互式的(因此在表格中最顶层)。鉴于我可以在此控件开始重新绘制之前执行指令,并且在完成重新绘制之后,父容器或包含表单可以做些什么来消除此控件的闪烁?

完整解释: 我正在制作用户控件。 ListView作为其中一个子项。我使用ListView在我的控件上有列和ListView的列提供的所有功能(因此,只有ListView的列标题部分实际上在我的控件上可见)。我需要在ListView的列标题中绘制一些额外的信息(让我们称之为"叠加"),但不要改变'看'否则是列标题。我显然不能以任何方式从我的用户控件中绘制叠加层(ListView是我控制下的一个孩子并覆盖了我的控件的绘图表面)。我无法在ListView顶部使用控件作为叠加层的绘图表面 - 确保我可以在其上绘制ListView,甚至可以通过弄乱WM_NCHITTEST消息来保持交互性,但是现在ListView没有重新绘制,我仍然坚持在任何鼠标事件上重绘整个ListView,或者手动跟踪需要重绘ListView的哪些区域。我的叠加控件收到的事件(方式太麻烦)。

所以显而易见的解决方案是覆盖ListView的列标题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的列标题的绘图表面上有两个不同的绘制操作。尽管如此,我可以在marker 1marker 3中设置我想要的任何状态,所以我必须有某种方式能够以某种方式暂停某些绘图吗?好吧,我不能为我的生活想办法做到这一点。 所以我的问题是 - 有可能吗?

基本上,无论我试图以某种方式暂停重绘的任何解决方案,显然会触发重新绘制'取消暂停'导致无休止的WM_PAINT s。我真的试图避免只是摆脱base.WndProc(ref m)并自己绘制列标题,以及任何样式绘制的东西,以保留列标题'原生的' look'。但是在摆弄WndProc s几天之后,我开始认为从头开始绘制列标题实际上是最不痛苦的解决方案...

1 个答案:

答案 0 :(得分:0)

嗯,我刚刚意识到,这一次我错过了一个相当简单的解决方案 - 只需将基础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;

不再闪烁!最好的部分是,这回答了一般意义上的原始问题,因为这种技术可以用来“修复”任何糟糕的绘图控件(或者向其中添加额外的绘图代码),只要控件正确处理{{1}消息。