您正在使用名为“拖动时显示窗口内容”的 Windows 系统选项进行战斗。对于所有现代版本的 Windows,它都已打开。关闭它不是一个现实的目标,因为它是一个系统选项,它会影响所有应用程序的所有窗口。没有后门可以选择性地绕过此选项。
启用后,操作系统会优化窗口的滚动。它执行快速 bitblt 以移动视频帧缓冲区中的像素,并仅为滚动显示的窗口部分生成绘制消息。就像向下滚动时底部的几行像素一样。底层 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 年前窗户被设计成不透明的。