袁永福 ( ) 2007-8-10
程序全部源代码下载(工程文件使用VS.NET2003格式):
在一些Web程序中,有一种页面效果,当弹出一个模拟的对话框时,主页面就整体灰化了,其他的元素不能动弹,只有这个对话框能用,用户关闭了对话框,整个页面才恢复原来的操作.这种用户体验是不错的,提示了用户必须处理的对话框才能继续处理页面.如何实现的我猜想是动态生成一个大的DIV层,把它置于顶层并设置半透明的灰色.
在WinForm程序中也需要这种用户体验,我们有时观察到主窗体显示了一个对话框,此时用户还试图用鼠标点击主窗体搞些操作,但这种操作注定是要失败,影响到软件可用性.于是我就想到把Web程序中的这种用户体验移植到WinForm程序中来帮助用户意识到主窗体的当前状态.
实现这个用户体验有两个问题,一是如何知道主窗体弹出对话框,二是如何灰化主窗体.
首先是解决如何知道主窗体何时弹出对话框,研究了一下,没发现窗体对象System.Windows.Forms.Form类型提供有所帮助的事件方法属性.我们可以在程序代码中,每次弹出对话框前添加灰化主窗体的代码,这样加大了程序开发量,而且代码移植性不好。后来想了又想,试了又试,发现弹出对话框时,主窗体的状态是不可用的,但此时窗体的Enabled属性不能反应这种状态,使用Win32API函数却能正确获得其状态.因此最后决定使用计时器System.Windows.Forms.Timer来不断的调用Win32API函数来测试主窗体是否可用,若可用则不必灰化主窗体,若不可用则灰化主窗体。
第二步就是灰化主窗体了,根据WEB程序中的实现过程,我们很自然的想到用一个半透明的控件来覆盖整个主窗体,于是我们又如何创造这个半透明控件。纵观System.Windows.Forms名称空间,号称提供半透明效果效果的只有Label类型了,经过测试,发现Label类型的半透明属性是假的,是模拟出来的,它是在控件背景中模拟绘制窗体的背景来搞出半透明的效果,若Label控件背后有其他控件还是要被Label无情的覆盖掉。因此我们需要一个真正的半透明控件,于是我又想了又想,试了又试,找了又找,终于把这个真正的半透明控件搞出来了。
基础问题解决了,然后就是代码的组成和组件化了,我定义了一个DisableMaskControl控件,实现了真正的半透明处理,里面有个定时器,不断的使用Win32API函数测试这个控件所在的窗体是否有效,若有效则隐藏控件,若无效则显示控件,把控件覆盖整个窗体并置于顶层。这样我就用一百来行的C#代码实现了这种弹出对话框灰化主窗体的用户体验,而且这个代码使用非常简单,只要在需要这种效果的主窗体上添加一行代码 this.Controls.Add( new DisableMaskControl()) 即可。
此处或许有人提出这个定时器的效率问题,我觉得没多大问题,首先控件少,一个窗体才用一个,相对于高速的CPU,用户手动操作来显示和关闭对话框是极其缓慢的操作。而且定时器中进行的判断不多,只调用了一个API函数,无伤大雅。
以下是程序的运行效果。
以下是控件 DisableMaskControl的全部代码。
1 using System; 2 using System.Runtime.InteropServices ; 3 namespace DisableMask 4 { 5 /// <summary> 6 /// 窗体无效时用于掩盖整个状态的半透明控件 7 /// </summary> 8 /// <remarks> 编制 袁永福( http://www.xdesigner.cn ) </remarks> 9 public class DisableMaskControl : System.Windows.Forms.Control 10 { 11 /// <summary> 12 /// 初始化对象 13 /// </summary> 14 public DisableMaskControl() 15 { 16 this .SetStyle( System.Windows.Forms.ControlStyles.SupportsTransparentBackColor , true ); 17 myTimer = new System.Windows.Forms.Timer(); 18 myTimer.Interval = 100 ; 19 myTimer.Tick += new EventHandler(myTimer_Tick); 20 this .BackColor = System.Drawing.Color.FromArgb( 80 , 0 , 0 , 0 ); 21 myTimer.Start(); 22 } 23 /// <summary> 24 /// 内部用于定时处理的计时器 25 /// </summary> 26 private System.Windows.Forms.Timer myTimer = null ; 27 28 /// <summary> 29 /// 已重载:返回控件创建参数 30 /// </summary> 31 protected override System.Windows.Forms.CreateParams CreateParams 32 { 33 get 34 { 35 System.Windows.Forms.CreateParams ps = base .CreateParams; 36 ps.ExStyle = ps.ExStyle | 0x20 ; 37 return ps ; 38 } 39 } 40 41 /// <summary> 42 /// 绘制控件的背景,啥也不干. 43 /// </summary> 44 /// <param name="pevent"> 事件参数 </param> 45 protected override void OnPaintBackground(System.Windows.Forms.PaintEventArgs pevent) 46 { 47 } 48 /// <summary> 49 /// 绘制控件 50 /// </summary> 51 /// <param name="e"> 事件参数 </param> 52 protected override void OnPaint(System.Windows.Forms.PaintEventArgs e) 53 { 54 using ( System.Drawing.SolidBrush b = new System.Drawing.SolidBrush( this .BackColor )) 55 { 56 e.Graphics.FillRectangle( b , e.ClipRectangle ); 57 } 58 } 59 60 /// <summary> 61 /// 定时器处理 62 /// </summary> 63 /// <param name="sender"> 事件参数 </param> 64 /// <param name="e"> 事件参数 </param> 65 private void myTimer_Tick( object sender, EventArgs e) 66 { 67 System.Windows.Forms.Form frm = this .FindForm(); 68 if ( frm == null ) 69 return ; 70 if ( frm.IsDisposed ) 71 return ; 72 if ( this .IsDisposed ) 73 return ; 74 if ( this .IsHandleCreated == false ) 75 return ; 76 77 // 主窗体显示对话框时窗体不可用,但此时它的Enable属性无法判断其是否真的 78 // 不可用,因此必须调用Win32API来判断其是否真的不可用. 79 if ( IsWindowEnabled( frm.Handle ) == false ) 80 { 81 if ( this .Visible == false ) 82 { 83 this .Dock = System.Windows.Forms.DockStyle.None ; 84 this .Bounds = new System.Drawing.Rectangle( 85 0 , 86 0 , 87 frm.ClientSize.Width , 88 frm.ClientSize.Height ); 89 this .BringToFront(); 90 this .Visible = true ; 91 this .Refresh(); 92 frm.Refresh(); 93 } 94 } 95 else 96 { 97 if ( this .Visible ) 98 this .Visible = false ; 99 } 100 } 101 102 /// <summary> 103 /// 用于判断窗体是否有效的Win32API函数 104 /// </summary> 105 /// <param name="hWnd"> 窗体句柄 </param> 106 /// <returns> 窗体是否有效 </returns> 107 [DllImport( " user32.dll " , CharSet = CharSet.Auto, ExactSpelling = true )] 108 private static extern bool IsWindowEnabled(IntPtr hWnd); 109 110 } // public class DisableMaskControl : System.Windows.Forms.Control 111 }