You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

439 lines
15 KiB

  1. using BPA.UIControl.Commons.KnownBoxes;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Text.RegularExpressions;
  7. using System.Threading.Tasks;
  8. using System.Windows;
  9. using System.Windows.Controls;
  10. using System.Windows.Controls.Primitives;
  11. using System.Windows.Data;
  12. using System.Windows.Input;
  13. namespace BPA.UIControl
  14. {
  15. /// <summary>
  16. /// 数值框
  17. /// </summary>
  18. [TemplatePart(Name = TextBoxPartName, Type = typeof(TextBox))]
  19. [TemplatePart(Name = ButtonIncreasePartName, Type = typeof(ButtonBase))]
  20. [TemplatePart(Name = ButtonDecreasePartName, Type = typeof(ButtonBase))]
  21. public class NumericBox : Control
  22. {
  23. /// <summary>
  24. /// 文本框名称
  25. /// </summary>
  26. public const string TextBoxPartName = "PART_TextBox";
  27. /// <summary>
  28. /// 增加按钮名称
  29. /// </summary>
  30. public const string ButtonIncreasePartName = "PART_IncreaseButton";
  31. /// <summary>
  32. /// 减少按钮名称
  33. /// </summary>
  34. public const string ButtonDecreasePartName = "PART_DecreaseButton";
  35. /// <summary>
  36. /// 默认 int 类型正则匹配
  37. /// </summary>
  38. public const string DefaultIntPattern = "[0-9-]";
  39. /// <summary>
  40. /// 默认 double 类型正则匹配
  41. /// </summary>
  42. public const string DefaultDoublePattern = "[0-9-+Ee\\.]";
  43. private TextBox textBox;
  44. /// <summary>
  45. /// Initializes a new instance of the <see cref="NumericBox"/> class.
  46. /// </summary>
  47. static NumericBox()
  48. {
  49. DefaultStyleKeyProperty.OverrideMetadata(typeof(NumericBox), new FrameworkPropertyMetadata(typeof(NumericBox)));
  50. }
  51. /// <inheritdoc/>
  52. public override void OnApplyTemplate()
  53. {
  54. base.OnApplyTemplate();
  55. if (GetTemplateChild(TextBoxPartName) is TextBox textBox)
  56. {
  57. var textBinding = new Binding(nameof(Text));
  58. textBinding.Source = this;
  59. textBinding.Mode = BindingMode.TwoWay;
  60. textBox.SetBinding(TextBox.TextProperty, textBinding);
  61. textBox.TextChanged += TextBox_TextChanged;
  62. textBox.PreviewTextInput += TextBox_PreviewTextInput;
  63. textBox.PreviewKeyDown += TextBox_PreviewKeyDown;
  64. textBox.CommandBindings.Add(new CommandBinding(ApplicationCommands.Paste, null, new CanExecuteRoutedEventHandler(TextBox_CanExecutePaste)));
  65. this.textBox = textBox;
  66. }
  67. if (GetTemplateChild(ButtonIncreasePartName) is ButtonBase upButton)
  68. {
  69. upButton.Click += IncreaseButton_Click;
  70. }
  71. if (GetTemplateChild(ButtonDecreasePartName) is ButtonBase downButton)
  72. {
  73. downButton.Click += DecreaseButton_Click;
  74. }
  75. }
  76. #region events
  77. /// <summary>
  78. /// 值改变事件
  79. /// </summary>
  80. public static readonly RoutedEvent ValueChangedEvent =
  81. EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler<double?>), typeof(NumericBox));
  82. /// <summary>
  83. /// 值改变事件处理
  84. /// </summary>
  85. public event RoutedPropertyChangedEventHandler<double?> ValueChanged
  86. {
  87. add { AddHandler(ValueChangedEvent, value); }
  88. remove { RemoveHandler(ValueChangedEvent, value); }
  89. }
  90. #endregion events
  91. #region propteries
  92. /// <summary>
  93. /// 显示文本
  94. /// </summary>
  95. public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
  96. "Text", typeof(string), typeof(NumericBox), new PropertyMetadata(null, OnTextChanged));
  97. /// <summary>
  98. /// 显示文本
  99. /// </summary>
  100. public string Text
  101. {
  102. get { return (string)GetValue(TextProperty); }
  103. set { SetValue(TextProperty, value); }
  104. }
  105. /// <summary>
  106. /// 文本格式
  107. /// </summary>
  108. public static readonly DependencyProperty TextFormatProperty = DependencyProperty.Register(
  109. "TextFormat", typeof(string), typeof(NumericBox), new PropertyMetadata(default(string)));
  110. /// <summary>
  111. /// 文本格式
  112. /// </summary>
  113. public string TextFormat
  114. {
  115. get { return (string)GetValue(TextFormatProperty); }
  116. set { SetValue(TextFormatProperty, value); }
  117. }
  118. /// <summary>
  119. /// 值
  120. /// </summary>
  121. public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
  122. "Value", typeof(double?), typeof(NumericBox), new FrameworkPropertyMetadata(default(double?), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnValueChanged));
  123. /// <summary>
  124. /// 值
  125. /// </summary>
  126. public double? Value
  127. {
  128. get { return (double?)GetValue(ValueProperty); }
  129. set { SetValue(ValueProperty, value); }
  130. }
  131. /// <summary>
  132. /// 最小值
  133. /// </summary>
  134. public static readonly DependencyProperty MinValueProperty = DependencyProperty.Register(
  135. "MinValue", typeof(double), typeof(NumericBox), new PropertyMetadata(double.MinValue));
  136. /// <summary>
  137. /// 最小值
  138. /// </summary>
  139. public double MinValue
  140. {
  141. get { return (double)GetValue(MinValueProperty); }
  142. set { SetValue(MinValueProperty, value); }
  143. }
  144. /// <summary>
  145. /// 最大值
  146. /// </summary>
  147. public static readonly DependencyProperty MaxValueProperty = DependencyProperty.Register(
  148. "MaxValue", typeof(double), typeof(NumericBox), new PropertyMetadata(double.MaxValue));
  149. /// <summary>
  150. /// 最大值
  151. /// </summary>
  152. public double MaxValue
  153. {
  154. get { return (double)GetValue(MaxValueProperty); }
  155. set { SetValue(MaxValueProperty, value); }
  156. }
  157. /// <summary>
  158. /// 是否显示增减按钮
  159. /// </summary>
  160. public static readonly DependencyProperty ShowButtonProperty = DependencyProperty.Register(
  161. "ShowButton", typeof(bool), typeof(NumericBox), new PropertyMetadata(BooleanBoxes.FalseBox));
  162. /// <summary>
  163. /// 是否显示增减按钮
  164. /// </summary>
  165. public bool ShowButton
  166. {
  167. get { return (bool)GetValue(ShowButtonProperty); }
  168. set { SetValue(ShowButtonProperty, BooleanBoxes.Box(value)); }
  169. }
  170. /// <summary>
  171. /// 最大长度
  172. /// </summary>
  173. public static readonly DependencyProperty MaxLengthProperty = DependencyProperty.Register(
  174. "MaxLength", typeof(int), typeof(NumericBox), new PropertyMetadata(default(int)));
  175. /// <summary>
  176. /// 最大长度
  177. /// </summary>
  178. public int MaxLength
  179. {
  180. get { return (int)GetValue(MaxLengthProperty); }
  181. set { SetValue(MaxLengthProperty, value); }
  182. }
  183. /// <summary>
  184. /// 增减间隔
  185. /// </summary>
  186. public static readonly DependencyProperty IntervalProperty = DependencyProperty.Register(
  187. "Interval", typeof(double), typeof(NumericBox), new PropertyMetadata(default(double)));
  188. /// <summary>
  189. /// 增减间隔
  190. /// </summary>
  191. public double Interval
  192. {
  193. get { return (double)GetValue(IntervalProperty); }
  194. set { SetValue(IntervalProperty, value); }
  195. }
  196. /// <summary>
  197. /// 数值类型
  198. /// </summary>
  199. public static readonly DependencyProperty NumericTypeProperty = DependencyProperty.Register(
  200. "NumericType", typeof(NumericType), typeof(NumericBox), new PropertyMetadata(default(NumericType)));
  201. /// <summary>
  202. /// 数值类型
  203. /// </summary>
  204. public NumericType NumericType
  205. {
  206. get { return (NumericType)GetValue(NumericTypeProperty); }
  207. set { SetValue(NumericTypeProperty, value); }
  208. }
  209. /// <summary>
  210. /// 数值输入正则匹配
  211. /// </summary>
  212. public static readonly DependencyProperty NumericPatternProperty = DependencyProperty.Register(
  213. "NumericPattern", typeof(string), typeof(NumericBox), new PropertyMetadata(default(string)));
  214. /// <summary>
  215. /// 数值输入正则匹配
  216. /// </summary>
  217. public string NumericPattern
  218. {
  219. get { return (string)GetValue(NumericPatternProperty); }
  220. set { SetValue(NumericPatternProperty, value); }
  221. }
  222. /// <summary>
  223. /// 只读
  224. /// </summary>
  225. public static readonly DependencyProperty IsReadOnlyProperty = DependencyProperty.Register(
  226. "IsReadOnly", typeof(bool), typeof(NumericBox), new PropertyMetadata(BooleanBoxes.FalseBox));
  227. /// <summary>
  228. /// 只读
  229. /// </summary>
  230. public bool IsReadOnly
  231. {
  232. get { return (bool)GetValue(IsReadOnlyProperty); }
  233. set { SetValue(IsReadOnlyProperty, BooleanBoxes.Box(value)); }
  234. }
  235. #endregion propteries
  236. #region methods
  237. private static double GetCalculatedValue(NumericBox numberBox, double value)
  238. {
  239. if (value > numberBox.MaxValue)
  240. {
  241. return numberBox.MaxValue;
  242. }
  243. else if (value < numberBox.MinValue)
  244. {
  245. return numberBox.MinValue;
  246. }
  247. else
  248. {
  249. return value;
  250. }
  251. }
  252. private void GetIntervalAndMin(out double interval, out double min)
  253. {
  254. interval = NumericType == NumericType.Double ? Interval : Math.Round(Interval);
  255. min = MinValue == double.MinValue ? 0 : MinValue;
  256. }
  257. private void IncreaseButton_Click(object sender, RoutedEventArgs e)
  258. {
  259. GetIntervalAndMin(out double interval, out double min);
  260. Value = GetCalculatedValue(this, Value == null ? min + interval : Value.GetValueOrDefault() + interval);
  261. textBox.Focus();
  262. }
  263. private void DecreaseButton_Click(object sender, RoutedEventArgs e)
  264. {
  265. GetIntervalAndMin(out double interval, out double min);
  266. Value = GetCalculatedValue(this, Value == null ? min : Value.GetValueOrDefault() - interval);
  267. textBox.Focus();
  268. }
  269. private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  270. {
  271. var numberBox = d as NumericBox;
  272. if (double.TryParse(numberBox.Text, out double value))
  273. {
  274. var newValue = GetCalculatedValue(numberBox, value);
  275. if (numberBox.Value != newValue)
  276. {
  277. if (numberBox.IsLoaded)
  278. {
  279. numberBox.Value = newValue;
  280. }
  281. else
  282. {
  283. numberBox.Loaded += NumberBox_Loaded;
  284. }
  285. }
  286. }
  287. else if (string.IsNullOrEmpty(numberBox.Text))
  288. {
  289. if (InputBoxHelper.GetIsClearable(numberBox))
  290. {
  291. numberBox.Value = null;
  292. }
  293. else
  294. {
  295. if (numberBox.Value.HasValue)
  296. {
  297. numberBox.Text = numberBox.Value.Value.ToString(numberBox.TextFormat);
  298. }
  299. }
  300. }
  301. if (!numberBox.Value.HasValue)
  302. {
  303. numberBox.Text = null;
  304. }
  305. else
  306. {
  307. numberBox.Text = numberBox.Value.Value.ToString(numberBox.TextFormat);
  308. }
  309. }
  310. private static void NumberBox_Loaded(object sender, RoutedEventArgs e)
  311. {
  312. var numberBox = sender as NumericBox;
  313. if (double.TryParse(numberBox.Text, out double value))
  314. {
  315. var newValue = GetCalculatedValue(numberBox, value);
  316. if (numberBox.Value != newValue)
  317. {
  318. numberBox.Value = newValue;
  319. }
  320. }
  321. numberBox.Loaded -= NumberBox_Loaded;
  322. }
  323. private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  324. {
  325. var numberBox = d as NumericBox;
  326. numberBox.Text = numberBox.Value?.ToString(numberBox.TextFormat);
  327. numberBox.textBox?.Select(numberBox.textBox.Text.Length, 1);
  328. var args = new RoutedPropertyChangedEventArgs<double?>((double?)e.OldValue, (double?)e.NewValue);
  329. args.RoutedEvent = NumericBox.ValueChangedEvent;
  330. numberBox.RaiseEvent(args);
  331. }
  332. private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
  333. {
  334. // 限制与数值无关输入
  335. string pattern = string.IsNullOrEmpty(NumericPattern) ? (NumericType == NumericType.Int ? DefaultIntPattern : DefaultDoublePattern) : NumericPattern;
  336. var regex = new Regex(pattern);
  337. e.Handled = !regex.IsMatch(e.Text);
  338. }
  339. private void TextBox_PreviewKeyDown(object sender, KeyEventArgs e)
  340. {
  341. // 限制空格输入
  342. if (e.Key == Key.Space)
  343. {
  344. e.Handled = true;
  345. }
  346. if (e.Key == Key.Enter)
  347. {
  348. if (textBox.IsFocused)
  349. {
  350. this.Focus();
  351. }
  352. }
  353. }
  354. private void TextBox_CanExecutePaste(object sender, CanExecuteRoutedEventArgs e)
  355. {
  356. // 限制粘贴
  357. e.CanExecute = false;
  358. e.Handled = true;
  359. }
  360. private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
  361. {
  362. var textBox = sender as TextBox;
  363. if (double.TryParse(textBox.Text, out double value))
  364. {
  365. if (value < MinValue || value > MaxValue)
  366. {
  367. var newValue = GetCalculatedValue(this, value);
  368. if (Value != newValue)
  369. {
  370. Value = newValue;
  371. }
  372. else
  373. {
  374. textBox.Text = newValue.ToString(TextFormat);
  375. textBox.Select(textBox.Text.Length, 0);
  376. }
  377. }
  378. }
  379. }
  380. #endregion methods
  381. }
  382. }