C#屏保程序“抖动”原因的真正查明

咳,纯编程技术话题,不喜勿进。

话说是去年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,这个过年的问题总算解答了。

你可能感兴趣的:(C#屏保程序“抖动”原因的真正查明)