読者です 読者をやめる 読者になる 読者になる

TextBox でプレースホルダーを表示する方法

WPF の TextBox にはプレースホルダーを表示する機能がありません。あ、プレースホルダーっていうのは、WPF で以前 Watermark って呼んでいたやつです。HTML5 の影響で、プレースホルダーなんて格好つけて呼ぶようになってしまいました。


WPF がベータ版のときは WatermarkedTextBox っていうコントロールがあったんですが、機能が TextBox に統合されるという理由で廃止されました。でも .NET Framework 4 の TextBox にその機能はまだ見当たりません。私が見逃してるだけ?


TextBox にプレースホルダーを表示する方法は、Microsoft が運営する CodeRecipe で紹介されています。

でも、毎回 TextBlock に TextBox を重ねて半透明にするのは面倒ですね。


私は面倒くさがりなので、プレースホルダーを表示する添付ビヘイビアを作ってみました。

// プレースホルダーを表示する添付ビヘイビア
public static class PlaceHolderBehavior
{
    // プレースホルダーとして表示するテキスト
    public static readonly DependencyProperty PlaceHolderTextProperty = DependencyProperty.RegisterAttached(
        "PlaceHolderText",
        typeof(string),
        typeof(PlaceHolderBehavior),
        new PropertyMetadata(null, OnPlaceHolderChanged));

    private static void OnPlaceHolderChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        var textBox = sender as TextBox;
        if (textBox == null)
        {
            return;
        }

        var placeHolder = e.NewValue as string;
        var handler = CreateEventHandler(placeHolder);
        if (string.IsNullOrEmpty(placeHolder))
        {
            textBox.TextChanged -= handler;
        }
        else
        {
            textBox.TextChanged += handler;
            if (string.IsNullOrEmpty(textBox.Text))
            {
                textBox.Background = CreateVisualBrush(placeHolder);
            }
        }
    }

    private static TextChangedEventHandler CreateEventHandler(string placeHolder)
    {
        // TextChanged イベントをハンドルし、TextBox が未入力のときだけ
        // プレースホルダーを表示するようにする。
        return (sender, e) =>
        {
            // 背景に TextBlock を描画する VisualBrush を使って
            // プレースホルダーを実現
            var textBox = (TextBox)sender;
            if (string.IsNullOrEmpty(textBox.Text))
            {
                textBox.Background = CreateVisualBrush(placeHolder);
            }
            else
            {
                textBox.Background = new SolidColorBrush(Colors.Transparent);
            }
        };
    }

    private static VisualBrush CreateVisualBrush(string placeHolder)
    {
        var visual = new Label()
        {
            Content = placeHolder,
            Padding = new Thickness(5, 1, 1, 1),
            Foreground = new SolidColorBrush(Colors.LightGray),
            HorizontalAlignment = HorizontalAlignment.Left,
            VerticalAlignment = VerticalAlignment.Center,
        };
        return new VisualBrush(visual)
        {
            Stretch = Stretch.None,
            TileMode = TileMode.None,
            AlignmentX = AlignmentX.Left,
            AlignmentY = AlignmentY.Center,
        };
    }

    public static void SetPlaceHolderText(TextBox textBox, string placeHolder)
    {
        textBox.SetValue(PlaceHolderTextProperty, placeHolder);
    }

    public static string GetPlaceHolderText(TextBox textBox)
    {
        return textBox.GetValue(PlaceHolderTextProperty) as string;
    }
}

VisualBrush を使って、TextBox の背景に TextBlock を描画しているところがポイント。


使い方はこんな感じです。

<Window x:Class="PlaceHolderSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:PlaceHolderSample"
        Title="MainWindow" Height="350" Width="525">
    <Canvas>
        <TextBox x:Name="_nameTextBox"
                 Canvas.Left="30"
                 Canvas.Top="30"
                 Width="200"
                 local:PlaceHolderBehavior.PlaceHolderText="名前"/>
    </Canvas>
</Window>

実行すると、TextBox が未入力のときにプレースホルダーが表示されます。
f:id:griefworker:20100929100509j:image
文字列を入力すると、プレースホルダーは消えます。
f:id:griefworker:20100929100523j:image


即興で作りましたが、TextBox にプレースホルダー機能が付くまでのつなぎとしては十分じゃないかな。