Files
ShrlAlgoToolkit/NeuWPF/NeoUI/Assists/InputAssist.cs

349 lines
13 KiB
C#
Raw Normal View History

2025-08-20 12:10:13 +08:00
using System.Windows.Controls.Primitives;
using NeumUI.Extensions;
namespace NeumUI.Assists;
/// <summary>
/// InputAssist 类提供了一系列附加属性,用于增强 WPF 应用程序中输入控件的功能。这些功能包括设置占位符文本、占位符颜色、前缀和后缀内容等,支持 TextBoxBase、PasswordBox 和 ComboBox 控件。
/// </summary>
public static class InputAssist
{
#region Attached Properties
/// <summary>
/// 用于设置或获取控件的占位符文本。
/// </summary>
public static readonly DependencyProperty PlaceholderProperty =
DependencyProperty.RegisterAttached("Placeholder", typeof(string), typeof(InputAssist), new PropertyMetadata(string.Empty));
/// <summary>
/// Gets the placeholder for the input control.
/// </summary>
[AttachedPropertyBrowsableForType(typeof(TextBoxBase))]
[AttachedPropertyBrowsableForType(typeof(PasswordBox))]
[AttachedPropertyBrowsableForType(typeof(ComboBox))]
public static string GetPlaceholder(DependencyObject obj)
{
return (string)obj.GetValue(PlaceholderProperty);
}
/// <summary>
/// 设置输入控件的占位符。
/// </summary>
/// <param name="obj">要设置占位符的依赖对象。</param>
/// <param name="value">占位符的文本值。</param>
public static void SetPlaceholder(DependencyObject obj, string value)
{
obj.SetValue(PlaceholderProperty, value);
}
/// <summary>
/// 用于设置或获取占位符文本的颜色刷。
/// </summary>
public static readonly DependencyProperty PlaceholderBrushProperty =
DependencyProperty.RegisterAttached("PlaceholderBrush", typeof(Brush), typeof(InputAssist),
new FrameworkPropertyMetadata(
Brushes.Silver,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender |
FrameworkPropertyMetadataOptions.Inherits));
/// <summary>
/// Gets the placeholder foreground brush for the input control.
/// </summary>
[AttachedPropertyBrowsableForType(typeof(TextBoxBase))]
[AttachedPropertyBrowsableForType(typeof(PasswordBox))]
[AttachedPropertyBrowsableForType(typeof(ComboBox))]
public static Brush GetPlaceholderBrush(DependencyObject obj)
{
return (Brush)obj.GetValue(PlaceholderBrushProperty);
}
/// <summary>
/// 设置输入控件的占位符画刷。
/// </summary>
/// <param name="obj">依赖对象通常为TextBoxBase、PasswordBox或ComboBox。</param>
/// <param name="value">要设置的占位符画刷。</param>
public static void SetPlaceholderBrush(DependencyObject obj, Brush value)
{
obj.SetValue(PlaceholderBrushProperty, value);
}
/// <summary>
/// 用于设置或获取控件的前缀内容。
/// </summary>
public static readonly DependencyProperty PrefixProperty =
DependencyProperty.RegisterAttached("Prefix", typeof(object), typeof(InputAssist), new PropertyMetadata(null));
/// <summary>
/// 获取与指定依赖对象关联的前缀。
/// </summary>
/// <param name="obj">要获取前缀的依赖对象。</param>
/// <returns>与指定依赖对象关联的前缀对象。</returns>
[AttachedPropertyBrowsableForType(typeof(TextBox))]
[AttachedPropertyBrowsableForType(typeof(PasswordBox))]
[AttachedPropertyBrowsableForType(typeof(ComboBox))]
public static object GetPrefix(DependencyObject obj)
{
return obj.GetValue(PrefixProperty);
}
/// <summary>
/// 设置输入控件的前缀。
/// </summary>
/// <param name="obj">要设置前缀的依赖对象。</param>
/// <param name="value">要设置为前缀的对象。</param>
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;
[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);
}
/// <summary>
/// 用于设置或获取附着到控件上的后缀内容。
/// </summary>
public static readonly DependencyProperty SuffixProperty =
DependencyProperty.RegisterAttached("Suffix", typeof(object), typeof(InputAssist), new PropertyMetadata(null));
/// <summary>
/// Gets the suffix object of the input control.
/// </summary>
[AttachedPropertyBrowsableForType(typeof(TextBox))]
[AttachedPropertyBrowsableForType(typeof(PasswordBox))]
public static object GetSuffix(DependencyObject obj)
{
return obj.GetValue(SuffixProperty);
}
/// <summary>
/// 设置输入控件的后缀。
/// </summary>
/// <param name="obj">要设置后缀的依赖对象。</param>
/// <param name="value">要设置为后缀的对象。</param>
public static void SetSuffix(DependencyObject obj, object value)
{
obj.SetValue(SuffixProperty, value);
}
/// <summary>
/// 用于设置或获取控件的密码文本。
/// </summary>
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);
}
/// <summary>
/// Gets the password currently held by the PasswordBox.
/// </summary>
[AttachedPropertyBrowsableForType(typeof(PasswordBox))]
public static string GetPassword(DependencyObject obj)
{
return (string)obj.GetValue(PasswordProperty);
}
/// <summary>
/// Sets the password currently held by the PasswordBox.
/// </summary>
public static void SetPassword(DependencyObject obj, string value)
{
obj.SetValue(PasswordProperty, value);
}
/// <summary>
/// 切换可见密码的样板的作用
/// </summary>
public static readonly DependencyProperty EyeableProperty =
DependencyProperty.RegisterAttached("Eyeable", typeof(bool), typeof(InputAssist), new PropertyMetadata(false));
/// <summary>
/// Gets whether the password currently held by PasswordBox is displayed in text.
/// </summary>
[AttachedPropertyBrowsableForType(typeof(PasswordBox))]
public static bool GetEyeable(DependencyObject obj)
{
return (bool)obj.GetValue(EyeableProperty);
}
/// <summary>
/// 设置是否可以切换密码框的显示模式(显示/隐藏密码)。
/// </summary>
/// <param name="obj">依赖对象通常是PasswordBox。</param>
/// <param name="value">布尔值,表示是否启用显示/隐藏密码的功能。</param>
public static void SetEyeable(DependencyObject obj, bool value)
{
obj.SetValue(EyeableProperty, value);
}
/// <summary>
/// 用于设置或获取控件是否可以被清空。
/// </summary>
public static readonly DependencyProperty ClearEnabledProperty =
DependencyProperty.RegisterAttached("ClearEnabled", typeof(bool), typeof(InputAssist), new PropertyMetadata(false, OnClearEnabledChanged));
/// <summary>
/// Gets the clear enable state of the control.
/// </summary>
[AttachedPropertyBrowsableForType(typeof(TextBox))]
[AttachedPropertyBrowsableForType(typeof(PasswordBox))]
[AttachedPropertyBrowsableForType(typeof(ComboBox))]
public static bool GetClearEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(ClearEnabledProperty);
}
/// <summary>
/// Sets the clear enable state of the control.
/// </summary>
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 DependencyObject d)
{
//var ance = d.GetAncestors();
var parent = d.GetAncestors().FirstOrDefault(a => a is TextBox || a is PasswordBox || a is ComboBox);
if (!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
}