2025-08-20 12:10:13 +08:00
|
|
|
|
using System.Windows.Controls.Primitives;
|
|
|
|
|
|
|
2026-01-02 17:30:41 +08:00
|
|
|
|
using Melskin.Extensions;
|
2025-08-20 12:10:13 +08:00
|
|
|
|
|
2026-01-02 17:30:41 +08:00
|
|
|
|
namespace Melskin.Assists;
|
2025-08-20 12:10:13 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// InputAssist 类提供了一系列附加属性,用于增强 WPF 应用程序中输入控件的功能。这些功能包括设置占位符文本、占位符颜色、前缀和后缀内容等,支持 TextBoxBase、PasswordBox 和 ComboBox 控件。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static class InputAssist
|
|
|
|
|
|
{
|
|
|
|
|
|
#region Attached Properties
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 用于设置或获取控件的占位符文本。
|
|
|
|
|
|
/// </summary>
|
2025-12-23 21:35:54 +08:00
|
|
|
|
public static readonly DependencyProperty PlaceholderTextProperty =
|
|
|
|
|
|
DependencyProperty.RegisterAttached("PlaceholderText", typeof(string), typeof(InputAssist), new PropertyMetadata(string.Empty));
|
2025-08-20 12:10:13 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2025-08-20 12:10:35 +08:00
|
|
|
|
/// 获取输入控件的占位符文本。
|
2025-08-20 12:10:13 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
[AttachedPropertyBrowsableForType(typeof(TextBoxBase))]
|
|
|
|
|
|
[AttachedPropertyBrowsableForType(typeof(PasswordBox))]
|
|
|
|
|
|
[AttachedPropertyBrowsableForType(typeof(ComboBox))]
|
2025-12-23 21:35:54 +08:00
|
|
|
|
public static string GetPlaceholderText(DependencyObject obj)
|
2025-08-20 12:10:13 +08:00
|
|
|
|
{
|
2025-12-23 21:35:54 +08:00
|
|
|
|
return (string)obj.GetValue(PlaceholderTextProperty);
|
2025-08-20 12:10:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 设置输入控件的占位符。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="obj">要设置占位符的依赖对象。</param>
|
|
|
|
|
|
/// <param name="value">占位符的文本值。</param>
|
2025-12-23 21:35:54 +08:00
|
|
|
|
public static void SetPlaceholderText(DependencyObject obj, string value)
|
2025-08-20 12:10:13 +08:00
|
|
|
|
{
|
2025-12-23 21:35:54 +08:00
|
|
|
|
obj.SetValue(PlaceholderTextProperty, value);
|
2025-08-20 12:10:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <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));
|
|
|
|
|
|
|
2025-08-20 12:10:35 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 用于获取或设置是否显示密码框内有内容的指示器。
|
|
|
|
|
|
/// </summary>
|
2025-08-20 12:10:13 +08:00
|
|
|
|
public static readonly DependencyProperty HasPasswordProperty = HasTextPropertyKey.DependencyProperty;
|
|
|
|
|
|
|
2025-08-20 12:10:35 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取指定控件是否启用了密码模式。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="obj">要检查的DependencyObject对象。</param>
|
|
|
|
|
|
/// <returns>如果控件启用了密码模式,则返回true;否则返回false。</returns>
|
2025-08-20 12:10:13 +08:00
|
|
|
|
[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>
|
2025-08-20 12:10:35 +08:00
|
|
|
|
/// 获取附加到指定控件的后缀内容。
|
2025-08-20 12:10:13 +08:00
|
|
|
|
/// </summary>
|
2025-08-20 12:10:35 +08:00
|
|
|
|
/// <param name="obj">要获取后缀内容的DependencyObject对象。</param>
|
|
|
|
|
|
/// <returns>返回控件的后缀内容,如果未设置则为null。</returns>
|
2025-08-20 12:10:13 +08:00
|
|
|
|
[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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-23 21:35:54 +08:00
|
|
|
|
#region PasswordBox
|
|
|
|
|
|
|
2025-08-20 12:10:13 +08:00
|
|
|
|
/// <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;
|
|
|
|
|
|
}
|
2025-12-28 11:47:54 +08:00
|
|
|
|
UpdateHasPassword(passwordBox);
|
2025-08-20 12:10:13 +08:00
|
|
|
|
// 确保监听用户输入
|
|
|
|
|
|
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>
|
2025-08-20 12:10:35 +08:00
|
|
|
|
/// 获取输入控件的密码文本。
|
2025-08-20 12:10:13 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
[AttachedPropertyBrowsableForType(typeof(PasswordBox))]
|
|
|
|
|
|
public static string GetPassword(DependencyObject obj)
|
|
|
|
|
|
{
|
|
|
|
|
|
return (string)obj.GetValue(PasswordProperty);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2025-08-20 12:10:35 +08:00
|
|
|
|
/// 设置输入控件的密码文本。
|
2025-08-20 12:10:13 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static void SetPassword(DependencyObject obj, string value)
|
|
|
|
|
|
{
|
|
|
|
|
|
obj.SetValue(PasswordProperty, value);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 切换可见密码的样板的作用
|
|
|
|
|
|
/// </summary>
|
2025-12-23 21:35:54 +08:00
|
|
|
|
public static readonly DependencyProperty IsPasswordVisibleProperty =
|
|
|
|
|
|
DependencyProperty.RegisterAttached("IsPasswordVisible", typeof(bool), typeof(InputAssist), new PropertyMetadata(false));
|
2025-08-20 12:10:13 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Gets whether the password currently held by PasswordBox is displayed in text.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[AttachedPropertyBrowsableForType(typeof(PasswordBox))]
|
2025-12-23 21:35:54 +08:00
|
|
|
|
public static bool GetIsPasswordVisible(DependencyObject obj)
|
2025-08-20 12:10:13 +08:00
|
|
|
|
{
|
2025-12-23 21:35:54 +08:00
|
|
|
|
return (bool)obj.GetValue(IsPasswordVisibleProperty);
|
2025-08-20 12:10:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 设置是否可以切换密码框的显示模式(显示/隐藏密码)。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="obj">依赖对象,通常是PasswordBox。</param>
|
|
|
|
|
|
/// <param name="value">布尔值,表示是否启用显示/隐藏密码的功能。</param>
|
2025-12-23 21:35:54 +08:00
|
|
|
|
public static void SetIsPasswordVisible(DependencyObject obj, bool value)
|
2025-08-20 12:10:13 +08:00
|
|
|
|
{
|
2025-12-23 21:35:54 +08:00
|
|
|
|
obj.SetValue(IsPasswordVisibleProperty, value);
|
2025-08-20 12:10:13 +08:00
|
|
|
|
}
|
2025-12-23 21:35:54 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
2025-08-20 12:10:13 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 用于设置或获取控件是否可以被清空。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static readonly DependencyProperty ClearEnabledProperty =
|
|
|
|
|
|
DependencyProperty.RegisterAttached("ClearEnabled", typeof(bool), typeof(InputAssist), new PropertyMetadata(false, OnClearEnabledChanged));
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2025-08-20 12:10:35 +08:00
|
|
|
|
/// 获取指定控件是否启用了清除功能。
|
2025-08-20 12:10:13 +08:00
|
|
|
|
/// </summary>
|
2025-08-20 12:10:35 +08:00
|
|
|
|
/// <param name="obj">要获取属性的依赖对象。</param>
|
|
|
|
|
|
/// <returns>如果启用了清除功能,则返回 true;否则返回 false。</returns>
|
2025-08-20 12:10:13 +08:00
|
|
|
|
[AttachedPropertyBrowsableForType(typeof(TextBox))]
|
|
|
|
|
|
[AttachedPropertyBrowsableForType(typeof(PasswordBox))]
|
|
|
|
|
|
[AttachedPropertyBrowsableForType(typeof(ComboBox))]
|
|
|
|
|
|
public static bool GetClearEnabled(DependencyObject obj)
|
|
|
|
|
|
{
|
|
|
|
|
|
return (bool)obj.GetValue(ClearEnabledProperty);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2025-08-20 12:10:35 +08:00
|
|
|
|
/// 设置输入控件是否可以被清空。
|
2025-08-20 12:10:13 +08:00
|
|
|
|
/// </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;
|
2025-12-23 21:35:54 +08:00
|
|
|
|
// 根据新值订阅或取消订阅鼠标左键释放事件
|
2025-08-20 12:10:13 +08:00
|
|
|
|
if ((bool)e.NewValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
element.MouseLeftButtonUp += OnClear;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
element.MouseLeftButtonUp -= OnClear;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-20 12:10:35 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 用于设置或获取控件是否显示清除按钮。
|
|
|
|
|
|
/// </summary>
|
2025-08-20 12:10:13 +08:00
|
|
|
|
public static readonly DependencyProperty ClearableProperty = DependencyProperty.RegisterAttached(
|
|
|
|
|
|
"Clearable", typeof(bool), typeof(InputAssist), new PropertyMetadata(default(bool)));
|
|
|
|
|
|
|
2025-08-20 12:10:35 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 设置输入控件是否可清除。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="d">要设置属性的依赖对象。</param>
|
|
|
|
|
|
/// <param name="value">布尔值,表示输入控件是否可以被用户清除其内容。</param>
|
2025-08-20 12:10:13 +08:00
|
|
|
|
public static void SetClearable(DependencyObject d, bool value)
|
|
|
|
|
|
{
|
|
|
|
|
|
d.SetValue(ClearableProperty, value);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-20 12:10:35 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取指定依赖对象的可清除属性值。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="d">要获取属性值的依赖对象。</param>
|
|
|
|
|
|
/// <returns>返回一个布尔值,表示该控件是否可以被清除内容。</returns>
|
2025-08-20 12:10:13 +08:00
|
|
|
|
public static bool GetClearable(DependencyObject d)
|
|
|
|
|
|
{
|
|
|
|
|
|
return (bool)d.GetValue(ClearableProperty);
|
|
|
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region Private Methods
|
2025-12-23 21:35:54 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 清除事件的处理程序
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="sender"></param>
|
|
|
|
|
|
/// <param name="e"></param>
|
2025-08-20 12:10:13 +08:00
|
|
|
|
private static void OnClear(object sender, RoutedEventArgs e)
|
|
|
|
|
|
{
|
2025-08-20 12:10:35 +08:00
|
|
|
|
if (sender is not DependencyObject d) return;
|
2025-09-08 19:49:09 +08:00
|
|
|
|
//查找父级控件中第一个是 TextBox、PasswordBox 或 ComboBox 的控件
|
2025-08-20 12:10:35 +08:00
|
|
|
|
var parent = d.GetAncestors().FirstOrDefault(a => a is TextBox or PasswordBox or ComboBox);
|
|
|
|
|
|
if (parent != null && !GetClearable(parent)) return;
|
2025-12-23 21:35:54 +08:00
|
|
|
|
switch (parent)
|
2025-08-20 12:10:13 +08:00
|
|
|
|
{
|
2025-12-23 21:35:54 +08:00
|
|
|
|
case TextBox textBox:
|
|
|
|
|
|
textBox.Clear();
|
|
|
|
|
|
textBox.GetBindingExpression(TextBox.TextProperty)?.UpdateSource();
|
|
|
|
|
|
break;
|
|
|
|
|
|
case PasswordBox passwordBox:
|
|
|
|
|
|
passwordBox.Clear();
|
|
|
|
|
|
passwordBox.GetBindingExpression(PasswordProperty)?.UpdateSource();
|
|
|
|
|
|
break;
|
|
|
|
|
|
case ComboBox comboBox:
|
2025-08-20 12:10:13 +08:00
|
|
|
|
{
|
2025-12-23 21:35:54 +08:00
|
|
|
|
if (comboBox.IsEditable)
|
|
|
|
|
|
{
|
|
|
|
|
|
comboBox.Text = string.Empty;
|
|
|
|
|
|
comboBox.GetBindingExpression(ComboBox.TextProperty)?.UpdateSource();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
comboBox.SelectedItem = null;
|
|
|
|
|
|
comboBox.GetBindingExpression(Selector.SelectedItemProperty)?.UpdateSource();
|
|
|
|
|
|
break;
|
2025-08-20 12:10:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
}
|