您的位置:首页 > 编程语言

Windows用户界面编程中的界面闪烁问题

2006-12-08 16:04 253 查看
在Windows图形化用户界面编程中,若程序自己绘制用户界面时,会经常碰到界面闪烁,比如其他窗口在上面移动,用户界面滚动,这些都有可能导致闪烁。在一个容器中绘制特定的文档,需要相应作为绘图容器的控件的OnPaint事件,需要在OnPaint事件处理中重新绘制文档,而Windows操作系统一般会在两种情况下触发OnPaint事件:容器控件被其他窗体覆盖后又显示,还有就是容器控件的滚动处理。在这些情况下,Windows操作系统会频繁的触发OnPaint事件,而应用程序会频繁的在绘图容器中重新绘制图形,若应用程序没有进行很好的优化,则很有可能导致用户界面闪烁。

用户界面出现闪烁自然害处多多,首先它使得你的程序看起来不专业,甚至有不稳定的嫌疑,对于追求完美的你这么会容许它的存在呢;其次闪烁会损害用户的视力,容易让用户产生视觉疲劳。

好了,废话我不多说了,我们就来发现问题,分析问题,解决问题。

首先说说闪烁的本质,说到本质,就不得不提一些计算机系统结构和Windows图形用户子系统的一些知识。我们知道,在计算机内存中有一个区域叫做显存,而显卡则每过一些毫秒就从扫描显存,然后根据操作显示器来绘制一个个象素,因此每过一些毫秒显示器显示的内容就会重新设置一遍,由于这是硬件操作,非常快,若画面内容没有变化,则人类肉眼是看不到这个刷新的,此时用户界面是没有任何闪烁。


右图就是应用程序绘制用户界面的原理,应用程序在CPU的支持下向显存填充数据,而以此同时显卡也从显存加载数据操作显示器绘制图形,(笔者想若应用程序能直接访问显示器则绘制速度不要太快哦),而用户界面闪烁也就根源于这种显示结构。前面提到,显卡每过一些毫秒就会扫描显存,刷新显示器的显示。假设有个显示卡,设置其刷新频率为50赫兹,则它每20毫秒就扫描显存刷新显示器,而显卡的操作和应用程序的操作和应用程序的操作之间没有任何关系,显卡是自带处理器的,于是应用程序和显卡这两个对象同时操作显存,显卡只读取显存,而应用程序则修改显存,这就导致了类似多线程程序的数据同步的问题了。但这时硬件结构决定此时没有什么锁定机制可使用。显卡每20毫秒就进行刷新操作,连操作系统也挡不住,而且应用程序根本不知道显卡会何时进行刷新操作。

某个时刻,应用程序需要绘制用户界面,首先需要清空绘制容器,因此将显存一大片区域设置为白色,应用程序刚完成了清空操作,还每来得及绘制内容时,显卡就刷新了,很快显示器上显示了一大片白色。同时,应用程序开始绘制内容,应用程序运行缓慢,它化了20毫秒绘制了文档的上半身,文档上半身主要为红色,刚绘制了上半身,显卡就好不留情的进行刷新,很快显示器上显示了一半的文档,刚才一半的白色大半变成了红色,此时用户看来,显示器一下变成一片白,然后很快一半变成红色,此时显示器内容产生了两次大面积的内容变幻,然后应用程序又化了20毫秒显示了文档的下半身,文档下半身主要为绿色,此时显卡进行刷新,显示器上另一半还残存的白色又变成绿色。由于应用程序绘制文档完毕,因此不再修改显存,此时显示器的显示的内容不再发生改变。

在上面的描述中,显示器首先从花花绿绿变成一片白,20毫秒后一半变成红色,又20毫秒后另一半变成绿色,如此大面积的显示内容短期的改变就是所谓的闪烁,此时计算机显示器就是在折磨用户的眼睛。

其实从广义上说,计算机显示器显示的内容频繁的发生大面积的改变都是考验人类的眼睛。有些人玩一些激烈的3D游戏,比如雷神CS之类的,若玩的时间长点眼睛就受不了。就是因为这些游戏程序导致显示器显示的内容频繁的发生大面积的改变。因此游戏程序也算导致用户界面闪烁。只不过这种闪烁不算难受。

