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

带水印的自定义文本框

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (27投票s)

2012 年 1 月 25 日

CPOL

7分钟阅读

viewsIcon

118886

downloadIcon

14515

一篇关于如何编写带有水印选项的自定义文本框的文章。

CTextBox_Screenshot.png

引言

在本文中,我将向您展示如何编写一个带有水印/占位符的自定义文本框。我一直在寻找这样的控件,因为我认为在用户填写表单时,提供一些信息而不是使用标签作为提示非常酷。它节省空间,而且看起来很棒。

背景

我曾经在网上搜索过这样的控件,但没有成功。我得到的唯一示例是使用某种 `user32.dll` 调用,这就像 Windows 7 在“开始”菜单搜索字段中使用的那样。当你点击文本框时,提示就会消失,在我看来,这看起来很不专业。我并不是说我的很专业,但它以一种流畅的方式完成了任务。我还发现一篇文章,作者使用标签来编写水印,由于缺乏填充,这使用起来很麻烦。它只是与闪烁的光标重叠,而光标是告诉用户输入文本的信号。所以我想我应该尝试使用面板,因为你可以将面板设置为负坐标,这正是我需要的 :)

使用代码

使用此代码非常简单。您所要做的就是将 DLL 导入到您的项目中,然后像使用常规 `TextBox` 一样拖放它。此外,在设计时,您可以设置水印在活动或非活动时的颜色。您还可以设置水印的文本和字体。默认情况下,字体与其父级(即文本框基准)相同。

那么,让我们开始吧

请注意,我将按时间顺序从上到下逐一讲解类。这将在编写过程中导致很多错误,但最终它将完美运行 :) 请耐心并创建一个新类并跟着我操作。否则,如果您遇到任何错误,可以下载源代码。

首先我们声明类。(请记住添加以下引用:`System.Windows.Forms` 和 `System.Drawing`。)

using System;
using System.Windows.Forms;
using System.Drawing;
using System.ComponentModel;

namespace ChreneLib.Controls.TextBoxes
{
    public class CTextBox : TextBox
    {

接下来要做的是声明一些变量

在这里,我们设置一个字符串来保存水印文本,并根据控件是否获得焦点来设置水印的颜色。我们还设置了一个新面板,稍后将在其中绘制水印文本。最后,我们有一个字体和一个画刷。字体将是绘制水印的字体,默认情况下,我们将它设置为其父级的字体。实心画刷将用于保存动态变化的字体颜色。

#region Fields

#region Protected Fields

protected string _waterMarkText = "Default Watermark..."; //The watermark text
protected Color _waterMarkColor; //Color of the watermark when the control does not have focus
protected Color _waterMarkActiveColor; //Color of the watermark when the control has focus

#endregion

#region Private Fields

private Panel waterMarkContainer; //Container to hold the watermark
private Font waterMarkFont; //Font of the watermark
private SolidBrush waterMarkBrush; //Brush for the watermark

#endregion

#endregion

现在我们将设置我们的构造函数。

构造函数实际上除了调用我们稍后会讲到的 `Initialize` 方法之外,什么都不做。所以复制粘贴或者你想做什么就做什么吧 :)

#region Constructors

public CTextBox()
{
    Initialize();
}

#endregion

添加方法

这一章会有点长,但我相信你无论如何都会明白的 :)

初始化();

我们首先创建 `Initialize` 方法,它基本上只是将我们的变量设置为一些默认值。之后,我们调用 `DrawWaterMark()` 方法,该方法将绘制水印,以便我们可以像放置它一样看到它。实际上,我认为它不需要被调用,因为我们稍后会在 `onPaint` 方法中调用它,但管它呢。绘制水印后,我们设置一些事件监听器。我们需要它们,因为它们实际上决定了控件应该是什么样子。例如,输入一些文本,水印应该被移除,因此我们稍后需要编写这些指令。如您所见,我们正在添加一个新的 `EventHandler`,并且在括号中我们编写了当特定事件发生时应该触发的方法,例如,当我们点击控件时。

移除水印();

接下来,我们将创建移除水印的方法。当用户输入的文本长度大于零时,将调用此方法。所以,假设您在文本框中输入了一些内容,那么控件将移除水印。如果您决定再次删除该文本,水印将重新绘制。但对于算法来说,它是这样的。首先,我们检查水印容器(一个面板)是否已创建。如果它没有创建,那么我们不应该做任何事情,但如果它已创建,那么它应该移除它并释放为其分配的内存。

绘制水印();

当文本框为空且尚未绘制水印时,会调用此方法。首先,我们初始化水印容器,然后设置其属性。然后我们添加一个新的事件监听器,用于点击时。这样,如果我们点击容器,控件也会获得焦点。如果它不存在,控件将不会获得焦点,并且您将遇到严重的焦点获取问题,我的意思是,输入文本会很困难。

#region Private Methods

/// <summary>
/// Initializes watermark properties and adds CtextBox events
/// </summary>
private void Initialize()
{
    //Sets some default values to the watermark properties
    _waterMarkColor = Color.LightGray;
    _waterMarkActiveColor = Color.Gray;
    waterMarkFont = this.Font;
    waterMarkBrush = new SolidBrush(_waterMarkActiveColor);
    waterMarkContainer = null;

    //Draw the watermark, so we can see it in design time
    DrawWaterMark();

    //Eventhandlers which contains function calls. 
    //Either to draw or to remove the watermark
    this.Enter += new EventHandler(ThisHasFocus);
    this.Leave += new EventHandler(ThisWasLeaved);
    this.TextChanged += new EventHandler(ThisTextChanged);
}

