C#のWPFでWatermarkedTextBox(プレースホルダー)を作る

プレースホルダーとかウォーターマークなどと呼ばれる機能(入力例やヒントなどが透かしのように表示される機能)を追加したTextBoxを作成する。

 

TextBoxに何も入力されていない場合に透かしが表示され、何か入力されると透かしが消える。

 

 

 

入力すると


 

WPFにはAdornerというUIElementを装飾する為の抽象クラスがある。

Adornerを使ってTextBoxコントロールの上に透かし表示用のTextBlockを重ねて表示する仕組み。

 

Adornerを継承したクラスを作成

    public class WatermarkedAdorner : Adorner
    {
        private TextBlock        watermark;
        private VisualCollection visualChildren;


        public WatermarkedAdorner(UIElement adornedElement) : base(adornedElement)
        {
            watermark = new TextBlock();
            watermark.Margin = new Thickness(5, 3, 5, 3);
            watermark.Opacity = 0.3;
            watermark.IsHitTestVisible = false;

            visualChildren = new VisualCollection(this);
            visualChildren.Add(watermark);
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            var element = this.AdornedElement as FrameworkElement;

            var x = 0;
            var y = 0;
            var w = element.ActualWidth;
            var h = element.ActualHeight;
            watermark.Arrange(new Rect(x, y, w, h));
            return finalSize;
        }

        protected override int VisualChildrenCount
        {
            get { return visualChildren.Count; }
        }

        protected override Visual GetVisualChild(int index)
        {
            return visualChildren[index];
        }

        public void SetWatermarkText(string text)
        {
            watermark.Text = text;
        }
    }

透かし表示用のTextBlockコントロールを作成 (9~12行目)

TextBlockコントロールを作成する。

透かしっぽく薄く表示させるためOpacityプロパティを使って少し透過させて表示する。

このコントロールがクリックに反応しないようにIsHitTestVisibleプロパティをfalseに設定。

 

 

作成したコントロールの管理 (14~15行目)

作成したTextBlockコントロールはVisualCollectionクラスを使って管理する。

Adornerが子要素を管理する場合、VisualChildrenCountプロパティとGetVisualChildメソッドをオーバライドして適切な値を返すよう作り込む必要がある。

※既存のコントロールを使わずDrawingContextを使って独自の描画を行うだけの場合は、VisualCollectionで管理する必要は無く

 OnRenderをオーバーライドして描画を実装する

 

VisualCollectionクラスを作成して、そこに作成したTextBlockコントロールを追加する。

 

 

サイズが変更されたらTextBlockコントロールのサイズも変える (18~28行目)

ArrangeOverrideメソッドをオーバーライドする。

TextBlockコントロールのArrangeメソッド呼び出してサイズを変える。

 

 

Adornerで既存コントロールを管理する為のお約束 (30~38行目)

VisualChildrenCountプロパティをオーバーライドしてVisualCorrectionの件数を返す。

GetVisualChildメソッドをオーバライドしてVisualCorrectionの指定番目の要素を返す。

 

 

透かしの文字列を変更するメソッドを作成 (40~43行目)

 TextBlockのTextに値をセットするメソッドを用意する。

 

 

 

 

 

 

Adornerを追加したTextBoxを作成

    public class WatermarkedTextBox : TextBox
    {
        public static readonly DependencyProperty WatermarkProperty =
            DependencyProperty.Register(
                "Watermark", // プロパティ名を指定
                typeof(string), // プロパティの型を指定
                typeof(WatermarkedTextBox), // プロパティを所有する型を指定
                new UIPropertyMetadata("",
                    (d, e) => {(d as WatermarkedTextBox).OnWatermarkPropertyChanged(e); }));
        public string Watermark
        {
            get { return (string)GetValue(WatermarkProperty); }
            set { SetValue(WatermarkProperty, value); }
        }

        private WatermarkedAdorner MyAdoner;


        protected override void OnInitialized(EventArgs e)
        {
            base.OnInitialized(e);
            this.Loaded += new RoutedEventHandler(InitializeAdorner);
        }

        private void InitializeAdorner(object sender, RoutedEventArgs e)
        {
            var layer = AdornerLayer.GetAdornerLayer(this);
            MyAdoner = new WatermarkedAdorner(this);
            MyAdoner.SetWatermarkText(Watermark);
            layer.Add(MyAdoner);
        }

        protected override void OnTextChanged(TextChangedEventArgs e)
        {
            if (null != MyAdoner)
            {
                if (string.IsNullOrEmpty(this.Text))
                    MyAdoner.Visibility = Visibility.Visible;
                else
                    MyAdoner.Visibility = Visibility.Collapsed;
            }
        }

        protected void OnWatermarkPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
            if (null != MyAdoner)
            {
                MyAdoner.SetWatermarkText(e.NewValue.ToString());
            }
        }

透かし用文字列のプロパティを作成 (3~14行目)

透かし用の文字列をセットするWatermarkプロパティを依存関係プロパティとして作成。

依存関係プロパティについてはこちらを参照。

 

 

OnInitializedメソッドをオーバーライド (19~23行目)

ロード時にAdornerをセットする為に、Loadedイベントにハンドラーを追加。

 

 

ロード時にAdornerをセットする (25~31行目)

AdornerLayerクラスのGetAdornerLayerメソッドを使ってAdonerを追加するレイヤーを取得。

そのレイヤーに作ったAdornerを追加する。

 

 

透かし文字の表示・非表示を制御 (33~42行目)

OnTextChangedメソッドをオーバーライドして、TextBoxに入力値が有るか無いかによって、AdornerのVisivilityプロパティを変更する。