Files
ShrlAlgoToolkit/Melskin/Assists/InputAssist.cs
2026-02-12 21:29:00 +08:00

323 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Windows.Controls.Primitives;
using Melskin.Extensions;
namespace Melskin.Assists;
/// <summary>
/// InputAssist 类提供了一系列附加属性,用于增强 WPF 应用程序中输入控件的功能。这些功能包括设置占位符文本、占位符颜色、前缀和后缀内容等,支持 TextBoxBase、PasswordBox 和 ComboBox 控件。
/// </summary>
public static class InputAssist
{
#region Attached Properties
/// <summary>
/// 用于设置或获取控件的占位符文本。
/// </summary>
public static readonly DependencyProperty PlaceholderTextProperty =
DependencyProperty.RegisterAttached("PlaceholderText", typeof(string), typeof(InputAssist), new PropertyMetadata(string.Empty));
/// <summary>
/// 获取输入控件的占位符文本。
/// </summary>
[AttachedPropertyBrowsableForType(typeof(TextBoxBase))]
[AttachedPropertyBrowsableForType(typeof(PasswordBox))]
[AttachedPropertyBrowsableForType(typeof(ComboBox))]
public static string GetPlaceholderText(DependencyObject obj)
{
return (string)obj.GetValue(PlaceholderTextProperty);
}
/// <summary>
/// 设置输入控件的占位符。
/// </summary>
/// <param name="obj">要设置占位符的依赖对象。</param>
/// <param name="value">占位符的文本值。</param>
public static void SetPlaceholderText(DependencyObject obj, string value)
{
obj.SetValue(PlaceholderTextProperty, 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));
/// <summary>
/// 用于获取或设置是否显示密码框内有内容的指示器。
/// </summary>
public static readonly DependencyProperty HasPasswordProperty = HasTextPropertyKey.DependencyProperty;
/// <summary>
/// 获取指定控件是否启用了密码模式。
/// </summary>
/// <param name="obj">要检查的DependencyObject对象。</param>
/// <returns>如果控件启用了密码模式则返回true否则返回false。</returns>
[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>
/// 获取附加到指定控件的后缀内容。
/// </summary>
/// <param name="obj">要获取后缀内容的DependencyObject对象。</param>
/// <returns>返回控件的后缀内容如果未设置则为null。</returns>
[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);
}
#region PasswordBox
/// <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;
}
UpdateHasPassword(passwordBox);
// 确保监听用户输入
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>
/// 获取输入控件的密码文本。
/// </summary>
[AttachedPropertyBrowsableForType(typeof(PasswordBox))]
public static string GetPassword(DependencyObject obj)
{
return (string)obj.GetValue(PasswordProperty);
}
/// <summary>
/// 设置输入控件的密码文本。
/// </summary>
public static void SetPassword(DependencyObject obj, string value)
{
obj.SetValue(PasswordProperty, value);
}
/// <summary>
/// 切换可见密码的样板的作用
/// </summary>
public static readonly DependencyProperty IsPasswordVisibleProperty =
DependencyProperty.RegisterAttached("IsPasswordVisible", 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 GetIsPasswordVisible(DependencyObject obj)
{
return (bool)obj.GetValue(IsPasswordVisibleProperty);
}
/// <summary>
/// 设置是否可以切换密码框的显示模式(显示/隐藏密码)。
/// </summary>
/// <param name="obj">依赖对象通常是PasswordBox。</param>
/// <param name="value">布尔值,表示是否启用显示/隐藏密码的功能。</param>
public static void SetIsPasswordVisible(DependencyObject obj, bool value)
{
obj.SetValue(IsPasswordVisibleProperty, value);
}
#endregion
/// <summary>
/// 用于设置或获取控件是否可以被清空。
/// </summary>
public static readonly DependencyProperty ClearEnabledProperty =
DependencyProperty.RegisterAttached("ClearEnabled", typeof(bool), typeof(InputAssist), new PropertyMetadata(false, OnClearEnabledChanged));
/// <summary>
/// 获取指定控件是否启用了清除功能。
/// </summary>
/// <param name="obj">要获取属性的依赖对象。</param>
/// <returns>如果启用了清除功能,则返回 true否则返回 false。</returns>
[AttachedPropertyBrowsableForType(typeof(TextBox))]
[AttachedPropertyBrowsableForType(typeof(PasswordBox))]
[AttachedPropertyBrowsableForType(typeof(ComboBox))]
public static bool GetClearEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(ClearEnabledProperty);
}
/// <summary>
/// 设置输入控件是否可以被清空。
/// </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;
}
}
/// <summary>
/// 用于设置或获取控件是否显示清除按钮。
/// </summary>
public static readonly DependencyProperty ClearableProperty = DependencyProperty.RegisterAttached(
"Clearable", typeof(bool), typeof(InputAssist), new PropertyMetadata(default(bool)));
/// <summary>
/// 设置输入控件是否可清除。
/// </summary>
/// <param name="d">要设置属性的依赖对象。</param>
/// <param name="value">布尔值,表示输入控件是否可以被用户清除其内容。</param>
public static void SetClearable(DependencyObject d, bool value)
{
d.SetValue(ClearableProperty, value);
}
/// <summary>
/// 获取指定依赖对象的可清除属性值。
/// </summary>
/// <param name="d">要获取属性值的依赖对象。</param>
/// <returns>返回一个布尔值,表示该控件是否可以被清除内容。</returns>
public static bool GetClearable(DependencyObject d)
{
return (bool)d.GetValue(ClearableProperty);
}
#endregion
#region Private Methods
/// <summary>
/// 清除事件的处理程序
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void OnClear(object sender, RoutedEventArgs e)
{
if (sender is not DependencyObject d) return;
//查找父级控件中第一个是 TextBox、PasswordBox 或 ComboBox 的控件
var parent = d.GetAncestors().FirstOrDefault(a => a is TextBox or PasswordBox or ComboBox);
if (parent != null && !GetClearable(parent)) return;
switch (parent)
{
case TextBox textBox:
textBox.Clear();
textBox.GetBindingExpression(TextBox.TextProperty)?.UpdateSource();
break;
case PasswordBox passwordBox:
passwordBox.Clear();
passwordBox.GetBindingExpression(PasswordProperty)?.UpdateSource();
break;
case ComboBox comboBox:
{
if (comboBox.IsEditable)
{
comboBox.Text = string.Empty;
comboBox.GetBindingExpression(ComboBox.TextProperty)?.UpdateSource();
}
comboBox.SelectedItem = null;
comboBox.GetBindingExpression(Selector.SelectedItemProperty)?.UpdateSource();
break;
}
}
}
#endregion
}