水印管理器:2、Win32实现

花了一些时间,把一条简单的SendMessage进行了包装,制作了一个用于水印管理的组件,先看一下图。

 


这两个类是实现水印管理器的主要类型,分别是Cuer和CueInfo。Cuer就是我所说的水印管理器,为了和DIY的进行区分,没有称之为WatermarkManager。CueInfo保存了水印的信息,包括文字和一个选项。

先来说CueInfo,它有两个主要属性,分别是Text和HideOnFocus,Text很明显,就是水印的文字,而HideOnFocus是表示是否需要在控件获得焦点的时候隐藏水印文字,默认为true。至于另外两个属性,Control表示当前CueInfo应用在哪个控件上,Manager表示当前CueInfo是由哪个Cuer组件来管理的,这两个属性是只读的,由Cuer组件维护。

Win32版水印管理器的所有功能都由Cuer这个类来实现,它从Component继承,并且实现了IExtenderProvider接口。下面对Cuer组件的几个公开成员做一个简要的说明。
public static bool IsSupported(Control control);
此方法的功能是测试指定的控件是否受Cuer组件支持,由于受到EM_SETCUEBANNER消息只能支持TextBox控件的限制,目前支持的控件只有TextBox和ComboBox。
public void SetCue(Control control, CueInfo cue);
它的功能是为控件指定水印信息,包括文字内容,以及一个是否需要自动隐藏的选项。
public CueInfo GetCue(Control control);
根据指定的控件,获取其相应的水印信息,如果没有找到则返回一个空值。
public bool Enabled { get; set; }
对于这个属性,我想我不要多说了,当设置为false以后,所有水印信息全部消失。

先来段例子,有个感性的认识。假设有一个窗体用于用户登录,有两个控件,一个是用于输入用户名的tbUsername,另一个是用于输入密码的tbPassword,分别设置文字“User name”和“Password”作为它们的水印文本,请看以下代码。

Cuer cuer1  =   new  Cuer();

CueInfo cue 
=   new  CueInfo();
cue.Text 
=   " User name " ;
cuer1.SetCue(tbUsername, cue);
cue 
=   new  CueInfo();
cue.Text 
=   " Password " ;
cuer1.SetCue(tbPassword, cue);

是不是很简单呢,简单的几行代码就可以实现水印的效果。好了,以上我对这两个主要的类Cuer和CuerInfo进行了说明,接下来我将把这个组件实现细节中几个关键问题进行一些说明。

控件(Control)与水印信息(CueInfo)的对应

这点不难理解,因为Cuer是在Control外部进行操作的,要将Control与CueInfo一一对应,就必须借助一个字典,以Control为键,CueInfo为值,我采用的是泛型字典类Dictionary<Control, CueInfo>。字典的维护主要在SetCue方法中完成,假如参数cue的值为空就从字典中移除,非空就加入字典。不过事情并不会这么简单,在对字典进行操作时还有一些额外的问题要考虑,想想前面提到的CueInfo的Control和Manager属性,这两个属性是由Cuer进行维护的,因此在添加和移除时,也必须同时对这两个属性进行维护。

SetCue方法会检查cue参数是否已经被管理(也就是CueInfo.Manager属性非空),无论是否由当前Cuer组件管理,都会引发InvalidOperationException。如果不进行这样的限制,可能会造成几个控件共享一个CueInfo实例,修改一下先前的示例,参考以下代码:

CueInfo cue  =   new  CueInfo();
cue.Text 
=   " User name " ;
cuer1.SetCue(tbUsername, cue);
cue.Text 
=   " Password " ;
cuer1.SetCue(tbPassword, cue);

这样的程序到底这个cue变量应该代表用户名呢还是密码呢,呵呵,总之我能想到的是两个输入框都显示了Passowrd的水印文字。我不清楚这种限制是否会对实际的应用造成影响,至少在我编写Demo时没有碰到过麻烦。

看起来这样的对应关系应该是比较可靠的了,不过没有考虑控件的动态移除。假如在运行时控件因为某些原因要从窗体中移除掉,那么在字典中会保留一个无用的控件引用,虽然可能不会造成太大的影响,但会使程序看起来不那么严密。Control.Disposed事件可以通知我们控件的非托管资源被释放了,从某种意义上来看,就是它被称除了,只要捕获这个事件并在处理程序中将其从字典中移除就可以了。具体的实现请参考源代码中的SetCue方法和control_Disposed方法。

