using Microsoft.Xaml.Behaviors; using System; using System.Windows; using System.Windows.Controls; using System.Windows.Input; namespace TeamAAS_VP.Behaviors { /// /// 为 `MahApps.Metro.Controls.NumericUpDown` 提供用户输入检测行为。 /// 当控件值由用户交互(鼠标、键盘或文本输入)更改时,触发绑定的命令并传递 (CommandParameter, previousValue, currentValue)。 /// public class NumericUpDownUserInputBehavior : Behavior { /// /// 依赖属性:用户输入触发时要执行的命令。 /// 命令接收一个 Tuple<object, double?, double?> 参数,分别是 (CommandParameter, previousValue, currentValue)。 /// public static readonly DependencyProperty UserInputCommandProperty = DependencyProperty.Register( nameof(UserInputCommand), typeof(ICommand), typeof(NumericUpDownUserInputBehavior)); /// /// 要在用户输入时执行的命令。 /// public ICommand UserInputCommand { get => (ICommand)GetValue(UserInputCommandProperty); set => SetValue(UserInputCommandProperty, value); } /// /// 依赖属性:传递给命令的参数(作为命令的第一个项,最终传递给命令的是 Tuple<object, double?, double?>)。 /// public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register( nameof(CommandParameter), typeof(object), typeof(NumericUpDownUserInputBehavior)); /// /// 命令参数,最终与 previousValue 和 currentValue 一起封装成 Tuple 传递给命令。 /// public object CommandParameter { get => GetValue(CommandParameterProperty); set => SetValue(CommandParameterProperty, value); } // 标记当前操作是否为用户交互触发 private bool _isUserInteraction = false; // 保存上一次值(用于在值变化时向命令提供前后对比) private double? _previousValue; /// /// 行为附加时订阅控件的相关事件用于检测用户交互与值变化。 /// protected override void OnAttached() { base.OnAttached(); AssociatedObject.PreviewMouseDown += OnPreviewMouseDown; AssociatedObject.PreviewKeyDown += OnPreviewKeyDown; AssociatedObject.ValueChanged += OnValueChanged; AssociatedObject.LostFocus += OnLostFocus; AssociatedObject.GotFocus += OnGotFocus; } /// /// 行为分离时取消订阅事件,防止内存泄漏。 /// protected override void OnDetaching() { base.OnDetaching(); AssociatedObject.PreviewMouseDown -= OnPreviewMouseDown; AssociatedObject.PreviewKeyDown -= OnPreviewKeyDown; AssociatedObject.ValueChanged -= OnValueChanged; AssociatedObject.LostFocus -= OnLostFocus; AssociatedObject.GotFocus -= OnGotFocus; } /// /// 当控件获得焦点时,标记为用户交互并记录当前值。 /// 这有助于检测通过文本输入直接修改值的场景。 /// private void OnGotFocus(object sender, RoutedEventArgs e) { _isUserInteraction = true; _previousValue = AssociatedObject.Value; // 如果需要按当前语言设置文本框语言,可在此处获取应用级服务并设置 PART_TextBox 的 Language。 //var management = App.Host.Services.GetRequiredService(); //var partTextBox = AssociatedObject.Template.FindName("PART_TextBox", AssociatedObject) as TextBox; //partTextBox.Language = System.Windows.Markup.XmlLanguage.GetLanguage(management.CurrentLanguage); } /// /// 在鼠标按下时标记为用户交互并记录当前值(例如点击上下箭头)。 /// private void OnPreviewMouseDown(object sender, MouseButtonEventArgs e) { _isUserInteraction = true; _previousValue = AssociatedObject.Value; } /// /// 在键盘按下时检测常见的用户输入按键(上下箭头、数字、退格、删除等),以标记为用户交互并记录当前值。 /// private void OnPreviewKeyDown(object sender, KeyEventArgs e) { // 检测键盘输入(上下箭头、数字键、删除、退格等) if (e.Key == Key.Up || e.Key == Key.Down || e.Key == Key.Back || e.Key == Key.Delete || (e.Key >= Key.D0 && e.Key <= Key.D9) || (e.Key >= Key.NumPad0 && e.Key <= Key.NumPad9)) { _isUserInteraction = true; _previousValue = AssociatedObject.Value; } } /// /// 值变化时触发:若是用户交互导致的变化且命令可执行,则构造参数并执行命令。 /// 同时更新保存的前一个值以便连续变化时能够正确传递历史值。 /// private void OnValueChanged(object sender, RoutedPropertyChangedEventArgs e) { if (_isUserInteraction && UserInputCommand?.CanExecute(CommandParameter) == true) { var parameter = new Tuple( CommandParameter, _previousValue, AssociatedObject.Value); UserInputCommand.Execute(parameter); // 更新前一个值,方便下一次变化比较 _previousValue = AssociatedObject.Value; } } /// /// 失去焦点时的处理: /// 1. 处理通过文本框直接输入导致的值变化(可能在 LostFocus 时才完成绑定/解析),执行命令。 /// 2. 重置用户交互标记。 /// 3. 当文本框为空时,恢复绑定的值(UpdateTarget),避免空文本导致模型被清空。 /// private void OnLostFocus(object sender, RoutedEventArgs e) { // 处理通过文本框直接输入的情况:若是用户交互且值有变化,则执行命令 if (_isUserInteraction && _previousValue != AssociatedObject.Value) { if (UserInputCommand?.CanExecute(CommandParameter) == true) { var parameter = new Tuple( CommandParameter, _previousValue, AssociatedObject.Value); UserInputCommand.Execute(parameter); } } // 重置用户交互标记 _isUserInteraction = false; // 处理空文本框的情况(恢复绑定值),避免用户清空文本导致绑定端值丢失 var partTextBox = AssociatedObject.Template.FindName("PART_TextBox", AssociatedObject) as TextBox; if (string.IsNullOrWhiteSpace(partTextBox?.Text)) { var bindingExpression = AssociatedObject.GetBindingExpression(MahApps.Metro.Controls.NumericUpDown.ValueProperty); bindingExpression?.UpdateTarget(); } } } }