(转载)在TextBox、ComboBox上实现水印效果(WinForms)

转载自:一风
TextBox水印实现
使用Vista的用户都知道,在登录时输入用户名和密码的文本框都具有水印效果,在文本框里没有文字时,分别会以灰色显示“用户名”和“密码”,当输入内容以后这些说明文字就消失了。

由于目前手头上在开发的一个软件需要用到这样的功能,因此对水印的效果做了一些研究。很明显,水印的文字并不是真正的文字内容(也就是TextBox.Text属性),而是使用GDI绘制在TextBox控件表面上的图形。水印的实现并不复杂,创建一个类继承TextBox,当TextBox需要绘制时,先由TextBox完成自身的绘制,再根据当前TextBox中是否有内容来确定是否要绘制水印文本。

什么时候需要绘制呢,有过控件开发经验的人都知道,一般是在TextBox的Paint事件(OnPaint方法)里放入绘制水印文字的代码,但很快被否决,因为TextBox根本不会引发Paint事件。道理很简单,TextBox是.net对原生的Windows控件EDIT进行了封装,所有的控件绘制都是由Windows自己完成的,在.net的代码里是无法介入的,除非使用SetStyle方法设置UserPaint为true。通过UserPaint可以由.net代码来完全绘制控件,当然就可以引发Paint事件,但这种做法有太多的东西要考虑太多,并不是我要研究的主要方向。

那么,要怎么样才能在无法使用Paint事件的情况下,知道何时应该绘制水印文本呢?很简单,Windows Message,消息,强大的消息。Control类提供了WndProc方法用于控件开发人员处理消息,只要重载这个方法,并判断是否为绘制控件的消息WM_PAINT,就可以知道什么时候应该绘制水印了。

OK,接下来就简单了,下面的代码演示了一个简单的水印效果的实现。

 

ContractedBlock.gif ExpandedBlockStart.gif Code
protected override void WndProc(ref Message m)
{
  
// 由基类先处理消息,因为我们绘制水印的工作是在原生控件绘制完毕之后。
  base.WndProc(ref m);

  
// 0x000F为常数WM_PAINT的实际值。
  if (m.Msg == 0x000F)
  {
    
// 此时已经抓到了绘制事件,开始绘制水印。

    
if (this.Text.Length == 0)               // 只有在文本框里没有文字的情况下才绘制水印。
    {
      Brush brush 
= SystemBrushes.GrayText;  // 使用灰色的文本色,即表示禁用状态的文字颜色。
      Font font = this.Font;                 // 使用当前控件的字体。
      Rectangle rect = this.ClientRectangle; // 在当前控件的客户区域绘制。

      
using (Graphics g = this.CreateGraphics())
      {
        g.DrawString(
"水印", font, brush, rect);
      }
    }
  }
}

 

 ComboBox水印实现
简要说明了如何在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类的源码。

 

ContractedBlock.gif ExpandedBlockStart.gif Code
 1ExpandedBlockStart.gifContractedBlock.gif        /**//// <summary>
 2        /// 获取内部EDIT的句柄。
 3         /// </summary>

 4        private void RetreiveEditControl()
 5ExpandedBlockStart.gifContractedBlock.gif        {
 6            IntPtr handle = new IntPtr();
 7
 8            EnumChildWindows(this.Handle, GetChildCallback, ref handle);
 9
10            this._editHandle = handle;
11        }

12
13ExpandedBlockStart.gifContractedBlock.gif        /**//// <summary>
14        /// EnumChildWindows的回调函数。
15         /// </summary>

16        private bool GetChildCallback(IntPtr hWnd, ref IntPtr lParam)
17ExpandedBlockStart.gifContractedBlock.gif        {
18            // 因为原生COMBOBOX只有一个子控件,因此不用作任何判断直接返回。
19            lParam = hWnd;
20            return false;
21        }


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

ContractedBlock.gif ExpandedBlockStart.gif Code
 1    Brush brush = SystemBrushes.GrayText;
 2    Font font = this.Font;
 3    RECT rect = new RECT();
 4
 5    // 通过API获取EDIT的客户区域大小。
 6    GetClientRect(_editHandle, ref rect);
 7    
 8    StringFormat stringFormat = new StringFormat();
 9    stringFormat.Alignment = StringAlignment.Near;
10    stringFormat.LineAlignment = StringAlignment.Center;
11
12    // Graphics从EDIT的句柄获取。
13    using (Graphics g = Graphics.FromHwnd(_editHandle))
14ExpandedBlockStart.gifContractedBlock.gif    {
15        g.DrawString(_watermark, font, brush, rect.ToRectangle(), stringFormat);
16    }

17
18    // 释放非托管资源。
19    stringFormat.Dispose();
20


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

效果图如下:

未输入任何内容


输入了用户名之后

本文的演示程序和源代码请点击这里下载

转载于:https://www.cnblogs.com/yxy19860811/archive/2009/08/30/1556801.html

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值