zoukankan      html  css  js  c++  java
  • 水印管理器:4、DIY

    我在先前的文章里已经把水印功能的实现思路进行了阐述,这里就不再复述了。
    当一个DIYer有时并不是件容易的事,要在完全不明白个中奥妙的时候要做做出个东西,只有靠猜。我不是一个喜欢上网到处找东西的人,只有遇到实在解决不了的时候才会去Google,所以开始做这个水印管理器的时候花了一点心思,主要先解决了两个技术问题。
    第一,就是怎么在控件的外获取控件的绘制事件。Paint事件不可用,原因就是象TextBox或ComboBox这种原生的控件,除非启用UserPaint,否则是不会引发Paint事件的,所以一想就想到了WM_PAINT这个消息。
    第二,就是如何拦截控件的WM_PAINT消息。我忘了在什么看到过一个叫IMessageFilter的接口,当时只是带过一眼,从字面理解应该就是消息过滤吧,现在正好用得上。查MSDN的说明,原来它正在我所需要的东西,可以通过Application.AddMessageFilter方法把一个自定义的IMessageFilter添加到当前应用程序里用于对消息的过滤。而我只要在这里类里处理WM_PAINT事件就可以了。
    好了,万事俱备,接下来就是把这些零零散散的技术拼在一起,来实现新的水印管理器——WatermarkManager。在之前关于Cuer组件的的两篇文章里,我已经说明了整个组件的工作方式和设计时的支持,而这个WatermarkManager组件和Cuer大同小异,在对控件的管理上没有太大的区别,只是在具体的实现细节有所不同。Cuer只用了一句SendMessage就实现了,而WatermarkManger组件却要通过拦截消息来处理。先来看看相关类型的定义:

    Watermark这个类与CueInfo的作用相同,是用来存放水印文字及设置的,但与CueInfo相比,它有更多的设置,从上面的图中可以看出,多了Alignment、ForeColor和UseWin32Behavior三个属性。Alignment和ForeColor定义了水印的位置和颜色,UseWin32Behavior表示是否使用Cuer的那种方式处理水印,如果这个属性为true,Alignment和ForeColor都会被忽略。
    WatermarkManager的一个嵌套类型实现了IMessageFilter接口。IMessageFilter接口只有一个成员,是一个名为PreFilterMessage的方法,从字面就可以看出来,它的作用就是在进行消息调度之前进行处理。该方法的返回值是一个布尔值,如果返回为true,则这个消息是不会再由后续的程序管理,否则将按原有的逻辑交由下一个处理程序或控件进行处理。
    在PreFilterMessage方法里,我通过检测消息是不是WM_PAINT来确定是否要进行水印的绘制。如果消息是WM_PAINT,并且当前消息的目标是WatermarkManager正在管理的控件之一,就进行水印的绘制。这里有一个细节问题必须要考虑,就是这个方法的返回值,在进行水印的绘制以后应该是返回true呢还是false呢。因为PreFilterMessage方法是在控件接到消息之前进行处理的,假如返回了false控件接收到消息并进行了绘制,那么我们自己绘制的水印就不见了,白忙活,而如果返回的是true,那控件就没办法绘制了,窗体上的控件可能是一块白板。所以呢,在绘制之前应该让控件先处理WM_PAINT,然后再来绘制。那么,要怎么样才能在PreFilterMessage方法里让控件收到消息呢?
    用SendMessage把消息发到控件,这个方法可行吗?其实我没有试过,因为有点害怕,为什么呢,因为我担心SendMessage后PreFilterMessage可能会再次收到我所发送的WM_PAINT消息,这样不是会死循环了嘛,这是第一,这第二呢,据MSDN里的说明,WM_PAINT是由系统发送的,不应该由应用程序发送,因此我担心自己发送这个消息可能会带来不可预测的的后果。在多次的偿试之后,我决定使用DispatchMessage这个方法将WM_PAINT消息分发到控件上,经过测试证明,这个方法是可行的。这样,在消息的处理方面所遇到的困难都解决了。
    那么,接下来就可以直接绘制了吗?非也,还需要再解决一个问题。由于所收到的消息仅仅只有一个窗口的句柄,怎么从句柄转换为相应的控件呢?其实不难,用Control.FromHandle方法就可以了。打住,想想ComboBox控件,因为接到的句柄实际上是它内部的EDIT控件而不是ComboBox本身,这样的转换是无效的。其实这个也不难,只要改用Control.FromChildHandle方法就可以了,是不是很简单呢?.net类库已经为我无考虑了很多东西了。
    再接下来,就是要绘制了,因为要绘制的对象不一定是.net控件,因此,不能使用常规的办法。首先是获取绘制区域的大小,Control.ClientRectangle是肯定不行的了,改用Win32 API里的GetClientRect方法。而Graphics对象的获取也不能用Control.CreateGraphics,而只能用Graphics.FromHwnd,而其它的方法就差不多了。下面的代码演示了先前所说内容的具体实现:
        if (_handles.Contains(handle))
        {
            
    // 从句柄到Control对象的转换。
            Control ctl = Control.FromChildHandle(handle);
            Watermark wm;
            
    // 如果是有效控件,并且控件已由WamtermarkManager进行管理,并且控件没有输入内容,
            
    // 还需要测试控件是否当前已获得焦点。如果这些条件都满足了,才进行水印的绘制。
            if (ctl != null && _watermarks.TryGetValue(ctl, out wm) && IsControlEmpty(ctl) &&
                (
    !wm.HideOnFocus || !NativeMethods.IsFocused(handle)))
            {
                
    // 将消息先分发到控件使它完成绘制。
                var msg = NativeMethods.MSG.Create(m);
                NativeMethods.DispatchMessage(
    ref msg);

                
    // 获取控件的客户区域。
                NativeMethods.RECT rect = new NativeMethods.RECT();
                
    if (NativeMethods.GetClientRect(handle, ref rect))
                {
                    
    // 获取句柄所对应的Graphics对象。
                    using (Graphics g = Graphics.FromHwnd(handle))
                    {
                        
    // 开始绘制。
                        wm.Draw(g, rect.ToRectangle(), ctl.Font);
                    }
                }
                
    return true;
            }
        }
    在代码中有两个之前未提到的变量,_watermarks和_handles。前者是存放控件与水印对象关联的字典(Dictionary<Control, Watermark>),后者是存放了需要进行绘制的句柄,类型为HashSet<IntPtr>。字典的作用与Cuer里的一样,没什么好多说的,而这个_handles是个新东西,它是用来匹配句柄用的,由WatermarkManager维护。当一个控件被注册到WatermarkManager里进行管理时,它的HandleCreated和HandleDestroyed事件都被接管,而_handles就是在这两个事件处理程序里进行维护的,这种做法最直接的好处就是可以提高程序的性能。_watermarks和_handles两个变量在一般情况下数量应该是相同的,但也有可能不相同,当Watermark的UseWin32Behavior设置为true时,它相应的句柄是不会加入到_handles中,因为不需要对它进行绘制。另外,为了尽可能的提高性能,当_handle的个数大于0时,才会将IMessageFilter添加到Application中,如果等于0了,会从消息过滤中移除。
    至于绘制的过程,因为比较简单就不在这里详细说明了,最后来看看设计时和运行时效果图吧。


    很抱歉的是,因为WatermarkManager目前还不是非常完善,有些部分需要改进,因此暂时不提供源码。如果有兴趣的可以下载DLL以供体会,如果有任何意见或建议欢迎提出。

    到此为止,关于水印的话题就告一个段落。因为在Windows控件的开发方面本人经验还不是非常多,如果有说得不正确的,请大家提出,我会不断改进。谢谢!

    (本系列完)

  • 相关阅读:
    [Notes] 如何使用abode audition录歌
    [Tips] matlab save load
    [Tips] matlab csv格式文件读写
    [Tips] 随机数 随机序列 随机排列生成
    [Tips] csv 读写
    [record] 初入android
    HTML中表格table边框border(1px还嫌粗)的解决方案:
    CSS颜色代码大全
    ie9下面的console的bug
    js 性能优化 篇一
  • 原文地址:https://www.cnblogs.com/effun/p/1552596.html
Copyright © 2011-2022 走看看