using Microsoft.Xaml.Behaviors;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace TeamAAS_VP.Behaviors
{
///
/// Behavior:在 TextBox 上检测“用户输入”并通过绑定的命令回调旧/新文本值。
/// 仅在检测到明确的用户交互(键入、删除、粘贴、输入法确认等)时触发命令,避免程序性修改触发。
///
public class TextBoxUserInputBehavior : Behavior
{
#region 依赖属性
///
/// 绑定的命令,当检测到用户输入且命令可执行时调用。
/// 命令接收类型为 (自定义)。
///
public static readonly DependencyProperty UserInputCommandProperty =
DependencyProperty.Register(
nameof(UserInputCommand),
typeof(ICommand),
typeof(TextBoxUserInputBehavior));
///
/// 要执行的命令(由视图模型绑定)。
///
public ICommand UserInputCommand
{
get => (ICommand)GetValue(UserInputCommandProperty);
set => SetValue(UserInputCommandProperty, value);
}
///
/// 可选的外部命令参数,将透传到自定义事件参数中。
///
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register(
nameof(CommandParameter),
typeof(object),
typeof(TextBoxUserInputBehavior));
///
/// 绑定的命令参数(用于在触发命令时携带额外上下文)。
///
public object CommandParameter
{
get => GetValue(CommandParameterProperty);
set => SetValue(CommandParameterProperty, value);
}
#endregion
#region 私有字段
// 是否由用户触发的输入(用于在 TextChanged 中判断)
private bool _isUserInput = false;
// 记录变更前的文本值,用于在触发时传递旧值
private string _previousText;
#endregion
#region 附加/分离
///
/// Behavior 附加到 TextBox 时订阅必要的事件。
/// 订阅 PreviewTextInput、PreviewKeyDown、TextChanged、LostFocus 以检测不同的用户交互场景。
///
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.PreviewTextInput += OnPreviewTextInput;
AssociatedObject.PreviewKeyDown += OnPreviewKeyDown;
AssociatedObject.TextChanged += OnTextChanged;
AssociatedObject.LostFocus += OnLostFocus;
}
///
/// Behavior 分离时取消订阅事件,防止内存泄漏。
///
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.PreviewTextInput -= OnPreviewTextInput;
AssociatedObject.PreviewKeyDown -= OnPreviewKeyDown;
AssociatedObject.TextChanged -= OnTextChanged;
AssociatedObject.LostFocus -= OnLostFocus;
}
#endregion
#region 事件处理
///
/// 处理预输入文本事件(用户通过键盘输入字符)。
/// 标记为用户输入并保存变更前的文本。
///
private void OnPreviewTextInput(object sender, TextCompositionEventArgs e)
{
// 用户正在键入文本,记录标志并缓存旧值
_isUserInput = true;
_previousText = AssociatedObject.Text;
}
///
/// 处理按键事件(用于检测删除、退格、空格及粘贴快捷键 Ctrl+V)。
/// 对这些键也将视为用户输入。
///
private void OnPreviewKeyDown(object sender, KeyEventArgs e)
{
// 检测删除、退格、空格等按键,这些也属于用户输入操作
if (e.Key == Key.Back || e.Key == Key.Delete || e.Key == Key.Space)
{
_isUserInput = true;
_previousText = AssociatedObject.Text;
}
// 处理粘贴操作 (Ctrl+V),将其视为用户输入
if (e.Key == Key.V && (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
{
_isUserInput = true;
_previousText = AssociatedObject.Text;
}
}
///
/// TextChanged 事件处理器:当文本发生变化且标记为用户输入时,构造参数并执行绑定命令。
/// 之后清除用户输入标记以避免重复触发。
///
private void OnTextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{
if (_isUserInput)
{
// 清除标记,避免多次触发
_isUserInput = false;
// 如果命令可执行,构造包含旧值/新值的参数并执行
if (UserInputCommand?.CanExecute(CommandParameter) == true)
{
var parameter = new TextChangedEventArgs(
CommandParameter,
_previousText,
AssociatedObject.Text);
UserInputCommand.Execute(parameter);
}
}
}
///
/// LostFocus 事件处理器:处理通过输入法(IME)输入时,确认输入后触发命令。
/// 当用户在输入法中输入完成并失去焦点时也应视为用户输入。
///
private void OnLostFocus(object sender, RoutedEventArgs e)
{
// 如果之前有标记为用户输入(例如输入法未触发 TextChanged),在失焦时触发一次
if (_isUserInput)
{
_isUserInput = false;
if (UserInputCommand?.CanExecute(CommandParameter) == true)
{
var parameter = new TextChangedEventArgs(
CommandParameter,
_previousText,
AssociatedObject.Text);
UserInputCommand.Execute(parameter);
}
}
}
#endregion
}
///
/// 自定义事件参数:用于封装命令参数、文本变更的旧值与新值。
/// 该类是不可变的,仅用于传递数据到命令执行逻辑中。
///
public class TextChangedEventArgs
{
///
/// 外部传入的命令参数(如果有)。
///
public object CommandParameter { get; }
///
/// 变更前的文本值。
///
public string OldValue { get; }
///
/// 变更后的文本值。
///
public string NewValue { get; }
///
/// 构造函数:创建包含命令上下文和文本新旧值的参数对象。
///
public TextChangedEventArgs(object commandParameter, string oldValue, string newValue)
{
CommandParameter = commandParameter;
OldValue = oldValue;
NewValue = newValue;
}
}
}