次の方法で共有


TextBox にプレースホルダーを追加する

次の例は、TextBoxが空の場合にTextBoxにプレースホルダー テキストを表示する方法を示しています。 TextBoxにテキストがある場合、プレースホルダー テキストは非表示になります。 プレースホルダー テキストは、ユーザーが TextBox が想定する入力の種類を理解するのに役立ちます。

プレースホルダーが含まれる 2 つの TextBox コントロールを含むアプリの例。最初のテキスト ボックスには、名前の例と、2 番目の電子メールの例が表示されます。

この記事では、次の方法について説明します。

  • プレースホルダー テキストを提供する添付プロパティを作成します。
  • プレースホルダー テキストを表示する装飾を作成します。
  • 添付プロパティを TextBox コントロールに追加します。

添付プロパティを作成する

添付プロパティを使用すると、コントロールに値を追加できます。 この機能は、コントロールに Grid.Row プロパティや Panel.ZIndex プロパティを設定する場合など、WPF で多くを使用します。 詳細については、「 添付プロパティの概要」を参照してください。 この例では、添付プロパティを使用してプレースホルダー テキストを TextBoxに追加します。

  1. TextBoxHelperという名前の新しいクラスをプロジェクトに追加して開きます。

  2. 次の名前空間を追加します。

    using System.Linq;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Media;
    
    Imports System.Linq
    Imports System.Security.Cryptography
    Imports System.Windows
    Imports System.Windows.Controls
    Imports System.Windows.Documents
    Imports System.Windows.Media
    
  3. Placeholderという名前の新しい依存関係プロパティを作成します。

    この依存関係プロパティは、プロパティ変更コールバック デリゲートを使用します。

    public static string GetPlaceholder(DependencyObject obj) =>
        (string)obj.GetValue(PlaceholderProperty);
    
    public static void SetPlaceholder(DependencyObject obj, string value) =>
        obj.SetValue(PlaceholderProperty, value);
    
    public static readonly DependencyProperty PlaceholderProperty =
        DependencyProperty.RegisterAttached(
            "Placeholder",
            typeof(string),
            typeof(TextBoxHelper),
            new FrameworkPropertyMetadata(
                defaultValue: null,
                propertyChangedCallback: OnPlaceholderChanged)
            );
    
    Public Shared Function GetPlaceholder(obj As DependencyObject) As String
        Return obj.GetValue(PlaceholderProperty)
    End Function
    
    Public Shared Sub SetPlaceholder(obj As DependencyObject, value As String)
        obj.SetValue(PlaceholderProperty, value)
    End Sub
    
    Public Shared ReadOnly PlaceholderProperty As DependencyProperty =
        DependencyProperty.RegisterAttached(
            "Placeholder",
            GetType(String),
            GetType(TextBoxHelper),
            New FrameworkPropertyMetadata(
                defaultValue:=Nothing,
                propertyChangedCallback:=AddressOf OnPlaceholderChanged)
            )
    
  4. 添付プロパティをOnPlaceholderChangedと統合するTextBox メソッドを作成します。

    private static void OnPlaceholderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is TextBox textBoxControl)
        {
            if (!textBoxControl.IsLoaded)
            {
                // Ensure that the events are not added multiple times
                textBoxControl.Loaded -= TextBoxControl_Loaded;
                textBoxControl.Loaded += TextBoxControl_Loaded;
            }
    
            textBoxControl.TextChanged -= TextBoxControl_TextChanged;
            textBoxControl.TextChanged += TextBoxControl_TextChanged;
    
            // If the adorner exists, invalidate it to draw the current text
            if (GetOrCreateAdorner(textBoxControl, out PlaceholderAdorner adorner))
                adorner.InvalidateVisual();
        }
    }
    
    Private Shared Sub OnPlaceholderChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
        Dim textBoxControl = TryCast(d, TextBox)
    
        If textBoxControl IsNot Nothing Then
    
            If Not textBoxControl.IsLoaded Then
    
                'Ensure that the events are not added multiple times
                RemoveHandler textBoxControl.Loaded, AddressOf TextBoxControl_Loaded
                AddHandler textBoxControl.Loaded, AddressOf TextBoxControl_Loaded
    
            End If
    
            RemoveHandler textBoxControl.TextChanged, AddressOf TextBoxControl_TextChanged
            AddHandler textBoxControl.TextChanged, AddressOf TextBoxControl_TextChanged
    
            'If the adorner exists, invalidate it to draw the current text
            Dim adorner As PlaceholderAdorner = Nothing
            If GetOrCreateAdorner(textBoxControl, adorner) Then
                adorner.InvalidateVisual()
            End If
    
        End If
    
    End Sub
    

    添付プロパティの値が変更されたときにこのメソッドを呼び出す方法は 2 つあります。

    • 添付プロパティが最初に TextBoxに追加されると、このメソッドが呼び出されます。 このアクションは、添付プロパティがコントロールのイベントと統合される機会を提供します。
    • このプロパティが変更されるたびに、装飾を無効にして、視覚的なプレースホルダー テキストを更新できます。

    GetOrCreateAdorner メソッドは、次のセクションで作成します。

  5. TextBoxのイベント ハンドラーを作成します。

    private static void TextBoxControl_Loaded(object sender, RoutedEventArgs e)
    {
        if (sender is TextBox textBoxControl)
        {
            textBoxControl.Loaded -= TextBoxControl_Loaded;
            GetOrCreateAdorner(textBoxControl, out _);
        }
    }
    
    private static void TextBoxControl_TextChanged(object sender, TextChangedEventArgs e)
    {
        if (sender is TextBox textBoxControl
            && GetOrCreateAdorner(textBoxControl, out PlaceholderAdorner adorner))
        {
            // Control has text. Hide the adorner.
            if (textBoxControl.Text.Length > 0)
                adorner.Visibility = Visibility.Hidden;
    
            // Control has no text. Show the adorner.
            else
                adorner.Visibility = Visibility.Visible;
        }
    }
    
    Private Shared Sub TextBoxControl_Loaded(sender As Object, e As RoutedEventArgs)
        Dim textBoxControl As TextBox = TryCast(sender, TextBox)
    
        If textBoxControl IsNot Nothing Then
            RemoveHandler textBoxControl.Loaded, AddressOf TextBoxControl_Loaded
            GetOrCreateAdorner(textBoxControl, Nothing)
        End If
    End Sub
    
    Private Shared Sub TextBoxControl_TextChanged(sender As Object, e As TextChangedEventArgs)
        Dim textBoxControl As TextBox = TryCast(sender, TextBox)
        Dim adorner As PlaceholderAdorner = Nothing
    
        If textBoxControl IsNot Nothing AndAlso GetOrCreateAdorner(textBoxControl, adorner) Then
    
            If textBoxControl.Text.Length > 0 Then
                'Control has text. Hide the adorner.
                adorner.Visibility = Visibility.Hidden
            Else
                'Control has no text. Show the adorner.
                adorner.Visibility = Visibility.Visible
            End If
    
        End If
    End Sub
    

    Loaded イベントは、コントロールのテンプレートが適用された後に装飾を作成できるように処理されます。 イベントが発生して装飾が作成された後でハンドラーは自身を削除します。

    TextChanged イベントは、Textが値に設定されているかどうかに応じて装飾が非表示または表示されるように処理されます。

