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);
- }
- }
- }
|