WinForms分层控件与背景图像导致滚动时撕裂

我有一个具有以下属性的Form

  • 背景图片
  • 具有透明背景的可滚动Panel ,以及Dock = DockStyle.Fill
  • PictureBox具有较大的WidthHeight ,显示滚动条

现在所有控件都设置为DoubleBuffered,包括表单本身。 一切都按预期工作,除了滚动PictureBox的面板时,表单背景图像滚动,它重复显示垂直和水平撕裂,虽然它的静态图像符合表单的大小,当你停止滚动它显示正确。 这仅在拖动滚动条时发生,如果我单击滚动条中的任何一点来移动它,它会正确显示。

根据我的理解,双缓冲应该消除这种情况,但即使双缓冲它也一样,可能稍微好一点,但滚动时仍然是一个巨大的问题。

我试图将所有控件放在另一个面板中,而不是使用表单背景图像,并将此面板放在窗体上,但它没有任何区别。

您正在与Windows系统选项进行战斗,名为“拖动时显示窗口内容”。 它为所有现代版本的Windows启用。 关闭它不是一个现实的目标,因为它是一个系统选项,它影响所有应用程序的所有窗口。 没有后门有选择地绕过这个选项。

启用它后,操作系统会优化窗口的滚动。 它执行快速bitblt以移动video帧缓冲区中的像素,并仅为滚动显示的窗口部分生成绘制消息。 向下滚动时,就像底部几行像素一样。 底层的winapi调用是ScrollWindowEx() 。 目的是为应用程序提供响应更快的UI,实现滚动的工作量要少得多。

你可能会看到它的前进方向,ScrollWindowEx()也会移动由窗体的BackgroundImage绘制的像素。 你可以看到。 接下来你看到的是优化涂料的副作用,它只重绘了所显示的窗口部分。 因此移动的背景图像像素不会重绘。 看起来像是“涂抹”效果。

有一个简单的解决方法,只需为面板的Scroll事件实现一个事件处理程序并调用Invalidate()。 所以整个面板再次重绘:

  private void panel1_Scroll(object sender, ScrollEventArgs e) { panel1.Invalidate(); } 

但现在你会注意到油漆的副作用不再被优化。 你仍然看到像素被移动,然后透支。 这有多明显取决于BackgroundImage的绘制成本。 通常从不便宜,因为它没有最佳的像素格式(32bppPArgb),并且没有合适的尺寸,因此需要重新调整以适应窗口。 视觉效果类似于“pogo”,在面板的一个边缘上快速抖动。

您不太可能发现可接受或想要完成优化BackgroundImage的工作。 阻止ScrollWindowEx()完成其工作需要一个相当大的武器,你可以调整LockWindowUpdate()。 像这样:

  using System.Runtime.InteropServices; ... private void panel1_Scroll(object sender, ScrollEventArgs e) { if (e.Type == ScrollEventType.First) { LockWindowUpdate(this.Handle); } else { LockWindowUpdate(IntPtr.Zero); panel1.Update(); if (e.Type != ScrollEventType.Last) LockWindowUpdate(this.Handle); } } [DllImport("user32.dll", SetLastError = true)] private static extern bool LockWindowUpdate(IntPtr hWnd); 

效果很好,背景图像像素现在摇滚稳定。 任何其他像素,嗯,不是那么多。 另一种视觉效果,让我们称之为“皱纹”。 可以通过将窗口置于合成模式来完成摆脱该工件。 哪个双缓冲整个窗口表面,包括子控件:

  protected override CreateParams CreateParams { get { const int WS_EX_COMPOSITED = 0x02000000; var cp = base.CreateParams; cp.ExStyle |= WS_EX_COMPOSITED; return cp; } } 

只有剩下的工件是这不是非常便宜的代码的副作用。 滚动时可能看起来不那么平滑。 否则会告诉你为什么28年前窗户被设计成不透明的。