创建带有提示文字的文本框控件

在互联网上浏览时,经常会遇到各种类型的文本框,比如eBay的搜索框,它是一个带有提示文字的文本框,如“在这里输入搜索字符串”。当点击该框时,提示文字会消失,留下一个空的文本框供输入搜索字符串。微软甚至在其Atlas网站上提供了一个名为TextBoxWatermark的AJAX示例控件。最近,这种类型的控件开始出现在Windows应用程序中,如Outlook 2007、IE7以及Firefox 2.0。这种控件非常方便,因为它基本上将文本框和标签的功能合二为一,而不会占用大量的屏幕空间。在Web上,这种巧妙的功能通常通过使用JavaScript来处理,但在Windows上,需要自己想办法提供相同的功能。

在Web应用程序中,开发者通常会在文本框的onBlur和onFocus事件中添加JavaScript代码,以放置提示文字。开发者还必须意识到表单提交(或ASP.NET中的“回发”)可能会导致提示文字包含在提交数据中,因此代码必须对此有所了解。此外,JavaScript代码被迫操作文本框的value属性以实现这一点,因此即使是JavaScript解决方案也有一种“hack”的感觉。

标准的WinForms文本框不支持此功能,因此这个类将通过继承System.Windows.Forms.TextBox并自行处理提示文字的显示来解决这个不足。

使用代码

覆盖WndProc方法可能看起来有些过度,但在这种情况下,代码相对简单。对于这个控件,只需要关注WM_SETFOCUS和WM_KILLFOCUS消息,它们应该与GotFocus和SetFocus事件相同,以及WM_PAINT:

protected override void WndProc(ref System.Windows.Forms.Message m) { switch (m.Msg) { case WM_SETFOCUS: _drawPrompt = false; break; case WM_KILLFOCUS: _drawPrompt = true; break; } base.WndProc(ref m); // 只有在WM_PAINT事件和_drawPrompt为true时才绘制提示文字 if (m.Msg == WM_PAINT && _drawPrompt && this.Text.Length == 0 && !this.GetStyle(ControlStyles.UserPaint)) DrawTextPrompt(); }

DrawTextPrompt方法做了大部分工作。它确定了要绘制提示的客户端矩形区域,并根据HorizontalAlignment进行任何偏移,然后使用TextRenderer在矩形区域内绘制PromptText:

protected virtual void DrawTextPrompt(Graphics g) { TextFormatFlags flags = TextFormatFlags.NoPadding | TextFormatFlags.Top | TextFormatFlags.EndEllipsis; Rectangle rect = this.ClientRectangle; // 根据HorizontalAlignment偏移矩形区域,否则显示看起来有点奇怪 switch (this.TextAlign) { case HorizontalAlignment.Center: flags = flags | TextFormatFlags.HorizontalCenter; rect.Offset(0, 1); break; case HorizontalAlignment.Left: flags = flags | TextFormatFlags.Left; rect.Offset(1, 1); break; case HorizontalAlignment.Right: flags = flags | TextFormatFlags.Right; rect.Offset(0, 1); break; } // 使用TextRenderer绘制提示文字 TextRenderer.DrawText(g, _promptText, this.Font, rect, _promptColor, this.BackColor, flags); }

要点

在处理这个控件时,第一个想法是覆盖OnGotFocus/OnLostFocus方法,只是交换Text和PromptText值(除了改变ForeColor)。这立即被证明是一个糟糕的想法,因为一旦测试它,就注意到设计时的Text属性突然被PromptText替换了(ForeColor也被PromptForeColor替换了)。这有效地移除了开发者设置默认Text值的能力,所以是时候尝试新的方法了。

在考虑了一些替代方案之后,决定覆盖OnPaint方法,并手动绘制提示覆盖在TextBox区域上。这将解决尝试操作Text/ForeColor属性的“hack”性质。所以开发了DrawTextPrompt函数,并在OnPaint中添加了一个调用。不幸的是,这也被证明是一个有问题的解决方案(已经留下了代码,以便可以自己测试)。提示根本无法正确地绘制在TextBox上,显示出各种奇怪的行为,如消失的文字、错误的字体等。第一个想法是错误地编写了DrawTextPrompt,但一个简单的测试表明它确实正确地绘制了提示。

所以在更加努力之后,终于决定覆盖WndProc。知道必须弄清楚哪个消息是解决方案的关键(WM_PAINT显然是一个选择,但那不是OnPaint应该做的吗?)。经过几个小时的挫败,没有其他候选人,决定尝试看看WM_PAINT是否会有不同的结果。所以添加了对DrawTextPrompt的调用,天哪,它起作用了!!!还没有解释为什么WM_PAINT起作用而OnPaint不起作用,但可以自己试试,通过取消注释构造函数中的SetStyle行。工作理论是SetStyle调用禁用了还没有考虑到的额外行为。

在浏览代码时,注意所有调用Invalidate()的地方。这是为了让控件在设计时属性变化时重新绘制自己。控件在设计时应与运行时表现相同。如果Text属性中有值,PromptText将不会显示。如果更改PromptText、PromptForeColor或TextAlign属性,控件应立即更改。

还添加了一个FocusSelect布尔属性(自从VB3以来就渴望的一个功能,当时继承不是一个选项),当设置为true时,当控件获得焦点时,将选择控件中的所有文本。

与EM_SETCUEBANNER的比较

正如用户评论中指出的,Windows XP及更高版本有一个消息可以发送到TextBox控件,几乎可以实现相同的目标,那就是EM_SETCUEBANNER。使用此消息有一些优点:

  • 也支持ComboBox控件
  • 与Windows的未来版本向前兼容
  • 更好地支持不同的UI增强和布局,包括主题、Aero、TabletPC等

但也有一些缺点:

  • 仅支持Windows XP及更高版本
  • 不支持Windows CE/Mobile
  • 不适用于多行TextBox
  • 开发者还必须为EXE包含Comctl32.dll的清单
  • 开发者无法控制提示的字体属性或颜色
  • 显然有一个bug,如果在XP上安装了一种亚洲语言包,EM_SETCUEBANNER就不会起作用
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485