咳,纯编程技术话题,不喜勿进。
话说是去年12月左右的时候,拿到一个开发屏保的源代码。
《Creating a Screen Saver with C#》https://www.harding.edu/fmccown/screensaver/screensaver.html 非常精彩的源代码(彼时,正在修改一个 黑客帝国 的C#屏保)。
运行起来还是蛮顺利的,后期再加入自定义文字等功能,基本上已经能算是成品屏保的,不过仔细看起来的时候,发现屏保的文字在固定之前有一次跳动,就像这样。

后来因为种种事(要过年)就一直放着在,现在抽时间调查了一下。找到了几种可能,按时间顺序:
程序坐标被其他子程序进行了修改
C# UI 自身的问题,闪烁
Timer 执行的问题,可能执行了多次
首先对所有的源码进行了地毯式搜索,没发现可疑的地方,首先取消第一点怀疑。
其次,C# UI 问题,这个比较复杂,因为确实有人反映控件会自动重置,造成闪烁。找到了一段代码,比如启用双重缓冲:
//双缓冲技术
this.SetStyle(ControlStyles.OptimizedDoubleBuffer,true);
this.SetStyle(ControlStyles.AllPaintingInWmPaint,true);
this.SetStyle(ControlStyles.UserPaint,true);
给 form 以及 label 的构造函数都加上了,无效。
然后又是一段无效代码,但是后来却很有用(禁用背景重绘,这个效果很有趣,就是相当于在当前截图上直接运行屏保):
//禁用背景重新绘制
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x0014)
return;
base.WndProc(ref m);
}
跟前面说的一样,无效,但是,贴上之后可以直观看到 Label 确实重绘了两次!
然后,考虑是 Timer 的问题,加断点,调试参数,显示 Timer_Tick() 只运行了一次。
那么是 Windows.Forms.Timers 的 Bug 吗?
我很怀疑,于是转向 System.Timers,使用了如下的程序,进行调试:
//初始化
System.Timers.Timer timer = new System.Timers.Timer(2000);
//以下在 Form_Load
timer.Elapsed += timer_Elapsed;
timer.AutoReset = true;
timer.Start();
//Timer 执行的子程序
//执行
private bool isInTimer = false;
void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (isInTimer)
return;
isInTimer = true;
//创建一个委托,用于封装一个方法,在这里是封装了 控制更新控件 的方法
Action invokeAction = new Action(updateLabel);
//判断操作控件的线程是否创建控件的线程
//调用方调用方位于创建控件所在的线程以外的线程中,如果在其他线程则对控件进行方法调用时必须调用 Invoke 方法
if (this.InvokeRequired)
{
//与调用线程不同的线程上创建(说明您必须通过 Invoke 方法对控件进行调用)
this.Invoke(invokeAction);
}
else{
//窗体线程,即主线程
Console.WriteLine("Timer已跨线程更新了控件Label");
Console.WriteLine("");
}//跨 UI 调用结束
// 注意,此线程不是ui线程
isInTimer = false;
}
结果,依然存在“抖动”的现象!
基本快要绝望的时候,仔细看了一下 Label 抖动的位置,挺有规律的,为什么 Label 的位置横坐标一致呢?再仔细看了一下 updateLabel 的子程序:
//Move text to new location
textLabel.Left = rand.Next(Math.Max(1, Bounds.Width - textLabel.Width));
textLabel.Top = rand.Next(Math.Max(1, Bounds.Height - textLabel.Height));
!!!仔细看才发现,其实 每次 Label 都移动了两次,只是因为通常 left 和 top 的改变很快,基本都忽略了,但是在我的慢电脑上却很明显,所以感觉像是抖动或者说“残影”。
至于解决办法,那当然就是同时移动横坐标和纵坐标:
int newX = rand.Next(Math.Max(1, Bounds.Width - textLabel.Width));
int newY = rand.Next(Math.Max(1, Bounds.Height - textLabel.Height));
textLabel.SetBounds(newX, newY,textLabel.Width, textLabel.Height);
然后,屏保就完整地平滑移动了,没想到是这样,没想到这样。。。改完后,我不知重复了多少次这句话——
OK,这个过年的问题总算解答了。