您正在与一个名为“在拖动时显示窗口内容”的Windows系统选项进行斗争。它已经在所有现代版本的Windows中被开启。关闭它不是一个现实的目标,因为它是一个系统选项,会影响所有应用程序中所有窗口。没有后门可以选择性地绕过此选项。
启用它后,操作系统将优化窗口的滚动。它执行快速位块传输以移动视频帧缓冲区中的像素,并仅为滚动所揭示的窗口部分生成绘画消息。例如当您向下滚动时底部的几行像素。底层winapi调用是ScrollWindowEx()。意图是为应用程序提供更响应的UI,实现滚动需要做的工作少得多。
您可能已经看到了,ScrollWindowEx()还会移动通过表单的BackgroundImage绘制的像素。您可以看到这一点。接下来您会看到优化的绘画的副作用,它只会重绘被揭示出来的窗口部分。因此,移动的背景图片像素不会被重新绘制。看起来像是一个“模糊”效果。
有一个简单的解决方法,只需为面板的Scroll事件实现一个事件处理程序,并调用Invalidate()即可。这样整个面板就会再次被重新绘制:
private void panel1_Scroll(object sender, ScrollEventArgs e) {
panel1.Invalidate();
}
然而现在你会注意到涂料不再被优化的副作用。 你仍然会看到像素被移动,然后被重绘的情况。这种效果的可见程度取决于BackgroundImage的绘制成本有多高。通常情况下成本都很高,因为它没有最佳像素格式(32bppPArgb),并且大小不合适,需要重新调整大小以适应窗口。视觉效果类似于“pogo”,面板边缘出现快速抖动。
很难想象你会接受这种情况或者想要去优化BackgroundImage。阻止ScrollWindowEx()完成其工作需要一个非常强大的武器,您可以使用pinvoke 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年前设计的窗口需要是不透明的。