Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
NumericTextBox.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 using System;
4 using System.Globalization;
5 using System.Reflection;
6 using System.Windows;
7 using System.Windows.Controls;
8 using System.Windows.Controls.Primitives;
9 using System.Windows.Data;
10 using System.Windows.Documents;
11 using System.Windows.Input;
12 using System.Windows.Media;
13 
14 using SiliconStudio.Core.Mathematics;
15 using SiliconStudio.Presentation.Core;
16 using SiliconStudio.Presentation.Extensions;
17 
19 
20 namespace SiliconStudio.Presentation.Controls
21 {
22  /// <summary>
23  /// An enum describing when the related <see cref="NumericTextBox"/> should be validated, when the user uses the mouse to change its value.
24  /// </summary>
26  {
27  /// <summary>
28  /// The validation occurs every time the mouse moves.
29  /// </summary>
31  /// <summary>
32  /// The validation occurs when the mouse button is released.
33  /// </summary>
34  OnMouseUp,
35  }
36 
38  {
39  public RepeatButtonPressedRoutedEventArgs(NumericTextBox.RepeatButtons button, RoutedEvent routedEvent)
40  : base(routedEvent)
41  {
42  Button = button;
43  }
44 
45  public NumericTextBox.RepeatButtons Button { get; private set; }
46  }
47 
48  /// <summary>
49  /// A specialization of the <see cref="TextBoxBase"/> control that can be used for numeric values.
50  /// It contains a <see cref="Value"/> property that is updated on validation.
51  /// </summary>
52  /// PART_IncreaseButton") as RepeatButton;
53  [TemplatePart(Name = "PART_IncreaseButton", Type = typeof(RepeatButton))]
54  [TemplatePart(Name = "PART_DecreaseButton", Type = typeof(RepeatButton))]
55  [TemplatePart(Name = "PART_ContentHost", Type = typeof(ScrollViewer))]
56  public class NumericTextBox : TextBoxBase
57  {
58  public enum RepeatButtons
59  {
60  IncreaseButton,
61  DecreaseButton,
62  }
63 
64  private enum DragState
65  {
66  None,
67  Starting,
68  Dragging,
69  }
70 
71  private DragState dragState;
72  private Point mouseDownPosition;
73  private DragDirectionAdorner adorner;
74  private Orientation dragOrientation;
75  private RepeatButton increaseButton;
76  private RepeatButton decreaseButton;
77  private ScrollViewer contentHost;
78  private bool updatingValue;
79 
80  /// <summary>
81  /// Identifies the <see cref="Value"/> dependency property.
82  /// </summary>
83  public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(double), typeof(NumericTextBox), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnValuePropertyChanged, null, true, UpdateSourceTrigger.Explicit));
84 
85  /// <summary>
86  /// Identifies the <see cref="DecimalPlaces"/> dependency property.
87  /// </summary>
88  public static readonly DependencyProperty DecimalPlacesProperty = DependencyProperty.Register("DecimalPlaces", typeof(int), typeof(NumericTextBox), new FrameworkPropertyMetadata(-1, OnDecimalPlacesPropertyChanged));
89 
90  /// <summary>
91  /// Identifies the <see cref="Minimum"/> dependency property.
92  /// </summary>
93  public static readonly DependencyProperty MinimumProperty = DependencyProperty.Register("Minimum", typeof(double), typeof(NumericTextBox), new FrameworkPropertyMetadata(double.MinValue, OnMinimumPropertyChanged));
94 
95  /// <summary>
96  /// Identifies the <see cref="Maximum"/> dependency property.
97  /// </summary>
98  public static readonly DependencyProperty MaximumProperty = DependencyProperty.Register("Maximum", typeof(double), typeof(NumericTextBox), new FrameworkPropertyMetadata(double.MaxValue, OnMaximumPropertyChanged));
99 
100  /// <summary>
101  /// Identifies the <see cref="ValueRatio"/> dependency property.
102  /// </summary>
103  public static readonly DependencyProperty ValueRatioProperty = DependencyProperty.Register("ValueRatio", typeof(double), typeof(NumericTextBox), new PropertyMetadata(default(double), ValueRatioChanged));
104 
105  /// <summary>
106  /// Identifies the <see cref="LargeChange"/> dependency property.
107  /// </summary>
108  public static readonly DependencyProperty LargeChangeProperty = DependencyProperty.Register("LargeChange", typeof(double), typeof(NumericTextBox), new PropertyMetadata(10.0));
109 
110  /// <summary>
111  /// Identifies the <see cref="SmallChange"/> dependency property.
112  /// </summary>
113  public static readonly DependencyProperty SmallChangeProperty = DependencyProperty.Register("SmallChange", typeof(double), typeof(NumericTextBox), new PropertyMetadata(1.0));
114 
115  /// <summary>
116  /// Identifies the <see cref="DisplayUpDownButtons"/> dependency property.
117  /// </summary>
118  public static readonly DependencyProperty DisplayUpDownButtonsProperty = DependencyProperty.Register("DisplayUpDownButtons", typeof(bool), typeof(NumericTextBox), new PropertyMetadata(true));
119 
120  /// <summary>
121  /// Identifies the <see cref="AllowMouseDrag"/> dependency property.
122  /// </summary>
123  public static readonly DependencyProperty AllowMouseDragProperty = DependencyProperty.Register("AllowMouseDrag", typeof(bool), typeof(NumericTextBox), new PropertyMetadata(true));
124 
125  /// <summary>
126  /// Identifies the <see cref="MouseValidationTrigger"/> dependency property.
127  /// </summary>
128  public static readonly DependencyProperty MouseValidationTriggerProperty = DependencyProperty.Register("MouseValidationTrigger", typeof(MouseValidationTrigger), typeof(NumericTextBox), new PropertyMetadata(MouseValidationTrigger.OnMouseUp));
129 
130  /// <summary>
131  /// Identifies the <see cref="MouseValidationTrigger"/> dependency property.
132  /// </summary>
133  public static readonly DependencyProperty DragCursorProperty = DependencyProperty.Register("DragCursor", typeof(Cursor), typeof(NumericTextBox), new PropertyMetadata(Cursors.ScrollAll));
134 
135  /// <summary>
136  /// Raised when the <see cref="Value"/> property has changed.
137  /// </summary>
138  public static readonly RoutedEvent ValueChangedEvent = EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler<double>), typeof(NumericTextBox));
139 
140  /// <summary>
141  /// Raised when one of the repeat button is pressed.
142  /// </summary>
143  public static readonly RoutedEvent RepeatButtonPressedEvent = EventManager.RegisterRoutedEvent("RepeatButtonPressed", RoutingStrategy.Bubble, typeof(EventHandler<RepeatButtonPressedRoutedEventArgs>), typeof(NumericTextBox));
144 
145  /// <summary>
146  /// Raised when one of the repeat button is released.
147  /// </summary>
148  public static readonly RoutedEvent RepeatButtonReleasedEvent = EventManager.RegisterRoutedEvent("RepeatButtonReleased", RoutingStrategy.Bubble, typeof(EventHandler<RepeatButtonPressedRoutedEventArgs>), typeof(NumericTextBox));
149 
150  /// <summary>
151  /// Increases the current value with the value of the <see cref="LargeChange"/> property.
152  /// </summary>
153  public static RoutedCommand LargeIncreaseCommand { get; private set; }
154 
155  /// <summary>
156  /// Increases the current value with the value of the <see cref="SmallChange"/> property.
157  /// </summary>
158  public static RoutedCommand SmallIncreaseCommand { get; private set; }
159 
160  /// <summary>
161  /// Decreases the current value with the value of the <see cref="LargeChange"/> property.
162  /// </summary>
163  public static RoutedCommand LargeDecreaseCommand { get; private set; }
164 
165  /// <summary>
166  /// Decreases the current value with the value of the <see cref="SmallChange"/> property.
167  /// </summary>
168  public static RoutedCommand SmallDecreaseCommand { get; private set; }
169 
170  static NumericTextBox()
171  {
172  DefaultStyleKeyProperty.OverrideMetadata(typeof(NumericTextBox), new FrameworkPropertyMetadata(typeof(NumericTextBox)));
173  HorizontalScrollBarVisibilityProperty.OverrideMetadata(typeof(NumericTextBox), new FrameworkPropertyMetadata(ScrollBarVisibility.Hidden, OnForbiddenPropertyChanged));
174  VerticalScrollBarVisibilityProperty.OverrideMetadata(typeof(NumericTextBox), new FrameworkPropertyMetadata(ScrollBarVisibility.Hidden, OnForbiddenPropertyChanged));
175  AcceptsReturnProperty.OverrideMetadata(typeof(NumericTextBox), new FrameworkPropertyMetadata(false, OnForbiddenPropertyChanged));
176  AcceptsTabProperty.OverrideMetadata(typeof(NumericTextBox), new FrameworkPropertyMetadata(false, OnForbiddenPropertyChanged));
177 
178  // Since the NumericTextBox is not focusable itself, we have to bind the commands to the inner text box of the control.
179  // The handlers will then find the parent that is a NumericTextBox and process the command on this control if it is found.
180  LargeIncreaseCommand = new RoutedCommand("LargeIncreaseCommand", typeof(System.Windows.Controls.TextBox));
181  CommandManager.RegisterClassCommandBinding(typeof(System.Windows.Controls.TextBox), new CommandBinding(LargeIncreaseCommand, OnLargeIncreaseCommand));
182  CommandManager.RegisterClassInputBinding(typeof(System.Windows.Controls.TextBox), new InputBinding(LargeIncreaseCommand, new KeyGesture(Key.PageUp)));
183  CommandManager.RegisterClassInputBinding(typeof(System.Windows.Controls.TextBox), new InputBinding(LargeIncreaseCommand, new KeyGesture(Key.Up, ModifierKeys.Shift)));
184 
185  LargeDecreaseCommand = new RoutedCommand("LargeDecreaseCommand", typeof(System.Windows.Controls.TextBox));
186  CommandManager.RegisterClassCommandBinding(typeof(System.Windows.Controls.TextBox), new CommandBinding(LargeDecreaseCommand, OnLargeDecreaseCommand));
187  CommandManager.RegisterClassInputBinding(typeof(System.Windows.Controls.TextBox), new InputBinding(LargeDecreaseCommand, new KeyGesture(Key.PageDown)));
188  CommandManager.RegisterClassInputBinding(typeof(System.Windows.Controls.TextBox), new InputBinding(LargeDecreaseCommand, new KeyGesture(Key.Down, ModifierKeys.Shift)));
189 
190  SmallIncreaseCommand = new RoutedCommand("SmallIncreaseCommand", typeof(System.Windows.Controls.TextBox));
191  CommandManager.RegisterClassCommandBinding(typeof(System.Windows.Controls.TextBox), new CommandBinding(SmallIncreaseCommand, OnSmallIncreaseCommand));
192  CommandManager.RegisterClassInputBinding(typeof(System.Windows.Controls.TextBox), new InputBinding(SmallIncreaseCommand, new KeyGesture(Key.Up)));
193 
194  SmallDecreaseCommand = new RoutedCommand("SmallDecreaseCommand", typeof(System.Windows.Controls.TextBox));
195  CommandManager.RegisterClassCommandBinding(typeof(System.Windows.Controls.TextBox), new CommandBinding(SmallDecreaseCommand, OnSmallDecreaseCommand));
196  CommandManager.RegisterClassInputBinding(typeof(System.Windows.Controls.TextBox), new InputBinding(SmallDecreaseCommand, new KeyGesture(Key.Down)));
197  }
198 
199  /// <summary>
200  /// Gets or sets the current value of the <see cref="NumericTextBox"/>.
201  /// </summary>
202  public double Value { get { return (double)GetValue(ValueProperty); } set { SetValue(ValueProperty, value); } }
203 
204  /// <summary>
205  /// Gets or sets the number of decimal places displayed in the <see cref="NumericTextBox"/>.
206  /// </summary>
207  public int DecimalPlaces { get { return (int)GetValue(DecimalPlacesProperty); } set { SetValue(DecimalPlacesProperty, value); } }
208 
209  /// <summary>
210  /// Gets or sets the minimum value that can be set on the <see cref="Value"/> property.
211  /// </summary>
212  public double Minimum { get { return (double)GetValue(MinimumProperty); } set { SetValue(MinimumProperty, value); } }
213 
214  /// <summary>
215  /// Gets or sets the maximum value that can be set on the <see cref="Value"/> property.
216  /// </summary>
217  public double Maximum { get { return (double)GetValue(MaximumProperty); } set { SetValue(MaximumProperty, value); } }
218 
219  /// <summary>
220  /// Gets or sets the ratio of the <see cref="NumericTextBox.Value"/> between <see cref="NumericTextBox.Minimum"/> (0.0) and
221  /// <see cref="NumericTextBox.Maximum"/> (1.0).
222  /// </summary>
223  public double ValueRatio { get { return (double)GetValue(ValueRatioProperty); } set { SetValue(ValueRatioProperty, value); } }
224 
225  /// <summary>
226  /// Gets or sets the value to be added to or substracted from the <see cref="NumericTextBox.Value"/>.
227  /// </summary>
228  public double LargeChange { get { return (double)GetValue(LargeChangeProperty); } set { SetValue(LargeChangeProperty, value); } }
229 
230  /// <summary>
231  /// Gets or sets the value to be added to or substracted from the <see cref="NumericTextBox.Value"/>.
232  /// </summary>
233  public double SmallChange { get { return (double)GetValue(SmallChangeProperty); } set { SetValue(SmallChangeProperty, value); } }
234 
235  /// <summary>
236  /// Gets or sets whether to display Up and Down buttons on the side of the <see cref="NumericTextBox"/>.
237  /// </summary>
238  public bool DisplayUpDownButtons { get { return (bool)GetValue(DisplayUpDownButtonsProperty); } set { SetValue(DisplayUpDownButtonsProperty, value); } }
239 
240  /// <summary>
241  /// Gets or sets whether dragging the value of the <see cref="NumericTextBox"/> is enabled.
242  /// </summary>
243  public bool AllowMouseDrag { get { return (bool)GetValue(AllowMouseDragProperty); } set { SetValue(AllowMouseDragProperty, value); } }
244 
245  /// <summary>
246  /// Gets or sets the <see cref="Cursor"/> to display when the value can be modified via dragging.
247  /// </summary>
248  public Cursor DragCursor { get { return (Cursor)GetValue(DragCursorProperty); } set { SetValue(DragCursorProperty, value); } }
249 
250  /// <summary>
251  /// Gets or sets when the <see cref="NumericTextBox"/> should be validated when the user uses the mouse to change its value.
252  /// </summary>
253  public MouseValidationTrigger MouseValidationTrigger { get { return (MouseValidationTrigger)GetValue(MouseValidationTriggerProperty); } set { SetValue(MouseValidationTriggerProperty, value); } }
254 
255  /// <summary>
256  /// Raised when the <see cref="Value"/> property has changed.
257  /// </summary>
258  public event RoutedPropertyChangedEventHandler<double> ValueChanged { add { AddHandler(ValueChangedEvent, value); } remove { RemoveHandler(ValueChangedEvent, value); } }
259 
260  /// <summary>
261  /// Raised when one of the repeat button is pressed.
262  /// </summary>
263  public event EventHandler<RepeatButtonPressedRoutedEventArgs> RepeatButtonPressed { add { AddHandler(RepeatButtonPressedEvent, value); } remove { RemoveHandler(RepeatButtonPressedEvent, value); } }
264 
265  /// <summary>
266  /// Raised when one of the repeat button is released.
267  /// </summary>
268  public event EventHandler<RepeatButtonPressedRoutedEventArgs> RepeatButtonReleased { add { AddHandler(RepeatButtonReleasedEvent, value); } remove { RemoveHandler(RepeatButtonReleasedEvent, value); } }
269 
270  /// <inheritdoc/>
271  public override void OnApplyTemplate()
272  {
273  base.OnApplyTemplate();
274 
275  increaseButton = GetTemplateChild("PART_IncreaseButton") as RepeatButton;
276  if (increaseButton == null)
277  throw new InvalidOperationException("A part named 'PART_IncreaseButton' must be present in the ControlTemplate, and must be of type 'RepeatButton'.");
278 
279  decreaseButton = GetTemplateChild("PART_DecreaseButton") as RepeatButton;
280  if (decreaseButton == null)
281  throw new InvalidOperationException("A part named 'PART_DecreaseButton' must be present in the ControlTemplate, and must be of type 'RepeatButton'.");
282 
283  contentHost = GetTemplateChild("PART_ContentHost") as ScrollViewer;
284  if (contentHost == null)
285  throw new InvalidOperationException("A part named 'PART_ContentHost' must be present in the ControlTemplate, and must be of type 'ScrollViewer'.");
286 
287  var increasePressedWatcher = new DependencyPropertyWatcher(increaseButton);
288  increasePressedWatcher.RegisterValueChangedHandler(ButtonBase.IsPressedProperty, RepeatButtonIsPressedChanged);
289  var decreasePressedWatcher = new DependencyPropertyWatcher(decreaseButton);
290  decreasePressedWatcher.RegisterValueChangedHandler(ButtonBase.IsPressedProperty, RepeatButtonIsPressedChanged);
291  var textValue = FormatValue(Value);
292 
293  SetCurrentValue(TextProperty, textValue);
294 
295  contentHost.QueryCursor += HostQueryCursor;
296  }
297 
298  /// <summary>
299  /// Raised when the <see cref="Value"/> property has changed.
300  /// </summary>
301  protected virtual void OnValueChanged(double oldValue, double newValue)
302  {
303  }
304 
305  /// <inheritdoc/>
306  protected override void OnInitialized(EventArgs e)
307  {
308  base.OnInitialized(e);
309  var textValue = FormatValue(Value);
310  SetCurrentValue(TextProperty, textValue);
311  }
312 
313  /// <inheritdoc/>
314  protected override void OnPreviewMouseDown(MouseButtonEventArgs e)
315  {
316  base.OnPreviewMouseDown(e);
317 
318  if (!IsContentHostPart(e.OriginalSource))
319  return;
320 
321  if (AllowMouseDrag && IsReadOnly == false && IsFocused == false)
322  {
323  dragState = DragState.Starting;
324 
325  if (adorner == null)
326  {
327  adorner = new DragDirectionAdorner(this, contentHost.ActualWidth);
328  var adornerLayer = AdornerLayer.GetAdornerLayer(this);
329  if (adornerLayer != null)
330  adornerLayer.Add(adorner);
331  }
332 
333  mouseDownPosition = e.GetPosition(this);
334 
335  Mouse.OverrideCursor = Cursors.None;
336  e.Handled = true;
337  }
338  }
339 
340 
341  /// <inheritdoc/>
342  protected override void OnPreviewMouseMove(MouseEventArgs e)
343  {
344  base.OnPreviewMouseMove(e);
345 
346  Point position = e.GetPosition(this);
347 
348  if (AllowMouseDrag && dragState == DragState.Starting && e.LeftButton == MouseButtonState.Pressed)
349  {
350  double dx = Math.Abs(position.X - mouseDownPosition.X);
351  double dy = Math.Abs(position.Y - mouseDownPosition.Y);
352  dragOrientation = dx >= dy ? Orientation.Horizontal : Orientation.Vertical;
353 
354  if (dx > SystemParameters.MinimumHorizontalDragDistance || dy > SystemParameters.MinimumVerticalDragDistance)
355  {
356  e.MouseDevice.Capture(this);
357  dragState = DragState.Dragging;
358  SelectAll();
359  if (adorner != null)
360  adorner.SetOrientation(dragOrientation);
361  }
362  }
363 
364  if (dragState == DragState.Dragging)
365  {
366  double delta;
367 
368  if (dragOrientation == Orientation.Horizontal)
369  delta = position.X - mouseDownPosition.X;
370  else
371  delta = mouseDownPosition.Y - position.Y;
372 
373  var newValue = Value + delta * SmallChange;
374 
375  SetCurrentValue(ValueProperty, newValue);
376 
378  {
379  Validate();
380  }
381  NativeHelper.SetCursorPos(PointToScreen(mouseDownPosition));
382  }
383  }
384 
385  /// <inheritdoc/>
386  protected override void OnPreviewMouseUp(MouseButtonEventArgs e)
387  {
388  base.OnPreviewMouseUp(e);
389 
390  if (ReferenceEquals(e.MouseDevice.Captured, this))
391  e.MouseDevice.Capture(null);
392 
393  if (dragState == DragState.Starting)
394  {
395  Select(0, Text.Length);
396  if (!IsFocused)
397  {
398  Keyboard.Focus(this);
399  }
400  }
401  else if (dragState == DragState.Dragging && AllowMouseDrag)
402  {
403  if (adorner != null)
404  {
405  var adornerLayer = AdornerLayer.GetAdornerLayer(this);
406  if (adornerLayer != null)
407  {
408  adornerLayer.Remove(adorner);
409  adorner = null;
410  }
411  }
412  Validate();
413  }
414 
415  Mouse.OverrideCursor = null;
416  dragState = DragState.None;
417  }
418 
419  protected sealed override void OnCancelled()
420  {
421  var textValue = FormatValue(Value);
422  SetCurrentValue(TextProperty, textValue);
423  }
424 
425  /// <inheritdoc/>
426  protected sealed override void OnValidated()
427  {
428  double value;
429  try
430  {
431  try
432  {
433  value = double.Parse(Text);
434  }
435  catch (Exception)
436  {
437  value = double.Parse(Text, CultureInfo.InvariantCulture);
438  }
439  }
440  catch (Exception)
441  {
442  value = Value;
443  }
444  SetCurrentValue(ValueProperty, value);
445 
446  BindingExpression expression = GetBindingExpression(ValueProperty);
447  if (expression != null)
448  expression.UpdateSource();
449  }
450 
451  /// <inheritdoc/>
452  protected override string CoerceTextForValidation(string baseValue)
453  {
454  baseValue = base.CoerceTextForValidation(baseValue);
455  double value;
456  try
457  {
458  try
459  {
460  value = double.Parse(baseValue);
461  }
462  catch (Exception)
463  {
464  value = double.Parse(baseValue, CultureInfo.InvariantCulture);
465  }
466  if (value > Maximum)
467  {
468  value = Maximum;
469  }
470  if (value < Minimum)
471  {
472  value = Minimum;
473  }
474  }
475  catch (Exception)
476  {
477  value = Value;
478  }
479 
480  return FormatValue(value);
481  }
482 
483  /// <summary>
484  /// Formats the text to
485  /// </summary>
486  /// <param name="value"></param>
487  /// <returns></returns>
488  protected string FormatValue(double value)
489  {
490  int decimalPlaces = DecimalPlaces;
491  double coercedValue = decimalPlaces < 0 ? value : Math.Round(value, decimalPlaces);
492  return coercedValue.ToString(CultureInfo.InvariantCulture);
493  }
494 
495  private void RepeatButtonIsPressedChanged(object sender, EventArgs e)
496  {
497  var repeatButton = (RepeatButton)sender;
498  if (ReferenceEquals(repeatButton, increaseButton))
499  {
500  RaiseEvent(new RepeatButtonPressedRoutedEventArgs(RepeatButtons.IncreaseButton, repeatButton.IsPressed ? RepeatButtonPressedEvent : RepeatButtonReleasedEvent));
501  }
502  if (ReferenceEquals(repeatButton, decreaseButton))
503  {
504  RaiseEvent(new RepeatButtonPressedRoutedEventArgs(RepeatButtons.DecreaseButton, repeatButton.IsPressed ? RepeatButtonPressedEvent : RepeatButtonReleasedEvent));
505  }
506  }
507 
508  private void OnValuePropertyChanged(double oldValue, double newValue)
509  {
510  if (newValue > Maximum)
511  {
512  SetCurrentValue(ValueProperty, Maximum);
513  return;
514  }
515  if (newValue < Minimum)
516  {
517  SetCurrentValue(ValueProperty, Minimum);
518  return;
519  }
520 
521  var textValue = FormatValue(newValue);
522  updatingValue = true;
523  SetCurrentValue(TextProperty, textValue);
524  SetCurrentValue(ValueRatioProperty, MathUtil.InverseLerp(Minimum, Maximum, newValue));
525  updatingValue = false;
526 
527  RaiseEvent(new RoutedPropertyChangedEventArgs<double>(oldValue, newValue, ValueChangedEvent));
528  OnValueChanged(oldValue, newValue);
529  }
530 
531  private void UpdateValue(double value)
532  {
533  if (IsReadOnly == false)
534  {
535  SetCurrentValue(ValueProperty, value);
536  }
537  }
538 
539  private void HostQueryCursor(object sender, QueryCursorEventArgs e)
540  {
541  if (!IsContentHostPart(e.OriginalSource))
542  return;
543 
544  if (AllowMouseDrag && !IsFocused && DragCursor != null)
545  {
546  e.Cursor = DragCursor;
547  e.Handled = true;
548  }
549  }
550 
551  private bool IsContentHostPart(object obj)
552  {
553  var frameworkElement = obj as FrameworkElement;
554  return Equals(obj, contentHost) || (frameworkElement != null && Equals(frameworkElement.Parent, contentHost));
555  }
556 
557  private static void OnValuePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
558  {
559  ((NumericTextBox)sender).OnValuePropertyChanged((double)e.OldValue, (double)e.NewValue);
560  }
561 
562  private static void OnDecimalPlacesPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
563  {
564  var numericInput = (NumericTextBox)sender;
565  numericInput.CoerceValue(ValueProperty);
566  }
567 
568  private static void OnMinimumPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
569  {
570  var numericInput = (NumericTextBox)sender;
571  bool needValidation = false;
572  if (numericInput.Maximum < numericInput.Minimum)
573  {
574  numericInput.SetCurrentValue(MaximumProperty, numericInput.Minimum);
575  needValidation = true;
576  }
577  if (numericInput.Value < numericInput.Minimum)
578  {
579  numericInput.SetCurrentValue(ValueProperty, numericInput.Minimum);
580  needValidation = true;
581  }
582 
583  // Do not overwrite the Value, it is already correct!
584  numericInput.updatingValue = true;
585  numericInput.SetCurrentValue(ValueRatioProperty, MathUtil.InverseLerp(numericInput.Minimum, numericInput.Maximum, numericInput.Value));
586  numericInput.updatingValue = false;
587 
588  if (needValidation)
589  {
590  numericInput.Validate();
591  }
592  }
593 
594  private static void OnMaximumPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
595  {
596  var numericInput = (NumericTextBox)sender;
597  bool needValidation = false;
598  if (numericInput.Minimum > numericInput.Maximum)
599  {
600  numericInput.SetCurrentValue(MinimumProperty, numericInput.Maximum);
601  needValidation = true;
602  }
603  if (numericInput.Value > numericInput.Maximum)
604  {
605  numericInput.SetCurrentValue(ValueProperty, numericInput.Maximum);
606  needValidation = true;
607  }
608 
609  // Do not overwrite the Value, it is already correct!
610  numericInput.updatingValue = true;
611  numericInput.SetCurrentValue(ValueRatioProperty, MathUtil.InverseLerp(numericInput.Minimum, numericInput.Maximum, numericInput.Value));
612  numericInput.updatingValue = false;
613 
614  if (needValidation)
615  {
616  numericInput.Validate();
617  }
618  }
619 
620  private static void ValueRatioChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
621  {
622  var control = (NumericTextBox)d;
623  if (control != null && !control.updatingValue)
624  control.UpdateValue(MathUtil.Lerp(control.Minimum, control.Maximum, (double)e.NewValue));
625  }
626 
627  private static void UpdateValueCommand(object sender, Func<NumericTextBox, double> getValue)
628  {
629  var control = (sender as NumericTextBox) ?? ((System.Windows.Controls.TextBox)sender).FindVisualParentOfType<NumericTextBox>();
630  if (control != null)
631  {
632  var value = getValue(control);
633  control.UpdateValue(value);
634  control.SelectAll();
635  control.Validate();
636  }
637  }
638 
639  private static void OnLargeIncreaseCommand(object sender, ExecutedRoutedEventArgs e)
640  {
641  UpdateValueCommand(sender, x => x.Value + x.LargeChange);
642  }
643 
644  private static void OnLargeDecreaseCommand(object sender, ExecutedRoutedEventArgs e)
645  {
646  UpdateValueCommand(sender, x => x.Value - x.LargeChange);
647  }
648 
649  private static void OnSmallIncreaseCommand(object sender, ExecutedRoutedEventArgs e)
650  {
651  UpdateValueCommand(sender, x => x.Value + x.SmallChange);
652  }
653 
654  private static void OnSmallDecreaseCommand(object sender, ExecutedRoutedEventArgs e)
655  {
656  UpdateValueCommand(sender, x => x.Value - x.SmallChange);
657  }
658 
659  private static void OnForbiddenPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
660  {
661  var metadata = e.Property.GetMetadata(d);
662  if (!Equals(e.NewValue, metadata.DefaultValue))
663  {
664  var message = string.Format("The value of the property '{0}' cannot be different from the value '{1}'", e.Property.Name, metadata.DefaultValue);
665  throw new InvalidOperationException(message);
666  }
667  }
668 
669  private class DragDirectionAdorner : Adorner
670  {
671  private readonly double contentWidth;
672  private static readonly ImageSource CursorHorizontalImageSource;
673  private static readonly ImageSource CursorVerticalImageSource;
674 
675  static DragDirectionAdorner()
676  {
677  var asmName = Assembly.GetExecutingAssembly().GetName().Name;
678  CursorHorizontalImageSource = ImageExtensions.ImageSourceFromFile(string.Format("pack://application:,,,/{0};component/Resources/Images/cursor_west_east.png", asmName));
679  CursorVerticalImageSource = ImageExtensions.ImageSourceFromFile(string.Format("pack://application:,,,/{0};component/Resources/Images/cursor_north_south.png", asmName));
680  }
681 
682  private Orientation dragOrientation;
683  private bool ready;
684 
685  internal DragDirectionAdorner(UIElement adornedElement, double contentWidth)
686  : base(adornedElement)
687  {
688  this.contentWidth = contentWidth;
689  }
690 
691  internal void SetOrientation(Orientation orientation)
692  {
693  ready = true;
694  dragOrientation = orientation;
695  InvalidateVisual();
696  }
697 
698  protected override void OnRender(DrawingContext drawingContext)
699  {
700  base.OnRender(drawingContext);
701 
702  if (ready == false)
703  return;
704 
705  VisualEdgeMode = EdgeMode.Aliased;
706  var source = dragOrientation == Orientation.Horizontal ? CursorHorizontalImageSource : CursorVerticalImageSource;
707  double left = Math.Round(contentWidth - source.Width);
708  double top = Math.Round((AdornedElement.RenderSize.Height - source.Height) * 0.5);
709  drawingContext.DrawImage(source, new Rect(new Point(left, top), new Size(source.Width, source.Height)));
710  }
711  }
712 
713  // TODO: Constraint accepted characters? Legacy code that can help here:
714  //public static readonly string NumberDecimalSeparator;
715  //public static readonly string NumberGroupSeparator;
716  //public static readonly string NegativeSign;
717 
718  // Previously in static constructor:
719  //NumberFormatInfo numberFormatInfo = CultureInfo.CurrentCulture.NumberFormat;
720  //NumberDecimalSeparator = numberFormatInfo.NumberDecimalSeparator;
721  //NumberGroupSeparator = numberFormatInfo.NumberGroupSeparator;
722  //NegativeSign = numberFormatInfo.NegativeSign;
723  // -> But we should fetch these value each time we need them because the current culture can be changed at runtime!
724 
725  //protected override void OnPreviewKeyDown(KeyEventArgs e)
726  //{
727  // if (IsInvalidCommon(e.Key))
728  // {
729  // e.Handled = true;
730  // return;
731  // }
732 
733  // base.OnPreviewKeyDown(e);
734  //}
735 
736  //protected virtual bool IsInvalidCommon(Key key)
737  //{
738  // return false;
739  //}
740 
741  //protected override void OnPreviewTextInput(TextCompositionEventArgs e)
742  //{
743  // base.OnPreviewTextInput(e);
744 
745  // string str = e.Text;
746  // if (str == null || str.Length != 1)
747  // {
748  // e.Handled = true;
749  // return;
750  // }
751 
752  // e.Handled = !IsValidStringElement(str);
753  //}
754 
755  //protected virtual bool IsValidStringElement(string c)
756  //{
757  // return IsValidFloatingPointStringElement(c);
758  //}
759 
760  //public static bool IsValidFloatingPointStringElement(string c)
761  //{
762  // if (c == NumberDecimalSeparator || c == NumberGroupSeparator || c == NegativeSign)
763  // return true;
764 
765  // char cc = c[0];
766  // if (cc >= '0' && cc <= '9')
767  // return true;
768 
769  // return false;
770  //}
771 
772  //public static bool IsValidIntegerStringElement(string c)
773  //{
774  // if (c == NumberGroupSeparator || c == NegativeSign)
775  // return true;
776 
777  // char cc = c[0];
778  // if (cc >= '0' && cc <= '9')
779  // return true;
780 
781  // return false;
782  //}
783 
784  //public static bool IsValidHexadecimalStringElement(string c)
785  //{
786  // char cc = c[0];
787  // if ((cc >= 'a' && cc <= 'f') || (cc >= 'A' && cc <= 'F'))
788  // return true;
789 
790  // return false;
791  //}
792  }
793 }
System.Windows.Point Point
override string CoerceTextForValidation(string baseValue)
Coerces the text during the validation process. This method is invoked by Validate. The value to coerce.The coerced value.
sealed override void OnCancelled()
Raised when the current changes have been cancelled.
override void OnPreviewMouseMove(MouseEventArgs e)
A specialization of the TextBoxBase control that can be used for numeric values. It contains a Value ...
The validation occurs every time the mouse moves.
override void OnPreviewMouseUp(MouseButtonEventArgs e)
The validation occurs when the mouse button is released.
An implementation of the System.Windows.Controls.TextBox control that provides additional features su...
Definition: TextBoxBase.cs:13
virtual void OnValueChanged(double oldValue, double newValue)
Raised when the Value property has changed.
_In_ size_t _In_ const TexMetadata & metadata
Definition: DirectXTexP.h:116
string FormatValue(double value)
Formats the text to
Structure using the same layout than System.Drawing.Point.
Definition: Point.cs:35
RepeatButtonPressedRoutedEventArgs(NumericTextBox.RepeatButtons button, RoutedEvent routedEvent)
MouseValidationTrigger
An enum describing when the related NumericTextBox should be validated, when the user uses the mouse ...
override void OnPreviewMouseDown(MouseButtonEventArgs e)
sealed override void OnValidated()
Raised when the current changes have has been validated.
Android.Widget.Orientation Orientation
Definition: Section.cs:9
static float InverseLerp(float min, float max, float value)
Inverse-interpolates a value linearly.
Definition: MathUtil.cs:312