装飾を作成する

Adornerは、コントロールにバインドされ、AdornerLayerでレンダリングされるビジュアルです。 詳細情報については、「装飾の概要」を参照してください。

  1. TextBoxHelper クラスを開きます。

  2. 次のコードを追加して、 GetOrCreateAdorner メソッドを作成します。

    private static bool GetOrCreateAdorner(TextBox textBoxControl, out PlaceholderAdorner adorner)
    {
        // Get the adorner layer
        AdornerLayer layer = AdornerLayer.GetAdornerLayer(textBoxControl);
    
        // If null, it doesn't exist or the control's template isn't loaded
        if (layer == null)
        {
            adorner = null;
            return false;
        }
    
        // Layer exists, try to find the adorner
        adorner = layer.GetAdorners(textBoxControl)?.OfType<PlaceholderAdorner>().FirstOrDefault();
    
        // Adorner never added to control, so add it
        if (adorner == null)
        {
            adorner = new PlaceholderAdorner(textBoxControl);
            layer.Add(adorner);
        }
    
        return true;
    }
    
    Private Shared Function GetOrCreateAdorner(textBoxControl As TextBox, ByRef adorner As PlaceholderAdorner) As Boolean
    
        'Get the adorner layer
        Dim layer As AdornerLayer = AdornerLayer.GetAdornerLayer(textBoxControl)
    
        'If nothing, it doesn't exist or the control's template isn't loaded
        If layer Is Nothing Then
            adorner = Nothing
            Return False
        End If
    
        'Layer exists, try to find the adorner
        adorner = layer.GetAdorners(textBoxControl)?.OfType(Of PlaceholderAdorner)().FirstOrDefault()
    
        'Adorner never added to control, so add it
        If adorner Is Nothing Then
            adorner = New PlaceholderAdorner(textBoxControl)
            layer.Add(adorner)
        End If
    
        Return True
    
    End Function
    

    このメソッドは、 Adornerを追加または取得する安全な方法を提供します。 装飾は、存在しない可能性があるコントロールの AdornerLayerに追加されるため、追加の安全性が必要です。 XAML 添付プロパティがコントロールに適用されている場合、ビジュアル ツリーを作成するためにコントロールのテンプレートがまだ適用されていないため、装飾レイヤーは存在しません。 装飾レイヤーは、コントロールが読み込まれた後に取得する必要があります。 装飾レイヤーを省略したテンプレートがコントロールに適用されている場合も、装飾レイヤーが見つからない可能性があります。

  3. PlaceholderAdorner クラスに TextBoxHelper という名前の子クラスを追加します。

    public class PlaceholderAdorner : Adorner
    {
        public PlaceholderAdorner(TextBox textBox) : base(textBox) { }
    
        protected override void OnRender(DrawingContext drawingContext)
        {
            TextBox textBoxControl = (TextBox)AdornedElement;
    
            string placeholderValue = TextBoxHelper.GetPlaceholder(textBoxControl);
    
            if (string.IsNullOrEmpty(placeholderValue))
                return;
    
            // Create the formatted text object
            FormattedText text = new FormattedText(
                                        placeholderValue,
                                        System.Globalization.CultureInfo.CurrentCulture,
                                        textBoxControl.FlowDirection,
                                        new Typeface(textBoxControl.FontFamily,
                                                     textBoxControl.FontStyle,
                                                     textBoxControl.FontWeight,
                                                     textBoxControl.FontStretch),
                                        textBoxControl.FontSize,
                                        SystemColors.InactiveCaptionBrush,
                                        VisualTreeHelper.GetDpi(textBoxControl).PixelsPerDip);
    
            text.MaxTextWidth = System.Math.Max(textBoxControl.ActualWidth - textBoxControl.Padding.Left - textBoxControl.Padding.Right, 10);
            text.MaxTextHeight = System.Math.Max(textBoxControl.ActualHeight, 10);
    
            // Render based on padding of the control, to try and match where the textbox places text
            Point renderingOffset = new Point(textBoxControl.Padding.Left, textBoxControl.Padding.Top);
    
            // Template contains the content part; adjust sizes to try and align the text
            if (textBoxControl.Template.FindName("PART_ContentHost", textBoxControl) is FrameworkElement part)
            {
                Point partPosition = part.TransformToAncestor(textBoxControl).Transform(new Point(0, 0));
                renderingOffset.X += partPosition.X;
                renderingOffset.Y += partPosition.Y;
    
                text.MaxTextWidth = System.Math.Max(part.ActualWidth - renderingOffset.X, 10);
                text.MaxTextHeight = System.Math.Max(part.ActualHeight, 10);
            }
    
            // Draw the text
            drawingContext.DrawText(text, renderingOffset);
        }
    }
    
    Public Class PlaceholderAdorner
        Inherits Adorner
    
        Public Sub New(adornedElement As UIElement)
            MyBase.New(adornedElement)
        End Sub
    
        Protected Overrides Sub OnRender(drawingContext As DrawingContext)
            Dim textBoxControl As TextBox = DirectCast(AdornedElement, TextBox)
    
            Dim placeholderValue As String = TextBoxHelper.GetPlaceholder(textBoxControl)
    
            If String.IsNullOrEmpty(placeholderValue) Then
                Return
            End If
    
            'Create the formatted text object
            Dim text As New FormattedText(
                placeholderValue,
                System.Globalization.CultureInfo.CurrentCulture,
                textBoxControl.FlowDirection,
                New Typeface(textBoxControl.FontFamily,
                             textBoxControl.FontStyle,
                             textBoxControl.FontWeight,
                             textBoxControl.FontStretch),
                textBoxControl.FontSize,
                SystemColors.InactiveCaptionBrush,
                VisualTreeHelper.GetDpi(textBoxControl).PixelsPerDip)
    
            text.MaxTextWidth = Math.Max(textBoxControl.ActualWidth - textBoxControl.Padding.Left - textBoxControl.Padding.Right, 10)
            text.MaxTextHeight = Math.Max(textBoxControl.ActualHeight, 10)
    
            'Render based on padding of the control, to try and match where the textbox places text
            Dim renderingOffset As New Point(textBoxControl.Padding.Left, textBoxControl.Padding.Top)
    
            'Template contains the content part; adjust sizes to try and align the text
            Dim part As FrameworkElement = TryCast(textBoxControl.Template.FindName("PART_ContentHost", textBoxControl), FrameworkElement)
    
            If part IsNot Nothing Then
                Dim partPosition As Point = part.TransformToAncestor(textBoxControl).Transform(New Point(0, 0))
                renderingOffset.X += partPosition.X
                renderingOffset.Y += partPosition.Y
    
                text.MaxTextWidth = Math.Max(part.ActualWidth - renderingOffset.X, 10)
                text.MaxTextHeight = Math.Max(part.ActualHeight, 10)
            End If
    
            ' Draw the text
            drawingContext.DrawText(text, renderingOffset)
        End Sub
    
    End Class
    

    装飾は、Adorner クラスから継承します。 この特定の装飾は、プレースホルダー テキストを描画するために OnRender(DrawingContext) メソッドをオーバーライドします。 コードの詳細を見てみましょう。

    • まず、 TextBoxHelper.GetPlaceholder(textBoxControl)を呼び出してプレースホルダー テキストが存在することを確認します。
    • FormattedText オブジェクトを作成します。 このオブジェクトには、ビジュアルに描画されるテキストに関するすべての情報が含まれています。
    • FormattedText.MaxTextWidthプロパティとFormattedText.MaxTextHeightプロパティの両方がコントロールの領域に設定されます。 また、 FormattedText オブジェクトが有効であることを確認するために、最小値として 10 が設定されます。
    • renderingOffsetは、描画されたテキストの位置を格納します。
    • コントロールのテンプレートで宣言されている場合は、 PART_ContentHost を使用します。 この部分は、コントロールのテンプレートでテキストが描画される場所を表します。 そのパーツが見つかった場合は、その位置を考慮するように renderingOffset を変更します。
    • DrawText(FormattedText, Point)を呼び出し、FormattedText オブジェクトとテキストの位置を渡してテキストを描画します。