关于ComboBox控件

前面我说过,EM_SETCUEBANNER只支持TextBox控件,但为什么又可以支持ComboBox呢?关于这个问题我在先前的随笔中明说过,ComboBox实际上会在内部嵌入一个原生EDIT控件,也就是TextBox控件,那么只要能够获取到这个EDIT控件,也同样可以由这个API来实现水印的功能。不过还是有遗憾的是,ComboBox有三种风格(DropDownStyle),分别是Simple、DropDown和DropDownList,前面两种都是允许用户在控件内输入文字的,而第三种是一个纯粹的下拉列表,不能输入,因此在它内部是没有EDIT控件的,所以不能在DropDownList的ComboBox上实现水印功能。但我觉得这个没有问题,因为对于下拉列表,有没有水印提示已经不那么重要了。

关于如何获取ComboBox的内部EDIT控件,先前的程序处理得过于简单,在这里需要进行修正一下,先看代码:

         private   static   bool  GetChildCallback(IntPtr hWnd,  ref  IntPtr lParam)
        {
            StringBuilder clsname 
=   new  StringBuilder( 1024 );
            GetClassName(hWnd, clsname, clsname.Capacity);
            
if  ( string .Compare(clsname.ToString(),  " EDIT " true ==   0 )
            {
                lParam 
=  hWnd;
                
return   false ;
            }
            
return   true ;
        }

 这是EnumChildWindows的回调函数,在这个回调过程中,必须判断当前正在枚举的这个句柄所表示的控件是否为EDIT。API函数GetClassName可获取句柄所表示窗口的类,如果它是EDIT,说明这是一个TextBox,直接返回false以终止后续的枚举。这样做的原因是,当ComboBox的风格设置为Simple时,会在它内部生成两个子窗口,而且EDIT的位置是在第2个,因此如果按照原先的方式所返回的结果是肯定不正确的。

在控件上应用水印效果

首先要把握的一个是时机,也就是什么时候需要应用水印效果。我一共找了5个时机来应用水印效果:

  • 在SetCue方法中,当CueInfo添加到字典之后,将水印应用到控件上。
  • 在CueInfo的Text属性或HideOnFocus属性的值发生更改以后,并且CueInfo已经受到某个Cuer的管理时。
  • 当Cuer.Enabled属性的值发生更改以后,会将当前正在管理的(也就是字典中的)所有CueInfo重新应用一次。
  • 在所管理的控件的HandleCreated事件发生后,重新应用水印。

这里需要着重说明的是最后一种情况。HandleCreated事件是在控件的句柄被创建以后发生的,有两种情况需要用到这个事件,一种是在调用SetCue方法时,控件的句柄还没有创建,这个可以通过Control.IsHandleCreated属性来检测,原因是控件还没有被加载;另一种情况是在控件已经加载的情况下,句柄被重新创建了。第二种情况比较要命,因为它会导致水印无法显示,这点我在开发的过程中已经遇到了,ComboBox的DropDownStyle默认值是DropDown,如果设置为Simple,控件的句柄会被重新创建,如果不捕获这个事件水印就失效了。

时机找到了,接下来就该干正事了,调用SendMessage发送EM_SETCUEBANNER消息,将水印信息应用在控件上。EM_SETCUEBANNER消息有两个参数,wParam为0时表示在控件接受到焦点时自动隐藏水印,1表示只有在用户开始输入时才隐藏。lParam就是要显示的文字,这个不用多解释了。Cuer.ApplyCue用于实现这个功能,首先根据指定的CueInfo获取控件的句柄,如果句柄的值不是IntPtr.Zero,就把CueInfo的Text和HideOnFocus属性的值分别转换为lParam和wParam,再发送消息。GetHandle方法封装了获取控件句柄的逻辑,如果指定的控件是TextBox就直接返回它的Handle属性,如果是ComboBox,就根据之前所说的方法获取它的句柄。

 

文章到此为止,已经把Cuer实现原理的几个主要问题做了解释,接下来就是设计时支持功能的开发。考虑到文章太长容易引起阅读疲劳,我会再另起一篇文章再说明。有兴趣的朋友可以点击这里下载源程序和演示程序,下图是演示程序的截图。(解释一下,因为这个组件我有打算要发到CodeProject上,所以源码里没有写过一个中文字,敬请各位看官谅解 :D)



(未完待续)

 

转载于:https://www.cnblogs.com/effun/archive/2009/08/21/1551548.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值