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