WinForms 中的水印文本框

问题描述 投票:0回答:11

任何人都可以向我指出一个基本的 Windows 窗体文本框的良好实现吗?该文本框最初会显示水印文本,当光标进入它时,水印文本就会消失?我想我可以通过一些创造性地使用 Enter 和 Leave 事件来创建自己的事件,但我确信某个地方有一个完全可用的实现。我看到了 WPF 实现,如有必要,我可以嵌套它,但本机 WinForms TextBox 衍生品会更好。

到目前为止我已经有了这个;还没有尝试过,但有人发现任何明显的问题吗?

public class WatermarkTextBox:TextBox
{
    public string WatermarkText { get; set; }

    public Color WatermarkColor { get; set; }

    private Color TextColor { get; set; }

    private bool isInTransition;

    public WatermarkTextBox()
    {
        WatermarkColor = SystemColors.GrayText;
    }

    private bool HasText { get { return Text.IsNotNullOrBlankOr(WatermarkText); }}

    protected override void OnEnter(EventArgs e)
    {
        base.OnEnter(e);

        if (HasText) return;

        isInTransition = true;
        ForeColor = TextColor;
        Text = String.Empty;
        isInTransition = false;
    }

    protected override void OnForeColorChanged(EventArgs e)
    {
        base.OnForeColorChanged(e);
        if (!isInTransition) //the change came from outside
            TextColor = ForeColor;
    }

    protected override void OnLeave(EventArgs e)
    {
        base.OnLeave(e);

        if (HasText) return;

        isInTransition = true;
        ForeColor = WatermarkColor;
        Text = WatermarkText.EmptyIfNull();
        isInTransition = false;
    }
}

编辑:上面的内容最终可以通过一些技巧来实现,但是 CueProvider 的效果要好得多。这是我的最终实现:

public class WatermarkTextBox:TextBox
{
    private string watermarkText;
    public string WatermarkText
    {
        get { return watermarkText; }
        set
        {
            watermarkText = value;
            if (watermarkText.IsNullOrBlank())
                CueProvider.ClearCue(this);
            else
                CueProvider.SetCue(this, watermarkText);
        }
    }
}

我本可以完全集成 CueProvider 功能,但这效果非常好。

.net winforms textbox watermark
11个回答
108
投票

官方术语是“提示横幅”。这是另一种方法,只需继承 TextBox 也可以完成工作。将新类添加到您的项目中并粘贴下面所示的代码。编译。从工具箱顶部拖放新控件并设置 Cue 属性。

您可以在设计器中实时预览 Cue 值,该值已本地化为表单的 Language 属性。花很少的钱就能得到很多好处,这是 Winforms 优点的完美演示。

using System;
using System.ComponentModel;
using System.Windows.Forms;
using System.Runtime.InteropServices;

class CueTextBox : TextBox {
    [Localizable(true)]
    public string Cue {
        get { return mCue; }
        set { mCue = value; updateCue(); }
    }

    private void updateCue() {
        if (this.IsHandleCreated && mCue != null) {
            SendMessage(this.Handle, 0x1501, (IntPtr)1, mCue);
        }
    }
    protected override void OnHandleCreated(EventArgs e) {
        base.OnHandleCreated(e);
        updateCue();
    }
    private string mCue;

    // PInvoke
    [DllImport("user32.dll", CharSet = CharSet.Unicode)]
    private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, string lp);
}

更新:现在从 .NETCore 3.0(以及 .NET5 及更高版本)开始也可用,他们添加了 PlaceholderText 属性


32
投票

我更新了上面@Hans Passant给出的答案,引入常量,使其与pinvoke.net定义一致,并让代码通过FxCop验证。

class CueTextBox : TextBox
{
    private static class NativeMethods
    {
        private const uint ECM_FIRST = 0x1500;
        internal const uint EM_SETCUEBANNER = ECM_FIRST + 1;

        [DllImport("user32.dll", CharSet = CharSet.Unicode)]
        public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, string lParam);
    }

    private string _cue;

    public string Cue
    {
        get
        {
            return _cue;
        }
        set
        {
            _cue = value;
            UpdateCue();
        }
    }

    private void UpdateCue()
    {
        if (IsHandleCreated && _cue != null)
        {
            NativeMethods.SendMessage(Handle, NativeMethods.EM_SETCUEBANNER, (IntPtr)1, _cue);
        }
    }

    protected override void OnHandleCreated(EventArgs e)
    {
        base.OnHandleCreated(e);
        UpdateCue();
    }
}

