zoukankan      html  css  js  c++  java
  • 水印管理器: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)



    (未完待续)
  • 相关阅读:
    课堂作业04 2017.10.27
    课程作业 03 动手动脑 2017.10.20
    课程作业 03 2017.10.20
    HDU 3974 Assign the task
    POJ 2155 Matrix
    POJ 2481 Cows
    HDU 3038 How Many Answers Are Wrong
    CS Academy Array Removal
    POJ_1330 Nearest Common Ancestors LCA
    CF Round 427 D. Palindromic characteristics
  • 原文地址:https://www.cnblogs.com/effun/p/1551548.html
Copyright © 2011-2022 走看看