添付プロパティを適用する

添付プロパティが定義されたら、その名前空間を XAML にインポートし、 TextBox コントロールで参照する必要があります。 次のコードは、.NET 名前空間 DotnetDocsSample を XML 名前空間 lにマップします。

<Window x:Class="DotnetDocsSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:l="clr-namespace:DotnetDocsSample"
        Title="Recipe Tracker" Width="400" SizeToContent="Height">
    <StackPanel Margin="10">
        <TextBlock FontSize="20" TextWrapping="Wrap">Welcome to Recipe Tracker! To get started, create a new account.</TextBlock>
        
        <Label Padding="0,5">Name</Label>
        <TextBox l:TextBoxHelper.Placeholder="Ex. Jeffry Goh" />
        
        <Label Padding="0,5">Email</Label>
        <TextBox l:TextBoxHelper.Placeholder="jeffry@contoso.com" />
        
        <Label Padding="0,5">Password</Label>
        <PasswordBox />
        
        <Button HorizontalAlignment="Right" Width="100" Margin="0,10,0,5">Submit</Button>
    </StackPanel>
</Window>

添付プロパティは、構文TextBoxを使用してxmlNamespace:Class.Propertyに追加されます。

完全な例

次のコードは、完全な TextBoxHelper クラスです。

public static class TextBoxHelper
{
    public static string GetPlaceholder(DependencyObject obj) =>
        (string)obj.GetValue(PlaceholderProperty);

