上一篇文章中,我简要说明了如何在TextBox里实现水印效果。把同样的实现方法搬到ComboBox中不对了,虽然代码运行没有出现错误,但却达不到我们在TextBox上的应用效果,根本看不到水印。这是怎么回事呢?
与TextBox一样,ComboBox是对Windows的原生控件COMBOBOX的封装。通过使用Spy++查看ComboBox控件,不难发现其实ComboBox内还有一个窗口,而这个窗口才是真正用于编辑文字的,它是一个EDIT控件,ComboBox只是实现了下拉列表的功能。因此,要在ComboBox上实现水印的效果,必须要在它内部的EDIT原生控件上绘制,而不是在ComboBox上。由于内部的这个EDIT是Windows原生的,通过Control.Controls集合无法获取到它的,因此只能通过Windows API实现。
在Windows API中,EnumChildWindows这个函数可以通过回调的方式枚举指定窗口的所有子窗口,关于这个函数的使用在这里我就不详细说明了,有兴趣的可以参考MSDN里的相关说明。因为原生的COMBOBOX中只有一个子控件,因此要获取内部的这个EDIT并不困难。具体实现见以下代码,摘自我的提供的WatermarkComboBox类的源码。
        /// <summary>
        
/// 获取内部EDIT的句柄。
         
/// </summary>
        private void RetreiveEditControl()
        {
            IntPtr handle 
= new IntPtr();

            EnumChildWindows(
this.Handle, GetChildCallback, ref handle);

            
this._editHandle = handle;
        }

        
/// <summary>
        
/// EnumChildWindows的回调函数。
         
/// </summary>
        private bool GetChildCallback(IntPtr hWnd, ref IntPtr lParam)
        {
            
// 因为原生COMBOBOX只有一个子控件,因此不用作任何判断直接返回。
            lParam = hWnd;
            
return false;
        }

从以上代码可以看出,_editHandle就是内部EDIT控件的句柄,这样,与TextBox水印的的绘制代码相比,只要做两个修改就可以了。
第一个修改的地方是获取绘制区域的Rectangle,因为EDIT不是.net控件,因此只能使用API函数GetClientRect,而不能直接使用this.ClientRectangle属性。
第二个修改的地方是Graphics的获取,改为使用Graphics.FromHwnd方法。
修改之后的代码如下:
    Brush brush = SystemBrushes.GrayText;
    Font font 
= this.Font;
    RECT rect 
= new RECT();

    
// 通过API获取EDIT的客户区域大小。
    GetClientRect(_editHandle, ref rect);
    
    StringFormat stringFormat 
= new StringFormat();
    stringFormat.Alignment 
= StringAlignment.Near;
    stringFormat.LineAlignment 
= StringAlignment.Center;

    
// Graphics从EDIT的句柄获取。
    using (Graphics g = Graphics.FromHwnd(_editHandle))
    {
        g.DrawString(_watermark, font, brush, rect.ToRectangle(), stringFormat);
    }

    
// 释放非托管资源。
    stringFormat.Dispose();

这样,在ComboBox上绘制水印的功能就完成了,不过还存在两个BUG。
1 在窗体出现时水印不会立刻显示,只有鼠标在上面移过以后才会显示。
2 水印的闪烁比较明显,特别是在启用了视觉主题以后。
以上的2个问题,我目前还不清楚是什么原因造成的,估计是和消息有关系。因为在WndProc方法中所处理的消息都是发给这个控件的,而EDIT并不等于ComboBox本身,因此可能会造成不正常的行为。要解决这个问题,看来只能从其它方面入手,先卖个关子,稍后的文章中我会提到这个问题的解决办法。

效果图如下:

未输入任何内容


输入了用户名之后

本文的演示程序和源代码请点击这里下载
posted on 2009-08-20 12:30  一风  阅读(1587)  评论(4编辑  收藏  举报