/// <summary>
/// Removes the watermark if it should
/// </summary>
private void RemoveWaterMark()
{
    if (waterMarkContainer != null)
    {
        this.Controls.Remove(waterMarkContainer);
        waterMarkContainer = null;
    }
}

/// <summary>
/// Draws the watermark if the text length is 0
/// </summary>
private void DrawWaterMark()
{
    if (this.waterMarkContainer == null && this.TextLength <= 0)
    {
        waterMarkContainer = new Panel(); // Creates the new panel instance
        waterMarkContainer.Paint += new PaintEventHandler(waterMarkContainer_Paint);
        waterMarkContainer.Invalidate();
        waterMarkContainer.Click += new EventHandler(waterMarkContainer_Click);
        this.Controls.Add(waterMarkContainer); // adds the control
    }
}
#endregion

添加事件监听器

我们快完成了。现在我们需要添加一些事件监听器。

ThisHasFocus();

此事件处理程序在控件获得焦点时触发。首先,它会将水印颜色更改为活动颜色。然后我们检查用户是否在控件中输入了任何内容,如果输入了,我们不应该做任何事情。但是,如果它为空,我们将移除水印并重新绘制它。

ThisWasLeaved();

所以基本上,在这个事件处理程序中,我们将检查用户是否输入了任何内容。如果输入了内容,我们将移除水印,并且再次……我不知道这是否需要,但双重检查总是比零检查更好。好吧,如果用户没有输入任何内容,我们将使控件失效,这意味着我们将触发绘制事件,它将重新绘制控件。我们这样做是为了确保水印将其颜色更改为被动颜色。

ThistextChanged();

当用户更改文本时触发此方法。在这里,我们将再次检查文本长度,因此如果控件中没有文本,我们将绘制水印,但如果有文本,我们将删除它。

OnPaint();

我们重写现有的 `OnPaint` 方法,以确保当控件失效时,水印也会刷新。这甚至在设计时也会起作用。

OnInvalidated();

我们还重写了此方法,以确保水印容器也失效。

#region CTextBox Events

private void ThisHasFocus(object sender, EventArgs e)
{
    //if focused use focus color
    waterMarkBrush = new SolidBrush(this._waterMarkActiveColor);

    //The watermark should not be drawn if the user has already written some text
    if (this.TextLength <= 0)
    {
        RemoveWaterMark();
        DrawWaterMark();
    }
}

private void ThisWasLeaved(object sender, EventArgs e)
{
    //if the user has written something and left the control
    if (this.TextLength > 0)
    {
        //Remove the watermark
        RemoveWaterMark();
    }
    else
    {
        //But if the user didn't write anything, Then redraw the control.
        this.Invalidate();
    }
}

private void ThisTextChanged(object sender, EventArgs e)
{
    //If the text of the textbox is not empty
    if (this.TextLength > 0)
    {
        //Remove the watermark
        RemoveWaterMark();
    }
    else
    {
        //But if the text is empty, draw the watermark again.
        DrawWaterMark();
    }
}

#region Overrided Events

protected override void OnPaint(PaintEventArgs e)
{
    base.OnPaint(e);
    //Draw the watermark even in design time
    DrawWaterMark();
}

protected override void OnInvalidated(InvalidateEventArgs e)
{
    base.OnInvalidated(e);
    //Check if there is a watermark
    if (waterMarkContainer != null)
        //if there is a watermark it should also be invalidated();
        waterMarkContainer.Invalidate();
}

#endregion

#endregion

设置属性

最后一步是设置一些属性和方法,以便我们可以在运行时和设计时更改属性。我不会过多解释,因为这只是更改水印颜色、字体和文本的一些 get/set 方法。但是,您可能会在每个方法中看到一些不同的东西。这些被称为属性,并在设计时使用。类别将在设计时创建一个小节点,以便您可以将属性集中在一个地方。描述显示了关于该特定属性将做什么的一些文本。

        #region Properties
        [Category("Watermark attribtues")]
        [Description("Sets the text of the watermark")]
        public string WaterMark
        {
            get { return this._waterMarkText; }
            set
            {
                this._waterMarkText = value;
                this.Invalidate();
            }
        }

        [Category("Watermark attribtues")]
        [Description("When the control gaines focus, " + 
             "this color will be used as the watermark's forecolor")]
        public Color WaterMarkActiveForeColor
        {
            get { return this._waterMarkActiveColor; }

            set
            {
                this._waterMarkActiveColor = value;
                this.Invalidate();
            }
        }

        [Category("Watermark attribtues")]
        [Description("When the control looses focus, this color " + 
                     "will be used as the watermark's forecolor")]
        public Color WaterMarkForeColor
        {
            get { return this._waterMarkColor; }

            set
            {
                this._waterMarkColor = value;
                this.Invalidate();
            }
        }

        [Category("Watermark attribtues")]
        [Description("The font used on the watermark. " + 
                     "Default is the same as the control")]
        public Font WaterMarkFont
        {
            get
            {
                return this.waterMarkFont;
            }

            set
            {
                this.waterMarkFont = value;
                this.Invalidate();
            }
        }

        #endregion   
    }
}

关注点

这是我第一次尝试编写自己的自定义控件,这是一次非常有趣的体验。我学到了很多关于 .NET Framework 及其模式的知识。

再次感谢您的阅读 :) 如果有任何错误,请告诉我。

历史

First version.

带水印的自定义文本框 - CodeProject - 代码之家
© . All rights reserved.