编辑:更新 PInvoke 调用以设置

CharSet
属性,以确保安全。有关更多信息,请参阅 pinvoke.net 上的 SendMessage 页面。


30
投票

.NET 5.0+、.NET Core 3.0+

您可以使用 PlaceholderText 属性。它支持多行和单行文本框。

textBox1.PlaceholderText = "Something";

如果您查看 TextBox

.NET CORE 实现,您会发现它是通过处理绘制消息来实现的。

.NET框架

这是

TextBox
的实现,支持显示提示(或水印或提示):

  • MultiLine
    为 true 时,它还会显示提示。
  • 它基于处理
    WM_PAINT
    消息并绘制提示。因此,您可以简单地自定义提示并添加一些属性,例如提示颜色,或者您可以从右到左绘制它或控制何时显示提示。
using System.Drawing;
using System.Windows.Forms;
public class ExTextBox : TextBox
{
    string hint;
    public string Hint
    {
        get { return hint; }
        set { hint = value; this.Invalidate(); }
    }
    protected override void WndProc(ref Message m)
    {
        base.WndProc(ref m);
        if (m.Msg == 0xf)
        {
            if (!this.Focused && string.IsNullOrEmpty(this.Text)
                && !string.IsNullOrEmpty(this.Hint))
            {
                using (var g = this.CreateGraphics())
                {
                    TextRenderer.DrawText(g, this.Hint, this.Font,
                        this.ClientRectangle, SystemColors.GrayText , this.BackColor, 
                        TextFormatFlags.Top | TextFormatFlags.Left);
                }
            }
        }
    }
}

如果你使用

EM_SETCUEBANNER
,那么会有2个问题。文本始终以系统默认颜色显示。当
TextBox
MultiLine
时,也不会显示文本。

使用绘画解决方案,您可以用任何您想要的颜色显示文本。当控件是多行时,您还可以显示水印:

下载

您可以克隆或下载工作示例:


15
投票
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, string lParam);

以及消息常量:

private const uint EM_SETCUEBANNER = 0x1501;
private const uint CB_SETCUEBANNER = 0x1703;    // minimum supported client Windows Vista, minimum supported server Windows Server 2008

恕我直言,实现它的最佳方法是作为扩展方法。
因此,对于 TextBox 控件,语法为:

MyTextBox.CueBanner(false, "Password");

来自代码:

public static void CueBanner(this TextBox textbox, bool showcuewhenfocus, string cuetext)
{
    uint BOOL = 0;
    if (showcuewhenfocus == true) { BOOL = 1; }

    SendMessage(textbox.Handle, EM_SETCUEBANNER, (IntPtr)BOOL, cuetext); ;
}

5
投票

在 .NET Core 上使用 WinForms:

这在 .NET Core 中得到了极大的简化。您可以通过修改 TextBox 的新 PlaceholderText 属性来直接添加占位符文本。

public virtual string PlaceholderText { get; set; }

请注意,如果您想获得彩色占位符文本,您可能仍然需要编辑前景色。当文本字段为 null 或为空时,PlaceholderText 字段可见。


2
投票

您可以向文本框(多行或非多行)添加水印,通过在不同控件的 Paint 事件中绘制水印,效果非常好。例如:

    Private Sub Panel1_Paint(sender As Object, e As PaintEventArgs) Handles Panel1.Paint
        If TextBox1.Text = "" Then
            TextBox1.CreateGraphics.DrawString("Enter Text Here", Me.Font, New SolidBrush(Color.LightGray), 0, 0)
        End If
    End Sub

1
投票

我为我的项目编写了一个可重用的自定义控件类。
也许它可以帮助需要在项目中实现多个占位符文本框的人。 这是 C# 和 vb.net 版本:

C#:

namespace reusebleplaceholdertextbox
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            // implementation
            CustomPlaceHolderTextbox myCustomTxt = new CustomPlaceHolderTextbox(
                "Please Write Text Here...", Color.Gray, new Font("ARIAL", 11, FontStyle.Italic)
                , Color.Black, new Font("ARIAL", 11, FontStyle.Regular)
                );

            myCustomTxt.Multiline = true;
            myCustomTxt.Size = new Size(200, 50);
            myCustomTxt.Location = new Point(10, 10);
            this.Controls.Add(myCustomTxt);
        }
    }

    class CustomPlaceHolderTextbox : System.Windows.Forms.TextBox
    {
        public string PlaceholderText { get; private set; }
        public Color PlaceholderForeColor { get; private set; }
        public Font PlaceholderFont { get; private set; }

        public Color TextForeColor { get; private set; }
        public Font TextFont { get; private set; }

        public CustomPlaceHolderTextbox(string placeholdertext, Color placeholderforecolor,
            Font placeholderfont, Color textforecolor, Font textfont)
        {
            this.PlaceholderText = placeholdertext;
            this.PlaceholderFont = placeholderfont;
            this.PlaceholderForeColor = placeholderforecolor;
            this.PlaceholderFont = placeholderfont;
            this.TextForeColor = textforecolor;
            this.TextFont = textfont;
            if (!string.IsNullOrEmpty(this.PlaceholderText))
            {
                SetPlaceHolder(true);
                this.Update();
            }
        }

        private void SetPlaceHolder(bool addEvents)
        {
            if (addEvents)
            {  
                this.LostFocus += txt_lostfocus;
                this.Click += txt_click;
            }

            this.Text = PlaceholderText;
            this.ForeColor = PlaceholderForeColor;
            this.Font = PlaceholderFont;
        }

        private void txt_click(object sender, EventArgs e)
        {
            // IsNotFirstClickOnThis:
            // if there is no other control in the form
            // we will have a problem after the first load
            // because we dont other focusable control to move the focus to
            // and we dont want to remove the place holder
            // only on first time the place holder will be removed by click event
            RemovePlaceHolder();
            this.GotFocus += txt_focus;
            // no need for this event listener now
            this.Click -= txt_click;
        }

        private void RemovePlaceHolder()
        {
            this.Text = "";
            this.ForeColor = TextForeColor;
            this.Font = TextFont;
        }
        private void txt_lostfocus(object sender, EventArgs e)
        {
            if (string.IsNullOrEmpty(this.Text))
            {
                // set placeholder again
                SetPlaceHolder(false);
            }
        }

        private void txt_focus(object sender, EventArgs e)
        {
            if (this.Text == PlaceholderText)
            {
                // IsNotFirstClickOnThis:
                // if there is no other control in the form
                // we will have a problem after the first load
                // because we dont other focusable control to move the focus to
                // and we dont want to remove the place holder
                RemovePlaceHolder();
            }
        }
    }
}


VB.NET:


Namespace CustomControls

    Public Class PlaceHolderTextBox
        Inherits System.Windows.Forms.TextBox

        Public Property PlaceholderText As String
        Public Property PlaceholderForeColor As Color
        Public Property PlaceholderFont As Font
        Public Property TextForeColor As Color
        Public Property TextFont As Font

        Public Sub New(ByVal placeholdertext As String, ByVal placeholderforecolor As Color, ByVal placeholderfont As Font, ByVal txtboxbackcolor As Color, ByVal textforecolor As Color, ByVal textfont As Font)
            Me.PlaceholderText = placeholdertext
            Me.PlaceholderFont = placeholderfont
            Me.PlaceholderForeColor = placeholderforecolor
            Me.PlaceholderFont = placeholderfont
            Me.TextForeColor = textforecolor
            Me.TextFont = textfont
            Me.BackColor = txtboxbackcolor
            If Not String.IsNullOrEmpty(Me.PlaceholderText) Then
                SetPlaceHolder(True)
                Me.Update()
            End If
        End Sub

        Private Sub SetPlaceHolder(ByVal addEvents As Boolean)
            If addEvents Then
                AddHandler Me.LostFocus, AddressOf txt_lostfocus
                AddHandler Me.Click, AddressOf txt_click
            End If

            Me.Text = PlaceholderText
            Me.ForeColor = PlaceholderForeColor
            Me.Font = PlaceholderFont
        End Sub

        Private Sub txt_click(ByVal sender As Object, ByVal e As EventArgs)
            RemovePlaceHolder()
            AddHandler Me.GotFocus, AddressOf txt_focus
            RemoveHandler Me.Click, AddressOf txt_click
        End Sub

        Private Sub RemovePlaceHolder()
            Me.Text = ""
            Me.ForeColor = TextForeColor
            Me.Font = TextFont
        End Sub

        Private Sub txt_lostfocus(ByVal sender As Object, ByVal e As EventArgs)
            If String.IsNullOrEmpty(Me.Text) Then
                SetPlaceHolder(False)
            End If
        End Sub

        Private Sub txt_focus(ByVal sender As Object, ByVal e As EventArgs)
            If Me.Text = PlaceholderText Then
                RemovePlaceHolder()
            End If
        End Sub
    End Class

End Namespace

0
投票
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;

namespace PlaceHolderTextBoxCSharp
{
    public class CTextBox : TextBox
    {
        private Panel contenedor;
        protected string texto = "PlaceHolderText";
        protected Color colorTextoDefault = Color.Gray;
        public Color colorTexto = Color.Gray;
        protected Color colorTextoObligatorio = Color.Red;
        private Font fuente;
        private SolidBrush establecerColorTexto;
        private bool obligatoriedad = false;
        private bool colorConFoco = false;
        private int vuelta = 0;

        public CTextBox()
        {
            Inicializar();
        }

        private void Inicializar()
        {
            fuente = Font;
            CharacterCasing = CharacterCasing.Upper;
            contenedor = null;

            MuestraPlaceHolder();

            Leave += new EventHandler(PierdeFoco);
            TextChanged += new EventHandler(CambiaTexto);
        }

        private void EliminaPlaceHolder()
        {
            if (contenedor != null)
            {
                Controls.Remove(contenedor);
                contenedor = null;
            }
        }

        private void MuestraPlaceHolder()
        {
            if (contenedor == null && TextLength <= 0)
            {
                contenedor = new Panel();
                contenedor.Paint += new PaintEventHandler(contenedorPaint);
                contenedor.Invalidate();
                contenedor.Click += new EventHandler(contenedorClick);
                Controls.Add(contenedor);
            }
        }

        private void contenedorClick(object sender, EventArgs e)
        {
            Focus();
        }

        private void contenedorPaint(object sender, PaintEventArgs e)
        {
            contenedor.Location = new Point(2, 0);
            contenedor.Height = Height;
            contenedor.Width = Width;
            contenedor.Anchor = AnchorStyles.Left | AnchorStyles.Right;
            establecerColorTexto = new SolidBrush(colorTexto);
            Graphics g = e.Graphics;
            g.DrawString(texto, fuente, establecerColorTexto, new PointF(-1f, 1f));
        }

        private void PierdeFoco(object sender, EventArgs e)
        {
            if (TextLength > 0)
            {
                EliminaPlaceHolder();
            }
            else
            {
                if (obligatoriedad == true)
                {
                    colorTexto = colorTextoObligatorio;
                }
                else
                {
                    colorTexto = colorTextoDefault;
                }

                Invalidate();
            }
        }

        private void CambiaTexto(object sender, EventArgs e)
        {
            if (TextLength > 0)
            {
                EliminaPlaceHolder();
            }
            else
            {
                MuestraPlaceHolder();

                vuelta += 1;

                if (vuelta >= 1 && obligatoriedad == true)
                {
                    colorTexto = colorTextoObligatorio;
                }
            }
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            MuestraPlaceHolder();
        }

        protected override void OnInvalidated(InvalidateEventArgs e)
        {
            base.OnInvalidated(e);

            if (contenedor != null)
            {
                contenedor.Invalidate();
            }
        }

        [Category("Atributos PlaceHolder")]
        [Description("Establece el texto a mostrar.")]

        public string PlaceHolderText
        {
            get
            {
                return texto;
            }
            set
            {
                texto = value;
                Invalidate();
            }
        }

        [Category("Atributos PlaceHolder")]
        [Description("Establece el estilo de fuente del PlaceHolder.")]

        public Font PlaceHolderFont
        {
            get
            {
                return fuente;
            }
            set
            {
                fuente = value;
                Invalidate();
            }
        }

        [Category("Atributos PlaceHolder")]
        [Description("Indica si el campo es obligatorio.")]

        public bool PlaceHolderFieldRequired
        {
            get
            {
                return obligatoriedad;
            }
            set
            {
                obligatoriedad = value;
                Invalidate();
            }
        }
    }
}