通过上面的讨论,知道了闪烁的根源,于是我们发现了问题,并分析了问题,现在解决问题。在目前的计算机结构中,我们的应用程序只能通过填充显存来绘制用户界面,在这种情况下,对付闪烁的不二法则就是快,应用程序要非常快的修改显存。对于每20毫秒进行刷新操作的显卡,若我们的应用程序能在20毫秒内修改显存完毕,则在很多情况下显卡就只会导致显示器显示的内容发生改变,减少闪烁,应用程序填充显存前后显存的数据进行对比,若数据前后不一致的字节数越少,显示器中刷新操作前后颜色发生改变的象素数就越少,这用户界面的闪烁就越小。

提高应用程序修改显存的速度的方法有很多中,而且Windows操作系统为我们做了许多底层的操作。我们知道若一个窗体被覆盖后又显示了,则Windows操作系统就会向该窗体发送重绘消息,而且还会传一个矩形数据,该矩形表示窗体中需要刷新的区域,应用程序可以根据这个矩形来重新绘制文档的某个部分,这样就不必要绘制所有的内容,提高绘制速度,减少绘制时间,这就需要进行绘图代码的优化。

在某些情况下,绘图速度很难优化起来,此时可以采用所谓“双缓冲”的技术来减少闪烁,应用程序可以在内存中创建一个和屏幕相兼容的图形设备上下文。该上下文实际上处理一个保存在内存中的BMP图片对象,这样就容许应用程序缓慢地在这个BMP上绘制图形。应用程序绘制完毕,就用WIN32API函数BitBlt来将BMP图片填充到显存中,BitBlt函数速度非常快,足以在屏幕的刷新周期内完成绘图,这样能基本上避免闪烁。

俗话说,说得容量做到难,编程也一样,本文中对付闪烁的说的轻巧,但在实际编程中,闪烁一直是图形化用户界面编程的老大难问题,需要精心的设计程序结构,优化代码,提高速度。这需要靠很多的理论知识和深厚的编程功底,这些需要长期的编程实践。

最后附送一个C#程序,这段代码能演示图形化用户界面编辑中的一种优化手段,使用VS.NET创建一个C#的标准Win32程序

[code]using System;
using System.Drawing;
using System.ComponentModel;
using System.Windows.Forms;
namespace ViewPicture
{
	public class frmViewImg : System.Windows.Forms.Form
	{
		private System.Windows.Forms.Button cmdLoad;
		private System.Windows.Forms.Label label1;
		private System.Windows.Forms.Panel panel1;
		private System.Windows.Forms.Label label2;
		private System.Windows.Forms.Panel panel2;
		private System.ComponentModel.Container components = null;

		public frmViewImg()
		{
			InitializeComponent();
		}

