Lägga till en platshållare i en textruta

I följande exempel visas hur du visar platshållartext i en TextBox när den TextBox är tom. När TextBox innehåller text döljs platshållartexten. Platshållartext hjälper användarna att förstå vilken typ av indata som TextBox förväntar sig.

En exempelapp med två TextBox-kontroller som har platshållare i sig. Den första textrutan innehåller ett exempel på ett namn och det andra ett exempel på ett e-postmeddelande.

I den här artikeln lär du dig hur du:

  • Skapa en bifogad egenskap för att ange platshållartexten.
  • Skapa en dekoration för att visa platshållartexten.
  • Lägg till den anslutna egenskapen i en TextBox kontroll.

Skapa en bifogad egenskap

Med anslutna egenskaper kan du lägga till värden i en kontroll. Du använder den här funktionen mycket i WPF, till exempel när du anger Grid.Row eller Panel.ZIndex egenskaper för en kontroll. För mer information, se översikten över bifogade egenskaper. I det här exemplet används bifogade egenskaper för att lägga till platshållartext i en TextBox.

  1. Lägg till en ny klass i projektet med namnet TextBoxHelper och öppna den.

  2. Lägg till följande namnområden:

    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. Skapa en ny beroendeegenskap med namnet Placeholder.

    Den här beroendeegenskapen använder egenskapen ändrad återanropsdelegat.

    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. Skapa metoden OnPlaceholderChanged för att integrera den anslutna egenskapen med en 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
    

    Det finns två sätt som den här metoden anropas när det kopplade egenskapsvärdet ändras:

    • När den anslutna egenskapen först läggs till i en TextBoxanropas den här metoden. Den här åtgärden ger den bifogade egenskapen en möjlighet att integrera med kontrollens händelser.
    • När den här egenskapen ändras kan utsmyckningen ogiltigförklaras för att uppdatera den visuella platshållartexten.

    Metoden GetOrCreateAdorner skapas i nästa avsnitt.

  5. Skapa händelsehanterarna för 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
    

    Händelsen Loaded hanteras så att utsmyckningen kan skapas när kontrollens mall har tillämpats. Hanteraren tar bort sig själv när händelsen har tagits upp och utsmyckningen har skapats.

    Händelsen TextChanged hanteras för att säkerställa att prydnaden är dold eller visas beroende på om Text är inställt på ett värde.

Skapa en utsmyckning

Adorner är ett visuellt objekt som är bundet till en kontroll och återges i en AdornerLayer. För mer information, se Adorners-översikt.

  1. TextBoxHelper Öppna klassen.

  2. Lägg till följande kod för att skapa GetOrCreateAdorner metoden.

    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
    

    Den här metoden ger ett säkert sätt att antingen lägga till eller hämta Adorner. Utsmyckningar kräver extra säkerhet eftersom de läggs till i kontrollens AdornerLayer, som kanske inte finns. När en XAML-ansluten egenskap tillämpas på en kontroll, har kontrollens mall ännu inte tillämpats för att skapa det visuella trädet, så dekorskiktet finns inte. Utsmyckningsskiktet måste hämtas när kontrollen har lästs in. Dekorskiktet kanske också saknas om en mall som utelämnar dekorskiktet tillämpas på styrningen.

  3. Lägg till en underordnad klass med namnet PlaceholderAdorner i TextBoxHelper klassen.

    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
    

    En prydnad ärver från klassen Adorner. Den här utsmyckningen överrider OnRender(DrawingContext)-metoden för att rita platshållartexten. Låt oss bryta ner koden:

    • Kontrollera först att platshållartexten finns genom att anropa TextBoxHelper.GetPlaceholder(textBoxControl).
    • Skapa ett FormattedText objekt. Det här objektet innehåller all information om vilken text som ritas på det visuella.
    • Både FormattedText.MaxTextWidth och FormattedText.MaxTextHeight egenskaperna är inställda på kontrollens region. De har också ett minimivärde på 10 för att se till att objektet FormattedText är giltigt.
    • renderingOffset lagrar positionen för den ritade texten.
    • Använd PART_ContentHost om kontrollens mall deklarerar det. Den här delen representerar var texten ritas på kontrollens mall. Om den delen hittas ändrar du renderingOffset för att ta hänsyn till dess position.
    • Rita texten genom att anropa DrawText(FormattedText, Point) och skicka FormattedText objektet och textens position.

Använd den bifogade egenskapen

När den anslutna egenskapen har definierats måste dess namnområde importeras till XAML och sedan refereras till på en TextBox kontroll. Följande kod mappar .NET-namnområdet DotnetDocsSample till XML-namnområdet 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>

Den bifogade egenskapen läggs till i en TextBox med hjälp av syntaxen xmlNamespace:Class.Property.

Fullständigt exempel

Följande kod är den fullständiga TextBoxHelper klassen.

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

Se även