4 using System.Globalization;
5 using System.Reflection;
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;
14 using SiliconStudio.Core.Mathematics;
15 using SiliconStudio.Presentation.Core;
16 using SiliconStudio.Presentation.Extensions;
20 namespace SiliconStudio.Presentation.Controls
53 [TemplatePart(Name =
"PART_IncreaseButton", Type = typeof(RepeatButton))]
54 [TemplatePart(Name =
"PART_DecreaseButton", Type = typeof(RepeatButton))]
55 [TemplatePart(Name =
"PART_ContentHost", Type = typeof(ScrollViewer))]
64 private enum DragState
71 private DragState dragState;
72 private Point mouseDownPosition;
73 private DragDirectionAdorner adorner;
75 private RepeatButton increaseButton;
76 private RepeatButton decreaseButton;
77 private ScrollViewer contentHost;
78 private bool updatingValue;
83 public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
"Value", typeof(
double), typeof(
NumericTextBox),
new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnValuePropertyChanged, null,
true, UpdateSourceTrigger.Explicit));
88 public static readonly DependencyProperty DecimalPlacesProperty = DependencyProperty.Register(
"DecimalPlaces", typeof(
int), typeof(
NumericTextBox),
new FrameworkPropertyMetadata(-1, OnDecimalPlacesPropertyChanged));
93 public static readonly DependencyProperty MinimumProperty = DependencyProperty.Register(
"Minimum", typeof(
double), typeof(
NumericTextBox),
new FrameworkPropertyMetadata(
double.MinValue, OnMinimumPropertyChanged));
98 public static readonly DependencyProperty MaximumProperty = DependencyProperty.Register(
"Maximum", typeof(
double), typeof(
NumericTextBox),
new FrameworkPropertyMetadata(
double.MaxValue, OnMaximumPropertyChanged));
103 public static readonly DependencyProperty ValueRatioProperty = DependencyProperty.Register(
"ValueRatio", typeof(
double), typeof(
NumericTextBox),
new PropertyMetadata(
default(
double), ValueRatioChanged));
108 public static readonly DependencyProperty LargeChangeProperty = DependencyProperty.Register(
"LargeChange", typeof(
double), typeof(
NumericTextBox),
new PropertyMetadata(10.0));
113 public static readonly DependencyProperty SmallChangeProperty = DependencyProperty.Register(
"SmallChange", typeof(
double), typeof(
NumericTextBox),
new PropertyMetadata(1.0));
118 public static readonly DependencyProperty DisplayUpDownButtonsProperty = DependencyProperty.Register(
"DisplayUpDownButtons", typeof(
bool), typeof(
NumericTextBox),
new PropertyMetadata(
true));
123 public static readonly DependencyProperty AllowMouseDragProperty = DependencyProperty.Register(
"AllowMouseDrag", typeof(
bool), typeof(
NumericTextBox),
new PropertyMetadata(
true));
133 public static readonly DependencyProperty DragCursorProperty = DependencyProperty.Register(
"DragCursor", typeof(Cursor), typeof(
NumericTextBox),
new PropertyMetadata(Cursors.ScrollAll));
138 public static readonly RoutedEvent ValueChangedEvent = EventManager.RegisterRoutedEvent(
"ValueChanged", RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler<double>), typeof(
NumericTextBox));
143 public static readonly RoutedEvent RepeatButtonPressedEvent = EventManager.RegisterRoutedEvent(
"RepeatButtonPressed", RoutingStrategy.Bubble, typeof(EventHandler<RepeatButtonPressedRoutedEventArgs>), typeof(
NumericTextBox));
148 public static readonly RoutedEvent RepeatButtonReleasedEvent = EventManager.RegisterRoutedEvent(
"RepeatButtonReleased", RoutingStrategy.Bubble, typeof(EventHandler<RepeatButtonPressedRoutedEventArgs>), typeof(
NumericTextBox));
153 public static RoutedCommand LargeIncreaseCommand {
get;
private set; }
158 public static RoutedCommand SmallIncreaseCommand {
get;
private set; }
163 public static RoutedCommand LargeDecreaseCommand {
get;
private set; }
168 public static RoutedCommand SmallDecreaseCommand {
get;
private set; }
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));
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)));
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)));
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)));
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)));
202 public double Value {
get {
return (
double)GetValue(ValueProperty); } set { SetValue(ValueProperty, value); } }
207 public int DecimalPlaces {
get {
return (
int)GetValue(DecimalPlacesProperty); } set { SetValue(DecimalPlacesProperty, value); } }
212 public double Minimum {
get {
return (
double)GetValue(MinimumProperty); } set { SetValue(MinimumProperty, value); } }
217 public double Maximum {
get {
return (
double)GetValue(MaximumProperty); } set { SetValue(MaximumProperty, value); } }
223 public double ValueRatio {
get {
return (
double)GetValue(ValueRatioProperty); } set { SetValue(ValueRatioProperty, value); } }
228 public double LargeChange {
get {
return (
double)GetValue(LargeChangeProperty); } set { SetValue(LargeChangeProperty, value); } }
233 public double SmallChange {
get {
return (
double)GetValue(SmallChangeProperty); } set { SetValue(SmallChangeProperty, value); } }
238 public bool DisplayUpDownButtons {
get {
return (
bool)GetValue(DisplayUpDownButtonsProperty); } set { SetValue(DisplayUpDownButtonsProperty, value); } }
243 public bool AllowMouseDrag {
get {
return (
bool)GetValue(AllowMouseDragProperty); } set { SetValue(AllowMouseDragProperty, value); } }
248 public Cursor DragCursor {
get {
return (Cursor)GetValue(DragCursorProperty); } set { SetValue(DragCursorProperty, value); } }
258 public event RoutedPropertyChangedEventHandler<double> ValueChanged { add { AddHandler(ValueChangedEvent, value); }
remove { RemoveHandler(ValueChangedEvent, value); } }
263 public event EventHandler<RepeatButtonPressedRoutedEventArgs> RepeatButtonPressed { add { AddHandler(RepeatButtonPressedEvent, value); }
remove { RemoveHandler(RepeatButtonPressedEvent, value); } }
268 public event EventHandler<RepeatButtonPressedRoutedEventArgs> RepeatButtonReleased { add { AddHandler(RepeatButtonReleasedEvent, value); }
remove { RemoveHandler(RepeatButtonReleasedEvent, value); } }
273 base.OnApplyTemplate();
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'.");
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'.");
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'.");
288 increasePressedWatcher.RegisterValueChangedHandler(ButtonBase.IsPressedProperty, RepeatButtonIsPressedChanged);
290 decreasePressedWatcher.RegisterValueChangedHandler(ButtonBase.IsPressedProperty, RepeatButtonIsPressedChanged);
291 var textValue = FormatValue(Value);
293 SetCurrentValue(TextProperty, textValue);
295 contentHost.QueryCursor += HostQueryCursor;
308 base.OnInitialized(e);
309 var textValue = FormatValue(Value);
310 SetCurrentValue(TextProperty, textValue);
316 base.OnPreviewMouseDown(e);
318 if (!IsContentHostPart(e.OriginalSource))
321 if (AllowMouseDrag && IsReadOnly ==
false && IsFocused ==
false)
323 dragState = DragState.Starting;
327 adorner =
new DragDirectionAdorner(
this, contentHost.ActualWidth);
328 var adornerLayer = AdornerLayer.GetAdornerLayer(
this);
329 if (adornerLayer != null)
330 adornerLayer.Add(adorner);
333 mouseDownPosition = e.GetPosition(
this);
335 Mouse.OverrideCursor = Cursors.None;
344 base.OnPreviewMouseMove(e);
346 Point position = e.GetPosition(
this);
348 if (AllowMouseDrag && dragState == DragState.Starting && e.LeftButton == MouseButtonState.Pressed)
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;
354 if (dx > SystemParameters.MinimumHorizontalDragDistance || dy > SystemParameters.MinimumVerticalDragDistance)
356 e.MouseDevice.Capture(
this);
357 dragState = DragState.Dragging;
360 adorner.SetOrientation(dragOrientation);
364 if (dragState == DragState.Dragging)
369 delta = position.X - mouseDownPosition.X;
371 delta = mouseDownPosition.Y - position.Y;
373 var newValue = Value + delta * SmallChange;
375 SetCurrentValue(ValueProperty, newValue);
381 NativeHelper.SetCursorPos(PointToScreen(mouseDownPosition));
388 base.OnPreviewMouseUp(e);
390 if (ReferenceEquals(e.MouseDevice.Captured,
this))
391 e.MouseDevice.Capture(null);
393 if (dragState == DragState.Starting)
395 Select(0, Text.Length);
398 Keyboard.Focus(
this);
401 else if (dragState == DragState.Dragging && AllowMouseDrag)
405 var adornerLayer = AdornerLayer.GetAdornerLayer(
this);
406 if (adornerLayer != null)
408 adornerLayer.Remove(adorner);
415 Mouse.OverrideCursor = null;
416 dragState = DragState.None;
421 var textValue = FormatValue(Value);
422 SetCurrentValue(TextProperty, textValue);
433 value = double.Parse(Text);
437 value = double.Parse(Text, CultureInfo.InvariantCulture);
444 SetCurrentValue(ValueProperty, value);
446 BindingExpression expression = GetBindingExpression(ValueProperty);
447 if (expression != null)
448 expression.UpdateSource();
454 baseValue = base.CoerceTextForValidation(baseValue);
460 value = double.Parse(baseValue);
464 value = double.Parse(baseValue, CultureInfo.InvariantCulture);
480 return FormatValue(value);
490 int decimalPlaces = DecimalPlaces;
491 double coercedValue = decimalPlaces < 0 ? value : Math.Round(value, decimalPlaces);
492 return coercedValue.ToString(CultureInfo.InvariantCulture);
495 private void RepeatButtonIsPressedChanged(
object sender,
EventArgs e)
497 var repeatButton = (RepeatButton)sender;
498 if (ReferenceEquals(repeatButton, increaseButton))
502 if (ReferenceEquals(repeatButton, decreaseButton))
504 RaiseEvent(
new RepeatButtonPressedRoutedEventArgs(RepeatButtons.DecreaseButton, repeatButton.IsPressed ? RepeatButtonPressedEvent : RepeatButtonReleasedEvent));
508 private void OnValuePropertyChanged(
double oldValue,
double newValue)
510 if (newValue > Maximum)
512 SetCurrentValue(ValueProperty, Maximum);
515 if (newValue < Minimum)
517 SetCurrentValue(ValueProperty, Minimum);
521 var textValue = FormatValue(newValue);
522 updatingValue =
true;
523 SetCurrentValue(TextProperty, textValue);
525 updatingValue =
false;
527 RaiseEvent(
new RoutedPropertyChangedEventArgs<double>(oldValue, newValue, ValueChangedEvent));
528 OnValueChanged(oldValue, newValue);
531 private void UpdateValue(
double value)
533 if (IsReadOnly ==
false)
535 SetCurrentValue(ValueProperty, value);
539 private void HostQueryCursor(
object sender, QueryCursorEventArgs e)
541 if (!IsContentHostPart(e.OriginalSource))
544 if (AllowMouseDrag && !IsFocused && DragCursor != null)
546 e.Cursor = DragCursor;
551 private bool IsContentHostPart(
object obj)
553 var frameworkElement = obj as FrameworkElement;
554 return Equals(obj, contentHost) || (frameworkElement != null && Equals(frameworkElement.Parent, contentHost));
557 private static void OnValuePropertyChanged(
DependencyObject sender, DependencyPropertyChangedEventArgs e)
559 ((NumericTextBox)sender).OnValuePropertyChanged((double)e.OldValue, (
double)e.NewValue);
562 private static void OnDecimalPlacesPropertyChanged(
DependencyObject sender, DependencyPropertyChangedEventArgs e)
564 var numericInput = (NumericTextBox)sender;
565 numericInput.CoerceValue(ValueProperty);
568 private static void OnMinimumPropertyChanged(
DependencyObject sender, DependencyPropertyChangedEventArgs e)
570 var numericInput = (NumericTextBox)sender;
571 bool needValidation =
false;
572 if (numericInput.Maximum < numericInput.Minimum)
574 numericInput.SetCurrentValue(MaximumProperty, numericInput.Minimum);
575 needValidation =
true;
577 if (numericInput.Value < numericInput.Minimum)
579 numericInput.SetCurrentValue(ValueProperty, numericInput.Minimum);
580 needValidation =
true;
584 numericInput.updatingValue =
true;
585 numericInput.SetCurrentValue(ValueRatioProperty, MathUtil.InverseLerp(numericInput.Minimum, numericInput.Maximum, numericInput.Value));
586 numericInput.updatingValue =
false;
590 numericInput.Validate();
594 private static void OnMaximumPropertyChanged(
DependencyObject sender, DependencyPropertyChangedEventArgs e)
596 var numericInput = (NumericTextBox)sender;
597 bool needValidation =
false;
598 if (numericInput.Minimum > numericInput.Maximum)
600 numericInput.SetCurrentValue(MinimumProperty, numericInput.Maximum);
601 needValidation =
true;
603 if (numericInput.Value > numericInput.Maximum)
605 numericInput.SetCurrentValue(ValueProperty, numericInput.Maximum);
606 needValidation =
true;
610 numericInput.updatingValue =
true;
611 numericInput.SetCurrentValue(ValueRatioProperty, MathUtil.InverseLerp(numericInput.Minimum, numericInput.Maximum, numericInput.Value));
612 numericInput.updatingValue =
false;
616 numericInput.Validate();
620 private static void ValueRatioChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e)
622 var control = (NumericTextBox)d;
623 if (control != null && !control.updatingValue)
624 control.UpdateValue(MathUtil.Lerp(control.Minimum, control.Maximum, (double)e.NewValue));
627 private static void UpdateValueCommand(
object sender, Func<NumericTextBox, double> getValue)
629 var control = (sender as NumericTextBox) ?? ((
System.Windows.Controls.TextBox)sender).FindVisualParentOfType<NumericTextBox>();
632 var value = getValue(control);
633 control.UpdateValue(value);
639 private static void OnLargeIncreaseCommand(
object sender, ExecutedRoutedEventArgs e)
641 UpdateValueCommand(sender, x => x.Value + x.LargeChange);
644 private static void OnLargeDecreaseCommand(
object sender, ExecutedRoutedEventArgs e)
646 UpdateValueCommand(sender, x => x.Value - x.LargeChange);
649 private static void OnSmallIncreaseCommand(
object sender, ExecutedRoutedEventArgs e)
651 UpdateValueCommand(sender, x => x.Value + x.SmallChange);
654 private static void OnSmallDecreaseCommand(
object sender, ExecutedRoutedEventArgs e)
656 UpdateValueCommand(sender, x => x.Value - x.SmallChange);
659 private static void OnForbiddenPropertyChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e)
661 var
metadata = e.Property.GetMetadata(d);
662 if (!Equals(e.NewValue, metadata.DefaultValue))
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);
669 private class DragDirectionAdorner :
Adorner
671 private readonly
double contentWidth;
672 private static readonly ImageSource CursorHorizontalImageSource;
673 private static readonly ImageSource CursorVerticalImageSource;
675 static DragDirectionAdorner()
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));
685 internal DragDirectionAdorner(UIElement adornedElement,
double contentWidth)
686 : base(adornedElement)
688 this.contentWidth = contentWidth;
691 internal void SetOrientation(
Orientation orientation)
694 dragOrientation = orientation;
698 protected override void OnRender(DrawingContext drawingContext)
700 base.OnRender(drawingContext);
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)));
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.
override void OnInitialized(EventArgs e)
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...
virtual void OnValueChanged(double oldValue, double newValue)
Raised when the Value property has changed.
_In_ size_t _In_ const TexMetadata & metadata
string FormatValue(double value)
Formats the text to
Structure using the same layout than System.Drawing.Point.
override void OnApplyTemplate()
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
static float InverseLerp(float min, float max, float value)
Inverse-interpolates a value linearly.