    public static void SetPlaceholder(DependencyObject obj, string value) =>
        obj.SetValue(PlaceholderProperty, value);

    public static readonly DependencyProperty PlaceholderProperty =
        DependencyProperty.RegisterAttached(
            "Placeholder",
            typeof(string),
            typeof(TextBoxHelper),
            new FrameworkPropertyMetadata(
                defaultValue: null,
                propertyChangedCallback: OnPlaceholderChanged)
            );

    private static void OnPlaceholderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is TextBox textBoxControl)
        {
            if (!textBoxControl.IsLoaded)
            {
                // Ensure that the events are not added multiple times
                textBoxControl.Loaded -= TextBoxControl_Loaded;
                textBoxControl.Loaded += TextBoxControl_Loaded;
            }

            textBoxControl.TextChanged -= TextBoxControl_TextChanged;
            textBoxControl.TextChanged += TextBoxControl_TextChanged;

            // If the adorner exists, invalidate it to draw the current text
            if (GetOrCreateAdorner(textBoxControl, out PlaceholderAdorner adorner))
                adorner.InvalidateVisual();
        }
    }

    private static void TextBoxControl_Loaded(object sender, RoutedEventArgs e)
    {
        if (sender is TextBox textBoxControl)
        {
            textBoxControl.Loaded -= TextBoxControl_Loaded;
            GetOrCreateAdorner(textBoxControl, out _);
        }
    }

    private static void TextBoxControl_TextChanged(object sender, TextChangedEventArgs e)
    {
        if (sender is TextBox textBoxControl
            && GetOrCreateAdorner(textBoxControl, out PlaceholderAdorner adorner))
        {
            // Control has text. Hide the adorner.
            if (textBoxControl.Text.Length > 0)
                adorner.Visibility = Visibility.Hidden;

            // Control has no text. Show the adorner.
            else
                adorner.Visibility = Visibility.Visible;
        }
    }

    private static bool GetOrCreateAdorner(TextBox textBoxControl, out PlaceholderAdorner adorner)
    {
        // Get the adorner layer
        AdornerLayer layer = AdornerLayer.GetAdornerLayer(textBoxControl);

        // If null, it doesn't exist or the control's template isn't loaded
        if (layer == null)
        {
            adorner = null;
            return false;
        }

        // Layer exists, try to find the adorner
        adorner = layer.GetAdorners(textBoxControl)?.OfType<PlaceholderAdorner>().FirstOrDefault();

        // Adorner never added to control, so add it
        if (adorner == null)
        {
            adorner = new PlaceholderAdorner(textBoxControl);
            layer.Add(adorner);
        }

        return true;
    }

    public class PlaceholderAdorner : Adorner
    {
        public PlaceholderAdorner(TextBox textBox) : base(textBox) { }

        protected override void OnRender(DrawingContext drawingContext)
        {
            TextBox textBoxControl = (TextBox)AdornedElement;

            string placeholderValue = TextBoxHelper.GetPlaceholder(textBoxControl);

            if (string.IsNullOrEmpty(placeholderValue))
                return;

            // Create the formatted text object
            FormattedText text = new FormattedText(
                                        placeholderValue,
                                        System.Globalization.CultureInfo.CurrentCulture,
                                        textBoxControl.FlowDirection,
                                        new Typeface(textBoxControl.FontFamily,
                                                     textBoxControl.FontStyle,
                                                     textBoxControl.FontWeight,
                                                     textBoxControl.FontStretch),
                                        textBoxControl.FontSize,
                                        SystemColors.InactiveCaptionBrush,
                                        VisualTreeHelper.GetDpi(textBoxControl).PixelsPerDip);

            text.MaxTextWidth = System.Math.Max(textBoxControl.ActualWidth - textBoxControl.Padding.Left - textBoxControl.Padding.Right, 10);
            text.MaxTextHeight = System.Math.Max(textBoxControl.ActualHeight, 10);

            // Render based on padding of the control, to try and match where the textbox places text
            Point renderingOffset = new Point(textBoxControl.Padding.Left, textBoxControl.Padding.Top);

            // Template contains the content part; adjust sizes to try and align the text
            if (textBoxControl.Template.FindName("PART_ContentHost", textBoxControl) is FrameworkElement part)
            {
                Point partPosition = part.TransformToAncestor(textBoxControl).Transform(new Point(0, 0));
                renderingOffset.X += partPosition.X;
                renderingOffset.Y += partPosition.Y;

                text.MaxTextWidth = System.Math.Max(part.ActualWidth - renderingOffset.X, 10);
                text.MaxTextHeight = System.Math.Max(part.ActualHeight, 10);
            }

            // Draw the text
            drawingContext.DrawText(text, renderingOffset);
        }
    }
}
Public Class TextBoxHelper

    Public Shared Function GetPlaceholder(obj As DependencyObject) As String
        Return obj.GetValue(PlaceholderProperty)
    End Function

    Public Shared Sub SetPlaceholder(obj As DependencyObject, value As String)
        obj.SetValue(PlaceholderProperty, value)
    End Sub

    Public Shared ReadOnly PlaceholderProperty As DependencyProperty =
        DependencyProperty.RegisterAttached(
            "Placeholder",
            GetType(String),
            GetType(TextBoxHelper),
            New FrameworkPropertyMetadata(
                defaultValue:=Nothing,
                propertyChangedCallback:=AddressOf OnPlaceholderChanged)
            )

    Private Shared Sub OnPlaceholderChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
        Dim textBoxControl = TryCast(d, TextBox)

        If textBoxControl IsNot Nothing Then

            If Not textBoxControl.IsLoaded Then

                'Ensure that the events are not added multiple times
                RemoveHandler textBoxControl.Loaded, AddressOf TextBoxControl_Loaded
                AddHandler textBoxControl.Loaded, AddressOf TextBoxControl_Loaded

            End If

            RemoveHandler textBoxControl.TextChanged, AddressOf TextBoxControl_TextChanged
            AddHandler textBoxControl.TextChanged, AddressOf TextBoxControl_TextChanged

            'If the adorner exists, invalidate it to draw the current text
            Dim adorner As PlaceholderAdorner = Nothing
            If GetOrCreateAdorner(textBoxControl, adorner) Then
                adorner.InvalidateVisual()
            End If

        End If

    End Sub

    Private Shared Sub TextBoxControl_Loaded(sender As Object, e As RoutedEventArgs)
        Dim textBoxControl As TextBox = TryCast(sender, TextBox)

        If textBoxControl IsNot Nothing Then
            RemoveHandler textBoxControl.Loaded, AddressOf TextBoxControl_Loaded
            GetOrCreateAdorner(textBoxControl, Nothing)
        End If
    End Sub

    Private Shared Sub TextBoxControl_TextChanged(sender As Object, e As TextChangedEventArgs)
        Dim textBoxControl As TextBox = TryCast(sender, TextBox)
        Dim adorner As PlaceholderAdorner = Nothing

        If textBoxControl IsNot Nothing AndAlso GetOrCreateAdorner(textBoxControl, adorner) Then

            If textBoxControl.Text.Length > 0 Then
                'Control has text. Hide the adorner.
                adorner.Visibility = Visibility.Hidden
            Else
                'Control has no text. Show the adorner.
                adorner.Visibility = Visibility.Visible
            End If

        End If
    End Sub


    Private Shared Function GetOrCreateAdorner(textBoxControl As TextBox, ByRef adorner As PlaceholderAdorner) As Boolean

        'Get the adorner layer
        Dim layer As AdornerLayer = AdornerLayer.GetAdornerLayer(textBoxControl)

        'If nothing, it doesn't exist or the control's template isn't loaded
        If layer Is Nothing Then
            adorner = Nothing
            Return False
        End If

        'Layer exists, try to find the adorner
        adorner = layer.GetAdorners(textBoxControl)?.OfType(Of PlaceholderAdorner)().FirstOrDefault()

        'Adorner never added to control, so add it
        If adorner Is Nothing Then
            adorner = New PlaceholderAdorner(textBoxControl)
            layer.Add(adorner)
        End If

        Return True

    End Function

    Public Class PlaceholderAdorner
        Inherits Adorner

        Public Sub New(adornedElement As UIElement)
            MyBase.New(adornedElement)
        End Sub

        Protected Overrides Sub OnRender(drawingContext As DrawingContext)
            Dim textBoxControl As TextBox = DirectCast(AdornedElement, TextBox)

            Dim placeholderValue As String = TextBoxHelper.GetPlaceholder(textBoxControl)

            If String.IsNullOrEmpty(placeholderValue) Then
                Return
            End If

            'Create the formatted text object
            Dim text As New FormattedText(
                placeholderValue,
                System.Globalization.CultureInfo.CurrentCulture,
                textBoxControl.FlowDirection,
                New Typeface(textBoxControl.FontFamily,
                             textBoxControl.FontStyle,
                             textBoxControl.FontWeight,
                             textBoxControl.FontStretch),
                textBoxControl.FontSize,
                SystemColors.InactiveCaptionBrush,
                VisualTreeHelper.GetDpi(textBoxControl).PixelsPerDip)

            text.MaxTextWidth = Math.Max(textBoxControl.ActualWidth - textBoxControl.Padding.Left - textBoxControl.Padding.Right, 10)
            text.MaxTextHeight = Math.Max(textBoxControl.ActualHeight, 10)

            'Render based on padding of the control, to try and match where the textbox places text
            Dim renderingOffset As New Point(textBoxControl.Padding.Left, textBoxControl.Padding.Top)

            'Template contains the content part; adjust sizes to try and align the text
            Dim part As FrameworkElement = TryCast(textBoxControl.Template.FindName("PART_ContentHost", textBoxControl), FrameworkElement)

            If part IsNot Nothing Then
                Dim partPosition As Point = part.TransformToAncestor(textBoxControl).Transform(New Point(0, 0))
                renderingOffset.X += partPosition.X
                renderingOffset.Y += partPosition.Y

                text.MaxTextWidth = Math.Max(part.ActualWidth - renderingOffset.X, 10)
                text.MaxTextHeight = Math.Max(part.ActualHeight, 10)
            End If

            ' Draw the text
            drawingContext.DrawText(text, renderingOffset)
        End Sub

    End Class
End Class

こちらも参照ください