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