65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.72/5 (36投票s)

2006年10月12日

CPOL

5分钟阅读

viewsIcon

243857

downloadIcon

3343

一篇关于创建模仿 Outlook 2007、IE7 和 Firefox 2.0 风格的提示文本框的文章。

PromptedTextBox Sample

引言

您可能在网上浏览时看到过这类文本框,例如 eBay 的搜索框,它是一个带有提示信息的文本框,如“在此处输入您的搜索字符串”。一旦您单击该框,提示信息就会消失,留下一个空的文本框供您输入搜索字符串。微软甚至在其 Atlas 网站上有一个 此类控件的 AJAX 示例,称为 TextBoxWatermark 控件。

最近,这类控件开始出现在 Outlook 2007、IE7 以及 Firefox 2.0 等 Windows 应用程序中。这种控件非常有用,因为它基本上是一个 Textbox 和一个 Label 的合一,而且不占用大量屏幕空间。在 Web 上,这种巧妙的功能通常通过 JavaScript 实现,但在 Windows 上,我们需要自己想办法实现相同的功能。

背景

在 Web 应用程序中,开发人员通常会在 onBluronFocus 事件中添加 JavaScript 代码,以便在 TextBox 中显示提示信息。开发人员还必须注意,表单提交(或 ASP.NET 中的“postback”)可能会导致提示文本包含在 postdata 中,因此代码必须对此有所了解。此外,JavaScript 代码为了实现此功能,被迫操作 TextBox 的“value”属性,因此即使是 JavaScript 解决方案也带有“hack”的感觉。

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

使用代码

重写 WndProc 似乎有点小题大做,但在此情况下,代码实际上相当简单。对于此控件,我们只需要关注 WM_SETFOCUSWM_KILLFOCUS 消息,这应该与 GotFocusSetFocus 事件相同,以及 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 并简单地交换 TextPromptText 的值(以及更改 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。如果您更改 PromptTextPromptForeColorTextAlign 属性,控件应立即做出更改。

我还添加了一个 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
© . All rights reserved.