using System.Windows.Controls.Primitives; using NeoUI.Extensions; namespace NeoUI.Assists; /// /// InputAssist 类提供了一系列附加属性,用于增强 WPF 应用程序中输入控件的功能。这些功能包括设置占位符文本、占位符颜色、前缀和后缀内容等,支持 TextBoxBase、PasswordBox 和 ComboBox 控件。 /// public static class InputAssist { #region Attached Properties /// /// 用于设置或获取控件的占位符文本。 /// public static readonly DependencyProperty PlaceholderProperty = DependencyProperty.RegisterAttached("Placeholder", typeof(string), typeof(InputAssist), new PropertyMetadata(string.Empty)); /// /// 获取输入控件的占位符文本。 /// [AttachedPropertyBrowsableForType(typeof(TextBoxBase))] [AttachedPropertyBrowsableForType(typeof(PasswordBox))] [AttachedPropertyBrowsableForType(typeof(ComboBox))] public static string GetPlaceholder(DependencyObject obj) { return (string)obj.GetValue(PlaceholderProperty); } /// /// 设置输入控件的占位符。 /// /// 要设置占位符的依赖对象。 /// 占位符的文本值。 public static void SetPlaceholder(DependencyObject obj, string value) { obj.SetValue(PlaceholderProperty, value); } /// /// 用于设置或获取占位符文本的颜色刷。 /// public static readonly DependencyProperty PlaceholderBrushProperty = DependencyProperty.RegisterAttached("PlaceholderBrush", typeof(Brush), typeof(InputAssist), new FrameworkPropertyMetadata( Brushes.Silver, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender | FrameworkPropertyMetadataOptions.Inherits)); /// /// 获取输入控件的占位符画刷。 /// [AttachedPropertyBrowsableForType(typeof(TextBoxBase))] [AttachedPropertyBrowsableForType(typeof(PasswordBox))] [AttachedPropertyBrowsableForType(typeof(ComboBox))] public static Brush GetPlaceholderBrush(DependencyObject obj) { return (Brush)obj.GetValue(PlaceholderBrushProperty); } /// /// 设置输入控件的占位符画刷。 /// /// 依赖对象,通常为TextBoxBase、PasswordBox或ComboBox。 /// 要设置的占位符画刷。 public static void SetPlaceholderBrush(DependencyObject obj, Brush value) { obj.SetValue(PlaceholderBrushProperty, value); } /// /// 用于设置或获取控件的前缀内容。 /// public static readonly DependencyProperty PrefixProperty = DependencyProperty.RegisterAttached("Prefix", typeof(object), typeof(InputAssist), new PropertyMetadata(null)); /// /// 获取与指定依赖对象关联的前缀。 /// /// 要获取前缀的依赖对象。 /// 与指定依赖对象关联的前缀对象。 [AttachedPropertyBrowsableForType(typeof(TextBox))] [AttachedPropertyBrowsableForType(typeof(PasswordBox))] [AttachedPropertyBrowsableForType(typeof(ComboBox))] public static object GetPrefix(DependencyObject obj) { return obj.GetValue(PrefixProperty); } /// /// 设置输入控件的前缀。 /// /// 要设置前缀的依赖对象。 /// 要设置为前缀的对象。 public static void SetPrefix(DependencyObject obj, object value) { obj.SetValue(PrefixProperty, value); } // 这是一个只读的依赖属性,用于向XAML报告PasswordBox是否有文本。 // 只有这个类内部可以通过Key来修改它的值,保证了状态的可靠性。 private static readonly DependencyPropertyKey HasTextPropertyKey = DependencyProperty.RegisterAttachedReadOnly( "HasPassword", typeof(bool), typeof(InputAssist), new PropertyMetadata(false)); /// /// 用于获取或设置是否显示密码框内有内容的指示器。 /// public static readonly DependencyProperty HasPasswordProperty = HasTextPropertyKey.DependencyProperty; /// /// 获取指定控件是否启用了密码模式。 /// /// 要检查的DependencyObject对象。 /// 如果控件启用了密码模式,则返回true;否则返回false。 [AttachedPropertyBrowsableForType(typeof(PasswordBox))] public static bool GetHasPassword(DependencyObject obj) { return (bool)obj.GetValue(HasPasswordProperty); } // 核心更新逻辑 private static void UpdateHasPassword(PasswordBox passwordBox) { // 根据密码长度是否大于0,来设置HasText属性的值 passwordBox.SetValue(HasTextPropertyKey, passwordBox.Password.Length > 0); } /// /// 用于设置或获取附着到控件上的后缀内容。 /// public static readonly DependencyProperty SuffixProperty = DependencyProperty.RegisterAttached("Suffix", typeof(object), typeof(InputAssist), new PropertyMetadata(null)); /// /// 获取附加到指定控件的后缀内容。 /// /// 要获取后缀内容的DependencyObject对象。 /// 返回控件的后缀内容,如果未设置则为null。 [AttachedPropertyBrowsableForType(typeof(TextBox))] [AttachedPropertyBrowsableForType(typeof(PasswordBox))] public static object GetSuffix(DependencyObject obj) { return obj.GetValue(SuffixProperty); } /// /// 设置输入控件的后缀。 /// /// 要设置后缀的依赖对象。 /// 要设置为后缀的对象。 public static void SetSuffix(DependencyObject obj, object value) { obj.SetValue(SuffixProperty, value); } /// /// 用于设置或获取控件的密码文本。 /// public static readonly DependencyProperty PasswordProperty = DependencyProperty.RegisterAttached("Password", typeof(string), typeof(InputAssist), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnPasswordChanged)); private static void OnPasswordChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is PasswordBox passwordBox) { // 解除再绑定,防止重复订阅 passwordBox.PasswordChanged -= PasswordBox_PasswordChanged; var newVal = (string)e.NewValue ?? string.Empty; if (passwordBox.Password != newVal) { passwordBox.Password = newVal; } // 确保监听用户输入 passwordBox.PasswordChanged += PasswordBox_PasswordChanged; } } private static void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e) { if (sender is not PasswordBox box) return; var attached = GetPassword(box); var actual = box.Password; // 当用户输入变化时同步回附加属性 if (attached != actual) { // 这里调用 SetPassword 会再次触发 OnPasswordChanged,但因值相同不会再写入造成循环 SetPassword(box, actual); } UpdateHasPassword(box); } /// /// 获取输入控件的密码文本。 /// [AttachedPropertyBrowsableForType(typeof(PasswordBox))] public static string GetPassword(DependencyObject obj) { return (string)obj.GetValue(PasswordProperty); } /// /// 设置输入控件的密码文本。 /// public static void SetPassword(DependencyObject obj, string value) { obj.SetValue(PasswordProperty, value); } /// /// 切换可见密码的样板的作用 /// public static readonly DependencyProperty EyeableProperty = DependencyProperty.RegisterAttached("Eyeable", typeof(bool), typeof(InputAssist), new PropertyMetadata(false)); /// /// Gets whether the password currently held by PasswordBox is displayed in text. /// [AttachedPropertyBrowsableForType(typeof(PasswordBox))] public static bool GetEyeable(DependencyObject obj) { return (bool)obj.GetValue(EyeableProperty); } /// /// 设置是否可以切换密码框的显示模式(显示/隐藏密码)。 /// /// 依赖对象,通常是PasswordBox。 /// 布尔值,表示是否启用显示/隐藏密码的功能。 public static void SetEyeable(DependencyObject obj, bool value) { obj.SetValue(EyeableProperty, value); } /// /// 用于设置或获取控件是否可以被清空。 /// public static readonly DependencyProperty ClearEnabledProperty = DependencyProperty.RegisterAttached("ClearEnabled", typeof(bool), typeof(InputAssist), new PropertyMetadata(false, OnClearEnabledChanged)); /// /// 获取指定控件是否启用了清除功能。 /// /// 要获取属性的依赖对象。 /// 如果启用了清除功能,则返回 true;否则返回 false。 [AttachedPropertyBrowsableForType(typeof(TextBox))] [AttachedPropertyBrowsableForType(typeof(PasswordBox))] [AttachedPropertyBrowsableForType(typeof(ComboBox))] public static bool GetClearEnabled(DependencyObject obj) { return (bool)obj.GetValue(ClearEnabledProperty); } /// /// 设置输入控件是否可以被清空。 /// public static void SetClearEnabled(DependencyObject obj, bool value) { obj.SetValue(ClearEnabledProperty, value); } private static void OnClearEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is not UIElement element) return; if ((bool)e.NewValue) { element.MouseLeftButtonUp += OnClear; } else { element.MouseLeftButtonUp -= OnClear; } } /// /// 用于设置或获取控件是否显示清除按钮。 /// public static readonly DependencyProperty ClearableProperty = DependencyProperty.RegisterAttached( "Clearable", typeof(bool), typeof(InputAssist), new PropertyMetadata(default(bool))); /// /// 设置输入控件是否可清除。 /// /// 要设置属性的依赖对象。 /// 布尔值,表示输入控件是否可以被用户清除其内容。 public static void SetClearable(DependencyObject d, bool value) { d.SetValue(ClearableProperty, value); } /// /// 获取指定依赖对象的可清除属性值。 /// /// 要获取属性值的依赖对象。 /// 返回一个布尔值,表示该控件是否可以被清除内容。 public static bool GetClearable(DependencyObject d) { return (bool)d.GetValue(ClearableProperty); } #endregion #region Private Methods private static void OnClear(object sender, RoutedEventArgs e) { if (sender is not DependencyObject d) return; //var ance = d.GetAncestors(); var parent = d.GetAncestors().FirstOrDefault(a => a is TextBox or PasswordBox or ComboBox); if (parent != null && !GetClearable(parent)) return; if (parent is TextBox textBox) { textBox.Clear(); textBox.GetBindingExpression(TextBox.TextProperty)?.UpdateSource(); } else if (parent is PasswordBox passwordBox) { passwordBox.Clear(); passwordBox.GetBindingExpression(PasswordProperty)?.UpdateSource(); } else if (parent is ComboBox) { if (((ComboBox)parent).IsEditable) { ((ComboBox)parent).Text = string.Empty; ((ComboBox)parent).GetBindingExpression(ComboBox.TextProperty)?.UpdateSource(); } ((ComboBox)parent).SelectedItem = null; ((ComboBox)parent).GetBindingExpression(Selector.SelectedItemProperty)?.UpdateSource(); } // if (d is TextBox textBox) // { // textBox.Clear(); // textBox.GetBindingExpression(TextBox.TextProperty)?.UpdateSource(); // } // else if (d is PasswordBox passwordBox) // { // passwordBox.Clear(); // passwordBox.GetBindingExpression(PasswordProperty)?.UpdateSource(); // } // else if (d is ComboBox comboBox) // { // if (comboBox.IsEditable) // { // comboBox.Text = string.Empty; // comboBox.GetBindingExpression(ComboBox.TextProperty)?.UpdateSource(); // } // // comboBox.SelectedItem = null; // comboBox.GetBindingExpression(Selector.SelectedItemProperty)?.UpdateSource(); // } // else if(d is AutoComplete auto) // { // // 如果是 AutoComplete 控件,清空其文本 // // 并更新绑定源 // auto.Text = string.Empty; // auto.GetBindingExpression(TextBox.TextProperty)?.UpdateSource(); // } } #endregion }