using System; using System.ComponentModel; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using Microsoft.Xaml.Behaviors; namespace InABox.WPF; public class TextBoxPlaceholderBehaviour : Behavior { public static readonly DependencyProperty TextProperty = DependencyProperty.Register( nameof(Text), typeof(String), typeof(TextBoxPlaceholderBehaviour), new PropertyMetadata("")); public String Text { get => (String)GetValue(TextProperty); set { SetValue(TextProperty, value); UpdateAssociatedObject(true); } } public static readonly DependencyProperty TextColorProperty = DependencyProperty.Register( nameof(TextColor), typeof(System.Windows.Media.Color), typeof(TextBoxPlaceholderBehaviour), new PropertyMetadata(Colors.Gray)); public System.Windows.Media.Color TextColor { get => (System.Windows.Media.Color)GetValue(TextColorProperty); set { SetValue(TextColorProperty, value); UpdateAssociatedObject(true); } } private readonly PropertyDescriptor _propertyDescriptor = DependencyPropertyDescriptor.FromProperty(TextBox.BackgroundProperty, typeof(TextBox)); private Brush? _originalBackground; private Brush? _placeholderBackground; protected override void OnAttached() { base.OnAttached(); // Store the Background that has been set on the Text Box (usu through XAML) _originalBackground = AssociatedObject.Background; // Start Monitoring changes to the Associated object Background property _propertyDescriptor.AddValueChanged(AssociatedObject, OnBackgroundChanged); AssociatedObject.TextChanged += OnTextChanged; AssociatedObject.SizeChanged += OnSizeChanged; UpdateAssociatedObject(); } protected override void OnDetaching() { AssociatedObject.SizeChanged -= OnSizeChanged; AssociatedObject.TextChanged -= OnTextChanged; _propertyDescriptor?.RemoveValueChanged(AssociatedObject,OnBackgroundChanged); AssociatedObject.Background = _originalBackground; base.OnDetaching(); } private void OnBackgroundChanged(object? sender, EventArgs e) { // Update the Saved Background (usu a color, but might not be?) _originalBackground = AssociatedObject.Background; UpdateAssociatedObject(true); } private void OnTextChanged(object sender, TextChangedEventArgs e) { UpdateAssociatedObject(); } private void SetBackground(Brush? brush) { _propertyDescriptor.RemoveValueChanged(AssociatedObject, OnBackgroundChanged); AssociatedObject.Background = brush; _propertyDescriptor.AddValueChanged(AssociatedObject, OnBackgroundChanged); } private void OnSizeChanged(object sender, SizeChangedEventArgs e) { UpdateAssociatedObject(true); } private Brush CreatePlaceholder() { return new VisualBrush() { Visual = new Label() { Content = Text, Margin = new Thickness(0), Padding = new Thickness(4, 0, 2, 0), HorizontalAlignment = HorizontalAlignment.Stretch, VerticalAlignment = VerticalAlignment.Stretch, HorizontalContentAlignment = AssociatedObject.HorizontalContentAlignment, VerticalContentAlignment = AssociatedObject.VerticalContentAlignment, FontFamily = AssociatedObject.FontFamily, FontStretch = AssociatedObject.FontStretch, FontSize = AssociatedObject.FontSize, FontWeight = AssociatedObject.FontWeight, FontStyle = FontStyles.Italic, Background = _originalBackground?.Clone(), Foreground = new SolidColorBrush(TextColor), Width = AssociatedObject.ActualWidth, Height = AssociatedObject.ActualHeight }, Stretch = Stretch.None, TileMode = TileMode.None, AlignmentX = AlignmentX.Left, AlignmentY = AlignmentY.Center }; } private void UpdateAssociatedObject(bool force = false) { if (force) _placeholderBackground = null; if (String.IsNullOrWhiteSpace(AssociatedObject.Text) && _placeholderBackground == null) { _placeholderBackground = CreatePlaceholder(); SetBackground(_placeholderBackground); } else if (!String.IsNullOrWhiteSpace(AssociatedObject.Text) && _placeholderBackground != null) { _placeholderBackground = null; SetBackground(_originalBackground); } } }