NumericUpDownUserInputBehavior.cs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. using Microsoft.Xaml.Behaviors;
  2. using System;
  3. using System.Windows;
  4. using System.Windows.Controls;
  5. using System.Windows.Input;
  6. namespace TeamAAS_VP.Behaviors
  7. {
  8. /// <summary>
  9. /// 为 `MahApps.Metro.Controls.NumericUpDown` 提供用户输入检测行为。
  10. /// 当控件值由用户交互(鼠标、键盘或文本输入)更改时,触发绑定的命令并传递 (CommandParameter, previousValue, currentValue)。
  11. /// </summary>
  12. public class NumericUpDownUserInputBehavior : Behavior<MahApps.Metro.Controls.NumericUpDown>
  13. {
  14. /// <summary>
  15. /// 依赖属性:用户输入触发时要执行的命令。
  16. /// 命令接收一个 Tuple&lt;object, double?, double?&gt; 参数,分别是 (CommandParameter, previousValue, currentValue)。
  17. /// </summary>
  18. public static readonly DependencyProperty UserInputCommandProperty =
  19. DependencyProperty.Register(
  20. nameof(UserInputCommand),
  21. typeof(ICommand),
  22. typeof(NumericUpDownUserInputBehavior));
  23. /// <summary>
  24. /// 要在用户输入时执行的命令。
  25. /// </summary>
  26. public ICommand UserInputCommand
  27. {
  28. get => (ICommand)GetValue(UserInputCommandProperty);
  29. set => SetValue(UserInputCommandProperty, value);
  30. }
  31. /// <summary>
  32. /// 依赖属性:传递给命令的参数(作为命令的第一个项,最终传递给命令的是 Tuple&lt;object, double?, double?&gt;)。
  33. /// </summary>
  34. public static readonly DependencyProperty CommandParameterProperty =
  35. DependencyProperty.Register(
  36. nameof(CommandParameter),
  37. typeof(object),
  38. typeof(NumericUpDownUserInputBehavior));
  39. /// <summary>
  40. /// 命令参数,最终与 previousValue 和 currentValue 一起封装成 Tuple 传递给命令。
  41. /// </summary>
  42. public object CommandParameter
  43. {
  44. get => GetValue(CommandParameterProperty);
  45. set => SetValue(CommandParameterProperty, value);
  46. }
  47. // 标记当前操作是否为用户交互触发
  48. private bool _isUserInteraction = false;
  49. // 保存上一次值(用于在值变化时向命令提供前后对比)
  50. private double? _previousValue;
  51. /// <summary>
  52. /// 行为附加时订阅控件的相关事件用于检测用户交互与值变化。
  53. /// </summary>
  54. protected override void OnAttached()
  55. {
  56. base.OnAttached();
  57. AssociatedObject.PreviewMouseDown += OnPreviewMouseDown;
  58. AssociatedObject.PreviewKeyDown += OnPreviewKeyDown;
  59. AssociatedObject.ValueChanged += OnValueChanged;
  60. AssociatedObject.LostFocus += OnLostFocus;
  61. AssociatedObject.GotFocus += OnGotFocus;
  62. }
  63. /// <summary>
  64. /// 行为分离时取消订阅事件,防止内存泄漏。
  65. /// </summary>
  66. protected override void OnDetaching()
  67. {
  68. base.OnDetaching();
  69. AssociatedObject.PreviewMouseDown -= OnPreviewMouseDown;
  70. AssociatedObject.PreviewKeyDown -= OnPreviewKeyDown;
  71. AssociatedObject.ValueChanged -= OnValueChanged;
  72. AssociatedObject.LostFocus -= OnLostFocus;
  73. AssociatedObject.GotFocus -= OnGotFocus;
  74. }
  75. /// <summary>
  76. /// 当控件获得焦点时,标记为用户交互并记录当前值。
  77. /// 这有助于检测通过文本输入直接修改值的场景。
  78. /// </summary>
  79. private void OnGotFocus(object sender, RoutedEventArgs e)
  80. {
  81. _isUserInteraction = true;
  82. _previousValue = AssociatedObject.Value;
  83. // 如果需要按当前语言设置文本框语言,可在此处获取应用级服务并设置 PART_TextBox 的 Language。
  84. //var management = App.Host.Services.GetRequiredService<Management>();
  85. //var partTextBox = AssociatedObject.Template.FindName("PART_TextBox", AssociatedObject) as TextBox;
  86. //partTextBox.Language = System.Windows.Markup.XmlLanguage.GetLanguage(management.CurrentLanguage);
  87. }
  88. /// <summary>
  89. /// 在鼠标按下时标记为用户交互并记录当前值(例如点击上下箭头)。
  90. /// </summary>
  91. private void OnPreviewMouseDown(object sender, MouseButtonEventArgs e)
  92. {
  93. _isUserInteraction = true;
  94. _previousValue = AssociatedObject.Value;
  95. }
  96. /// <summary>
  97. /// 在键盘按下时检测常见的用户输入按键(上下箭头、数字、退格、删除等),以标记为用户交互并记录当前值。
  98. /// </summary>
  99. private void OnPreviewKeyDown(object sender, KeyEventArgs e)
  100. {
  101. // 检测键盘输入(上下箭头、数字键、删除、退格等)
  102. if (e.Key == Key.Up || e.Key == Key.Down ||
  103. e.Key == Key.Back || e.Key == Key.Delete ||
  104. (e.Key >= Key.D0 && e.Key <= Key.D9) ||
  105. (e.Key >= Key.NumPad0 && e.Key <= Key.NumPad9))
  106. {
  107. _isUserInteraction = true;
  108. _previousValue = AssociatedObject.Value;
  109. }
  110. }
  111. /// <summary>
  112. /// 值变化时触发:若是用户交互导致的变化且命令可执行,则构造参数并执行命令。
  113. /// 同时更新保存的前一个值以便连续变化时能够正确传递历史值。
  114. /// </summary>
  115. private void OnValueChanged(object sender, RoutedPropertyChangedEventArgs<double?> e)
  116. {
  117. if (_isUserInteraction && UserInputCommand?.CanExecute(CommandParameter) == true)
  118. {
  119. var parameter = new Tuple<object, double?, double?>(
  120. CommandParameter,
  121. _previousValue,
  122. AssociatedObject.Value);
  123. UserInputCommand.Execute(parameter);
  124. // 更新前一个值,方便下一次变化比较
  125. _previousValue = AssociatedObject.Value;
  126. }
  127. }
  128. /// <summary>
  129. /// 失去焦点时的处理:
  130. /// 1. 处理通过文本框直接输入导致的值变化(可能在 LostFocus 时才完成绑定/解析),执行命令。
  131. /// 2. 重置用户交互标记。
  132. /// 3. 当文本框为空时,恢复绑定的值(UpdateTarget),避免空文本导致模型被清空。
  133. /// </summary>
  134. private void OnLostFocus(object sender, RoutedEventArgs e)
  135. {
  136. // 处理通过文本框直接输入的情况:若是用户交互且值有变化,则执行命令
  137. if (_isUserInteraction && _previousValue != AssociatedObject.Value)
  138. {
  139. if (UserInputCommand?.CanExecute(CommandParameter) == true)
  140. {
  141. var parameter = new Tuple<object, double?, double?>(
  142. CommandParameter,
  143. _previousValue,
  144. AssociatedObject.Value);
  145. UserInputCommand.Execute(parameter);
  146. }
  147. }
  148. // 重置用户交互标记
  149. _isUserInteraction = false;
  150. // 处理空文本框的情况(恢复绑定值),避免用户清空文本导致绑定端值丢失
  151. var partTextBox = AssociatedObject.Template.FindName("PART_TextBox", AssociatedObject) as TextBox;
  152. if (string.IsNullOrWhiteSpace(partTextBox?.Text))
  153. {
  154. var bindingExpression = AssociatedObject.GetBindingExpression(MahApps.Metro.Controls.NumericUpDown.ValueProperty);
  155. bindingExpression?.UpdateTarget();
  156. }
  157. }
  158. }
  159. }