diff --git a/ExtendedWPFToolkitSolution/Src/Xceed.Wpf.Toolkit/Primitives/UpDownBase.cs b/ExtendedWPFToolkitSolution/Src/Xceed.Wpf.Toolkit/Primitives/UpDownBase.cs index 9d1b6e6a..2eaff822 100644 --- a/ExtendedWPFToolkitSolution/Src/Xceed.Wpf.Toolkit/Primitives/UpDownBase.cs +++ b/ExtendedWPFToolkitSolution/Src/Xceed.Wpf.Toolkit/Primitives/UpDownBase.cs @@ -1,930 +1,945 @@ -/************************************************************************************* - - Toolkit for WPF - - Copyright (C) 2007-2025 Xceed Software Inc. - - This program is provided to you under the terms of the XCEED SOFTWARE, INC. - COMMUNITY LICENSE AGREEMENT (for non-commercial use) as published at - https://github.com/xceedsoftware/wpftoolkit/blob/master/license.md - - For more features, controls, and fast professional support, - pick up the Plus Edition at https://xceed.com/xceed-toolkit-plus-for-wpf/ - - Stay informed: follow @datagrid on Twitter or Like http://facebook.com/datagrids - - ***********************************************************************************/ - -using System; -using System.Globalization; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Input; -using System.Windows.Threading; -using Xceed.Wpf.Toolkit.Core.Input; - -namespace Xceed.Wpf.Toolkit.Primitives -{ - [TemplatePart( Name = PART_TextBox, Type = typeof( TextBox ) )] - [TemplatePart( Name = PART_Spinner, Type = typeof( Spinner ) )] - public abstract class UpDownBase : InputBase, IValidateInput - { - #region Members - - internal const string PART_TextBox = "PART_TextBox"; - - internal const string PART_Spinner = "PART_Spinner"; - - internal bool _isTextChangedFromUI; - - private bool _isSyncingTextAndValueProperties; - private bool _internalValueSet; - - #endregion //Members - - #region Properties - - protected Spinner Spinner - { - get; - private set; - } - protected TextBox TextBox - { - get; - private set; - } - - #region AllowSpin - - public static readonly DependencyProperty AllowSpinProperty = DependencyProperty.Register( "AllowSpin", typeof( bool ), typeof( UpDownBase ), - -new UIPropertyMetadata( true ) ); - public bool AllowSpin - { - get - { - return ( bool )GetValue( AllowSpinProperty ); - } - set - { - SetValue( AllowSpinProperty, value ); - } - } - - #endregion //AllowSpin - - #region ButtonSpinnerHeight - - public static readonly DependencyProperty ButtonSpinnerHeightProperty = DependencyProperty.Register( "ButtonSpinnerHeight", typeof( double ), typeof( UpDownBase ), new UIPropertyMetadata( double.NaN ) ); - public double ButtonSpinnerHeight - { - get - { - return ( double )GetValue( ButtonSpinnerHeightProperty ); - } - set - { - SetValue( ButtonSpinnerHeightProperty, value ); - } - } - - #endregion //ButtonSpinnerHeight - - #region ButtonSpinnerLocation - - public static readonly DependencyProperty ButtonSpinnerLocationProperty = DependencyProperty.Register( "ButtonSpinnerLocation", typeof( Location - -), typeof( UpDownBase ), new UIPropertyMetadata( Location.Right ) ); - public Location ButtonSpinnerLocation - { - get - { - return ( Location )GetValue( ButtonSpinnerLocationProperty ); - } - set - { - SetValue( ButtonSpinnerLocationProperty, value ); - } - } - - #endregion //ButtonSpinnerLocation - - #region ButtonSpinnerDownContentTemplate - - public static readonly DependencyProperty ButtonSpinnerDownContentTemplateProperty = DependencyProperty.Register( "ButtonSpinnerDownContentTemplate", typeof( DataTemplate ), typeof( UpDownBase ), new UIPropertyMetadata( null ) ); - public DataTemplate ButtonSpinnerDownContentTemplate - { - get - { - return ( DataTemplate )GetValue( ButtonSpinnerDownContentTemplateProperty ); - } - set - { - SetValue( ButtonSpinnerDownContentTemplateProperty, value ); - } - } - - #endregion //ButtonSpinnerDownContentTemplate - - #region ButtonSpinnerDownDisabledContentTemplate - - public static readonly DependencyProperty ButtonSpinnerDownDisabledContentTemplateProperty = DependencyProperty.Register( "ButtonSpinnerDownDisabledContentTemplate", typeof( DataTemplate ), typeof( UpDownBase ), new UIPropertyMetadata( null ) ); - public DataTemplate ButtonSpinnerDownDisabledContentTemplate - { - get - { - return ( DataTemplate )GetValue( ButtonSpinnerDownDisabledContentTemplateProperty ); - } - set - { - SetValue( ButtonSpinnerDownDisabledContentTemplateProperty, value ); - } - } - - #endregion //ButtonSpinnerDownDisabledContentTemplate - - #region ButtonSpinnerUpContentTemplate - - public static readonly DependencyProperty ButtonSpinnerUpContentTemplateProperty = DependencyProperty.Register( "ButtonSpinnerUpContentTemplate", typeof( DataTemplate ), typeof( UpDownBase ), new UIPropertyMetadata( null ) ); - public DataTemplate ButtonSpinnerUpContentTemplate - { - get - { - return ( DataTemplate )GetValue( ButtonSpinnerUpContentTemplateProperty ); - } - set - { - SetValue( ButtonSpinnerUpContentTemplateProperty, value ); - } - } - - #endregion //ButtonSpinnerUpContentTemplate - - #region ButtonSpinnerUpDisabledContentTemplate - - public static readonly DependencyProperty ButtonSpinnerUpDisabledContentTemplateProperty = DependencyProperty.Register( "ButtonSpinnerUpDisabledContentTemplate", typeof( DataTemplate ), typeof( UpDownBase ), new UIPropertyMetadata( null ) ); - public DataTemplate ButtonSpinnerUpDisabledContentTemplate - { - get - { - return ( DataTemplate )GetValue( ButtonSpinnerUpDisabledContentTemplateProperty ); - } - set - { - SetValue( ButtonSpinnerUpDisabledContentTemplateProperty, value ); - } - } - - #endregion //ButtonSpinnerUpDisabledContentTemplate - - #region ButtonSpinnerWidth - - public static readonly DependencyProperty ButtonSpinnerWidthProperty = DependencyProperty.Register( "ButtonSpinnerWidth", typeof( double ), typeof( UpDownBase ), new UIPropertyMetadata( SystemParameters.VerticalScrollBarWidth ) ); - public double ButtonSpinnerWidth - { - get - { - return ( double )GetValue( ButtonSpinnerWidthProperty ); - } - set - { - SetValue( ButtonSpinnerWidthProperty, value ); - } - } - - #endregion //ButtonSpinnerWidth - - #region ClipValueToMinMax - - public static readonly DependencyProperty ClipValueToMinMaxProperty = DependencyProperty.Register( "ClipValueToMinMax", typeof( bool ), typeof( - -UpDownBase ), new UIPropertyMetadata( false ) ); - public bool ClipValueToMinMax - { - get - { - return ( bool )GetValue( ClipValueToMinMaxProperty ); - } - set - { - SetValue( ClipValueToMinMaxProperty, value ); - } - } - - #endregion //ClipValueToMinMax - - #region DisplayDefaultValueOnEmptyText - - public static readonly DependencyProperty DisplayDefaultValueOnEmptyTextProperty = DependencyProperty.Register( "DisplayDefaultValueOnEmptyText", - -typeof( bool ), typeof( UpDownBase ), new UIPropertyMetadata( false, OnDisplayDefaultValueOnEmptyTextChanged ) ); - public bool DisplayDefaultValueOnEmptyText - { - get - { - return ( bool )GetValue( DisplayDefaultValueOnEmptyTextProperty ); - } - set - { - SetValue( DisplayDefaultValueOnEmptyTextProperty, value ); - } - } - - private static void OnDisplayDefaultValueOnEmptyTextChanged( DependencyObject source, DependencyPropertyChangedEventArgs args ) - { - ( ( UpDownBase )source ).OnDisplayDefaultValueOnEmptyTextChanged( ( bool )args.OldValue, ( bool )args.NewValue ); - } - - private void OnDisplayDefaultValueOnEmptyTextChanged( bool oldValue, bool newValue ) - { - if( this.IsInitialized && string.IsNullOrEmpty( Text ) ) - { - this.SyncTextAndValueProperties( false, Text ); - } - } - - #endregion //DisplayDefaultValueOnEmptyText - - #region DefaultValue - - public static readonly DependencyProperty DefaultValueProperty = DependencyProperty.Register( "DefaultValue", typeof( T ), typeof( UpDownBase - -), new UIPropertyMetadata( default( T ), OnDefaultValueChanged ) ); - public T DefaultValue - { - get - { - return ( T )GetValue( DefaultValueProperty ); - } - set - { - SetValue( DefaultValueProperty, value ); - } - } - - private static void OnDefaultValueChanged( DependencyObject source, DependencyPropertyChangedEventArgs args ) - { - ( ( UpDownBase )source ).OnDefaultValueChanged( ( T )args.OldValue, ( T )args.NewValue ); - } - - private void OnDefaultValueChanged( T oldValue, T newValue ) - { - if( this.IsInitialized && string.IsNullOrEmpty( Text ) ) - { - this.SyncTextAndValueProperties( true, Text ); - } - } - - #endregion //DefaultValue - - #region Maximum - - public static readonly DependencyProperty MaximumProperty = DependencyProperty.Register( "Maximum", typeof( T ), typeof( UpDownBase ), new - -UIPropertyMetadata( default( T ), OnMaximumChanged, OnCoerceMaximum ) ); - public T Maximum - { - get - { - return ( T )GetValue( MaximumProperty ); - } - set - { - SetValue( MaximumProperty, value ); - } - } - - private static void OnMaximumChanged( DependencyObject o, DependencyPropertyChangedEventArgs e ) - { - UpDownBase upDown = o as UpDownBase; - if( upDown != null ) - upDown.OnMaximumChanged( ( T )e.OldValue, ( T )e.NewValue ); - } - - protected virtual void OnMaximumChanged( T oldValue, T newValue ) - { - if( this.IsInitialized ) - { - SetValidSpinDirection(); - } - } - - private static object OnCoerceMaximum( DependencyObject d, object baseValue ) - { - UpDownBase upDown = d as UpDownBase; - if( upDown != null ) - return upDown.OnCoerceMaximum( ( T )baseValue ); - - return baseValue; - } - - protected virtual T OnCoerceMaximum( T baseValue ) - { - return baseValue; - } - - #endregion //Maximum - - #region Minimum - - public static readonly DependencyProperty MinimumProperty = DependencyProperty.Register( "Minimum", typeof( T ), typeof( UpDownBase ), new - -UIPropertyMetadata( default( T ), OnMinimumChanged, OnCoerceMinimum ) ); - public T Minimum - { - get - { - return ( T )GetValue( MinimumProperty ); - } - set - { - SetValue( MinimumProperty, value ); - } - } - - private static void OnMinimumChanged( DependencyObject o, DependencyPropertyChangedEventArgs e ) - { - UpDownBase upDown = o as UpDownBase; - if( upDown != null ) - upDown.OnMinimumChanged( ( T )e.OldValue, ( T )e.NewValue ); - } - - protected virtual void OnMinimumChanged( T oldValue, T newValue ) - { - if( this.IsInitialized ) - { - SetValidSpinDirection(); - } - } - - private static object OnCoerceMinimum( DependencyObject d, object baseValue ) - { - UpDownBase upDown = d as UpDownBase; - if( upDown != null ) - return upDown.OnCoerceMinimum( ( T )baseValue ); - - return baseValue; - } - - protected virtual T OnCoerceMinimum( T baseValue ) - { - return baseValue; - } - - #endregion //Minimum - - #region MouseWheelActiveTrigger - - public static readonly DependencyProperty MouseWheelActiveTriggerProperty = DependencyProperty.Register( "MouseWheelActiveTrigger", typeof( - -MouseWheelActiveTrigger ), typeof( UpDownBase ), new UIPropertyMetadata( MouseWheelActiveTrigger.FocusedMouseOver ) ); - - public MouseWheelActiveTrigger MouseWheelActiveTrigger - { - get - { - return ( MouseWheelActiveTrigger )GetValue( MouseWheelActiveTriggerProperty ); - } - set - { - SetValue( MouseWheelActiveTriggerProperty, value ); - } - } - - #endregion //MouseWheelActiveTrigger - - #region MouseWheelActiveOnFocus - - [Obsolete( "Use MouseWheelActiveTrigger property instead" )] - public static readonly DependencyProperty MouseWheelActiveOnFocusProperty = DependencyProperty.Register( "MouseWheelActiveOnFocus", typeof( bool - -), typeof( UpDownBase ), new UIPropertyMetadata( true, OnMouseWheelActiveOnFocusChanged ) ); - - [Obsolete( "Use MouseWheelActiveTrigger property instead" )] - public bool MouseWheelActiveOnFocus - { - get - { -#pragma warning disable 618 - return ( bool )GetValue( MouseWheelActiveOnFocusProperty ); -#pragma warning restore 618 - } - set - { -#pragma warning disable 618 - SetValue( MouseWheelActiveOnFocusProperty, value ); -#pragma warning restore 618 - } - } - - private static void OnMouseWheelActiveOnFocusChanged( DependencyObject o, DependencyPropertyChangedEventArgs e ) - { - UpDownBase upDownBase = o as UpDownBase; - if( upDownBase != null ) - upDownBase.MouseWheelActiveTrigger = ( ( bool )e.NewValue ) - ? MouseWheelActiveTrigger.FocusedMouseOver - : MouseWheelActiveTrigger.MouseOver; - } - - #endregion //MouseWheelActiveOnFocus - - #region ShowButtonSpinner - - public static readonly DependencyProperty ShowButtonSpinnerProperty = DependencyProperty.Register( "ShowButtonSpinner", typeof( bool ), typeof( - -UpDownBase ), new UIPropertyMetadata( true ) ); - public bool ShowButtonSpinner - { - get - { - return ( bool )GetValue( ShowButtonSpinnerProperty ); - } - set - { - SetValue( ShowButtonSpinnerProperty, value ); - } - } - - #endregion //ShowButtonSpinner - - #region UpdateValueOnEnterKey - - public static readonly DependencyProperty UpdateValueOnEnterKeyProperty = DependencyProperty.Register( "UpdateValueOnEnterKey", typeof( bool ), typeof( UpDownBase ), - new FrameworkPropertyMetadata( false, OnUpdateValueOnEnterKeyChanged ) ); - public bool UpdateValueOnEnterKey - { - get - { - return ( bool )GetValue( UpdateValueOnEnterKeyProperty ); - } - set - { - SetValue( UpdateValueOnEnterKeyProperty, value ); - } - } - - private static void OnUpdateValueOnEnterKeyChanged( DependencyObject o, DependencyPropertyChangedEventArgs e ) - { - var upDownBase = o as UpDownBase; - if( upDownBase != null ) - upDownBase.OnUpdateValueOnEnterKeyChanged( ( bool )e.OldValue, ( bool )e.NewValue ); - } - - protected virtual void OnUpdateValueOnEnterKeyChanged( bool oldValue, bool newValue ) - { - } - - #endregion //UpdateValueOnEnterKey - - #region Value - - public static readonly DependencyProperty ValueProperty = DependencyProperty.Register( "Value", typeof( T ), typeof( UpDownBase ), - new FrameworkPropertyMetadata( default( T ), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnValueChanged, OnCoerceValue, false, UpdateSourceTrigger.PropertyChanged ) ); - public T Value - { - get - { - return ( T )GetValue( ValueProperty ); - } - set - { - SetValue( ValueProperty, value ); - } - } - - private void SetValueInternal( T value ) - { - _internalValueSet = true; - try - { - this.Value = value; - } - finally - { - _internalValueSet = false; - } - } - - private static object OnCoerceValue( DependencyObject o, object basevalue ) - { - return ( ( UpDownBase )o ).OnCoerceValue( basevalue ); - } - - protected virtual object OnCoerceValue( object newValue ) - { - return newValue; - } - - private static void OnValueChanged( DependencyObject o, DependencyPropertyChangedEventArgs e ) - { - UpDownBase upDownBase = o as UpDownBase; - if( upDownBase != null ) - upDownBase.OnValueChanged( ( T )e.OldValue, ( T )e.NewValue ); - } - - protected virtual void OnValueChanged( T oldValue, T newValue ) - { - if( !_internalValueSet && this.IsInitialized ) - { - SyncTextAndValueProperties( false, null, true ); - } - - SetValidSpinDirection(); - - this.RaiseValueChangedEvent( oldValue, newValue ); - } - - #endregion //Value - - #endregion //Properties - - #region Constructors - - internal UpDownBase() - { - - Core.Message.ShowMessage(); - - this.AddHandler( Mouse.PreviewMouseDownOutsideCapturedElementEvent, new RoutedEventHandler( this.HandleClickOutsideOfControlWithMouseCapture ), - -true ); - this.IsKeyboardFocusWithinChanged += this.UpDownBase_IsKeyboardFocusWithinChanged; - } - - #endregion //Constructors - - #region Base Class Overrides - - protected override void OnAccessKey( AccessKeyEventArgs e ) - { - if( TextBox != null ) - TextBox.Focus(); - - base.OnAccessKey( e ); - } - - public override void OnApplyTemplate() - { - base.OnApplyTemplate(); - - if( TextBox != null ) - { - TextBox.TextChanged -= new TextChangedEventHandler( TextBox_TextChanged ); - TextBox.RemoveHandler( Mouse.PreviewMouseDownEvent, new MouseButtonEventHandler( this.TextBox_PreviewMouseDown ) ); - } - - TextBox = GetTemplateChild( PART_TextBox ) as TextBox; - - if( TextBox != null ) - { - TextBox.Text = Text; - TextBox.TextChanged += new TextChangedEventHandler( TextBox_TextChanged ); - TextBox.AddHandler( Mouse.PreviewMouseDownEvent, new MouseButtonEventHandler( this.TextBox_PreviewMouseDown ), true ); - } - - if( Spinner != null ) - Spinner.Spin -= OnSpinnerSpin; - - Spinner = GetTemplateChild( PART_Spinner ) as Spinner; - - if( Spinner != null ) - Spinner.Spin += OnSpinnerSpin; - - SetValidSpinDirection(); - } - - protected override void OnKeyDown( KeyEventArgs e ) - { - switch( e.Key ) - { - case Key.Enter: - { - // Commit Text on "Enter" to raise Error event - bool commitSuccess = CommitInput(); - //Only handle if an exception is detected (Commit fails) - e.Handled = !commitSuccess; - break; - } - } - } - - protected override void OnTextChanged( string oldValue, string newValue ) - { - if( this.IsInitialized ) - { - // When text is typed, if UpdateValueOnEnterKey is true, - // Sync Value on Text only when Enter Key is pressed. - if( this.UpdateValueOnEnterKey ) - { - if( !_isTextChangedFromUI ) - { - this.SyncTextAndValueProperties( true, Text ); - } - } - else - { - this.SyncTextAndValueProperties( true, Text ); - } - } - } - - protected override void OnCultureInfoChanged( CultureInfo oldValue, CultureInfo newValue ) - { - if( IsInitialized ) - { - SyncTextAndValueProperties( false, null ); - } - } - - protected override void OnReadOnlyChanged( bool oldValue, bool newValue ) - { - SetValidSpinDirection(); - } - - #endregion //Base Class Overrides - - #region Event Handlers - - private void TextBox_PreviewMouseDown( object sender, RoutedEventArgs e ) - { - if( this.MouseWheelActiveTrigger == Primitives.MouseWheelActiveTrigger.Focused ) - { - //Capture the spinner when user clicks on the control. - if( Mouse.Captured != this.Spinner ) - { - //Delay the capture to let the DateTimeUpDown select a new DateTime part. - this.Dispatcher.BeginInvoke( DispatcherPriority.Input, new Action( () => - { - Mouse.Capture( this.Spinner ); - } - ) ); - } - } - } - - private void HandleClickOutsideOfControlWithMouseCapture( object sender, RoutedEventArgs e ) - { - if( Mouse.Captured is Spinner ) - { - //Release the captured spinner when user clicks away from spinner. - this.Spinner.ReleaseMouseCapture(); - } - } - - private void OnSpinnerSpin( object sender, SpinEventArgs e ) - { - if( AllowSpin && !IsReadOnly ) - { - var activeTrigger = this.MouseWheelActiveTrigger; - bool spin = !e.UsingMouseWheel; - spin |= ( activeTrigger == MouseWheelActiveTrigger.MouseOver ); - spin |= ( ( TextBox != null ) && TextBox.IsFocused && ( activeTrigger == MouseWheelActiveTrigger.FocusedMouseOver ) ); - spin |= ( ( TextBox != null ) && TextBox.IsFocused && ( activeTrigger == MouseWheelActiveTrigger.Focused ) && ( Mouse.Captured is Spinner ) ); - - if( spin ) - { - e.Handled = true; - OnSpin( e ); - } - } - } - - #endregion //Event Handlers - - #region Events - - public event InputValidationErrorEventHandler InputValidationError; - - public event EventHandler Spinned; - - #region ValueChanged Event - - //Due to a bug in Visual Studio, you cannot create event handlers for generic T args in XAML, so I have to use object instead. - public static readonly RoutedEvent ValueChangedEvent = EventManager.RegisterRoutedEvent( "ValueChanged", RoutingStrategy.Bubble, typeof( - -RoutedPropertyChangedEventHandler ), typeof( UpDownBase ) ); - public event RoutedPropertyChangedEventHandler ValueChanged - { - add - { - AddHandler( ValueChangedEvent, value ); - } - remove - { - RemoveHandler( ValueChangedEvent, value ); - } - } - - #endregion - - #endregion //Events - - #region Methods - - protected virtual void OnSpin( SpinEventArgs e ) - { - if( e == null ) - throw new ArgumentNullException( "e" ); - - if( e.Direction == SpinDirection.Increase ) - { - this.DoIncrement(); - } - else - { - this.DoDecrement(); - } - - // Raise the Spinned event to user - EventHandler handler = this.Spinned; - if( handler != null ) - { - handler( this, e ); - } - } - - protected virtual void RaiseValueChangedEvent( T oldValue, T newValue ) - { - RoutedPropertyChangedEventArgs args = new RoutedPropertyChangedEventArgs( oldValue, newValue ); - args.RoutedEvent = ValueChangedEvent; - RaiseEvent( args ); - } - - protected override void OnInitialized( EventArgs e ) - { - base.OnInitialized( e ); - // When both Value and Text are initialized, Value has priority. - // To be sure that the value is not initialized, it should - // have no local value, no binding, and equal to the default value. - bool updateValueFromText = - ( this.ReadLocalValue( ValueProperty ) == DependencyProperty.UnsetValue ) - && ( BindingOperations.GetBinding( this, ValueProperty ) == null ) - && ( object.Equals( this.Value, ValueProperty.DefaultMetadata.DefaultValue ) ); - - this.SyncTextAndValueProperties( updateValueFromText, Text, !updateValueFromText ); - } - - internal void DoDecrement() - { - if( Spinner == null || ( Spinner.ValidSpinDirection & ValidSpinDirections.Decrease ) == ValidSpinDirections.Decrease ) - { - OnDecrement(); - } - } - - internal void DoIncrement() - { - if( Spinner == null || ( Spinner.ValidSpinDirection & ValidSpinDirections.Increase ) == ValidSpinDirections.Increase ) - { - OnIncrement(); - } - } - - private void TextBox_TextChanged( object sender, TextChangedEventArgs e ) - { - if( !this.IsKeyboardFocusWithin ) - return; - - try - { - _isTextChangedFromUI = true; - Text = ( ( TextBox )sender ).Text; - } - finally - { - _isTextChangedFromUI = false; - } - } - - private void UpDownBase_IsKeyboardFocusWithinChanged( object sender, DependencyPropertyChangedEventArgs e ) - { - if( !( bool )e.NewValue ) - { - this.CommitInput(); - } - } - - private void RaiseInputValidationError( Exception e ) - { - if( InputValidationError != null ) - { - InputValidationErrorEventArgs args = new InputValidationErrorEventArgs( e ); - InputValidationError( this, args ); - if( args.ThrowException ) - { - throw args.Exception; - } - } - } - - public virtual bool CommitInput() - { - return this.SyncTextAndValueProperties( true, Text ); - } - - protected bool SyncTextAndValueProperties( bool updateValueFromText, string text ) - { - return this.SyncTextAndValueProperties( updateValueFromText, text, false ); - } - - private bool SyncTextAndValueProperties( bool updateValueFromText, string text, bool forceTextUpdate ) - { - if( _isSyncingTextAndValueProperties ) - return true; - - _isSyncingTextAndValueProperties = true; - bool parsedTextIsValid = true; - try - { - if( updateValueFromText ) - { - if( string.IsNullOrEmpty( text ) ) - { - // An empty input sets the value to the default value. - this.SetValueInternal( this.DefaultValue ); - } - else - { - try - { - T newValue = this.ConvertTextToValue( text ); - if( !object.Equals( newValue, this.Value ) ) - { - this.SetValueInternal( newValue ); - } - } - catch( Exception e ) - { - parsedTextIsValid = false; - - // From the UI, just allow any input. - if( !_isTextChangedFromUI ) - { - // This call may throw an exception. - // See RaiseInputValidationError() implementation. - this.RaiseInputValidationError( e ); - } - } - } - } - - // Do not touch the ongoing text input from user. - if( !_isTextChangedFromUI ) - { - // Don't replace the empty Text with the non-empty representation of DefaultValue. - bool shouldKeepEmpty = !forceTextUpdate && string.IsNullOrEmpty( Text ) && object.Equals( Value, DefaultValue ) && !this.DisplayDefaultValueOnEmptyText; - if( !shouldKeepEmpty ) - { - string newText = ConvertValueToText(); - if( !object.Equals( this.Text, newText ) ) - { - Text = newText; - } - } - - // Sync Text and textBox - if( TextBox != null ) - TextBox.Text = Text; - } - - if( _isTextChangedFromUI && !parsedTextIsValid ) - { - // Text input was made from the user and the text - // repesents an invalid value. Disable the spinner - // in this case. - if( Spinner != null ) - { - Spinner.ValidSpinDirection = ValidSpinDirections.None; - } - } - else - { - this.SetValidSpinDirection(); - } - } - finally - { - _isSyncingTextAndValueProperties = false; - } - return parsedTextIsValid; - } - - #region Abstract - - protected abstract T ConvertTextToValue( string text ); - - protected abstract string ConvertValueToText(); - - protected abstract void OnIncrement(); - - protected abstract void OnDecrement(); - - protected abstract void SetValidSpinDirection(); - - #endregion //Abstract - - #endregion //Methods - } -} +/************************************************************************************* + + Toolkit for WPF + + Copyright (C) 2007-2025 Xceed Software Inc. + + This program is provided to you under the terms of the XCEED SOFTWARE, INC. + COMMUNITY LICENSE AGREEMENT (for non-commercial use) as published at + https://github.com/xceedsoftware/wpftoolkit/blob/master/license.md + + For more features, controls, and fast professional support, + pick up the Plus Edition at https://xceed.com/xceed-toolkit-plus-for-wpf/ + + Stay informed: follow @datagrid on Twitter or Like http://facebook.com/datagrids + + ***********************************************************************************/ + +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Input; +using System.Windows.Threading; +using Xceed.Wpf.Toolkit.Core.Input; + +namespace Xceed.Wpf.Toolkit.Primitives +{ + [TemplatePart( Name = PART_TextBox, Type = typeof( TextBox ) )] + [TemplatePart( Name = PART_Spinner, Type = typeof( Spinner ) )] + public abstract class UpDownBase : InputBase, IValidateInput + { + #region Members + + internal const string PART_TextBox = "PART_TextBox"; + + internal const string PART_Spinner = "PART_Spinner"; + + internal bool _isTextChangedFromUI; + private T _initialValueOnFocus; + + private bool _isSyncingTextAndValueProperties; + private bool _internalValueSet; + + #endregion //Members + + #region Properties + + protected Spinner Spinner + { + get; + private set; + } + protected TextBox TextBox + { + get; + private set; + } + + #region AllowSpin + + public static readonly DependencyProperty AllowSpinProperty = DependencyProperty.Register( "AllowSpin", typeof( bool ), typeof( UpDownBase ), + +new UIPropertyMetadata( true ) ); + public bool AllowSpin + { + get + { + return ( bool )GetValue( AllowSpinProperty ); + } + set + { + SetValue( AllowSpinProperty, value ); + } + } + + #endregion //AllowSpin + + #region ButtonSpinnerHeight + + public static readonly DependencyProperty ButtonSpinnerHeightProperty = DependencyProperty.Register( "ButtonSpinnerHeight", typeof( double ), typeof( UpDownBase ), new UIPropertyMetadata( double.NaN ) ); + public double ButtonSpinnerHeight + { + get + { + return ( double )GetValue( ButtonSpinnerHeightProperty ); + } + set + { + SetValue( ButtonSpinnerHeightProperty, value ); + } + } + + #endregion //ButtonSpinnerHeight + + #region ButtonSpinnerLocation + + public static readonly DependencyProperty ButtonSpinnerLocationProperty = DependencyProperty.Register( "ButtonSpinnerLocation", typeof( Location + +), typeof( UpDownBase ), new UIPropertyMetadata( Location.Right ) ); + public Location ButtonSpinnerLocation + { + get + { + return ( Location )GetValue( ButtonSpinnerLocationProperty ); + } + set + { + SetValue( ButtonSpinnerLocationProperty, value ); + } + } + + #endregion //ButtonSpinnerLocation + + #region ButtonSpinnerDownContentTemplate + + public static readonly DependencyProperty ButtonSpinnerDownContentTemplateProperty = DependencyProperty.Register( "ButtonSpinnerDownContentTemplate", typeof( DataTemplate ), typeof( UpDownBase ), new UIPropertyMetadata( null ) ); + public DataTemplate ButtonSpinnerDownContentTemplate + { + get + { + return ( DataTemplate )GetValue( ButtonSpinnerDownContentTemplateProperty ); + } + set + { + SetValue( ButtonSpinnerDownContentTemplateProperty, value ); + } + } + + #endregion //ButtonSpinnerDownContentTemplate + + #region ButtonSpinnerDownDisabledContentTemplate + + public static readonly DependencyProperty ButtonSpinnerDownDisabledContentTemplateProperty = DependencyProperty.Register( "ButtonSpinnerDownDisabledContentTemplate", typeof( DataTemplate ), typeof( UpDownBase ), new UIPropertyMetadata( null ) ); + public DataTemplate ButtonSpinnerDownDisabledContentTemplate + { + get + { + return ( DataTemplate )GetValue( ButtonSpinnerDownDisabledContentTemplateProperty ); + } + set + { + SetValue( ButtonSpinnerDownDisabledContentTemplateProperty, value ); + } + } + + #endregion //ButtonSpinnerDownDisabledContentTemplate + + #region ButtonSpinnerUpContentTemplate + + public static readonly DependencyProperty ButtonSpinnerUpContentTemplateProperty = DependencyProperty.Register( "ButtonSpinnerUpContentTemplate", typeof( DataTemplate ), typeof( UpDownBase ), new UIPropertyMetadata( null ) ); + public DataTemplate ButtonSpinnerUpContentTemplate + { + get + { + return ( DataTemplate )GetValue( ButtonSpinnerUpContentTemplateProperty ); + } + set + { + SetValue( ButtonSpinnerUpContentTemplateProperty, value ); + } + } + + #endregion //ButtonSpinnerUpContentTemplate + + #region ButtonSpinnerUpDisabledContentTemplate + + public static readonly DependencyProperty ButtonSpinnerUpDisabledContentTemplateProperty = DependencyProperty.Register( "ButtonSpinnerUpDisabledContentTemplate", typeof( DataTemplate ), typeof( UpDownBase ), new UIPropertyMetadata( null ) ); + public DataTemplate ButtonSpinnerUpDisabledContentTemplate + { + get + { + return ( DataTemplate )GetValue( ButtonSpinnerUpDisabledContentTemplateProperty ); + } + set + { + SetValue( ButtonSpinnerUpDisabledContentTemplateProperty, value ); + } + } + + #endregion //ButtonSpinnerUpDisabledContentTemplate + + #region ButtonSpinnerWidth + + public static readonly DependencyProperty ButtonSpinnerWidthProperty = DependencyProperty.Register( "ButtonSpinnerWidth", typeof( double ), typeof( UpDownBase ), new UIPropertyMetadata( SystemParameters.VerticalScrollBarWidth ) ); + public double ButtonSpinnerWidth + { + get + { + return ( double )GetValue( ButtonSpinnerWidthProperty ); + } + set + { + SetValue( ButtonSpinnerWidthProperty, value ); + } + } + + #endregion //ButtonSpinnerWidth + + #region ClipValueToMinMax + + public static readonly DependencyProperty ClipValueToMinMaxProperty = DependencyProperty.Register( "ClipValueToMinMax", typeof( bool ), typeof( + +UpDownBase ), new UIPropertyMetadata( false ) ); + public bool ClipValueToMinMax + { + get + { + return ( bool )GetValue( ClipValueToMinMaxProperty ); + } + set + { + SetValue( ClipValueToMinMaxProperty, value ); + } + } + + #endregion //ClipValueToMinMax + + #region DisplayDefaultValueOnEmptyText + + public static readonly DependencyProperty DisplayDefaultValueOnEmptyTextProperty = DependencyProperty.Register( "DisplayDefaultValueOnEmptyText", + +typeof( bool ), typeof( UpDownBase ), new UIPropertyMetadata( false, OnDisplayDefaultValueOnEmptyTextChanged ) ); + public bool DisplayDefaultValueOnEmptyText + { + get + { + return ( bool )GetValue( DisplayDefaultValueOnEmptyTextProperty ); + } + set + { + SetValue( DisplayDefaultValueOnEmptyTextProperty, value ); + } + } + + private static void OnDisplayDefaultValueOnEmptyTextChanged( DependencyObject source, DependencyPropertyChangedEventArgs args ) + { + ( ( UpDownBase )source ).OnDisplayDefaultValueOnEmptyTextChanged( ( bool )args.OldValue, ( bool )args.NewValue ); + } + + private void OnDisplayDefaultValueOnEmptyTextChanged( bool oldValue, bool newValue ) + { + if( this.IsInitialized && string.IsNullOrEmpty( Text ) ) + { + this.SyncTextAndValueProperties( false, Text ); + } + } + + #endregion //DisplayDefaultValueOnEmptyText + + #region DefaultValue + + public static readonly DependencyProperty DefaultValueProperty = DependencyProperty.Register( "DefaultValue", typeof( T ), typeof( UpDownBase + +), new UIPropertyMetadata( default( T ), OnDefaultValueChanged ) ); + public T DefaultValue + { + get + { + return ( T )GetValue( DefaultValueProperty ); + } + set + { + SetValue( DefaultValueProperty, value ); + } + } + + private static void OnDefaultValueChanged( DependencyObject source, DependencyPropertyChangedEventArgs args ) + { + ( ( UpDownBase )source ).OnDefaultValueChanged( ( T )args.OldValue, ( T )args.NewValue ); + } + + private void OnDefaultValueChanged( T oldValue, T newValue ) + { + if( this.IsInitialized && string.IsNullOrEmpty( Text ) ) + { + this.SyncTextAndValueProperties( true, Text ); + } + } + + #endregion //DefaultValue + + #region Maximum + + public static readonly DependencyProperty MaximumProperty = DependencyProperty.Register( "Maximum", typeof( T ), typeof( UpDownBase ), new + +UIPropertyMetadata( default( T ), OnMaximumChanged, OnCoerceMaximum ) ); + public T Maximum + { + get + { + return ( T )GetValue( MaximumProperty ); + } + set + { + SetValue( MaximumProperty, value ); + } + } + + private static void OnMaximumChanged( DependencyObject o, DependencyPropertyChangedEventArgs e ) + { + UpDownBase upDown = o as UpDownBase; + if( upDown != null ) + upDown.OnMaximumChanged( ( T )e.OldValue, ( T )e.NewValue ); + } + + protected virtual void OnMaximumChanged( T oldValue, T newValue ) + { + if( this.IsInitialized ) + { + SetValidSpinDirection(); + } + } + + private static object OnCoerceMaximum( DependencyObject d, object baseValue ) + { + UpDownBase upDown = d as UpDownBase; + if( upDown != null ) + return upDown.OnCoerceMaximum( ( T )baseValue ); + + return baseValue; + } + + protected virtual T OnCoerceMaximum( T baseValue ) + { + return baseValue; + } + + #endregion //Maximum + + #region Minimum + + public static readonly DependencyProperty MinimumProperty = DependencyProperty.Register( "Minimum", typeof( T ), typeof( UpDownBase ), new + +UIPropertyMetadata( default( T ), OnMinimumChanged, OnCoerceMinimum ) ); + public T Minimum + { + get + { + return ( T )GetValue( MinimumProperty ); + } + set + { + SetValue( MinimumProperty, value ); + } + } + + private static void OnMinimumChanged( DependencyObject o, DependencyPropertyChangedEventArgs e ) + { + UpDownBase upDown = o as UpDownBase; + if( upDown != null ) + upDown.OnMinimumChanged( ( T )e.OldValue, ( T )e.NewValue ); + } + + protected virtual void OnMinimumChanged( T oldValue, T newValue ) + { + if( this.IsInitialized ) + { + SetValidSpinDirection(); + } + } + + private static object OnCoerceMinimum( DependencyObject d, object baseValue ) + { + UpDownBase upDown = d as UpDownBase; + if( upDown != null ) + return upDown.OnCoerceMinimum( ( T )baseValue ); + + return baseValue; + } + + protected virtual T OnCoerceMinimum( T baseValue ) + { + return baseValue; + } + + #endregion //Minimum + + #region MouseWheelActiveTrigger + + public static readonly DependencyProperty MouseWheelActiveTriggerProperty = DependencyProperty.Register( "MouseWheelActiveTrigger", typeof( + +MouseWheelActiveTrigger ), typeof( UpDownBase ), new UIPropertyMetadata( MouseWheelActiveTrigger.FocusedMouseOver ) ); + + public MouseWheelActiveTrigger MouseWheelActiveTrigger + { + get + { + return ( MouseWheelActiveTrigger )GetValue( MouseWheelActiveTriggerProperty ); + } + set + { + SetValue( MouseWheelActiveTriggerProperty, value ); + } + } + + #endregion //MouseWheelActiveTrigger + + #region MouseWheelActiveOnFocus + + [Obsolete( "Use MouseWheelActiveTrigger property instead" )] + public static readonly DependencyProperty MouseWheelActiveOnFocusProperty = DependencyProperty.Register( "MouseWheelActiveOnFocus", typeof( bool + +), typeof( UpDownBase ), new UIPropertyMetadata( true, OnMouseWheelActiveOnFocusChanged ) ); + + [Obsolete( "Use MouseWheelActiveTrigger property instead" )] + public bool MouseWheelActiveOnFocus + { + get + { +#pragma warning disable 618 + return ( bool )GetValue( MouseWheelActiveOnFocusProperty ); +#pragma warning restore 618 + } + set + { +#pragma warning disable 618 + SetValue( MouseWheelActiveOnFocusProperty, value ); +#pragma warning restore 618 + } + } + + private static void OnMouseWheelActiveOnFocusChanged( DependencyObject o, DependencyPropertyChangedEventArgs e ) + { + UpDownBase upDownBase = o as UpDownBase; + if( upDownBase != null ) + upDownBase.MouseWheelActiveTrigger = ( ( bool )e.NewValue ) + ? MouseWheelActiveTrigger.FocusedMouseOver + : MouseWheelActiveTrigger.MouseOver; + } + + #endregion //MouseWheelActiveOnFocus + + #region ShowButtonSpinner + + public static readonly DependencyProperty ShowButtonSpinnerProperty = DependencyProperty.Register( "ShowButtonSpinner", typeof( bool ), typeof( + +UpDownBase ), new UIPropertyMetadata( true ) ); + public bool ShowButtonSpinner + { + get + { + return ( bool )GetValue( ShowButtonSpinnerProperty ); + } + set + { + SetValue( ShowButtonSpinnerProperty, value ); + } + } + + #endregion //ShowButtonSpinner + + #region UpdateValueOnEnterKey + + public static readonly DependencyProperty UpdateValueOnEnterKeyProperty = DependencyProperty.Register( "UpdateValueOnEnterKey", typeof( bool ), typeof( UpDownBase ), + new FrameworkPropertyMetadata( false, OnUpdateValueOnEnterKeyChanged ) ); + public bool UpdateValueOnEnterKey + { + get + { + return ( bool )GetValue( UpdateValueOnEnterKeyProperty ); + } + set + { + SetValue( UpdateValueOnEnterKeyProperty, value ); + } + } + + private static void OnUpdateValueOnEnterKeyChanged( DependencyObject o, DependencyPropertyChangedEventArgs e ) + { + var upDownBase = o as UpDownBase; + if( upDownBase != null ) + upDownBase.OnUpdateValueOnEnterKeyChanged( ( bool )e.OldValue, ( bool )e.NewValue ); + } + + protected virtual void OnUpdateValueOnEnterKeyChanged( bool oldValue, bool newValue ) + { + } + + #endregion //UpdateValueOnEnterKey + + #region Value + + public static readonly DependencyProperty ValueProperty = DependencyProperty.Register( "Value", typeof( T ), typeof( UpDownBase ), + new FrameworkPropertyMetadata( default( T ), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnValueChanged, OnCoerceValue, false, UpdateSourceTrigger.PropertyChanged ) ); + public T Value + { + get + { + return ( T )GetValue( ValueProperty ); + } + set + { + SetValue( ValueProperty, value ); + } + } + + private void SetValueInternal( T value ) + { + _internalValueSet = true; + try + { + this.Value = value; + } + finally + { + _internalValueSet = false; + } + } + + private static object OnCoerceValue( DependencyObject o, object basevalue ) + { + return ( ( UpDownBase )o ).OnCoerceValue( basevalue ); + } + + protected virtual object OnCoerceValue( object newValue ) + { + return newValue; + } + + private static void OnValueChanged( DependencyObject o, DependencyPropertyChangedEventArgs e ) + { + UpDownBase upDownBase = o as UpDownBase; + if( upDownBase != null ) + upDownBase.OnValueChanged( ( T )e.OldValue, ( T )e.NewValue ); + } + + protected virtual void OnValueChanged( T oldValue, T newValue ) + { + if( !_internalValueSet && this.IsInitialized ) + { + SyncTextAndValueProperties( false, null, true ); + } + + SetValidSpinDirection(); + + this.RaiseValueChangedEvent( oldValue, newValue ); + } + + #endregion //Value + + #endregion //Properties + + #region Constructors + + internal UpDownBase() + { + + Core.Message.ShowMessage(); + + this.AddHandler( Mouse.PreviewMouseDownOutsideCapturedElementEvent, new RoutedEventHandler( this.HandleClickOutsideOfControlWithMouseCapture ), + +true ); + this.IsKeyboardFocusWithinChanged += this.UpDownBase_IsKeyboardFocusWithinChanged; + } + + #endregion //Constructors + + #region Base Class Overrides + + protected override void OnAccessKey( AccessKeyEventArgs e ) + { + if( TextBox != null ) + TextBox.Focus(); + + base.OnAccessKey( e ); + } + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + if( TextBox != null ) + { + TextBox.TextChanged -= new TextChangedEventHandler( TextBox_TextChanged ); + TextBox.RemoveHandler( Mouse.PreviewMouseDownEvent, new MouseButtonEventHandler( this.TextBox_PreviewMouseDown ) ); + } + + TextBox = GetTemplateChild( PART_TextBox ) as TextBox; + + if( TextBox != null ) + { + TextBox.Text = Text; + TextBox.TextChanged += new TextChangedEventHandler( TextBox_TextChanged ); + TextBox.AddHandler( Mouse.PreviewMouseDownEvent, new MouseButtonEventHandler( this.TextBox_PreviewMouseDown ), true ); + } + + if( Spinner != null ) + Spinner.Spin -= OnSpinnerSpin; + + Spinner = GetTemplateChild( PART_Spinner ) as Spinner; + + if( Spinner != null ) + Spinner.Spin += OnSpinnerSpin; + + SetValidSpinDirection(); + } + + protected override void OnKeyDown( KeyEventArgs e ) + { + switch( e.Key ) + { + case Key.Enter: + { + // Commit Text on "Enter" to raise Error event + bool commitSuccess = CommitInput(); + //Only handle if an exception is detected (Commit fails) + e.Handled = !commitSuccess; + break; + } + } + } + + protected override void OnTextChanged( string oldValue, string newValue ) + { + if( this.IsInitialized ) + { + // When text is typed, if UpdateValueOnEnterKey is true, + // Sync Value on Text only when Enter Key is pressed. + if( this.UpdateValueOnEnterKey ) + { + if( !_isTextChangedFromUI ) + { + this.SyncTextAndValueProperties( true, Text ); + } + } + else + { + this.SyncTextAndValueProperties( true, Text ); + } + } + } + + protected override void OnCultureInfoChanged( CultureInfo oldValue, CultureInfo newValue ) + { + if( IsInitialized ) + { + SyncTextAndValueProperties( false, null ); + } + } + + protected override void OnReadOnlyChanged( bool oldValue, bool newValue ) + { + SetValidSpinDirection(); + } + + #endregion //Base Class Overrides + + #region Event Handlers + + private void TextBox_PreviewMouseDown( object sender, RoutedEventArgs e ) + { + if( this.MouseWheelActiveTrigger == Primitives.MouseWheelActiveTrigger.Focused ) + { + //Capture the spinner when user clicks on the control. + if( Mouse.Captured != this.Spinner ) + { + //Delay the capture to let the DateTimeUpDown select a new DateTime part. + this.Dispatcher.BeginInvoke( DispatcherPriority.Input, new Action( () => + { + Mouse.Capture( this.Spinner ); + } + ) ); + } + } + } + + private void HandleClickOutsideOfControlWithMouseCapture( object sender, RoutedEventArgs e ) + { + if( Mouse.Captured is Spinner ) + { + //Release the captured spinner when user clicks away from spinner. + this.Spinner.ReleaseMouseCapture(); + } + } + + private void OnSpinnerSpin( object sender, SpinEventArgs e ) + { + if( AllowSpin && !IsReadOnly ) + { + var activeTrigger = this.MouseWheelActiveTrigger; + bool spin = !e.UsingMouseWheel; + spin |= ( activeTrigger == MouseWheelActiveTrigger.MouseOver ); + spin |= ( ( TextBox != null ) && TextBox.IsFocused && ( activeTrigger == MouseWheelActiveTrigger.FocusedMouseOver ) ); + spin |= ( ( TextBox != null ) && TextBox.IsFocused && ( activeTrigger == MouseWheelActiveTrigger.Focused ) && ( Mouse.Captured is Spinner ) ); + + if( spin ) + { + e.Handled = true; + OnSpin( e ); + } + } + } + + #endregion //Event Handlers + + #region Events + + public event InputValidationErrorEventHandler InputValidationError; + + public event EventHandler Spinned; + + #region ValueChanged Event + + //Due to a bug in Visual Studio, you cannot create event handlers for generic T args in XAML, so I have to use object instead. + public static readonly RoutedEvent ValueChangedEvent = EventManager.RegisterRoutedEvent( "ValueChanged", RoutingStrategy.Bubble, typeof( + +RoutedPropertyChangedEventHandler ), typeof( UpDownBase ) ); + public event RoutedPropertyChangedEventHandler ValueChanged + { + add + { + AddHandler( ValueChangedEvent, value ); + } + remove + { + RemoveHandler( ValueChangedEvent, value ); + } + } + + #endregion + + #endregion //Events + + #region Methods + + protected virtual void OnSpin( SpinEventArgs e ) + { + if( e == null ) + throw new ArgumentNullException( "e" ); + + if( e.Direction == SpinDirection.Increase ) + { + this.DoIncrement(); + } + else + { + this.DoDecrement(); + } + + // Raise the Spinned event to user + EventHandler handler = this.Spinned; + if( handler != null ) + { + handler( this, e ); + } + } + + protected virtual void RaiseValueChangedEvent( T oldValue, T newValue ) + { + RoutedPropertyChangedEventArgs args = new RoutedPropertyChangedEventArgs( oldValue, newValue ); + args.RoutedEvent = ValueChangedEvent; + RaiseEvent( args ); + } + + protected override void OnInitialized( EventArgs e ) + { + base.OnInitialized( e ); + // When both Value and Text are initialized, Value has priority. + // To be sure that the value is not initialized, it should + // have no local value, no binding, and equal to the default value. + bool updateValueFromText = + ( this.ReadLocalValue( ValueProperty ) == DependencyProperty.UnsetValue ) + && ( BindingOperations.GetBinding( this, ValueProperty ) == null ) + && ( object.Equals( this.Value, ValueProperty.DefaultMetadata.DefaultValue ) ); + + this.SyncTextAndValueProperties( updateValueFromText, Text, !updateValueFromText ); + } + + internal void DoDecrement() + { + if( Spinner == null || ( Spinner.ValidSpinDirection & ValidSpinDirections.Decrease ) == ValidSpinDirections.Decrease ) + { + OnDecrement(); + } + } + + internal void DoIncrement() + { + if( Spinner == null || ( Spinner.ValidSpinDirection & ValidSpinDirections.Increase ) == ValidSpinDirections.Increase ) + { + OnIncrement(); + } + } + + private void TextBox_TextChanged( object sender, TextChangedEventArgs e ) + { + if( !this.IsKeyboardFocusWithin ) + return; + + try + { + _isTextChangedFromUI = true; + Text = ( ( TextBox )sender ).Text; + } + finally + { + _isTextChangedFromUI = false; + } + } + + private void UpDownBase_IsKeyboardFocusWithinChanged( object sender, DependencyPropertyChangedEventArgs e ) + { + if( ( bool )e.NewValue ) + { + // Save the current value when focus is gained so we can restore it + // if the user enters an out-of-range value with ClipValueToMinMax=false. + _initialValueOnFocus = this.Value; + } + else + { + this.CommitInput(); + } + } + + private void RaiseInputValidationError( Exception e ) + { + if( InputValidationError != null ) + { + InputValidationErrorEventArgs args = new InputValidationErrorEventArgs( e ); + InputValidationError( this, args ); + if( args.ThrowException ) + { + throw args.Exception; + } + } + } + + public virtual bool CommitInput() + { + return this.SyncTextAndValueProperties( true, Text ); + } + + protected bool SyncTextAndValueProperties( bool updateValueFromText, string text ) + { + return this.SyncTextAndValueProperties( updateValueFromText, text, false ); + } + + private bool SyncTextAndValueProperties( bool updateValueFromText, string text, bool forceTextUpdate ) + { + if( _isSyncingTextAndValueProperties ) + return true; + + _isSyncingTextAndValueProperties = true; + bool parsedTextIsValid = true; + try + { + if( updateValueFromText ) + { + if( string.IsNullOrEmpty( text ) ) + { + // An empty input sets the value to the default value. + this.SetValueInternal( this.DefaultValue ); + } + else + { + try + { + T newValue = this.ConvertTextToValue( text ); + if( !object.Equals( newValue, this.Value ) ) + { + this.SetValueInternal( newValue ); + } + } + catch( Exception e ) + { + parsedTextIsValid = false; + + // From the UI, just allow any input. + if( !_isTextChangedFromUI ) + { + // If ClipValueToMinMax is disabled, restore the original value + // when the user enters an out-of-range value, instead of + // silently truncating digits to fit the range. + if( !this.ClipValueToMinMax ) + { + this.SetValueInternal( _initialValueOnFocus ); + } + + // This call may throw an exception. + // See RaiseInputValidationError() implementation. + this.RaiseInputValidationError( e ); + } + } + } + } + + // Do not touch the ongoing text input from user. + if( !_isTextChangedFromUI ) + { + // Don't replace the empty Text with the non-empty representation of DefaultValue. + bool shouldKeepEmpty = !forceTextUpdate && string.IsNullOrEmpty( Text ) && object.Equals( Value, DefaultValue ) && !this.DisplayDefaultValueOnEmptyText; + if( !shouldKeepEmpty ) + { + string newText = ConvertValueToText(); + if( !object.Equals( this.Text, newText ) ) + { + Text = newText; + } + } + + // Sync Text and textBox + if( TextBox != null ) + TextBox.Text = Text; + } + + if( _isTextChangedFromUI && !parsedTextIsValid ) + { + // Text input was made from the user and the text + // repesents an invalid value. Disable the spinner + // in this case. + if( Spinner != null ) + { + Spinner.ValidSpinDirection = ValidSpinDirections.None; + } + } + else + { + this.SetValidSpinDirection(); + } + } + finally + { + _isSyncingTextAndValueProperties = false; + } + return parsedTextIsValid; + } + + #region Abstract + + protected abstract T ConvertTextToValue( string text ); + + protected abstract string ConvertValueToText(); + + protected abstract void OnIncrement(); + + protected abstract void OnDecrement(); + + protected abstract void SetValidSpinDirection(); + + #endregion //Abstract + + #endregion //Methods + } +}