		protected override void Dispose( bool disposing )
		{
			if( disposing )
			{
				if (components != null) 
				{
					components.Dispose();
				}
			}
			base.Dispose( disposing );
		}
		private void InitializeComponent()
		{
			this.cmdLoad = new System.Windows.Forms.Button();
			this.label1 = new System.Windows.Forms.Label();
			this.panel1 = new System.Windows.Forms.Panel();
			this.label2 = new System.Windows.Forms.Label();
			this.panel2 = new System.Windows.Forms.Panel();
			this.SuspendLayout();
			this.cmdLoad.Location = new System.Drawing.Point(16, 8);
			this.cmdLoad.Name = "cmdLoad";
			this.cmdLoad.Size = new System.Drawing.Size(208, 32);
			this.cmdLoad.TabIndex = 0;
			this.cmdLoad.Text = "打开图片文件(最好图片要大)";
			this.cmdLoad.Click += new System.EventHandler(this.cmdLoad_Click);
			this.label1.AutoSize = true;
			this.label1.Location = new System.Drawing.Point(16, 48);
			this.label1.Name = "label1";
			this.label1.Size = new System.Drawing.Size(42, 17);
			this.label1.TabIndex = 1;
			this.label1.Text = "未优化";
			this.panel1.AutoScroll = true;
			this.panel1.BackColor = System.Drawing.SystemColors.Window;
			this.panel1.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
			this.panel1.Location = new System.Drawing.Point(8, 72);
			this.panel1.Name = "panel1";
			this.panel1.Size = new System.Drawing.Size(696, 224);
			this.panel1.TabIndex = 2;
			this.panel1.Paint += new System.Windows.Forms.PaintEventHandler(this.panel1_Paint);
			this.label2.AutoSize = true;
			this.label2.Location = new System.Drawing.Point(16, 312);
			this.label2.Name = "label2";
			this.label2.Size = new System.Drawing.Size(29, 17);
			this.label2.TabIndex = 1;
			this.label2.Text = "优化";
			this.panel2.AutoScroll = true;
			this.panel2.BackColor = System.Drawing.SystemColors.Window;
			this.panel2.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
			this.panel2.Location = new System.Drawing.Point(8, 336);
			this.panel2.Name = "panel2";
			this.panel2.Size = new System.Drawing.Size(696, 224);
			this.panel2.TabIndex = 2;
			this.panel2.Paint += new System.Windows.Forms.PaintEventHandler(this.panel2_Paint);
			this.AutoScaleBaseSize = new System.Drawing.Size(6, 14);
			this.ClientSize = new System.Drawing.Size(720, 573);
			this.Controls.Add(this.panel1);
			this.Controls.Add(this.label1);
			this.Controls.Add(this.cmdLoad);
			this.Controls.Add(this.label2);
			this.Controls.Add(this.panel2);
			this.Name = "frmViewImg";
			this.Text = "显示图片";
			this.Resize += new System.EventHandler(this.frmViewImg_Resize);
			this.ResumeLayout(false);
		}
		[STAThread]
		static void Main() 
		{
			Application.Run(new frmViewImg());
		}

		private void frmViewImg_Resize(object sender, System.EventArgs e)
		{
			panel1.Width = this.Width - 30 ;
			panel1.Height = (this.ClientSize.Height - 48 ) /2 - 26;

			label2.Top = panel1.Bottom + 2 ;
			panel2.Top = label2.Bottom + 2 ;
			panel2.Width = panel1.Width ;
			panel2.Height = panel1.Height  ;
		}

		private System.Drawing.Image myImage ;
		private void cmdLoad_Click(object sender, System.EventArgs e)
		{
			using( System.Windows.Forms.OpenFileDialog dlg = new OpenFileDialog())
			{
				dlg.CheckFileExists = true;
				dlg.Filter = "BMP,JPG,GIF,PNG图片|*.bmp;*.jpg;*.jpeg;*.png";
				if( dlg.ShowDialog( this ) == System.Windows.Forms.DialogResult.OK )
				{
					myImage = System.Drawing.Image.FromFile( dlg.FileName );
					panel1.AutoScrollMinSize = myImage.Size ;
					panel2.AutoScrollMinSize = myImage.Size ;
					this.Refresh();
				}
			}
		}

		// 未优化的绘制图片
		private void panel1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
		{
			if( myImage != null)
			{
				e.Graphics.DrawImage( myImage , 
                                                    panel1.AutoScrollPosition.X ,
                                                    panel1.AutoScrollPosition.Y ,
                                                    myImage.Size.Width , 
                                                    myImage.Size.Height  );
			}
		}
		// 优化的绘制图片
		private void panel2_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
		{
			if( myImage != null)
			{
				e.Graphics.DrawImage(
					myImage , 
					e.ClipRectangle ,
					new System.Drawing.Rectangle( 
					e.ClipRectangle.X - panel2.AutoScrollPosition.X ,
					e.ClipRectangle.Y - panel2.AutoScrollPosition.Y ,
					e.ClipRectangle.Width ,
					e.ClipRectangle.Height 
					) ,
					System.Drawing.GraphicsUnit.Pixel );
			}
		}

	}//public class frmViewImg : System.Windows.Forms.Form
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: