透かし文字表示機能つきTextBox(WinForms)

Windows Formsのテキストボックスには、プレースホルダー(=ウォーターマーク:入力する値のガイド・ヒントを透かしのように表示する)機能が備わっていません。

こんな風に表示するためには、テキストボックスの継承クラスを実装して使う必要があります。以下、ソース例です。

TextBoxEx.cs ソースコード

public class TextBoxEx : TextBox
{
    /// <summary>
    /// テキストが空の場合に表示する文字列を取得・設定します。
    /// </summary>
    [Category("表示")]
    [DefaultValue("")]
    [Description("テキストが空の場合に表示する文字列です。")]
    [RefreshProperties(RefreshProperties.Repaint)]
    public string WatermarkText
    {
        get { return _watermarkText; }
        set
        {
            _watermarkText = value;
            this.Invalidate();
        }
    }
    private string _watermarkText = ""; //ウォーターマーク表示内容text
    ///<summary>
    ///描画拡張(テキスト未設定時、ウォーターマークを描画)
    ///</summary>
    ///<param name="m"></param>
    protected override void WndProc(ref Message m)
    {
        const int WM_PAINT = 0x000F;
        base.WndProc(ref m);
        if (m.Msg == WM_PAINT && string.IsNullOrEmpty(this.Text) && string.IsNullOrEmpty(WatermarkText) == false)
        {
            using (Graphics g = Graphics.FromHwnd(this.Handle))
            {   //テキストボックス内の適切な座標に描画
                Rectangle rect = this.ClientRectangle;
                rect.Offset(1, 1);
                TextRenderer.DrawText(g, WatermarkText, this.Font,
                    rect, SystemColors.ControlDark, TextFormatFlags.Top | TextFormatFlags.Left);
            }
        }
    }
}

使い方

・ビルドするとツールボックスに「TextBoxEx」コンポーネントが出てくるようになるので、TextBoxの代わりにフォームデザイナへ貼り付けます。
・表示する透かし文字の文言は、WatermarkTextプロパティで指定します。

実現方法

ウォーターマークの実装ですが、

  1. フォーカスEnter/Leave時にテキストの色と表示内容を差し替えて疑似的に実現する方法
  2. Win32API(EM_SETCUEBANNERメッセージ)により表示する方法
  3. WM_PAINT描画メッセージを受け取ったタイミングで透かし文字文言を書き足す方法

あたりがメジャーなやり方です。

1.についてはComboBoxでの実装をこのblogでも紹介していますが、TextやColorを書き換える手間がかなりかかるのと、書き換えのつど発生してしまうイベントを抑止するのが大変なので、あまりおすすめはできません。

2.はWindowsXP以降の機能を利用するもので、一度EM_SETCUEBANNER(0x1501)メッセージを送りつけてしまえば、あとはWindowsがすべて自動でやってくれます。
実装もシンプルなのですが、Multiline=Trueの複数行TextBoxやRichTextBoxではウォーターマークが表示されなかったり、(今となってはもう利用者もわずかですが)日本含む東アジア版のWindowsXPでは不具合のため動かなかったりと、利用できないシチュエーションがあることに注意が必要です。
なおフォントの変更などを行うと内部的にはコントロールがいったん全破棄(再作成)されるらしく、OnHandleCreatedメソッドをオーバーライドして再度EM_SETCUEBANNERメッセージを送信しなおさないとウォーターマーク設定が復旧しません。

今回のソースは3.のやり方で実現しています。
TextBox(単一行/複数行)、RichTextBoxでは問題なく動作しますが、ComboBoxだとこの方法でテキスト欄にウォーターマークを上書きできなさそうです。(コンボボックス内のHWndItemに対して描画すればうまくいくかも?)

補足・透かし機能の表記

この透かし文字表示機能ですが、ウォーターマーク(Watermark)/プレースホルダー(Place Holder)/キューバナー(Cue Banner)/テキストヒント(Text Hint)などいろいろな呼ばれ方があり、統一されていません。
ウォーターマークといえばテレビ番組の局マーク透かし等、プレースホルダーだとSQL埋め込みパラメータあたりと混同されたりとややこしく、Web上で検索しずらいんですよね・・・・。

おまけ

WinFormsのTextBoxは、Ctrl-Aキーを押してもテキスト全選択ができません。
継承したTextBoxExクラスで以下のようにCtrl-Aを有効にしておくと不便が解消されます。

/// <summary>
/// なぜかTextBoxに実装されていないショートカットキー処理を組み込む
/// </summary>
protected override bool ProcessCmdKey(ref Message m, Keys keyData)
{
    Keys code = keyData & Keys.KeyCode;
    Keys modi = keyData & Keys.Modifiers;
    if ((keyData & Keys.Control) == Keys.Control)
    {   //Ctrlキー単独
        switch (code)
        {
            case Keys.A: //SelectAll
                this.SelectAll();
                return true;
            case Keys.Back: //ごみ文字が挿入されるのであえて握りつぶして無効にする
                return true;
        }
    }
    return base.ProcessCmdKey(ref m, keyData);
}

ついでに、Control+BackSpaceキーを押した際、テキストボックスに0x7Fの制御文字が挿入されてしまう不具合についても、対策も入れるとしたらこんな感じに。