| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143 | using System.Linq;using System.Windows;using System.Windows.Controls;using System.Windows.Documents;using System.Windows.Media;namespace InABox.Wpf;public static class TextBoxUtils{    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(TextBoxUtils),            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 = TextBoxUtils.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);        }    }}
 |