0
投票

在 .Net Core 3 中,TextBox 中引入了一个属性:PlaceHolderText

如果在 FrameWork 应用程序中需要此功能,则可以从官方开源代码中获取所需的代码部分,并将其放置在 TextBox 后代中(请参阅许可证)。

这支持多行文本框和 RTL 文本。

public class PlaceHolderTextBox : TextBox
{
    private const int WM_KILLFOCUS = 0x0008;
    private const int WM_PAINT = 0x000F;

    private string _placeholderText;

    protected override void WndProc(ref Message m)
    {
        base.WndProc(ref m);

        if (this.ShouldRenderPlaceHolderText(m))
        {
            using Graphics g = this.CreateGraphics();
            this.DrawPlaceholderText(g);
        }
    }

    #region PlaceHolder

    /// <summary>
    ///  Gets or sets the text that is displayed when the control has no text and does not have the focus.
    /// </summary>
    /// <value>The text that is displayed when the control has no text and does not have the focus.</value>
    [Localizable(true), DefaultValue("")]
    public virtual string PlaceholderText
    {
        get => _placeholderText;
        set
        {
            if (value == null)
            {
                value = string.Empty;
            }

            if (_placeholderText != value)
            {
                _placeholderText = value;
                if (this.IsHandleCreated)
                {
                    this.Invalidate();
                }
            }
        }
    }

    //-------------------------------------------------------------------------------------------------

    /// <summary>
    ///  Draws the <see cref="PlaceholderText"/> in the client area of the <see cref="TextBox"/> using the default font and color.
    /// </summary>
    private void DrawPlaceholderText(Graphics graphics)
    {
        TextFormatFlags flags = TextFormatFlags.NoPadding | TextFormatFlags.Top |
                                TextFormatFlags.EndEllipsis;
        Rectangle rectangle = this.ClientRectangle;

        if (this.RightToLeft == RightToLeft.Yes)
        {
            flags |= TextFormatFlags.RightToLeft;
            switch (this.TextAlign)
            {
                case HorizontalAlignment.Center:
                    flags |= TextFormatFlags.HorizontalCenter;
                    rectangle.Offset(0, 1);
                    break;
                case HorizontalAlignment.Left:
                    flags |= TextFormatFlags.Right;
                    rectangle.Offset(1, 1);
                    break;
                case HorizontalAlignment.Right:
                    flags |= TextFormatFlags.Left;
                    rectangle.Offset(0, 1);
                    break;
            }
        }
        else
        {
            flags &= ~TextFormatFlags.RightToLeft;
            switch (this.TextAlign)
            {
                case HorizontalAlignment.Center:
                    flags |= TextFormatFlags.HorizontalCenter;
                    rectangle.Offset(0, 1);
                    break;
                case HorizontalAlignment.Left:
                    flags |= TextFormatFlags.Left;
                    rectangle.Offset(1, 1);
                    break;
                case HorizontalAlignment.Right:
                    flags |= TextFormatFlags.Right;
                    rectangle.Offset(0, 1);
                    break;
            }
        }

        TextRenderer.DrawText(graphics, this.PlaceholderText, this.Font, rectangle, SystemColors.GrayText, this.BackColor, flags);
    }
    
    private bool ShouldRenderPlaceHolderText(in Message m) =>
        !string.IsNullOrEmpty(this.PlaceholderText) &&
        (m.Msg == WM_PAINT || m.Msg == WM_KILLFOCUS) &&
        !this.GetStyle(ControlStyles.UserPaint) &&
        !this.Focused && this.TextLength == 0;

    #endregion
}

0
投票

更新 2022 年 12 月 30 日 使用.NET6(或者可能较低版本有此选项,我不确定)

只需右键单击文本框 > 属性 > PlaceHolderText > 并将其设置为您想要的任何内容

click here to see that property in picture


-1
投票
Private Sub randomSubName() Handles txtWatermark.Click
   txtWatermark.text = ""
End Sub

将文本框的默认文本设置为您想要的水印,我假设在此示例中您将文本框命名为 txtWatermark

嘿,我是新来的。很抱歉,如果我把这篇文章搞砸了……我也不知道这是否有效……

© www.soinside.com 2019 - 2024. All rights reserved.