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; } } }