是否可以消除编写不佳的子控件中的重绘闪烁?



简短版本: 我正在使用一个WinForms控件,该控件在经常重绘时闪烁。我发现现有的"双缓冲区启用"类型的解决方案都不是有效的,因为此控件在重绘时直接对其绘图表面执行两种不同的绘画操作。此控件需要是交互式的(因此窗体上最顶层(。鉴于我可以在此控件开始重绘之前和完成重绘之后执行指令,父容器或包含 Form 是否可以执行任何措施来消除此控件的闪烁?

完整解释: 我正在制作一个用户控件。列表视图作为其上的子项之一。我使用 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。我真的试图避免摆脱base.WndProc(ref m)并自己绘制列标题,以及必须附带的任何样式绘图内容,以保留列标题的本机"外观"。但是在摆弄了几天WndProc之后,我开始认为从头开始绘制列标题实际上是最不痛苦的解决方案......

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

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;

不再闪烁!最好的部分是,这在一般意义上回答了原始问题,因为只要控件正确处理WM_PRINT消息,此技术可用于"修复"任何糟糕的绘图控件(或向一个控件添加其他绘图代码(。

最新更新