C# 带有 Outlook 2007 风格提示的 TextBox






4.72/5 (36投票s)
一篇关于创建模仿 Outlook 2007、IE7 和 Firefox 2.0 风格的提示文本框的文章。
引言
您可能在网上浏览时看到过这类文本框,例如 eBay 的搜索框,它是一个带有提示信息的文本框,如“在此处输入您的搜索字符串”。一旦您单击该框,提示信息就会消失,留下一个空的文本框供您输入搜索字符串。微软甚至在其 Atlas 网站上有一个 此类控件的 AJAX 示例,称为 TextBoxWatermark
控件。
最近,这类控件开始出现在 Outlook 2007、IE7 以及 Firefox 2.0 等 Windows 应用程序中。这种控件非常有用,因为它基本上是一个 Textbox
和一个 Label
的合一,而且不占用大量屏幕空间。在 Web 上,这种巧妙的功能通常通过 JavaScript 实现,但在 Windows 上,我们需要自己想办法实现相同的功能。
背景
在 Web 应用程序中,开发人员通常会在 onBlur
和 onFocus
事件中添加 JavaScript 代码,以便在 TextBox
中显示提示信息。开发人员还必须注意,表单提交(或 ASP.NET 中的“postback”)可能会导致提示文本包含在 postdata 中,因此代码必须对此有所了解。此外,JavaScript 代码为了实现此功能,被迫操作 TextBox
的“value
”属性,因此即使是 JavaScript 解决方案也带有“hack”的感觉。
标准的 WinForms TextBox
本身不支持此功能,因此此类将通过继承 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);
// Only draw the prompt on the WM_PAINT event
// and when the Text property is empty
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;
// Offset the rectangle based on the HorizontalAlignment,
// otherwise the display looks a little strange
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;
}
// Draw the prompt text using 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 的清单。
- 开发人员无法控制提示的字体属性或颜色。
- 据称,如果 XP 上安装了其中一种亚洲语言包,
EM_SETCUEBANNER
会出现一个 bug 导致其无法正常工作。
我尚未验证 PromptedTextBox
是否能在所有旧平台(或 CE)上运行,但该方法不需要任何特殊功能。事实上,该理论可以追溯到 Windows 3.1,但当然 .NET 只支持 Windows 98 及更高版本。
总之,如果您发现 PromptedTextBox
在某些情况下行为不如预期,并且您有选择,请尝试 EM_SETCUEBANNER
是否能解决问题。如果不能,请给我发消息,我会尽力而为。
发布历史
- 版本 1.0:2006 年 10 月 4 日 - 初始发布。
- 版本 1.1:2006 年 10 月 16 日 - 添加了
PromptFont
属性,允许开发人员更精细地控制提示显示。还将默认提示颜色更改为SystemColors.GrayText
。