From 751d606bdaa109f20093797805c30f52e513eed2 Mon Sep 17 00:00:00 2001 From: emartin_cp Date: Thu, 25 Oct 2012 16:57:21 +0000 Subject: [PATCH] 1.8.0 Release - .NET 35 branch --- .../AssemblyVersionInfo.cs | 2 +- .../Implementation/AutoSelectBehaviorEnum.cs | 31 + .../Implementation/AutoSelectTextBox.cs | 301 +++ .../Implementation/QueryMoveFocusEventArgs.cs | 76 + .../Implementation/CheckComboBox.cs | 22 +- .../ColorCanvas/Implementation/ColorCanvas.cs | 73 +- .../ColorCanvas/Themes/Generic.xaml | 2 +- .../ColorPicker/Implementation/ColorPicker.cs | 2 +- .../WizardPageButtonVisibilityConverter.cs | 4 +- .../Core/Input/IValidateInput.cs | 32 + .../Input/InputValidationErrorEventArgs.cs | 46 + .../Core/Primitives/CachedTextInfo.cs | 57 + .../Core/Primitives/Selector.cs | 557 +++-- .../Core/Primitives/SelectorItem.cs | 12 +- .../Core/Primitives/UpDownBase.cs | 113 +- .../Core/Primitives/ValueRangeTextBox.cs | 1192 ++++++++++ .../Core/QueryTextFromValueEventArgs.cs | 57 + .../Core/QueryValueFromTextEventArgs.cs | 70 + .../Core/Utilities/ColorUtilities.cs | 2 +- .../Utilities/NotifyPropertyChangedHelper.cs | 163 ++ .../Core/Utilities/ReflectionHelper.cs | 136 ++ .../Core/Utilities/TreeHelper.cs | 238 ++ .../Core/Utilities/ValueChangeHelper.cs | 153 ++ .../DateTimePicker/Themes/Generic.xaml | 24 +- .../Implementation/DateTimeUpDown.cs | 26 +- .../Magnifier/Implementation/Magnifier.cs | 4 +- .../AutoCompletingMaskEventArgs.cs | 108 + .../Implementation/InsertKeyModeEnum.cs | 32 + .../Implementation/MaskFormatEnum.cs | 33 + .../Implementation/MaskedTextBox.cs | 2116 +++++++++++++---- .../MessageBox/Implementation/MessageBox.cs | 80 +- .../MessageBox/Themes/Generic.xaml | 43 +- .../Implementation/ByteUpDown.cs | 55 + .../Implementation/CommonNumericUpDown.cs | 184 ++ .../Implementation/DecimalUpDown.cs | 86 +- .../Implementation/DoubleUpDown.cs | 86 +- .../Implementation/IntegerUpDown.cs | 87 +- .../Implementation/LongUpDown.cs | 55 + .../Implementation/NumericUpDown.cs | 15 - .../Implementation/SByteUpDown.cs | 55 + .../Implementation/ShortUpDown.cs | 55 + .../Implementation/SingleUpDown.cs | 55 + .../Implementation/UIntegerUpDown.cs | 55 + .../Implementation/ULongUpDown.cs | 55 + .../Implementation/UShortUpDown.cs | 55 + .../NumericUpDown/Themes/Generic.xaml | 67 +- .../Implementation/MaskedTextBox.cs | 726 ++++++ .../Properties/AssemblyInfo.cs | 2 + .../Converters/SelectedObjectConverter.cs | 92 + .../Implementation/Editors/UpDownEditors.cs | 52 + .../Implementation/IPropertyParent.cs | 36 + .../Implementation/PropertyGrid.cs | 344 +-- .../Implementation/PropertyGridUtilities.cs | 85 +- .../Implementation/PropertyItem.cs | 282 ++- .../Implementation/PropertyItemCollection.cs | 68 +- .../PropertyGrid/Themes/Generic.xaml | 42 +- .../RichTextBox/RichTextBox.cs | 28 +- .../WPFToolkit.Extended.csproj | 43 +- 58 files changed, 7281 insertions(+), 1291 deletions(-) create mode 100644 ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/AutoSelectTextBox/Implementation/AutoSelectBehaviorEnum.cs create mode 100644 ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/AutoSelectTextBox/Implementation/AutoSelectTextBox.cs create mode 100644 ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/AutoSelectTextBox/Implementation/QueryMoveFocusEventArgs.cs create mode 100644 ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Input/IValidateInput.cs create mode 100644 ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Input/InputValidationErrorEventArgs.cs create mode 100644 ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Primitives/CachedTextInfo.cs create mode 100644 ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Primitives/ValueRangeTextBox.cs create mode 100644 ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/QueryTextFromValueEventArgs.cs create mode 100644 ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/QueryValueFromTextEventArgs.cs create mode 100644 ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Utilities/NotifyPropertyChangedHelper.cs create mode 100644 ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Utilities/ReflectionHelper.cs create mode 100644 ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Utilities/TreeHelper.cs create mode 100644 ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Utilities/ValueChangeHelper.cs create mode 100644 ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/MaskedTextBox/Implementation/AutoCompletingMaskEventArgs.cs create mode 100644 ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/MaskedTextBox/Implementation/InsertKeyModeEnum.cs create mode 100644 ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/MaskedTextBox/Implementation/MaskFormatEnum.cs create mode 100644 ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/NumericUpDown/Implementation/ByteUpDown.cs create mode 100644 ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/NumericUpDown/Implementation/CommonNumericUpDown.cs create mode 100644 ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/NumericUpDown/Implementation/LongUpDown.cs create mode 100644 ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/NumericUpDown/Implementation/SByteUpDown.cs create mode 100644 ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/NumericUpDown/Implementation/ShortUpDown.cs create mode 100644 ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/NumericUpDown/Implementation/SingleUpDown.cs create mode 100644 ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/NumericUpDown/Implementation/UIntegerUpDown.cs create mode 100644 ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/NumericUpDown/Implementation/ULongUpDown.cs create mode 100644 ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/NumericUpDown/Implementation/UShortUpDown.cs create mode 100644 ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Obselete/MaskedTextBox/Implementation/MaskedTextBox.cs create mode 100644 ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/PropertyGrid/Implementation/Converters/SelectedObjectConverter.cs create mode 100644 ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/PropertyGrid/Implementation/Editors/UpDownEditors.cs create mode 100644 ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/PropertyGrid/Implementation/IPropertyParent.cs diff --git a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/AssemblyVersionInfo.cs b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/AssemblyVersionInfo.cs index 74011485..45843ae6 100644 --- a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/AssemblyVersionInfo.cs +++ b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/AssemblyVersionInfo.cs @@ -22,7 +22,7 @@ internal static class _XceedVersionInfo { [System.Diagnostics.CodeAnalysis.SuppressMessage( "Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields" )] - public const string BaseVersion = "1.7"; + public const string BaseVersion = "1.8"; [System.Diagnostics.CodeAnalysis.SuppressMessage( "Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields" )] public const string Version = BaseVersion + _XceedVersionInfoCommon.Build; diff --git a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/AutoSelectTextBox/Implementation/AutoSelectBehaviorEnum.cs b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/AutoSelectTextBox/Implementation/AutoSelectBehaviorEnum.cs new file mode 100644 index 00000000..2340995d --- /dev/null +++ b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/AutoSelectTextBox/Implementation/AutoSelectBehaviorEnum.cs @@ -0,0 +1,31 @@ +/************************************************************************ + + Extended WPF Toolkit + + Copyright (C) 2010-2012 Xceed Software Inc. + + This program is provided to you under the terms of the Microsoft Public + License (Ms-PL) as published at http://wpftoolkit.codeplex.com/license + + This program can be provided to you by Xceed Software Inc. under a + proprietary commercial license agreement for use in non-Open Source + projects. The commercial version of Extended WPF Toolkit also includes + priority technical support, commercial updates, and many additional + useful WPF controls if you license Xceed Business Suite for WPF. + + Visit http://xceed.com and follow @datagrid on Twitter. + + **********************************************************************/ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Xceed.Wpf.Toolkit +{ + public enum AutoSelectBehavior + { + Never, + OnFocus + } +} diff --git a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/AutoSelectTextBox/Implementation/AutoSelectTextBox.cs b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/AutoSelectTextBox/Implementation/AutoSelectTextBox.cs new file mode 100644 index 00000000..87aa6d0b --- /dev/null +++ b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/AutoSelectTextBox/Implementation/AutoSelectTextBox.cs @@ -0,0 +1,301 @@ +/************************************************************************ + + Extended WPF Toolkit + + Copyright (C) 2010-2012 Xceed Software Inc. + + This program is provided to you under the terms of the Microsoft Public + License (Ms-PL) as published at http://wpftoolkit.codeplex.com/license + + This program can be provided to you by Xceed Software Inc. under a + proprietary commercial license agreement for use in non-Open Source + projects. The commercial version of Extended WPF Toolkit also includes + priority technical support, commercial updates, and many additional + useful WPF controls if you license Xceed Business Suite for WPF. + + Visit http://xceed.com and follow @datagrid on Twitter. + + **********************************************************************/ + +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows; +using System.Windows.Automation; +using Xceed.Wpf.Toolkit.Core.Utilities; + +namespace Xceed.Wpf.Toolkit +{ + public class AutoSelectTextBox : TextBox + { + static AutoSelectTextBox() + { + AutomationProperties.AutomationIdProperty.OverrideMetadata( typeof( AutoSelectTextBox ), new UIPropertyMetadata( "AutoSelectTextBox" ) ); + } + + #region AutoSelectBehavior PROPERTY + + public AutoSelectBehavior AutoSelectBehavior + { + get + { + return ( AutoSelectBehavior )GetValue( AutoSelectBehaviorProperty ); + } + set + { + SetValue( AutoSelectBehaviorProperty, value ); + } + } + + public static readonly DependencyProperty AutoSelectBehaviorProperty = + DependencyProperty.Register( "AutoSelectBehavior", typeof( AutoSelectBehavior ), typeof( AutoSelectTextBox ), + new UIPropertyMetadata( AutoSelectBehavior.Never ) ); + + #endregion AutoSelectBehavior PROPERTY + + #region AutoMoveFocus PROPERTY + + public bool AutoMoveFocus + { + get + { + return ( bool )GetValue( AutoMoveFocusProperty ); + } + set + { + SetValue( AutoMoveFocusProperty, value ); + } + } + + public static readonly DependencyProperty AutoMoveFocusProperty = + DependencyProperty.Register( "AutoMoveFocus", typeof( bool ), typeof( AutoSelectTextBox ), new UIPropertyMetadata( false ) ); + + #endregion AutoMoveFocus PROPERTY + + #region QueryMoveFocus EVENT + + public static readonly RoutedEvent QueryMoveFocusEvent = EventManager.RegisterRoutedEvent( "QueryMoveFocus", + RoutingStrategy.Bubble, + typeof( QueryMoveFocusEventHandler ), + typeof( AutoSelectTextBox ) ); + #endregion QueryMoveFocus EVENT + + protected override void OnPreviewKeyDown( KeyEventArgs e ) + { + if( !this.AutoMoveFocus ) + { + base.OnPreviewKeyDown( e ); + return; + } + + if( ( e.Key == Key.Left ) + && ( ( Keyboard.Modifiers == ModifierKeys.None ) + || ( Keyboard.Modifiers == ModifierKeys.Control ) ) ) + { + e.Handled = this.MoveFocusLeft(); + } + + if( ( e.Key == Key.Right ) + && ( ( Keyboard.Modifiers == ModifierKeys.None ) + || ( Keyboard.Modifiers == ModifierKeys.Control ) ) ) + { + e.Handled = this.MoveFocusRight(); + } + + if( ( ( e.Key == Key.Up ) || ( e.Key == Key.PageUp ) ) + && ( ( Keyboard.Modifiers == ModifierKeys.None ) + || ( Keyboard.Modifiers == ModifierKeys.Control ) ) ) + { + e.Handled = this.MoveFocusUp(); + } + + if( ( ( e.Key == Key.Down ) || ( e.Key == Key.PageDown ) ) + && ( ( Keyboard.Modifiers == ModifierKeys.None ) + || ( Keyboard.Modifiers == ModifierKeys.Control ) ) ) + { + e.Handled = this.MoveFocusDown(); + } + + base.OnPreviewKeyDown( e ); + } + + protected override void OnPreviewGotKeyboardFocus( KeyboardFocusChangedEventArgs e ) + { + base.OnPreviewGotKeyboardFocus( e ); + + if( this.AutoSelectBehavior == AutoSelectBehavior.OnFocus ) + { + // If the focus was not in one of our child ( or popup ), we select all the text. + if( !TreeHelper.IsDescendantOf( e.OldFocus as DependencyObject, this ) ) + this.SelectAll(); + } + } + + protected override void OnPreviewMouseLeftButtonDown( MouseButtonEventArgs e ) + { + base.OnPreviewMouseLeftButtonDown( e ); + + if( this.AutoSelectBehavior == AutoSelectBehavior.Never ) + return; + + if( this.IsKeyboardFocusWithin == false ) + { + this.Focus(); + e.Handled = true; + } + } + + protected override void OnTextChanged( TextChangedEventArgs e ) + { + base.OnTextChanged( e ); + + if( !this.AutoMoveFocus ) + return; + + if( ( this.Text.Length != 0 ) + && ( this.Text.Length == this.MaxLength ) + && ( this.CaretIndex == this.MaxLength ) ) + { + if( this.CanMoveFocus( FocusNavigationDirection.Right, true ) == true ) + { + FocusNavigationDirection direction = ( this.FlowDirection == FlowDirection.LeftToRight ) + ? FocusNavigationDirection.Right + : FocusNavigationDirection.Left; + + this.MoveFocus( new TraversalRequest( direction ) ); + } + } + } + + private bool CanMoveFocus( FocusNavigationDirection direction, bool reachedMax ) + { + QueryMoveFocusEventArgs e = new QueryMoveFocusEventArgs( direction, reachedMax ); + this.RaiseEvent( e ); + return e.CanMoveFocus; + } + + private bool MoveFocusLeft() + { + if( this.FlowDirection == FlowDirection.LeftToRight ) + { + //occurs only if the cursor is at the beginning of the text + if( ( this.CaretIndex == 0 ) && ( this.SelectionLength == 0 ) ) + { + if( ComponentCommands.MoveFocusBack.CanExecute( null, this ) ) + { + ComponentCommands.MoveFocusBack.Execute( null, this ); + return true; + } + else if( this.CanMoveFocus( FocusNavigationDirection.Left, false ) ) + { + this.MoveFocus( new TraversalRequest( FocusNavigationDirection.Left ) ); + return true; + } + } + } + else + { + //occurs only if the cursor is at the end of the text + if( ( this.CaretIndex == this.Text.Length ) && ( this.SelectionLength == 0 ) ) + { + if( ComponentCommands.MoveFocusBack.CanExecute( null, this ) ) + { + ComponentCommands.MoveFocusBack.Execute( null, this ); + return true; + } + else if( this.CanMoveFocus( FocusNavigationDirection.Left, false ) ) + { + this.MoveFocus( new TraversalRequest( FocusNavigationDirection.Left ) ); + return true; + } + } + } + + return false; + } + + private bool MoveFocusRight() + { + if( this.FlowDirection == FlowDirection.LeftToRight ) + { + //occurs only if the cursor is at the beginning of the text + if( ( this.CaretIndex == this.Text.Length ) && ( this.SelectionLength == 0 ) ) + { + if( ComponentCommands.MoveFocusForward.CanExecute( null, this ) ) + { + ComponentCommands.MoveFocusForward.Execute( null, this ); + return true; + } + else if( this.CanMoveFocus( FocusNavigationDirection.Right, false ) ) + { + this.MoveFocus( new TraversalRequest( FocusNavigationDirection.Right ) ); + return true; + } + } + } + else + { + //occurs only if the cursor is at the end of the text + if( ( this.CaretIndex == 0 ) && ( this.SelectionLength == 0 ) ) + { + if( ComponentCommands.MoveFocusForward.CanExecute( null, this ) ) + { + ComponentCommands.MoveFocusForward.Execute( null, this ); + return true; + } + else if( this.CanMoveFocus( FocusNavigationDirection.Right, false ) ) + { + this.MoveFocus( new TraversalRequest( FocusNavigationDirection.Right ) ); + return true; + } + } + } + + return false; + } + + private bool MoveFocusUp() + { + int lineNumber = this.GetLineIndexFromCharacterIndex( this.SelectionStart ); + + //occurs only if the cursor is on the first line + if( lineNumber == 0 ) + { + if( ComponentCommands.MoveFocusUp.CanExecute( null, this ) ) + { + ComponentCommands.MoveFocusUp.Execute( null, this ); + return true; + } + else if( this.CanMoveFocus( FocusNavigationDirection.Up, false ) ) + { + this.MoveFocus( new TraversalRequest( FocusNavigationDirection.Up ) ); + return true; + } + } + + return false; + } + + private bool MoveFocusDown() + { + int lineNumber = this.GetLineIndexFromCharacterIndex( this.SelectionStart ); + + //occurs only if the cursor is on the first line + if( lineNumber == ( this.LineCount - 1 ) ) + { + if( ComponentCommands.MoveFocusDown.CanExecute( null, this ) ) + { + ComponentCommands.MoveFocusDown.Execute( null, this ); + return true; + } + else if( this.CanMoveFocus( FocusNavigationDirection.Down, false ) ) + { + this.MoveFocus( new TraversalRequest( FocusNavigationDirection.Down ) ); + return true; + } + } + + return false; + } + } +} + diff --git a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/AutoSelectTextBox/Implementation/QueryMoveFocusEventArgs.cs b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/AutoSelectTextBox/Implementation/QueryMoveFocusEventArgs.cs new file mode 100644 index 00000000..b77d1772 --- /dev/null +++ b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/AutoSelectTextBox/Implementation/QueryMoveFocusEventArgs.cs @@ -0,0 +1,76 @@ +/************************************************************************ + + Extended WPF Toolkit + + Copyright (C) 2010-2012 Xceed Software Inc. + + This program is provided to you under the terms of the Microsoft Public + License (Ms-PL) as published at http://wpftoolkit.codeplex.com/license + + This program can be provided to you by Xceed Software Inc. under a + proprietary commercial license agreement for use in non-Open Source + projects. The commercial version of Extended WPF Toolkit also includes + priority technical support, commercial updates, and many additional + useful WPF controls if you license Xceed Business Suite for WPF. + + Visit http://xceed.com and follow @datagrid on Twitter. + + **********************************************************************/ + +using System.Windows; +using System.Windows.Input; + +namespace Xceed.Wpf.Toolkit +{ + [System.Diagnostics.CodeAnalysis.SuppressMessage( "Microsoft.Design", "CA1003:UseGenericEventHandlerInstances" )] + public delegate void QueryMoveFocusEventHandler( object sender, QueryMoveFocusEventArgs e ); + + public class QueryMoveFocusEventArgs : RoutedEventArgs + { + //default CTOR private to prevent its usage. + private QueryMoveFocusEventArgs() + { + } + + //internal to prevent anybody from building this type of event. + internal QueryMoveFocusEventArgs( FocusNavigationDirection direction, bool reachedMaxLength ) + : base( AutoSelectTextBox.QueryMoveFocusEvent ) + { + m_navigationDirection = direction; + m_reachedMaxLength = reachedMaxLength; + } + + public FocusNavigationDirection FocusNavigationDirection + { + get + { + return m_navigationDirection; + } + } + + public bool ReachedMaxLength + { + get + { + return m_reachedMaxLength; + } + } + + public bool CanMoveFocus + { + get + { + return m_canMove; + } + set + { + m_canMove = value; + } + } + + private FocusNavigationDirection m_navigationDirection; + private bool m_reachedMaxLength; + private bool m_canMove = true; //defaults to true... if nobody does nothing, then its capable of moving focus. + + } +} diff --git a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/CheckComboBox/Implementation/CheckComboBox.cs b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/CheckComboBox/Implementation/CheckComboBox.cs index be17afce..1b06dc3c 100644 --- a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/CheckComboBox/Implementation/CheckComboBox.cs +++ b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/CheckComboBox/Implementation/CheckComboBox.cs @@ -22,11 +22,14 @@ using System.Linq; using System.Windows; using System.Windows.Input; using Xceed.Wpf.Toolkit.Primitives; +using Xceed.Wpf.Toolkit.Core.Utilities; namespace Xceed.Wpf.Toolkit { public class CheckComboBox : Selector { + private ValueChangeHelper _displayMemberPathValuesChangeHelper; + #region Constructors static CheckComboBox() @@ -37,6 +40,7 @@ namespace Xceed.Wpf.Toolkit public CheckComboBox() { Mouse.AddPreviewMouseDownOutsideCapturedElementHandler( this, OnMouseDownOutsideCapturedElement ); + _displayMemberPathValuesChangeHelper = new ValueChangeHelper( this.OnDisplayMemberPathValuesChanged ); } #endregion //Constructors @@ -98,7 +102,13 @@ namespace Xceed.Wpf.Toolkit protected override void OnDisplayMemberPathChanged( string oldDisplayMemberPath, string newDisplayMemberPath ) { base.OnDisplayMemberPathChanged( oldDisplayMemberPath, newDisplayMemberPath ); - UpdateText(); + this.UpdateDisplayMemberPathValuesBindings(); + } + + protected override void OnItemsSourceChanged( System.Collections.IEnumerable oldValue, System.Collections.IEnumerable newValue ) + { + base.OnItemsSourceChanged( oldValue, newValue ); + this.UpdateDisplayMemberPathValuesBindings(); } #endregion //Base Class Overrides @@ -114,6 +124,16 @@ namespace Xceed.Wpf.Toolkit #region Methods + private void UpdateDisplayMemberPathValuesBindings() + { + _displayMemberPathValuesChangeHelper.UpdateValueSource( ItemsCollection, this.DisplayMemberPath ); + } + + private void OnDisplayMemberPathValuesChanged() + { + this.UpdateText(); + } + private void UpdateText() { #if VS2008 diff --git a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/ColorCanvas/Implementation/ColorCanvas.cs b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/ColorCanvas/Implementation/ColorCanvas.cs index 01a0031c..d0303af0 100644 --- a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/ColorCanvas/Implementation/ColorCanvas.cs +++ b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/ColorCanvas/Implementation/ColorCanvas.cs @@ -25,17 +25,20 @@ using System.Windows.Media; using Xceed.Wpf.Toolkit.Core.Utilities; using Xceed.Wpf.Toolkit.Primitives; using System.IO; +using System; namespace Xceed.Wpf.Toolkit { [TemplatePart( Name = PART_ColorShadingCanvas, Type = typeof( Canvas ) )] [TemplatePart( Name = PART_ColorShadeSelector, Type = typeof( Canvas ) )] [TemplatePart( Name = PART_SpectrumSlider, Type = typeof( ColorSpectrumSlider ) )] + [TemplatePart( Name = PART_HexadecimalTextBox, Type = typeof( TextBox ) )] public class ColorCanvas : Control { private const string PART_ColorShadingCanvas = "PART_ColorShadingCanvas"; private const string PART_ColorShadeSelector = "PART_ColorShadeSelector"; private const string PART_SpectrumSlider = "PART_SpectrumSlider"; + private const string PART_HexadecimalTextBox = "PART_HexadecimalTextBox"; #region Private Members @@ -43,6 +46,7 @@ namespace Xceed.Wpf.Toolkit private Canvas _colorShadingCanvas; private Canvas _colorShadeSelector; private ColorSpectrumSlider _spectrumSlider; + private TextBox _hexadecimalTextBox; private Point? _currentColorPosition; private bool _surpressPropertyChanged; @@ -74,7 +78,7 @@ namespace Xceed.Wpf.Toolkit protected virtual void OnSelectedColorChanged( Color oldValue, Color newValue ) { - HexadecimalString = GetFormatedColorString( newValue ); + SetHexadecimalStringProperty( GetFormatedColorString( newValue ), false ); UpdateRGBValues( newValue ); UpdateColorShadeSelectorPosition( newValue ); @@ -237,6 +241,8 @@ namespace Xceed.Wpf.Toolkit string currentColorString = GetFormatedColorString( SelectedColor ); if( !currentColorString.Equals( newColorString ) ) UpdateSelectedColor( ( Color )ColorConverter.ConvertFromString( newColorString ) ); + + SetHexadecimalTextBoxTextProperty( newValue ); } private static object OnCoerceHexadecimalString( DependencyObject d, object basevalue ) @@ -251,16 +257,19 @@ namespace Xceed.Wpf.Toolkit private object OnCoerceHexadecimalString( object newValue ) { var value = newValue as string; + string retValue = value; + try { ColorConverter.ConvertFromString( value ); } catch { + //When HexadecimalString is changed via Code-Behind and hexadecimal format is bad, throw. throw new InvalidDataException( "Color provided is not in the correct format." ); } - return value; + return retValue; } #endregion //HexadecimalString @@ -289,7 +298,7 @@ namespace Xceed.Wpf.Toolkit protected virtual void OnUsingAlphaChannelChanged() { - HexadecimalString = GetFormatedColorString( SelectedColor ); + SetHexadecimalStringProperty( GetFormatedColorString( SelectedColor ), false ); } #endregion //UsingAlphaChannel @@ -342,17 +351,31 @@ namespace Xceed.Wpf.Toolkit if( _spectrumSlider != null ) _spectrumSlider.ValueChanged += SpectrumSlider_ValueChanged; + if( _hexadecimalTextBox != null ) + _hexadecimalTextBox.LostFocus -= new RoutedEventHandler( HexadecimalTextBox_LostFocus ); + + _hexadecimalTextBox = GetTemplateChild( PART_HexadecimalTextBox ) as TextBox; + + if( _hexadecimalTextBox != null ) + _hexadecimalTextBox.LostFocus += new RoutedEventHandler( HexadecimalTextBox_LostFocus ); + UpdateRGBValues( SelectedColor ); UpdateColorShadeSelectorPosition( SelectedColor ); + + // When changing theme, HexadecimalString needs to be set since it is not binded. + SetHexadecimalTextBoxTextProperty( GetFormatedColorString( SelectedColor ) ); } - protected override void OnPreviewKeyDown( KeyEventArgs e ) + protected override void OnKeyDown( KeyEventArgs e ) { - //hitting enter on textbox will update value of underlying source + base.OnKeyDown( e ); + + //hitting enter on textbox will update Hexadecimal string if( e.Key == Key.Enter && e.OriginalSource is TextBox ) { - BindingExpression be = ( ( TextBox )e.OriginalSource ).GetBindingExpression( TextBox.TextProperty ); - be.UpdateSource(); + TextBox textBox = ( TextBox )e.OriginalSource; + if( textBox.Name == PART_HexadecimalTextBox ) + SetHexadecimalStringProperty( textBox.Text, true ); } } @@ -404,6 +427,12 @@ namespace Xceed.Wpf.Toolkit } } + void HexadecimalTextBox_LostFocus( object sender, RoutedEventArgs e ) + { + TextBox textbox = sender as TextBox; + SetHexadecimalStringProperty( textbox.Text, true ); + } + #endregion //Event Handlers #region Events @@ -503,7 +532,7 @@ namespace Xceed.Wpf.Toolkit var currentColor = ColorUtilities.ConvertHsvToRgb( hsv.H, hsv.S, hsv.V ); currentColor.A = A; SelectedColor = currentColor; - HexadecimalString = GetFormatedColorString( SelectedColor ); + SetHexadecimalStringProperty( GetFormatedColorString( SelectedColor ), false ); } private string GetFormatedColorString( Color colorToFormat ) @@ -516,6 +545,34 @@ namespace Xceed.Wpf.Toolkit return ColorUtilities.FormatColorString( stringToFormat, UsingAlphaChannel ); } + private void SetHexadecimalStringProperty( string newValue, bool modifyFromUI ) + { + if( modifyFromUI ) + { + try + { + ColorConverter.ConvertFromString( newValue ); + HexadecimalString = newValue; + } + catch + { + //When HexadecimalString is changed via UI and hexadecimal format is bad, keep the previous HexadecimalString. + SetHexadecimalTextBoxTextProperty( HexadecimalString ); + } + } + else + { + //When HexadecimalString is changed via Code-Behind, hexadecimal format will be evaluated in OnCoerceHexadecimalString() + HexadecimalString = newValue; + } + } + + private void SetHexadecimalTextBoxTextProperty( string newValue ) + { + if( _hexadecimalTextBox != null ) + _hexadecimalTextBox.Text = newValue; + } + #endregion //Methods } } diff --git a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/ColorCanvas/Themes/Generic.xaml b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/ColorCanvas/Themes/Generic.xaml index 7164d0a0..4bb44947 100644 --- a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/ColorCanvas/Themes/Generic.xaml +++ b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/ColorCanvas/Themes/Generic.xaml @@ -260,7 +260,7 @@ - + diff --git a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/ColorPicker/Implementation/ColorPicker.cs b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/ColorPicker/Implementation/ColorPicker.cs index 42aca48a..899743e0 100644 --- a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/ColorPicker/Implementation/ColorPicker.cs +++ b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/ColorPicker/Implementation/ColorPicker.cs @@ -343,7 +343,7 @@ namespace Xceed.Wpf.Toolkit { return ( bool )GetValue( UsingAlphaChannelProperty ); } - protected set + set { SetValue( UsingAlphaChannelProperty, value ); } diff --git a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Converters/WizardPageButtonVisibilityConverter.cs b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Converters/WizardPageButtonVisibilityConverter.cs index 1c8bc7a9..317fafbf 100644 --- a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Converters/WizardPageButtonVisibilityConverter.cs +++ b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Converters/WizardPageButtonVisibilityConverter.cs @@ -28,7 +28,9 @@ namespace Xceed.Wpf.Toolkit.Core.Converters public object Convert( object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture ) { Visibility wizardVisibility = ( Visibility )values[ 0 ]; - WizardPageButtonVisibility wizardPageVisibility = ( WizardPageButtonVisibility )values[ 1 ]; + WizardPageButtonVisibility wizardPageVisibility = ( (values[ 1 ] == null) || (values[ 1 ] == DependencyProperty.UnsetValue) ) + ? WizardPageButtonVisibility.Hidden + : ( WizardPageButtonVisibility )values[ 1 ]; Visibility visibility = Visibility.Visible; diff --git a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Input/IValidateInput.cs b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Input/IValidateInput.cs new file mode 100644 index 00000000..15d600da --- /dev/null +++ b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Input/IValidateInput.cs @@ -0,0 +1,32 @@ +/************************************************************************ + + Extended WPF Toolkit + + Copyright (C) 2010-2012 Xceed Software Inc. + + This program is provided to you under the terms of the Microsoft Public + License (Ms-PL) as published at http://wpftoolkit.codeplex.com/license + + This program can be provided to you by Xceed Software Inc. under a + proprietary commercial license agreement for use in non-Open Source + projects. The commercial version of Extended WPF Toolkit also includes + priority technical support, commercial updates, and many additional + useful WPF controls if you license Xceed Business Suite for WPF. + + Visit http://xceed.com and follow @datagrid on Twitter. + + **********************************************************************/ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Xceed.Wpf.Toolkit.Core.Input +{ + public interface IValidateInput + { + event InputValidationErrorEventHandler InputValidationError; + void CommitInput(); + } +} diff --git a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Input/InputValidationErrorEventArgs.cs b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Input/InputValidationErrorEventArgs.cs new file mode 100644 index 00000000..312c90ce --- /dev/null +++ b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Input/InputValidationErrorEventArgs.cs @@ -0,0 +1,46 @@ +/************************************************************************ + + Extended WPF Toolkit + + Copyright (C) 2010-2012 Xceed Software Inc. + + This program is provided to you under the terms of the Microsoft Public + License (Ms-PL) as published at http://wpftoolkit.codeplex.com/license + + This program can be provided to you by Xceed Software Inc. under a + proprietary commercial license agreement for use in non-Open Source + projects. The commercial version of Extended WPF Toolkit also includes + priority technical support, commercial updates, and many additional + useful WPF controls if you license Xceed Business Suite for WPF. + + Visit http://xceed.com and follow @datagrid on Twitter. + + **********************************************************************/ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Xceed.Wpf.Toolkit.Core.Input +{ + public delegate void InputValidationErrorEventHandler( object sender, InputValidationErrorEventArgs e ); + + public class InputValidationErrorEventArgs : EventArgs + { + public InputValidationErrorEventArgs( string errorMsg ) + { + _errorMessage = errorMsg; + } + + public string ErrorMessage + { + get + { + return _errorMessage; + } + } + + private string _errorMessage; + } +} diff --git a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Primitives/CachedTextInfo.cs b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Primitives/CachedTextInfo.cs new file mode 100644 index 00000000..d887ffb8 --- /dev/null +++ b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Primitives/CachedTextInfo.cs @@ -0,0 +1,57 @@ +/************************************************************************ + + Extended WPF Toolkit + + Copyright (C) 2010-2012 Xceed Software Inc. + + This program is provided to you under the terms of the Microsoft Public + License (Ms-PL) as published at http://wpftoolkit.codeplex.com/license + + This program can be provided to you by Xceed Software Inc. under a + proprietary commercial license agreement for use in non-Open Source + projects. The commercial version of Extended WPF Toolkit also includes + priority technical support, commercial updates, and many additional + useful WPF controls if you license Xceed Business Suite for WPF. + + Visit http://xceed.com and follow @datagrid on Twitter. + + **********************************************************************/ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Controls; + +namespace Xceed.Wpf.Toolkit.Primitives +{ + internal class CachedTextInfo : ICloneable + { + private CachedTextInfo( string text, int caretIndex, int selectionStart, int selectionLength ) + { + this.Text = text; + this.CaretIndex = caretIndex; + this.SelectionStart = selectionStart; + this.SelectionLength = selectionLength; + } + + public CachedTextInfo( TextBox textBox ) + : this( textBox.Text, textBox.CaretIndex, textBox.SelectionStart, textBox.SelectionLength ) + { + } + + public string Text { get; private set; } + public int CaretIndex { get; private set; } + public int SelectionStart { get; private set; } + public int SelectionLength { get; private set; } + + #region ICloneable Members + + public object Clone() + { + return new CachedTextInfo( this.Text, this.CaretIndex, this.SelectionStart, this.SelectionLength ); + } + + #endregion + } +} diff --git a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Primitives/Selector.cs b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Primitives/Selector.cs index f8cfcd3f..12c902f4 100644 --- a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Primitives/Selector.cs +++ b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Primitives/Selector.cs @@ -26,16 +26,28 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Input; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Reflection; +using Xceed.Wpf.Toolkit.Core.Utilities; namespace Xceed.Wpf.Toolkit.Primitives { - public class Selector : ItemsControl //should probably make this control an ICommandSource + public class Selector : ItemsControl, IWeakEventListener //should probably make this control an ICommandSource { #region Members - private bool _ignoreSetSelectedValue; - private bool _surpressSelectionChanged; - private bool _surpressSelectedValueChanged; + private bool _surpressItemSelectionChanged; + private bool _ignoreSelectedItemChanged; + private bool _ignoreSelectedValueChanged; + private int _ignoreSelectedItemsCollectionChanged; + private int _ignoreSelectedMemberPathValuesChanged; + private IList _selectedItems; + + private ValueChangeHelper _selectedMemberPathValuesHelper; + private ValueChangeHelper _valueMemberPathValuesHelper; + + #endregion //Members @@ -43,9 +55,11 @@ namespace Xceed.Wpf.Toolkit.Primitives public Selector() { - SelectedItems = new ObservableCollection(); - AddHandler( Selector.SelectedEvent, new RoutedEventHandler( Selector_ItemSelected ) ); - AddHandler( Selector.UnSelectedEvent, new RoutedEventHandler( Selector_ItemUnselected ) ); + this.SelectedItems = new ObservableCollection(); + AddHandler( Selector.SelectedEvent, new RoutedEventHandler( ( s, args ) => this.OnItemSelectionChangedCore( args, false ) ) ); + AddHandler( Selector.UnSelectedEvent, new RoutedEventHandler( ( s, args ) => this.OnItemSelectionChangedCore( args, true ) ) ); + _selectedMemberPathValuesHelper = new ValueChangeHelper( this.OnSelectedMemberPathValuesChanged ); + _valueMemberPathValuesHelper = new ValueChangeHelper( this.OnValueMemberPathValuesChanged ); } #endregion //Constructors @@ -88,7 +102,9 @@ namespace Xceed.Wpf.Toolkit.Primitives #endregion - public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register( "SelectedItem", typeof( object ), typeof( Selector ), new UIPropertyMetadata( null ) ); + #region SelectedItem property + + public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register( "SelectedItem", typeof( object ), typeof( Selector ), new UIPropertyMetadata( null, OnSelectedItemChanged ) ); public object SelectedItem { get @@ -101,22 +117,91 @@ namespace Xceed.Wpf.Toolkit.Primitives } } - //Since you cannot data bind to ReadOnly DependencyProperty, I am leaving this a public get/set DP. This will allow you to data bind to the SelectedItems from a ViewModel, but it is - //intended to be ReadOnly. So you MUST set the binding Mode=OneWayToSource. Otherwise it will not behave as expected. - public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register( "SelectedItems", typeof( IList ), typeof( Selector ), new UIPropertyMetadata( null ) ); + private static void OnSelectedItemChanged( DependencyObject sender, DependencyPropertyChangedEventArgs args ) + { + ( ( Selector )sender ).OnSelectedItemChanged( args.OldValue, args.NewValue ); + } + + private void OnSelectedItemChanged( object oldValue, object newValue ) + { + if( _ignoreSelectedItemChanged ) + return; + + _ignoreSelectedItemsCollectionChanged++; + SelectedItems.Clear(); + if( newValue != null ) + { + SelectedItems.Add( newValue ); + } + this.UpdateFromSelectedItems(); + _ignoreSelectedItemsCollectionChanged--; + } + + #endregion + + #region SelectedItems Property + public IList SelectedItems { get { - return ( IList )GetValue( SelectedItemsProperty ); + return _selectedItems; + } + private set + { + if( value == null ) + throw new ArgumentNullException( "value" ); + + INotifyCollectionChanged oldCollection = _selectedItems as INotifyCollectionChanged; + INotifyCollectionChanged newCollection = value as INotifyCollectionChanged; + + if( oldCollection != null ) + { + CollectionChangedEventManager.RemoveListener( oldCollection, this ); + } + + if( newCollection != null ) + { + CollectionChangedEventManager.AddListener( newCollection, this ); + } + + _selectedItems = value; + + this.UpdateFromSelectedItems(); + } + } + + #endregion SelectedItems + + + #region SelectedItemsOverride property + + public static readonly DependencyProperty SelectedItemsOverrideProperty = DependencyProperty.Register( "SelectedItemsOverride", typeof( IList ), typeof( Selector ), new UIPropertyMetadata( null, SelectedItemsOverrideChanged ) ); + public IList SelectedItemsOverride + { + get + { + return ( IList )GetValue( SelectedItemsOverrideProperty ); } set { - SetValue( SelectedItemsProperty, value ); + SetValue( SelectedItemsOverrideProperty, value ); } } - public static readonly DependencyProperty SelectedMemberPathProperty = DependencyProperty.Register( "SelectedMemberPath", typeof( string ), typeof( Selector ), new UIPropertyMetadata( null ) ); + private static void SelectedItemsOverrideChanged( DependencyObject sender, DependencyPropertyChangedEventArgs args ) + { + ( ( Selector )sender ).OnSelectedItemsOverrideChanged( ( IList )args.OldValue, ( IList )args.NewValue ); + } + + private void OnSelectedItemsOverrideChanged( IList oldValue, IList newValue ) + { + this.SelectedItems = ( newValue != null ) ? newValue : new ObservableCollection(); + } + + #endregion + + public static readonly DependencyProperty SelectedMemberPathProperty = DependencyProperty.Register( "SelectedMemberPath", typeof( string ), typeof( Selector ), new UIPropertyMetadata( null, OnSelectedMemberPathChanged ) ); public string SelectedMemberPath { get @@ -129,6 +214,11 @@ namespace Xceed.Wpf.Toolkit.Primitives } } + private static void OnSelectedMemberPathChanged( DependencyObject o, DependencyPropertyChangedEventArgs e ) + { + ( ( Selector )o ).UpdateSelectedMemberPathValuesBindings(); + } + #region SelectedValue public static readonly DependencyProperty SelectedValueProperty = DependencyProperty.Register( "SelectedValue", typeof( string ), typeof( Selector ), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedValueChanged ) ); @@ -153,10 +243,10 @@ namespace Xceed.Wpf.Toolkit.Primitives protected virtual void OnSelectedValueChanged( string oldValue, string newValue ) { - if( _surpressSelectedValueChanged ) + if( _ignoreSelectedValueChanged ) return; - UpdateSelectedItemsFromSelectedValue(); + UpdateFromSelectedValue(); } #endregion //SelectedValue @@ -178,7 +268,20 @@ namespace Xceed.Wpf.Toolkit.Primitives private static void OnValueMemberPathChanged( DependencyObject o, DependencyPropertyChangedEventArgs e ) { - ( ( Selector )o ).UpdateSelectedValue(); + + ( ( Selector )o ).UpdateValueMemberPathValuesBindings(); + } + + #endregion + + #region ItemsCollection Property + + protected IEnumerable ItemsCollection + { + get + { + return ItemsSource ?? ( ( IEnumerable )Items ?? ( IEnumerable )new object[ 0 ] ); + } } #endregion @@ -199,79 +302,65 @@ namespace Xceed.Wpf.Toolkit.Primitives protected override void PrepareContainerForItemOverride( DependencyObject element, object item ) { - _surpressSelectionChanged = true; + base.PrepareContainerForItemOverride( element, item ); + + _surpressItemSelectionChanged = true; var selectorItem = element as FrameworkElement; - //first try resolving SelectorItem.IsSelected by data binding to the SelectedMemeberPath property - if( !String.IsNullOrEmpty( SelectedMemberPath ) ) + selectorItem.SetValue( SelectorItem.IsSelectedProperty, SelectedItems.Contains(item) ); + + _surpressItemSelectionChanged = false; + } + + protected override void OnItemsSourceChanged( IEnumerable oldValue, IEnumerable newValue ) + { + base.OnItemsSourceChanged( oldValue, newValue ); + + var oldCollection = oldValue as INotifyCollectionChanged; + var newCollection = newValue as INotifyCollectionChanged; + + if( oldCollection != null ) { - Binding selectedBinding = new Binding( SelectedMemberPath ) - { - Mode = BindingMode.TwoWay, - Source = item - }; - selectorItem.SetBinding( SelectorItem.IsSelectedProperty, selectedBinding ); + CollectionChangedEventManager.RemoveListener( oldCollection, this ); } - //now let's search the SelectedItems for the current item. If it's there then mark it as selected - if( SelectedItems != null ) + if( newCollection != null ) { - foreach( object selectedItem in SelectedItems ) - { - //a match was found so select it and get the hell out of here - if( item.Equals( selectedItem ) ) - { - selectorItem.SetValue( SelectorItem.IsSelectedProperty, true ); - break; - } - } + CollectionChangedEventManager.AddListener( newCollection, this ); } - base.PrepareContainerForItemOverride( element, item ); - _surpressSelectionChanged = false; + this.RemoveUnavailableSelectedItems(); + this.UpdateSelectedMemberPathValuesBindings(); + this.UpdateValueMemberPathValuesBindings(); } #endregion //Base Class Overrides #region Events - public static readonly RoutedEvent SelectedEvent = EventManager.RegisterRoutedEvent( "SelectedEvent", RoutingStrategy.Bubble, typeof( SelectedItemChangedEventHandler ), typeof( Selector ) ); - public static readonly RoutedEvent UnSelectedEvent = EventManager.RegisterRoutedEvent( "UnSelectedEvent", RoutingStrategy.Bubble, typeof( SelectedItemChangedEventHandler ), typeof( Selector ) ); + public static readonly RoutedEvent SelectedEvent = EventManager.RegisterRoutedEvent( "SelectedEvent", RoutingStrategy.Bubble, typeof( RoutedEventHandler ), typeof( Selector ) ); + public static readonly RoutedEvent UnSelectedEvent = EventManager.RegisterRoutedEvent( "UnSelectedEvent", RoutingStrategy.Bubble, typeof( RoutedEventHandler ), typeof( Selector ) ); - public static readonly RoutedEvent SelectedItemChangedEvent = EventManager.RegisterRoutedEvent( "SelectedItemChanged", RoutingStrategy.Bubble, typeof( SelectedItemChangedEventHandler ), typeof( Selector ) ); - public event SelectedItemChangedEventHandler SelectedItemChanged + public static readonly RoutedEvent ItemSelectionChangedEvent = EventManager.RegisterRoutedEvent( "ItemSelectionChanged", RoutingStrategy.Bubble, typeof( ItemSelectionChangedEventHandler ), typeof( Selector ) ); + public event ItemSelectionChangedEventHandler ItemSelectionChanged { add { - AddHandler( SelectedItemChangedEvent, value ); + AddHandler( ItemSelectionChangedEvent, value ); } remove { - RemoveHandler( SelectedItemChangedEvent, value ); + RemoveHandler( ItemSelectionChangedEvent, value ); } } #endregion //Events - #region Event Handlers - - protected virtual void Selector_ItemSelected( object sender, RoutedEventArgs e ) - { - OnItemSelected( e.OriginalSource, false ); - } - - protected virtual void Selector_ItemUnselected( object sender, RoutedEventArgs e ) - { - OnItemSelected( e.OriginalSource, true ); - } - - #endregion //Event Handlers - #region Methods protected object GetItemValue( object item ) { - if( !String.IsNullOrEmpty( ValueMemberPath ) && (item != null)) + if( !String.IsNullOrEmpty( ValueMemberPath ) && ( item != null ) ) { var property = item.GetType().GetProperty( ValueMemberPath ); if( property != null ) @@ -281,49 +370,86 @@ namespace Xceed.Wpf.Toolkit.Primitives return item; } - protected string GetDelimitedValue( object value ) + protected object ResolveItemByValue( string value ) { - return String.Format( "{0}{1}", value, Delimiter ); + if( !String.IsNullOrEmpty( ValueMemberPath ) ) + { + foreach( object item in ItemsCollection ) + { + var property = item.GetType().GetProperty( ValueMemberPath ); + if( property != null ) + { + var propertyValue = property.GetValue( item, null ); + if( value.Equals( propertyValue.ToString(), StringComparison.InvariantCultureIgnoreCase ) ) + return item; + } + } + } + + return value; } - protected virtual void OnItemSelected( object itemContainer, bool remove ) + private bool? GetSelectedMemberPathValue( object item ) { - object item = this.ItemContainerGenerator.ItemFromContainer( ( DependencyObject )itemContainer ); - Update( item, remove ); - RaiseSelectedItemChangedEvent( item, !remove ); //inverse the remove paramter to correctly reflect the IsSelected state + PropertyInfo prop = this.GetSelectedMemberPathProperty(item); + + return ( prop != null ) + ? ( bool )prop.GetValue( item, null ) + : ( bool? )null; } - protected virtual void RaiseSelectedItemChangedEvent( object item, bool isSelected ) + private void SetSelectedMemberPathValue( object item, bool value ) { - if( _surpressSelectionChanged ) - return; - - RaiseEvent( new SelectedItemChangedEventArgs( Selector.SelectedItemChangedEvent, this, item, isSelected ) ); + PropertyInfo prop = this.GetSelectedMemberPathProperty(item); - if( Command != null ) - Command.Execute( item ); + if( prop != null ) + { + prop.SetValue( item, value, null ); + } } - protected virtual void Update( object item, bool remove ) + private PropertyInfo GetSelectedMemberPathProperty(object item) { - UpdateSelectedItem( item ); - UpdateSelectedItems( item, remove ); - UpdateSelectedValue(); + PropertyInfo propertyInfo = null; + if( !String.IsNullOrEmpty( SelectedMemberPath ) && ( item != null ) ) + { + var property = item.GetType().GetProperty( SelectedMemberPath ); + if( property != null && property.PropertyType == typeof( bool ) ) + { + propertyInfo = property; + } + } + + return propertyInfo; } - private void UpdateSelectedItem( object item ) + /// + /// When SelectedItems collection implements INotifyPropertyChanged, this is the callback. + /// + /// + /// + private void OnSelectedItemsCollectionChanged( object sender, NotifyCollectionChangedEventArgs e ) { - SelectedItem = item; + if( _ignoreSelectedItemsCollectionChanged > 0 ) + return; + + // Keep it simple for now. Just update all + this.UpdateFromSelectedItems(); } - private void UpdateSelectedItems( object item, bool remove ) + private void OnItemSelectionChangedCore( RoutedEventArgs args, bool unselected ) { - if( SelectedItems == null ) - SelectedItems = new ObservableCollection(); + object item = this.ItemContainerGenerator.ItemFromContainer( ( DependencyObject )args.OriginalSource ); - if( remove ) + // When the item is it's own container, "UnsetValue" will be returned. + if( item == DependencyProperty.UnsetValue ) { - if( SelectedItems.Contains( item ) ) + item = args.OriginalSource; + } + + if( unselected ) + { + while( SelectedItems.Contains( item ) ) SelectedItems.Remove( item ); } else @@ -331,116 +457,263 @@ namespace Xceed.Wpf.Toolkit.Primitives if( !SelectedItems.Contains( item ) ) SelectedItems.Add( item ); } + + OnItemSelectionChanged( + new ItemSelectionChangedEventArgs( Selector.ItemSelectionChangedEvent, this, item, !unselected ) ); } - private void UpdateSelectedValue() + /// + /// When the ItemsSource implements INotifyPropertyChanged, this is the change callback. + /// + /// + /// + private void OnItemsSourceCollectionChanged( object sender, NotifyCollectionChangedEventArgs args ) { - //get out of here if we don't want to set the SelectedValue - if( _ignoreSetSelectedValue ) + this.RemoveUnavailableSelectedItems(); + this.UpdateSelectedMemberPathValuesBindings(); + this.UpdateValueMemberPathValuesBindings(); + } + + /// + /// This is called when any value of any item referenced by SelectedMemberPath + /// is modified. This may affect the SelectedItems collection. + /// + private void OnSelectedMemberPathValuesChanged() + { + if( _ignoreSelectedMemberPathValuesChanged > 0 ) return; - _surpressSelectedValueChanged = true; + this.UpdateFromSelectedMemberPathValues(); + } + + /// + /// This is called when any value of any item referenced by ValueMemberPath + /// is modified. This will affect the SelectedValue property + /// + private void OnValueMemberPathValuesChanged() + { + this.UpdateSelectedValue(); + } + + private void UpdateSelectedMemberPathValuesBindings() + { + _selectedMemberPathValuesHelper.UpdateValueSource( ItemsCollection, SelectedMemberPath ); + } + + private void UpdateValueMemberPathValuesBindings() + { + _valueMemberPathValuesHelper.UpdateValueSource( ItemsCollection, ValueMemberPath ); + } + /// + /// This method will be called when the "IsSelected" property of an SelectorItem + /// has been modified. + /// + /// + protected virtual void OnItemSelectionChanged( ItemSelectionChangedEventArgs args ) + { + if( _surpressItemSelectionChanged ) + return; + + RaiseEvent( args ); + + if( Command != null ) + Command.Execute( args.Item ); + } + + /// + /// Updates the SelectedValue property based on what is present in the SelectedItems property. + /// + private void UpdateSelectedValue() + { #if VS2008 string newValue = String.Join( Delimiter, SelectedItems.Cast().Select( x => GetItemValue( x ).ToString() ).ToArray() ); #else string newValue = String.Join( Delimiter, SelectedItems.Cast().Select( x => GetItemValue( x ) ) ); #endif if( String.IsNullOrEmpty( SelectedValue ) || !SelectedValue.Equals( newValue ) ) + { + _ignoreSelectedValueChanged = true; SelectedValue = newValue; - - _surpressSelectedValueChanged = false; + _ignoreSelectedValueChanged = false; + } } - private void UpdateSelectedItemsFromSelectedValue() + /// + /// Updates the SelectedItem property based on what is present in the SelectedItems property. + /// + private void UpdateSelectedItem() { - _surpressSelectionChanged = true; - - //first we have to unselect everything - ClearSelectedItems(); + if( !SelectedItems.Contains( SelectedItem ) ) + { + _ignoreSelectedItemChanged = true; + SelectedItem = ( SelectedItems.Count > 0 ) ? SelectedItems[ 0 ] : null; + _ignoreSelectedItemChanged = false; + } + } - if( !String.IsNullOrEmpty( SelectedValue ) ) + /// + /// Update the SelectedItems collection based on the values + /// refered to by the SelectedMemberPath property. + /// + private void UpdateFromSelectedMemberPathValues() + { + _ignoreSelectedItemsCollectionChanged++; + foreach( var item in ItemsCollection ) { - string[] values = SelectedValue.Split( new string[] { Delimiter }, StringSplitOptions.RemoveEmptyEntries ); - foreach( string value in values ) + bool? isSelected = this.GetSelectedMemberPathValue( item ); + if( isSelected != null ) { - var item = ResolveItemByValue( value ); - - if( item != null ) + if( isSelected.Value ) { - SelectedItems.Add( item ); - - //now try to select it in the list - var selectorItem = ItemContainerGenerator.ContainerFromItem( item ) as SelectorItem; - if( selectorItem != null ) + if( !SelectedItems.Contains( item ) ) + { + SelectedItems.Add( item ); + } + } + else + { + if( SelectedItems.Contains( item ) ) { - if( !selectorItem.IsSelected ) - selectorItem.IsSelected = true; + SelectedItems.Remove( item ); } } } } - - _surpressSelectionChanged = false; + _ignoreSelectedItemsCollectionChanged--; + this.UpdateFromSelectedItems(); } - private void ClearSelectedItems() + /// + /// Updates the following based on the content of SelectedItems: + /// - All SelectorItems "IsSelected" properties + /// - Values refered to by SelectedMemberPath + /// - SelectedItem property + /// - SelectedValue property + /// Refered to by the SelectedMemberPath property. + /// + private void UpdateFromSelectedItems() { - if( SelectedItems != null ) - SelectedItems.Clear(); - else - SelectedItems = new ObservableCollection(); + foreach( object o in ItemsCollection ) + { + bool isSelected = SelectedItems.Contains( o ); - UnselectAllInternal(); + _ignoreSelectedMemberPathValuesChanged++; + this.SetSelectedMemberPathValue(o, isSelected); + _ignoreSelectedMemberPathValuesChanged--; + + var selectorItem = ItemContainerGenerator.ContainerFromItem( o ) as SelectorItem; + if( selectorItem != null ) + { + selectorItem.IsSelected = isSelected; + } + } + + UpdateSelectedItem(); + UpdateSelectedValue(); } - private void UnselectAllInternal() + /// + /// Removes all items from SelectedItems that are no longer in ItemsSource. + /// + private void RemoveUnavailableSelectedItems() { - _ignoreSetSelectedValue = true; + _ignoreSelectedItemsCollectionChanged++; + HashSet hash = new HashSet( ItemsCollection.Cast() ); - if( ItemsSource != null ) + for( int i = 0; i < SelectedItems.Count; i++ ) { - foreach( object item in ItemsSource ) + if( !hash.Contains( SelectedItems[ i ] ) ) { - var selectorItem = ItemContainerGenerator.ContainerFromItem( item ) as SelectorItem; - if( selectorItem != null ) - { - if( selectorItem.IsSelected ) - selectorItem.IsSelected = false; - } + SelectedItems.RemoveAt( i ); + i--; } } + _ignoreSelectedItemsCollectionChanged--; - _ignoreSetSelectedValue = false; + UpdateSelectedItem(); + UpdateSelectedValue(); } - protected object ResolveItemByValue( string value ) + /// + /// Updates the SelectedItems collection based on the content of + /// the SelectedValue property. + /// + private void UpdateFromSelectedValue() { - if( !String.IsNullOrEmpty( ValueMemberPath ) ) + _ignoreSelectedItemsCollectionChanged++; + // Just update the SelectedItems collection content + // and let the synchronization be made from UpdateFromSelectedItems(); + SelectedItems.Clear(); + + if( !String.IsNullOrEmpty( SelectedValue ) ) { - if( ItemsSource != null ) + List selectedValues = SelectedValue.Split( new string[] { Delimiter }, StringSplitOptions.RemoveEmptyEntries ).ToList(); + ValueEqualityComparer comparer = new ValueEqualityComparer(); + + foreach( object item in ItemsCollection ) { - foreach( object item in ItemsSource ) + object itemValue = this.GetItemValue( item ); + + bool isSelected = ( itemValue != null ) + && selectedValues.Contains( itemValue.ToString(), comparer ); + + if( isSelected ) { - var property = item.GetType().GetProperty( ValueMemberPath ); - if( property != null ) - { - var propertyValue = property.GetValue( item, null ); - if( value.Equals( propertyValue.ToString(), StringComparison.InvariantCultureIgnoreCase ) ) - return item; - } + SelectedItems.Add( item ); } } } + _ignoreSelectedItemsCollectionChanged--; - return value; + this.UpdateFromSelectedItems(); } #endregion //Methods + + #region IWeakEventListener Members + + public bool ReceiveWeakEvent( Type managerType, object sender, EventArgs e ) + { + if( managerType == typeof( CollectionChangedEventManager ) ) + { + if( object.ReferenceEquals( _selectedItems, sender ) ) + { + this.OnSelectedItemsCollectionChanged( sender, ( NotifyCollectionChangedEventArgs )e ); + return true; + } + else if( object.ReferenceEquals( ItemsCollection, sender ) ) + { + this.OnItemsSourceCollectionChanged( sender, ( NotifyCollectionChangedEventArgs )e ); + } + } + + return false; + } + + #endregion + + #region ValueEqualityComparer private class + + private class ValueEqualityComparer : IEqualityComparer + { + public bool Equals( string x, string y ) + { + return string.Equals( x, y, StringComparison.InvariantCultureIgnoreCase ); + } + + public int GetHashCode( string obj ) + { + return 1; + } + } + + #endregion } - public delegate void SelectedItemChangedEventHandler( object sender, SelectedItemChangedEventArgs e ); - public class SelectedItemChangedEventArgs : RoutedEventArgs + + public delegate void ItemSelectionChangedEventHandler( object sender, ItemSelectionChangedEventArgs e ); + public class ItemSelectionChangedEventArgs : RoutedEventArgs { public bool IsSelected { @@ -453,7 +726,7 @@ namespace Xceed.Wpf.Toolkit.Primitives private set; } - public SelectedItemChangedEventArgs( RoutedEvent routedEvent, object source, object item, bool isSelected ) + public ItemSelectionChangedEventArgs( RoutedEvent routedEvent, object source, object item, bool isSelected ) : base( routedEvent, source ) { Item = item; diff --git a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Primitives/SelectorItem.cs b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Primitives/SelectorItem.cs index ab70ae5b..9996902e 100644 --- a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Primitives/SelectorItem.cs +++ b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Primitives/SelectorItem.cs @@ -64,9 +64,9 @@ namespace Xceed.Wpf.Toolkit.Primitives protected virtual void OnIsSelectedChanged( bool oldValue, bool newValue ) { if( newValue ) - RaiseSelectionChangedEvent( new RoutedEventArgs( Selector.SelectedEvent, this ) ); + this.RaiseEvent( new RoutedEventArgs( Selector.SelectedEvent, this ) ); else - RaiseSelectionChangedEvent( new RoutedEventArgs( Selector.UnSelectedEvent, this ) ); + this.RaiseEvent( new RoutedEventArgs( Selector.UnSelectedEvent, this ) ); } internal Selector ParentSelector @@ -95,13 +95,5 @@ namespace Xceed.Wpf.Toolkit.Primitives #endregion //Event Hanlders - #region Methods - - private void RaiseSelectionChangedEvent( RoutedEventArgs e ) - { - base.RaiseEvent( e ); - } - - #endregion //Methods } } diff --git a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Primitives/UpDownBase.cs b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Primitives/UpDownBase.cs index 86893750..021995d8 100644 --- a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Primitives/UpDownBase.cs +++ b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Primitives/UpDownBase.cs @@ -22,12 +22,14 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Input; +using Xceed.Wpf.Toolkit.Core; +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 + public abstract class UpDownBase : InputBase, IValidateInput { #region Members @@ -45,6 +47,7 @@ namespace Xceed.Wpf.Toolkit.Primitives /// Flags if the Text and Value properties are in the process of being sync'd /// private bool _isSyncingTextAndValueProperties; + private bool _isTextChangedFromUI; #endregion //Members @@ -114,7 +117,7 @@ namespace Xceed.Wpf.Toolkit.Primitives #region Value - public static readonly DependencyProperty ValueProperty = DependencyProperty.Register( "Value", typeof( T ), typeof( UpDownBase ), new FrameworkPropertyMetadata( default( T ), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnValueChanged, null, false, UpdateSourceTrigger.LostFocus ) ); + public static readonly DependencyProperty ValueProperty = DependencyProperty.Register( "Value", typeof( T ), typeof( UpDownBase ), new FrameworkPropertyMetadata( default( T ), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnValueChanged, OnCoerceValue, false, UpdateSourceTrigger.LostFocus ) ); public T Value { get @@ -127,6 +130,16 @@ namespace Xceed.Wpf.Toolkit.Primitives } } + 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; @@ -136,9 +149,7 @@ namespace Xceed.Wpf.Toolkit.Primitives protected virtual void OnValueChanged( T oldValue, T newValue ) { - ValidateValue( newValue ); - - SyncTextAndValueProperties( ValueProperty, string.Empty ); + SyncTextAndValueProperties( false, null ); SetValidSpinDirection(); @@ -174,6 +185,12 @@ namespace Xceed.Wpf.Toolkit.Primitives base.OnApplyTemplate(); TextBox = GetTemplateChild( PART_TextBox ) as TextBox; + if( TextBox != null ) + { + TextBox.Text = Text; + TextBox.LostFocus += new RoutedEventHandler( TextBox_LostFocus ); + TextBox.TextChanged += new TextChangedEventHandler( TextBox_TextChanged ); + } if( Spinner != null ) Spinner.Spin -= OnSpinnerSpin; @@ -213,6 +230,20 @@ namespace Xceed.Wpf.Toolkit.Primitives } } + protected override void OnKeyDown( KeyEventArgs e ) + { + switch( e.Key ) + { + case Key.Enter: + { + // Commit Text on "Enter" to raise Error event + CommitInput(); + e.Handled = true; + break; + } + } + } + protected override void OnMouseWheel( MouseWheelEventArgs e ) { base.OnMouseWheel( e ); @@ -234,7 +265,10 @@ namespace Xceed.Wpf.Toolkit.Primitives protected override void OnTextChanged( string oldValue, string newValue ) { - SyncTextAndValueProperties( InputBase.TextProperty, newValue ); + if( !_isTextChangedFromUI ) + { + SyncTextAndValueProperties( true, Text ); + } } #endregion //Base Class Overrides @@ -251,6 +285,10 @@ namespace Xceed.Wpf.Toolkit.Primitives #region Events + public event InputValidationErrorEventHandler InputValidationError; + + #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 @@ -265,6 +303,8 @@ namespace Xceed.Wpf.Toolkit.Primitives } } + #endregion + #endregion //Events #region Methods @@ -302,38 +342,65 @@ namespace Xceed.Wpf.Toolkit.Primitives } } - protected void SyncTextAndValueProperties( DependencyProperty p, string text ) + private void TextBox_TextChanged( object sender, TextChangedEventArgs e ) + { + _isTextChangedFromUI = true; + + TextBox textBox = sender as TextBox; + Text = textBox.Text; + + _isTextChangedFromUI = false; + } + + private void TextBox_LostFocus( object sender, RoutedEventArgs e ) + { + CommitInput(); + } + + public void CommitInput() + { + this.SyncTextAndValueProperties( true, Text ); + } + + protected void SyncTextAndValueProperties(bool updateValueFromText, string text ) { - //prevents recursive syncing properties if( _isSyncingTextAndValueProperties ) return; _isSyncingTextAndValueProperties = true; + Exception error = null; - //this only occures when the user typed in the value - if( InputBase.TextProperty == p ) + if( updateValueFromText ) { - Value = ConvertTextToValue( text ); + try + { + Value = ConvertTextToValue( Text ); + } + catch( Exception e ) + { + error = e; + } } Text = ConvertValueToText(); -#if VS2008 - //there is a bug in .NET 3.5 which will not correctly update the textbox text through binding. - if ( TextBox != null ) + if( TextBox != null ) TextBox.Text = Text; -#endif + + if( updateValueFromText ) + { + if( ( error != null ) && ( InputValidationError != null ) ) + { + InputValidationErrorEventArgs args = new InputValidationErrorEventArgs( error.Message ); + InputValidationError( this, args ); + } + } _isSyncingTextAndValueProperties = false; } #region Abstract - /// - /// Coerces the value. - /// - protected abstract T CoerceValue( T value ); - /// /// Converts the formatted text to a value. /// @@ -360,12 +427,6 @@ namespace Xceed.Wpf.Toolkit.Primitives /// protected abstract void SetValidSpinDirection(); - /// - /// Validates the value and keeps it between the Min and Max values. - /// - /// The value. - protected abstract void ValidateValue( T value ); - #endregion //Abstract #endregion //Methods diff --git a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Primitives/ValueRangeTextBox.cs b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Primitives/ValueRangeTextBox.cs new file mode 100644 index 00000000..7fcf8019 --- /dev/null +++ b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Primitives/ValueRangeTextBox.cs @@ -0,0 +1,1192 @@ +/************************************************************************ + + Extended WPF Toolkit + + Copyright (C) 2010-2012 Xceed Software Inc. + + This program is provided to you under the terms of the Microsoft Public + License (Ms-PL) as published at http://wpftoolkit.codeplex.com/license + + This program can be provided to you by Xceed Software Inc. under a + proprietary commercial license agreement for use in non-Open Source + projects. The commercial version of Extended WPF Toolkit also includes + priority technical support, commercial updates, and many additional + useful WPF controls if you license Xceed Business Suite for WPF. + + Visit http://xceed.com and follow @datagrid on Twitter. + + **********************************************************************/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows.Controls; +using System.Windows; +using System.Collections; +using System.Reflection; +using System.Windows.Input; +using System.Windows.Documents; +using System.Globalization; +using Microsoft.Win32; +using System.ComponentModel; +using System.Collections.Specialized; +using System.Diagnostics; +using System.Windows.Automation; +using Xceed.Wpf.Toolkit; +using Xceed.Wpf.Toolkit.Core; + +namespace Xceed.Wpf.Toolkit.Primitives +{ + public class ValueRangeTextBox : AutoSelectTextBox + { + static ValueRangeTextBox() + { + ValueRangeTextBox.TextProperty.OverrideMetadata( typeof( ValueRangeTextBox ), + new FrameworkPropertyMetadata( + null, + new CoerceValueCallback( ValueRangeTextBox.TextCoerceValueCallback ) ) ); + + ValueRangeTextBox.AcceptsReturnProperty.OverrideMetadata( typeof( ValueRangeTextBox ), + new FrameworkPropertyMetadata( + false, null, new CoerceValueCallback( ValueRangeTextBox.AcceptsReturnCoerceValueCallback ) ) ); + + ValueRangeTextBox.AcceptsTabProperty.OverrideMetadata( typeof( ValueRangeTextBox ), + new FrameworkPropertyMetadata( + false, null, new CoerceValueCallback( ValueRangeTextBox.AcceptsTabCoerceValueCallback ) ) ); + + AutomationProperties.AutomationIdProperty.OverrideMetadata( typeof( ValueRangeTextBox ), new UIPropertyMetadata( "ValueRangeTextBox" ) ); + } + + public ValueRangeTextBox() + { + } + + #region AcceptsReturn Property + + private static object AcceptsReturnCoerceValueCallback( DependencyObject sender, object value ) + { + bool acceptsReturn = ( bool )value; + + if( acceptsReturn ) + throw new NotSupportedException( "The ValueRangeTextBox does not support the AcceptsReturn property." ); + + return false; + } + + #endregion AcceptsReturn Property + + #region AcceptsTab Property + + private static object AcceptsTabCoerceValueCallback( DependencyObject sender, object value ) + { + bool acceptsTab = ( bool )value; + + if( acceptsTab ) + throw new NotSupportedException( "The ValueRangeTextBox does not support the AcceptsTab property." ); + + return false; + } + + #endregion AcceptsTab Property + + #region BeepOnError Property + + public bool BeepOnError + { + get + { + return ( bool )GetValue( BeepOnErrorProperty ); + } + set + { + SetValue( BeepOnErrorProperty, value ); + } + } + + public static readonly DependencyProperty BeepOnErrorProperty = + DependencyProperty.Register( "BeepOnError", typeof( bool ), typeof( ValueRangeTextBox ), new UIPropertyMetadata( false ) ); + + #endregion BeepOnError Property + + #region FormatProvider Property + + public IFormatProvider FormatProvider + { + get + { + return ( IFormatProvider )GetValue( FormatProviderProperty ); + } + set + { + SetValue( FormatProviderProperty, value ); + } + } + + public static readonly DependencyProperty FormatProviderProperty = + DependencyProperty.Register( "FormatProvider", typeof( IFormatProvider ), typeof( ValueRangeTextBox ), + new UIPropertyMetadata( null, + new PropertyChangedCallback( ValueRangeTextBox.FormatProviderPropertyChangedCallback ) ) ); + + private static void FormatProviderPropertyChangedCallback( DependencyObject sender, DependencyPropertyChangedEventArgs e ) + { + ValueRangeTextBox valueRangeTextBox = ( ValueRangeTextBox )sender; + + if( !valueRangeTextBox.IsInitialized ) + return; + + valueRangeTextBox.OnFormatProviderChanged(); + } + + internal virtual void OnFormatProviderChanged() + { + this.RefreshConversionHelpers(); + this.RefreshCurrentText( false ); + this.RefreshValue(); + } + + #endregion FormatProvider Property + + #region MinValue Property + + public object MinValue + { + get + { + return ( object )GetValue( MinValueProperty ); + } + set + { + SetValue( MinValueProperty, value ); + } + } + + public static readonly DependencyProperty MinValueProperty = + DependencyProperty.Register( "MinValue", typeof( object ), typeof( ValueRangeTextBox ), + new UIPropertyMetadata( + null, + null, + new CoerceValueCallback( ValueRangeTextBox.MinValueCoerceValueCallback ) ) ); + + private static object MinValueCoerceValueCallback( DependencyObject sender, object value ) + { + ValueRangeTextBox valueRangeTextBox = sender as ValueRangeTextBox; + + if( !valueRangeTextBox.IsInitialized ) + return DependencyProperty.UnsetValue; + + if( value == null ) + return value; + + Type type = valueRangeTextBox.ValueDataType; + + if( type == null ) + throw new InvalidOperationException( "An attempt was made to set a minimum value when the ValueDataType property is null." ); + + if( valueRangeTextBox.IsFinalizingInitialization ) + value = ValueRangeTextBox.ConvertValueToDataType( value, valueRangeTextBox.ValueDataType ); + + if( value.GetType() != type ) + throw new ArgumentException( "The value is not of type " + type.Name + ".", "MinValue" ); + + IComparable comparable = value as IComparable; + + if( comparable == null ) + throw new InvalidOperationException( "MinValue does not implement the IComparable interface." ); + + // ValidateValueInRange will throw if it must. + object maxValue = valueRangeTextBox.MaxValue; + + valueRangeTextBox.ValidateValueInRange( value, maxValue, valueRangeTextBox.Value ); + + return value; + } + + #endregion MinValue Property + + #region MaxValue Property + + public object MaxValue + { + get + { + return ( object )GetValue( MaxValueProperty ); + } + set + { + SetValue( MaxValueProperty, value ); + } + } + + public static readonly DependencyProperty MaxValueProperty = + DependencyProperty.Register( "MaxValue", typeof( object ), typeof( ValueRangeTextBox ), + new UIPropertyMetadata( + null, + null, + new CoerceValueCallback( MaxValueCoerceValueCallback ) ) ); + + private static object MaxValueCoerceValueCallback( DependencyObject sender, object value ) + { + ValueRangeTextBox valueRangeTextBox = sender as ValueRangeTextBox; + + if( !valueRangeTextBox.IsInitialized ) + return DependencyProperty.UnsetValue; + + if( value == null ) + return value; + + Type type = valueRangeTextBox.ValueDataType; + + if( type == null ) + throw new InvalidOperationException( "An attempt was made to set a maximum value when the ValueDataType property is null." ); + + if( valueRangeTextBox.IsFinalizingInitialization ) + value = ValueRangeTextBox.ConvertValueToDataType( value, valueRangeTextBox.ValueDataType ); + + if( value.GetType() != type ) + throw new ArgumentException( "The value is not of type " + type.Name + ".", "MinValue" ); + + IComparable comparable = value as IComparable; + + if( comparable == null ) + throw new InvalidOperationException( "MaxValue does not implement the IComparable interface." ); + + object minValue = valueRangeTextBox.MinValue; + + // ValidateValueInRange will throw if it must. + valueRangeTextBox.ValidateValueInRange( minValue, value, valueRangeTextBox.Value ); + + return value; + } + + #endregion MaxValue Property + + #region NullValue Property + + public object NullValue + { + get + { + return ( object )GetValue( NullValueProperty ); + } + set + { + SetValue( NullValueProperty, value ); + } + } + + public static readonly DependencyProperty NullValueProperty = + DependencyProperty.Register( "NullValue", typeof( object ), typeof( ValueRangeTextBox ), + new UIPropertyMetadata( + null, + new PropertyChangedCallback( ValueRangeTextBox.NullValuePropertyChangedCallback ), + new CoerceValueCallback( NullValueCoerceValueCallback ) ) ); + + private static object NullValueCoerceValueCallback( DependencyObject sender, object value ) + { + ValueRangeTextBox valueRangeTextBox = sender as ValueRangeTextBox; + + if( !valueRangeTextBox.IsInitialized ) + return DependencyProperty.UnsetValue; + + if( ( value == null ) || ( value == DBNull.Value ) ) + return value; + + Type type = valueRangeTextBox.ValueDataType; + + if( type == null ) + throw new InvalidOperationException( "An attempt was made to set a null value when the ValueDataType property is null." ); + + if( valueRangeTextBox.IsFinalizingInitialization ) + value = ValueRangeTextBox.ConvertValueToDataType( value, valueRangeTextBox.ValueDataType ); + + if( value.GetType() != type ) + throw new ArgumentException( "The value is not of type " + type.Name + ".", "NullValue" ); + + return value; + } + + private static void NullValuePropertyChangedCallback( DependencyObject sender, DependencyPropertyChangedEventArgs e ) + { + ValueRangeTextBox valueRangeTextBox = sender as ValueRangeTextBox; + + if( e.OldValue == null ) + { + if( valueRangeTextBox.Value == null ) + valueRangeTextBox.RefreshValue(); + } + else + { + if( e.OldValue.Equals( valueRangeTextBox.Value ) ) + valueRangeTextBox.RefreshValue(); + } + } + + #endregion NullValue Property + + #region Value Property + + public object Value + { + get + { + return ( object )GetValue( ValueProperty ); + } + set + { + SetValue( ValueProperty, value ); + } + } + + public static readonly DependencyProperty ValueProperty = + DependencyProperty.Register( "Value", typeof( object ), typeof( ValueRangeTextBox ), + new FrameworkPropertyMetadata( + null, + FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, + new PropertyChangedCallback( ValueRangeTextBox.ValuePropertyChangedCallback ), + new CoerceValueCallback( ValueRangeTextBox.ValueCoerceValueCallback ) ) ); + + private static object ValueCoerceValueCallback( object sender, object value ) + { + ValueRangeTextBox valueRangeTextBox = sender as ValueRangeTextBox; + + if( !valueRangeTextBox.IsInitialized ) + return DependencyProperty.UnsetValue; + + if( valueRangeTextBox.IsFinalizingInitialization ) + value = ValueRangeTextBox.ConvertValueToDataType( value, valueRangeTextBox.ValueDataType ); + + if( !valueRangeTextBox.IsForcingValue ) + valueRangeTextBox.ValidateValue( value ); + + return value; + } + + private static void ValuePropertyChangedCallback( object sender, DependencyPropertyChangedEventArgs e ) + { + ValueRangeTextBox valueRangeTextBox = sender as ValueRangeTextBox; + + if( valueRangeTextBox.IsForcingValue ) + return; + + // The ValueChangedCallback can be raised even though both values are the same since the property + // datatype is Object. + if( object.Equals( e.NewValue, e.OldValue ) ) + return; + + valueRangeTextBox.IsInValueChanged = true; + try + { + valueRangeTextBox.Text = valueRangeTextBox.GetTextFromValue( e.NewValue ); + } + finally + { + valueRangeTextBox.IsInValueChanged = false; + } + } + + #endregion Value Property + + #region ValueDataType Property + + public Type ValueDataType + { + get + { + return ( Type )GetValue( ValueDataTypeProperty ); + } + set + { + SetValue( ValueDataTypeProperty, value ); + } + } + + public static readonly DependencyProperty ValueDataTypeProperty = + DependencyProperty.Register( "ValueDataType", typeof( Type ), typeof( ValueRangeTextBox ), + new UIPropertyMetadata( + null, + new PropertyChangedCallback( ValueRangeTextBox.ValueDataTypePropertyChangedCallback ), + new CoerceValueCallback( ValueRangeTextBox.ValueDataTypeCoerceValueCallback ) ) ); + + private static object ValueDataTypeCoerceValueCallback( DependencyObject sender, object value ) + { + ValueRangeTextBox valueRangeTextBox = sender as ValueRangeTextBox; + + if( !valueRangeTextBox.IsInitialized ) + return DependencyProperty.UnsetValue; + + Type valueDataType = value as Type; + + try + { + valueRangeTextBox.ValidateDataType( valueDataType ); + } + catch( Exception exception ) + { + throw new ArgumentException( "An error occured while trying to change the ValueDataType.", exception ); + } + + return value; + } + + private static void ValueDataTypePropertyChangedCallback( DependencyObject sender, DependencyPropertyChangedEventArgs e ) + { + ValueRangeTextBox valueRangeTextBox = sender as ValueRangeTextBox; + + Type valueDataType = e.NewValue as Type; + + valueRangeTextBox.IsNumericValueDataType = ValueRangeTextBox.IsNumericType( valueDataType ); + + valueRangeTextBox.RefreshConversionHelpers(); + + valueRangeTextBox.ConvertValuesToDataType( valueDataType ); + } + + internal virtual void ValidateDataType( Type type ) + { + // Null will always be valid and will reset the MinValue, MaxValue, NullValue and Value to null. + if( type == null ) + return; + + // We use InvariantCulture instead of the active format provider since the FormatProvider is only + // used when the source type is String. When we are converting from a string, we are + // actually converting a value from XAML. Therefore, if the string will have a period as a + // decimal separator. If we were using the active format provider, we could end up expecting a coma + // as the decimal separator and the ChangeType method would throw. + + object minValue = this.MinValue; + + if( ( minValue != null ) && ( minValue.GetType() != type ) ) + minValue = System.Convert.ChangeType( minValue, type, CultureInfo.InvariantCulture ); + + object maxValue = this.MaxValue; + + if( ( maxValue != null ) && ( maxValue.GetType() != type ) ) + maxValue = System.Convert.ChangeType( maxValue, type, CultureInfo.InvariantCulture ); + + object nullValue = this.NullValue; + + if( ( ( nullValue != null ) && ( nullValue != DBNull.Value ) ) + && ( nullValue.GetType() != type ) ) + { + nullValue = System.Convert.ChangeType( nullValue, type, CultureInfo.InvariantCulture ); + } + + object value = this.Value; + + if( ( ( value != null ) && ( value != DBNull.Value ) ) + && ( value.GetType() != type ) ) + { + value = System.Convert.ChangeType( value, type, CultureInfo.InvariantCulture ); + } + + if( ( minValue != null ) || ( maxValue != null ) + || ( ( nullValue != null ) && ( nullValue != DBNull.Value ) ) ) + { + // Value comparaisons will occur. Therefore, the aspiring data type must implement IComparable. + + Type iComparable = type.GetInterface( "IComparable" ); + + if( iComparable == null ) + throw new InvalidOperationException( "MinValue, MaxValue, and NullValue must implement the IComparable interface." ); + } + } + + private void ConvertValuesToDataType( Type type ) + { + if( type == null ) + { + this.MinValue = null; + this.MaxValue = null; + this.NullValue = null; + + this.Value = null; + + return; + } + + object minValue = this.MinValue; + + if( ( minValue != null ) && ( minValue.GetType() != type ) ) + this.MinValue = ValueRangeTextBox.ConvertValueToDataType( minValue, type ); + + object maxValue = this.MaxValue; + + if( ( maxValue != null ) && ( maxValue.GetType() != type ) ) + this.MaxValue = ValueRangeTextBox.ConvertValueToDataType( maxValue, type ); + + object nullValue = this.NullValue; + + if( ( ( nullValue != null ) && ( nullValue != DBNull.Value ) ) + && ( nullValue.GetType() != type ) ) + { + this.NullValue = ValueRangeTextBox.ConvertValueToDataType( nullValue, type ); + } + + object value = this.Value; + + if( ( ( value != null ) && ( value != DBNull.Value ) ) + && ( value.GetType() != type ) ) + { + this.Value = ValueRangeTextBox.ConvertValueToDataType( value, type ); + } + } + + #endregion ValueDataType Property + + #region Text Property + + private static object TextCoerceValueCallback( object sender, object value ) + { + ValueRangeTextBox valueRangeTextBox = sender as ValueRangeTextBox; + + if( !valueRangeTextBox.IsInitialized ) + return DependencyProperty.UnsetValue; + + if( value == null ) + return string.Empty; + + return value; + } + + protected override void OnTextChanged( TextChangedEventArgs e ) + { + // If in IME Composition, RefreshValue already returns without doing anything. + this.RefreshValue(); + + base.OnTextChanged( e ); + } + + #endregion Text Property + + #region HasValidationError Property + + private static readonly DependencyPropertyKey HasValidationErrorPropertyKey = + DependencyProperty.RegisterReadOnly( "HasValidationError", typeof( bool ), typeof( ValueRangeTextBox ), new UIPropertyMetadata( false ) ); + + public static readonly DependencyProperty HasValidationErrorProperty = ValueRangeTextBox.HasValidationErrorPropertyKey.DependencyProperty; + + public bool HasValidationError + { + get + { + return ( bool )this.GetValue( ValueRangeTextBox.HasValidationErrorProperty ); + } + } + + private void SetHasValidationError( bool value ) + { + this.SetValue( ValueRangeTextBox.HasValidationErrorPropertyKey, value ); + } + + #endregion HasValidationError Property + + #region HasParsingError Property + + private static readonly DependencyPropertyKey HasParsingErrorPropertyKey = + DependencyProperty.RegisterReadOnly( "HasParsingError", typeof( bool ), typeof( ValueRangeTextBox ), new UIPropertyMetadata( false ) ); + + public static readonly DependencyProperty HasParsingErrorProperty = ValueRangeTextBox.HasParsingErrorPropertyKey.DependencyProperty; + + public bool HasParsingError + { + get + { + return ( bool )this.GetValue( ValueRangeTextBox.HasParsingErrorProperty ); + } + } + + internal void SetHasParsingError( bool value ) + { + this.SetValue( ValueRangeTextBox.HasParsingErrorPropertyKey, value ); + } + + #endregion HasParsingError Property + + #region IsValueOutOfRange Property + + private static readonly DependencyPropertyKey IsValueOutOfRangePropertyKey = + DependencyProperty.RegisterReadOnly( "IsValueOutOfRange", typeof( bool ), typeof( ValueRangeTextBox ), new UIPropertyMetadata( false ) ); + + public static readonly DependencyProperty IsValueOutOfRangeProperty = ValueRangeTextBox.IsValueOutOfRangePropertyKey.DependencyProperty; + + public bool IsValueOutOfRange + { + get + { + return ( bool )this.GetValue( ValueRangeTextBox.IsValueOutOfRangeProperty ); + } + } + + private void SetIsValueOutOfRange( bool value ) + { + this.SetValue( ValueRangeTextBox.IsValueOutOfRangePropertyKey, value ); + } + + #endregion IsValueOutOfRange Property + + #region IsInValueChanged property + + internal bool IsInValueChanged + { + get + { + return m_flags[ ( int )ValueRangeTextBoxFlags.IsInValueChanged ]; + } + private set + { + m_flags[ ( int )ValueRangeTextBoxFlags.IsInValueChanged ] = value; + } + } + + #endregion + + #region IsForcingValue property + + internal bool IsForcingValue + { + get + { + return m_flags[ ( int )ValueRangeTextBoxFlags.IsForcingValue ]; + } + private set + { + m_flags[ ( int )ValueRangeTextBoxFlags.IsForcingValue ] = value; + } + } + + #endregion + + #region IsForcingText property + + internal bool IsForcingText + { + get + { + return m_flags[ ( int )ValueRangeTextBoxFlags.IsForcingText ]; + } + private set + { + m_flags[ ( int )ValueRangeTextBoxFlags.IsForcingText ] = value; + } + } + + #endregion + + #region IsNumericValueDataType property + + internal bool IsNumericValueDataType + { + get + { + return m_flags[ ( int )ValueRangeTextBoxFlags.IsNumericValueDataType ]; + } + private set + { + m_flags[ ( int )ValueRangeTextBoxFlags.IsNumericValueDataType ] = value; + } + } + + #endregion + + #region IsTextReadyToBeParsed property + + internal virtual bool IsTextReadyToBeParsed + { + get + { + return true; + } + } + + #endregion + + #region IsInIMEComposition property + + internal bool IsInIMEComposition + { + get + { + return m_imePreCompositionCachedTextInfo != null; + } + } + + #endregion + + #region IsFinalizingInitialization Property + + private bool IsFinalizingInitialization + { + get + { + return m_flags[ ( int )ValueRangeTextBoxFlags.IsFinalizingInitialization ]; + } + set + { + m_flags[ ( int )ValueRangeTextBoxFlags.IsFinalizingInitialization ] = value; + } + } + + #endregion + + #region TEXT FROM VALUE + + public event EventHandler QueryTextFromValue; + + internal string GetTextFromValue( object value ) + { + string text = this.QueryTextFromValueCore( value ); + + QueryTextFromValueEventArgs e = new QueryTextFromValueEventArgs( value, text ); + + this.OnQueryTextFromValue( e ); + + return e.Text; + } + + protected virtual string QueryTextFromValueCore( object value ) + { + if( ( value == null ) || ( value == DBNull.Value ) ) + return string.Empty; + + IFormatProvider formatProvider = this.GetActiveFormatProvider(); + + CultureInfo cultureInfo = formatProvider as CultureInfo; + + if( cultureInfo != null ) + { + TypeConverter converter = TypeDescriptor.GetConverter( value.GetType() ); + + if( converter.CanConvertTo( typeof( string ) ) ) + return ( string )converter.ConvertTo( null, cultureInfo, value, typeof( string ) ); + } + + try + { + string result = System.Convert.ToString( value, formatProvider ); + + return result; + } + catch + { + } + + return value.ToString(); + } + + private void OnQueryTextFromValue( QueryTextFromValueEventArgs e ) + { + if( this.QueryTextFromValue != null ) + this.QueryTextFromValue( this, e ); + } + + #endregion TEXT FROM VALUE + + #region VALUE FROM TEXT + + public event EventHandler QueryValueFromText; + + internal object GetValueFromText( string text, out bool hasParsingError ) + { + object value = null; + bool success = this.QueryValueFromTextCore( text, out value ); + + QueryValueFromTextEventArgs e = new QueryValueFromTextEventArgs( text, value ); + e.HasParsingError = !success; + + this.OnQueryValueFromText( e ); + + hasParsingError = e.HasParsingError; + + return e.Value; + } + + protected virtual bool QueryValueFromTextCore( string text, out object value ) + { + value = null; + + Type validatingType = this.ValueDataType; + + text = text.Trim(); + + if( validatingType == null ) + return true; + + if( !validatingType.IsValueType && ( validatingType != typeof( string ) ) ) + return false; + + try + { + value = System.Convert.ChangeType( text, validatingType, this.GetActiveFormatProvider() ); + } + catch + { + return false; + } + + return true; + } + + private void OnQueryValueFromText( QueryValueFromTextEventArgs e ) + { + if( this.QueryValueFromText != null ) + this.QueryValueFromText( this, e ); + } + + #endregion VALUE FROM TEXT + + protected override void OnPreviewKeyDown( KeyEventArgs e ) + { + if( ( e.ImeProcessedKey != Key.None ) && ( !this.IsInIMEComposition ) ) + { + // Start of an IME Composition. Cache all the critical infos. + this.StartIMEComposition(); + } + + base.OnPreviewKeyDown( e ); + } + + protected override void OnGotFocus( RoutedEventArgs e ) + { + base.OnGotFocus( e ); + + this.RefreshCurrentText( true ); + } + + protected override void OnLostFocus( RoutedEventArgs e ) + { + base.OnLostFocus( e ); + + this.RefreshCurrentText( true ); + } + + protected override void OnTextInput( TextCompositionEventArgs e ) + { + if( this.IsInIMEComposition ) + this.EndIMEComposition(); + + base.OnTextInput( e ); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage( "Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly" )] + protected virtual void ValidateValue( object value ) + { + if( value == null ) + return; + + Type validatingType = this.ValueDataType; + + if( validatingType == null ) + throw new InvalidOperationException( "An attempt was made to set a value when the ValueDataType property is null." ); + + if( ( value != DBNull.Value ) && ( value.GetType() != validatingType ) ) + throw new ArgumentException( "The value is not of type " + validatingType.Name + ".", "Value" ); + + this.ValidateValueInRange( this.MinValue, this.MaxValue, value ); + } + + internal static bool IsNumericType( Type type ) + { + if( type == null ) + return false; + + if( type.IsValueType ) + { + if( ( type == typeof( int ) ) || ( type == typeof( double ) ) || ( type == typeof( decimal ) ) + || ( type == typeof( float ) ) || ( type == typeof( short ) ) || ( type == typeof( long ) ) + || ( type == typeof( ushort ) ) || ( type == typeof( uint ) ) || ( type == typeof( ulong ) ) + || ( type == typeof( byte ) ) + ) + { + return true; + } + } + + return false; + } + + internal void StartIMEComposition() + { + Debug.Assert( m_imePreCompositionCachedTextInfo == null, "EndIMEComposition should have been called before another IME Composition starts." ); + + m_imePreCompositionCachedTextInfo = new CachedTextInfo( this ); + } + + internal void EndIMEComposition() + { + CachedTextInfo cachedTextInfo = m_imePreCompositionCachedTextInfo.Clone() as CachedTextInfo; + m_imePreCompositionCachedTextInfo = null; + + this.OnIMECompositionEnded( cachedTextInfo ); + } + + internal virtual void OnIMECompositionEnded( CachedTextInfo cachedTextInfo ) + { + } + + internal virtual void RefreshConversionHelpers() + { + } + + internal IFormatProvider GetActiveFormatProvider() + { + IFormatProvider formatProvider = this.FormatProvider; + + if( formatProvider != null ) + return formatProvider; + + return CultureInfo.CurrentCulture; + } + + internal CultureInfo GetCultureInfo() + { + CultureInfo cultureInfo = this.GetActiveFormatProvider() as CultureInfo; + + if( cultureInfo != null ) + return cultureInfo; + + return CultureInfo.CurrentCulture; + } + + internal virtual string GetCurrentText() + { + return this.Text; + } + + internal virtual string GetParsableText() + { + return this.Text; + } + + internal void ForceText( string text, bool preserveCaret ) + { + this.IsForcingText = true; + try + { + int oldCaretIndex = this.CaretIndex; + + this.Text = text; + + if( ( preserveCaret ) && ( this.IsLoaded ) ) + { + try + { + this.SelectionStart = oldCaretIndex; + } + catch( NullReferenceException ) + { + } + } + } + finally + { + this.IsForcingText = false; + } + } + + internal bool IsValueNull( object value ) + { + if( ( value == null ) || ( value == DBNull.Value ) ) + return true; + + Type type = this.ValueDataType; + + if( value.GetType() != type ) + value = System.Convert.ChangeType( value, type ); + + object nullValue = this.NullValue; + + if( nullValue == null ) + return false; + + if( nullValue.GetType() != type ) + nullValue = System.Convert.ChangeType( nullValue, type ); + + return nullValue.Equals( value ); + } + + internal void ForceValue( object value ) + { + this.IsForcingValue = true; + try + { + this.Value = value; + } + finally + { + this.IsForcingValue = false; + } + } + + internal void RefreshCurrentText( bool preserveCurrentCaretPosition ) + { + string displayText = this.GetCurrentText(); + + if( !string.Equals( displayText, this.Text ) ) + this.ForceText( displayText, preserveCurrentCaretPosition ); + } + + internal void RefreshValue() + { + if( ( this.IsForcingValue ) || ( this.ValueDataType == null ) || ( this.IsInIMEComposition ) ) + return; + + object value; + bool hasParsingError; + + if( this.IsTextReadyToBeParsed ) + { + string parsableText = this.GetParsableText(); + + value = this.GetValueFromText( parsableText, out hasParsingError ); + + if( this.IsValueNull( value ) ) + value = this.NullValue; + } + else + { + // We don't consider empty text as a parsing error. + hasParsingError = !this.GetIsEditTextEmpty(); + value = this.NullValue; + } + + this.SetHasParsingError( hasParsingError ); + + bool hasValidationError = hasParsingError; + try + { + this.ValidateValue( value ); + + this.SetIsValueOutOfRange( false ); + } + catch( Exception exception ) + { + hasValidationError = true; + + if( exception is ArgumentOutOfRangeException ) + this.SetIsValueOutOfRange( true ); + + value = this.NullValue; + } + + if( !object.Equals( value, this.Value ) ) + this.ForceValue( value ); + + this.SetHasValidationError( hasValidationError ); + } + + internal virtual bool GetIsEditTextEmpty() + { + return this.Text == string.Empty; + } + + private static object ConvertValueToDataType( object value, Type type ) + { + // We use InvariantCulture instead of the active format provider since the FormatProvider is only + // used when the source type is String. When we are converting from a string, we are + // actually converting a value from XAML. Therefore, if the string will have a period as a + // decimal separator. If we were using the active format provider, we could end up expecting a coma + // as the decimal separator and the ChangeType method would throw. + if( type == null ) + return null; + + if( ( ( value != null ) && ( value != DBNull.Value ) ) + && ( value.GetType() != type ) ) + { + return System.Convert.ChangeType( value, type, CultureInfo.InvariantCulture ); + } + + return value; + } + + private void CanEnterLineBreak( object sender, CanExecuteRoutedEventArgs e ) + { + e.CanExecute = false; + e.Handled = true; + } + + private void CanEnterParagraphBreak( object sender, CanExecuteRoutedEventArgs e ) + { + e.CanExecute = false; + e.Handled = true; + } + + private void ValidateValueInRange( object minValue, object maxValue, object value ) + { + if( this.IsValueNull( value ) ) + return; + + Type type = this.ValueDataType; + + if( value.GetType() != type ) + value = System.Convert.ChangeType( value, type ); + + // Validate the value against the range. + if( minValue != null ) + { + IComparable minValueComparable = ( IComparable )minValue; + + if( ( maxValue != null ) && ( minValueComparable.CompareTo( maxValue ) > 0 ) ) + throw new ArgumentOutOfRangeException( "minValue", "MaxValue must be greater than MinValue." ); + + if( minValueComparable.CompareTo( value ) > 0 ) + throw new ArgumentOutOfRangeException( "minValue", "Value must be greater than MinValue." ); + } + + if( maxValue != null ) + { + IComparable maxValueComparable = ( IComparable )maxValue; + + if( maxValueComparable.CompareTo( value ) < 0 ) + throw new ArgumentOutOfRangeException( "maxValue", "Value must be less than MaxValue." ); + } + } + + #region ISupportInitialize + + protected override void OnInitialized( EventArgs e ) + { + this.IsFinalizingInitialization = true; + try + { + this.CoerceValue( ValueRangeTextBox.ValueDataTypeProperty ); + + this.IsNumericValueDataType = ValueRangeTextBox.IsNumericType( this.ValueDataType ); + this.RefreshConversionHelpers(); + + this.CoerceValue( ValueRangeTextBox.MinValueProperty ); + this.CoerceValue( ValueRangeTextBox.MaxValueProperty ); + + this.CoerceValue( ValueRangeTextBox.ValueProperty ); + + this.CoerceValue( ValueRangeTextBox.NullValueProperty ); + + this.CoerceValue( ValueRangeTextBox.TextProperty ); + } + catch( Exception exception ) + { + throw new InvalidOperationException( "Initialization of the ValueRangeTextBox failed.", exception ); + } + finally + { + this.IsFinalizingInitialization = false; + } + + base.OnInitialized( e ); + } + + #endregion ISupportInitialize + + private BitVector32 m_flags; + private CachedTextInfo m_imePreCompositionCachedTextInfo; + + [Flags] + private enum ValueRangeTextBoxFlags + { + IsFinalizingInitialization = 1, + IsForcingText = 2, + IsForcingValue = 4, + IsInValueChanged = 8, + IsNumericValueDataType = 16 + } + } +} diff --git a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/QueryTextFromValueEventArgs.cs b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/QueryTextFromValueEventArgs.cs new file mode 100644 index 00000000..c2c15882 --- /dev/null +++ b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/QueryTextFromValueEventArgs.cs @@ -0,0 +1,57 @@ +/************************************************************************ + + Extended WPF Toolkit + + Copyright (C) 2010-2012 Xceed Software Inc. + + This program is provided to you under the terms of the Microsoft Public + License (Ms-PL) as published at http://wpftoolkit.codeplex.com/license + + This program can be provided to you by Xceed Software Inc. under a + proprietary commercial license agreement for use in non-Open Source + projects. The commercial version of Extended WPF Toolkit also includes + priority technical support, commercial updates, and many additional + useful WPF controls if you license Xceed Business Suite for WPF. + + Visit http://xceed.com and follow @datagrid on Twitter. + + **********************************************************************/ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Xceed.Wpf.Toolkit.Core +{ + public class QueryTextFromValueEventArgs : EventArgs + { + public QueryTextFromValueEventArgs( object value, string text ) + { + m_value = value; + m_text = text; + } + + #region Value Property + + private object m_value; + + public object Value + { + get { return m_value; } + } + + #endregion Value Property + + #region Text Property + + private string m_text; + + public string Text + { + get { return m_text; } + set { m_text = value; } + } + + #endregion Text Property + } +} diff --git a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/QueryValueFromTextEventArgs.cs b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/QueryValueFromTextEventArgs.cs new file mode 100644 index 00000000..53ae9960 --- /dev/null +++ b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/QueryValueFromTextEventArgs.cs @@ -0,0 +1,70 @@ +/************************************************************************ + + Extended WPF Toolkit + + Copyright (C) 2010-2012 Xceed Software Inc. + + This program is provided to you under the terms of the Microsoft Public + License (Ms-PL) as published at http://wpftoolkit.codeplex.com/license + + This program can be provided to you by Xceed Software Inc. under a + proprietary commercial license agreement for use in non-Open Source + projects. The commercial version of Extended WPF Toolkit also includes + priority technical support, commercial updates, and many additional + useful WPF controls if you license Xceed Business Suite for WPF. + + Visit http://xceed.com and follow @datagrid on Twitter. + + **********************************************************************/ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Xceed.Wpf.Toolkit.Core +{ + public class QueryValueFromTextEventArgs : EventArgs + { + public QueryValueFromTextEventArgs( string text, object value ) + { + m_text = text; + m_value = value; + } + + #region Text Property + + private string m_text; + + public string Text + { + get { return m_text; } + } + + #endregion Text Property + + #region Value Property + + private object m_value; + + public object Value + { + get { return m_value; } + set { m_value = value; } + } + + #endregion Value Property + + #region HasParsingError Property + + private bool m_hasParsingError; + + public bool HasParsingError + { + get { return m_hasParsingError; } + set { m_hasParsingError = value; } + } + + #endregion HasParsingError Property + + } +} diff --git a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Utilities/ColorUtilities.cs b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Utilities/ColorUtilities.cs index db088862..c1b0549b 100644 --- a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Utilities/ColorUtilities.cs +++ b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Utilities/ColorUtilities.cs @@ -184,7 +184,7 @@ namespace Xceed.Wpf.Toolkit.Core.Utilities } - return Color.FromArgb( 255, ( byte )( r * 255 ), ( byte )( g * 255 ), ( byte )( b * 255 ) ); + return Color.FromArgb( 255, ( byte )( Math.Round(r * 255) ), ( byte )( Math.Round(g * 255) ), ( byte )( Math.Round(b * 255) ) ); } /// diff --git a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Utilities/NotifyPropertyChangedHelper.cs b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Utilities/NotifyPropertyChangedHelper.cs new file mode 100644 index 00000000..58a27f75 --- /dev/null +++ b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Utilities/NotifyPropertyChangedHelper.cs @@ -0,0 +1,163 @@ +/************************************************************************ + + Extended WPF Toolkit + + Copyright (C) 2010-2012 Xceed Software Inc. + + This program is provided to you under the terms of the Microsoft Public + License (Ms-PL) as published at http://wpftoolkit.codeplex.com/license + + This program can be provided to you by Xceed Software Inc. under a + proprietary commercial license agreement for use in non-Open Source + projects. The commercial version of Extended WPF Toolkit also includes + priority technical support, commercial updates, and many additional + useful WPF controls if you license Xceed Business Suite for WPF. + + Visit http://xceed.com and follow @datagrid on Twitter. + + **********************************************************************/ + +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq.Expressions; + +namespace Xceed.Wpf.Toolkit.Core.Utilities +{ + [DebuggerStepThrough] + internal sealed class NotifyPropertyChangedHelper + { + #region Constructor + + internal NotifyPropertyChangedHelper( + INotifyPropertyChanged owner, + Action notifyPropertyChangedDelegate ) + { + if( owner == null ) + throw new ArgumentNullException( "owner" ); + + if( notifyPropertyChangedDelegate == null ) + throw new ArgumentNullException( "notifyPropertyChangedDelegate" ); + + m_owner = owner; + m_delegate = notifyPropertyChangedDelegate; + } + + #endregion + + internal static bool PropertyChanged( string propertyName, PropertyChangedEventArgs e, bool targetPropertyOnly ) + { + string target = e.PropertyName; + if( target == propertyName ) + return true; + + return ( !targetPropertyOnly ) + && ( string.IsNullOrEmpty( target ) ); + } + + internal static bool PropertyChanged( + Expression> expression, + PropertyChangedEventArgs e, + bool targetPropertyOnly ) + { + var body = expression.Body as MemberExpression; + if( body == null ) + throw new ArgumentException( "The expression must target a property or field.", "expression" ); + + return NotifyPropertyChangedHelper.PropertyChanged( body, typeof( TOwner ), e, targetPropertyOnly ); + } + + internal static bool PropertyChanged( + Expression> expression, + PropertyChangedEventArgs e, + bool targetPropertyOnly ) + { + var body = expression.Body as MemberExpression; + if( body == null ) + throw new ArgumentException( "The expression must target a property or field.", "expression" ); + + return NotifyPropertyChangedHelper.PropertyChanged( body, typeof( TOwner ), e, targetPropertyOnly ); + } + + internal void RaisePropertyChanged( string propertyName ) + { + ReflectionHelper.ValidatePropertyName( m_owner, propertyName ); + + this.InvokeDelegate( propertyName ); + } + + internal void RaisePropertyChanged( Expression> expression ) + { + if( expression == null ) + throw new ArgumentNullException( "expression" ); + + var body = expression.Body as MemberExpression; + if( body == null ) + throw new ArgumentException( "The expression must target a property or field.", "expression" ); + + var propertyName = NotifyPropertyChangedHelper.GetPropertyName( body, m_owner.GetType() ); + + this.InvokeDelegate( propertyName ); + } + + internal void HandleReferenceChanged( Expression> expression, ref TMember localReference, TMember newValue ) where TMember : class + { + if( localReference != newValue ) + { + this.ExecutePropertyChanged( expression, ref localReference, newValue ); + } + } + + internal void HandleEqualityChanged( Expression> expression, ref TMember localReference, TMember newValue ) + { + if( !object.Equals( localReference, newValue ) ) + { + this.ExecutePropertyChanged( expression, ref localReference, newValue ); + } + } + + private void ExecutePropertyChanged( Expression> expression, ref TMember localReference, TMember newValue ) + { + TMember oldValue = localReference; + localReference = newValue; + this.RaisePropertyChanged( expression ); + } + + internal static string GetPropertyName( Expression> expression, Type ownerType ) + { + var body = expression.Body as MemberExpression; + if( body == null ) + throw new ArgumentException( "The expression must target a property or field.", "expression" ); + + return NotifyPropertyChangedHelper.GetPropertyName( body, ownerType ); + } + + private static bool PropertyChanged( MemberExpression expression, Type ownerType, PropertyChangedEventArgs e, bool targetPropertyOnly ) + { + var propertyName = NotifyPropertyChangedHelper.GetPropertyName( expression, ownerType ); + + return NotifyPropertyChangedHelper.PropertyChanged( propertyName, e, targetPropertyOnly ); + } + + private static string GetPropertyName( MemberExpression expression, Type ownerType ) + { + var targetType = expression.Expression.Type; + if( !targetType.IsAssignableFrom( ownerType ) ) + throw new ArgumentException( "The expression must target a property or field on the appropriate owner.", "expression" ); + + return ReflectionHelper.GetPropertyOrFieldName( expression ); + } + + private void InvokeDelegate( string propertyName ) + { + m_delegate.Invoke( propertyName ); + } + + #region Private Fields + + private readonly INotifyPropertyChanged m_owner; + private readonly Action m_delegate; + + #endregion + } +} diff --git a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Utilities/ReflectionHelper.cs b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Utilities/ReflectionHelper.cs new file mode 100644 index 00000000..4960c8db --- /dev/null +++ b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Utilities/ReflectionHelper.cs @@ -0,0 +1,136 @@ +/************************************************************************ + + Extended WPF Toolkit + + Copyright (C) 2010-2012 Xceed Software Inc. + + This program is provided to you under the terms of the Microsoft Public + License (Ms-PL) as published at http://wpftoolkit.codeplex.com/license + + This program can be provided to you by Xceed Software Inc. under a + proprietary commercial license agreement for use in non-Open Source + projects. The commercial version of Extended WPF Toolkit also includes + priority technical support, commercial updates, and many additional + useful WPF controls if you license Xceed Business Suite for WPF. + + Visit http://xceed.com and follow @datagrid on Twitter. + + **********************************************************************/ + +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq.Expressions; +using System.Reflection; + +namespace Xceed.Wpf.Toolkit.Core.Utilities +{ + internal static class ReflectionHelper + { + /// + /// Check the existence of the specified public instance (i.e. non static) property against + /// the type of the specified source object. If the property is not defined by the type, + /// a debug assertion will fail. Typically used to validate the parameter of a + /// RaisePropertyChanged method. + /// + /// The object for which the type will be checked. + /// The name of the property. + [System.Diagnostics.Conditional( "DEBUG" )] + internal static void ValidatePublicPropertyName( object sourceObject, string propertyName ) + { + if( sourceObject == null ) + throw new ArgumentNullException( "sourceObject" ); + + if( propertyName == null ) + throw new ArgumentNullException( "propertyName" ); + + System.Diagnostics.Debug.Assert( sourceObject.GetType().GetProperty( propertyName, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.FlattenHierarchy | System.Reflection.BindingFlags.Public ) != null, + string.Format( "Public property {0} not found on object of type {1}.", propertyName, sourceObject.GetType().FullName ) ); + } + + /// + /// Check the existence of the specified instance (i.e. non static) property against + /// the type of the specified source object. If the property is not defined by the type, + /// a debug assertion will fail. Typically used to validate the parameter of a + /// RaisePropertyChanged method. + /// + /// The object for which the type will be checked. + /// The name of the property. + [System.Diagnostics.Conditional( "DEBUG" )] + internal static void ValidatePropertyName( object sourceObject, string propertyName ) + { + if( sourceObject == null ) + throw new ArgumentNullException( "sourceObject" ); + + if( propertyName == null ) + throw new ArgumentNullException( "propertyName" ); + + System.Diagnostics.Debug.Assert( sourceObject.GetType().GetProperty( propertyName, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.FlattenHierarchy | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic ) != null, + string.Format( "Public property {0} not found on object of type {1}.", propertyName, sourceObject.GetType().FullName ) ); + } + + internal static bool TryGetEnumDescriptionAttributeValue( Enum enumeration, out string description ) + { + try + { + FieldInfo fieldInfo = enumeration.GetType().GetField( enumeration.ToString() ); + DescriptionAttribute[] attributes = fieldInfo.GetCustomAttributes( typeof( DescriptionAttribute ), true ) as DescriptionAttribute[]; + if( ( attributes != null ) && ( attributes.Length > 0 ) ) + { + description = attributes[ 0 ].Description; + return true; + } + } + catch + { + } + + description = String.Empty; + return false; + } + + [DebuggerStepThrough] + internal static string GetPropertyOrFieldName( MemberExpression expression ) + { + string propertyOrFieldName; + if( !ReflectionHelper.TryGetPropertyOrFieldName( expression, out propertyOrFieldName ) ) + throw new InvalidOperationException( "Unable to retrieve the property or field name." ); + + return propertyOrFieldName; + } + + [DebuggerStepThrough] + internal static string GetPropertyOrFieldName( Expression> expression ) + { + string propertyOrFieldName; + if( !ReflectionHelper.TryGetPropertyOrFieldName( expression, out propertyOrFieldName ) ) + throw new InvalidOperationException( "Unable to retrieve the property or field name." ); + + return propertyOrFieldName; + } + + [DebuggerStepThrough] + internal static bool TryGetPropertyOrFieldName( MemberExpression expression, out string propertyOrFieldName ) + { + propertyOrFieldName = null; + + if( expression == null ) + return false; + + propertyOrFieldName = expression.Member.Name; + + return true; + } + + [DebuggerStepThrough] + internal static bool TryGetPropertyOrFieldName( Expression> expression, out string propertyOrFieldName ) + { + propertyOrFieldName = null; + + if( expression == null ) + return false; + + return ReflectionHelper.TryGetPropertyOrFieldName( expression.Body as MemberExpression, out propertyOrFieldName ); + } + } +} diff --git a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Utilities/TreeHelper.cs b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Utilities/TreeHelper.cs new file mode 100644 index 00000000..e410dbee --- /dev/null +++ b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Utilities/TreeHelper.cs @@ -0,0 +1,238 @@ +/************************************************************************ + + Extended WPF Toolkit + + Copyright (C) 2010-2012 Xceed Software Inc. + + This program is provided to you under the terms of the Microsoft Public + License (Ms-PL) as published at http://wpftoolkit.codeplex.com/license + + This program can be provided to you by Xceed Software Inc. under a + proprietary commercial license agreement for use in non-Open Source + projects. The commercial version of Extended WPF Toolkit also includes + priority technical support, commercial updates, and many additional + useful WPF controls if you license Xceed Business Suite for WPF. + + Visit http://xceed.com and follow @datagrid on Twitter. + + **********************************************************************/ + +using System; +using System.Windows; +using System.Windows.Media; +using System.Windows.Controls.Primitives; + +namespace Xceed.Wpf.Toolkit.Core.Utilities +{ + internal static class TreeHelper + { + /// + /// Tries its best to return the specified element's parent. It will + /// try to find, in this order, the VisualParent, LogicalParent, LogicalTemplatedParent. + /// It only works for Visual, FrameworkElement or FrameworkContentElement. + /// + /// The element to which to return the parent. It will only + /// work if element is a Visual, a FrameworkElement or a FrameworkContentElement. + /// If the logical parent is not found (Parent), we check the TemplatedParent + /// (see FrameworkElement.Parent documentation). But, we never actually witnessed + /// this situation. + public static DependencyObject GetParent( DependencyObject element ) + { + return TreeHelper.GetParent( element, true ); + } + + private static DependencyObject GetParent( DependencyObject element, bool recurseIntoPopup ) + { + if( recurseIntoPopup ) + { + // Case 126732 : To correctly detect parent of a popup we must do that exception case + Popup popup = element as Popup; + + if( ( popup != null ) && ( popup.PlacementTarget != null ) ) + return popup.PlacementTarget; + } + + Visual visual = element as Visual; + DependencyObject parent = ( visual == null ) ? null : VisualTreeHelper.GetParent( visual ); + + if( parent == null ) + { + // No Visual parent. Check in the logical tree. + FrameworkElement fe = element as FrameworkElement; + + if( fe != null ) + { + parent = fe.Parent; + + if( parent == null ) + { + parent = fe.TemplatedParent; + } + } + else + { + FrameworkContentElement fce = element as FrameworkContentElement; + + if( fce != null ) + { + parent = fce.Parent; + + if( parent == null ) + { + parent = fce.TemplatedParent; + } + } + } + } + + return parent; + } + + /// + /// This will search for a parent of the specified type. + /// + /// The type of the element to find + /// The node where the search begins. This element is not checked. + /// Returns the found element. Null if nothing is found. + public static T FindParent( DependencyObject startingObject ) where T : DependencyObject + { + return TreeHelper.FindParent( startingObject, false, null ); + } + + /// + /// This will search for a parent of the specified type. + /// + /// The type of the element to find + /// The node where the search begins. + /// Should the specified startingObject be checked first. + /// Returns the found element. Null if nothing is found. + public static T FindParent( DependencyObject startingObject, bool checkStartingObject ) where T : DependencyObject + { + return TreeHelper.FindParent( startingObject, checkStartingObject, null ); + } + + /// + /// This will search for a parent of the specified type. + /// + /// The type of the element to find + /// The node where the search begins. + /// Should the specified startingObject be checked first. + /// Provide a callback to check additional properties + /// of the found elements. Can be left Null if no additional criteria are needed. + /// Returns the found element. Null if nothing is found. + /// Button button = TreeHelper.FindParent<Button>( this, foundChild => foundChild.Focusable ); + public static T FindParent( DependencyObject startingObject, bool checkStartingObject, Func additionalCheck ) where T : DependencyObject + { + T foundElement; + DependencyObject parent = ( checkStartingObject ? startingObject : TreeHelper.GetParent( startingObject, true ) ); + + while( parent != null ) + { + foundElement = parent as T; + + if( foundElement != null ) + { + if( additionalCheck == null ) + { + return foundElement; + } + else + { + if( additionalCheck( foundElement ) ) + return foundElement; + } + } + + parent = TreeHelper.GetParent( parent, true ); + } + + return null; + } + + /// + /// This will search for a child of the specified type. The search is performed + /// hierarchically, breadth first (as opposed to depth first). + /// + /// The type of the element to find + /// The root of the tree to search for. This element itself is not checked. + /// Returns the found element. Null if nothing is found. + public static T FindChild( DependencyObject parent ) where T : DependencyObject + { + return TreeHelper.FindChild( parent, null ); + } + + /// + /// This will search for a child of the specified type. The search is performed + /// hierarchically, breadth first (as opposed to depth first). + /// + /// The type of the element to find + /// The root of the tree to search for. This element itself is not checked. + /// Provide a callback to check additional properties + /// of the found elements. Can be left Null if no additional criteria are needed. + /// Returns the found element. Null if nothing is found. + /// Button button = TreeHelper.FindChild<Button>( this, foundChild => foundChild.Focusable ); + public static T FindChild( DependencyObject parent, Func additionalCheck ) where T : DependencyObject + { + int childrenCount = VisualTreeHelper.GetChildrenCount( parent ); + T child; + + for( int index = 0; index < childrenCount; index++ ) + { + child = VisualTreeHelper.GetChild( parent, index ) as T; + + if( child != null ) + { + if( additionalCheck == null ) + { + return child; + } + else + { + if( additionalCheck( child ) ) + return child; + } + } + } + + for( int index = 0; index < childrenCount; index++ ) + { + child = TreeHelper.FindChild( VisualTreeHelper.GetChild( parent, index ), additionalCheck ); + + if( child != null ) + return child; + } + + return null; + } + + /// + /// Returns true if the specified element is a child of parent somewhere in the visual + /// tree. This method will work for Visual, FrameworkElement and FrameworkContentElement. + /// + /// The element that is potentially a child of the specified parent. + /// The element that is potentially a parent of the specified element. + public static bool IsDescendantOf( DependencyObject element, DependencyObject parent ) + { + return TreeHelper.IsDescendantOf( element, parent, true ); + } + + /// + /// Returns true if the specified element is a child of parent somewhere in the visual + /// tree. This method will work for Visual, FrameworkElement and FrameworkContentElement. + /// + /// The element that is potentially a child of the specified parent. + /// The element that is potentially a parent of the specified element. + public static bool IsDescendantOf( DependencyObject element, DependencyObject parent, bool recurseIntoPopup ) + { + while( element != null ) + { + if( element == parent ) + return true; + + element = TreeHelper.GetParent( element, recurseIntoPopup ); + } + + return false; + } + } +} diff --git a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Utilities/ValueChangeHelper.cs b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Utilities/ValueChangeHelper.cs new file mode 100644 index 00000000..1a94f382 --- /dev/null +++ b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Core/Utilities/ValueChangeHelper.cs @@ -0,0 +1,153 @@ +/************************************************************************ + + Extended WPF Toolkit + + Copyright (C) 2010-2012 Xceed Software Inc. + + This program is provided to you under the terms of the Microsoft Public + License (Ms-PL) as published at http://wpftoolkit.codeplex.com/license + + This program can be provided to you by Xceed Software Inc. under a + proprietary commercial license agreement for use in non-Open Source + projects. The commercial version of Extended WPF Toolkit also includes + priority technical support, commercial updates, and many additional + useful WPF controls if you license Xceed Business Suite for WPF. + + Visit http://xceed.com and follow @datagrid on Twitter. + + **********************************************************************/ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Collections; +using System.Windows.Data; + +namespace Xceed.Wpf.Toolkit.Core.Utilities +{ + /// + /// This helper class will raise events when a specific + /// path value on one or many items changes. + /// + internal class ValueChangeHelper : DependencyObject + { + + #region Value Property + /// + /// This private property serves as the target of a binding that monitors the value of the binding + /// of each item in the source. + /// + private static readonly DependencyProperty ValueProperty = DependencyProperty.Register( "Value", typeof( object ), typeof( ValueChangeHelper ), new UIPropertyMetadata( null, OnValueChanged ) ); + private object Value + { + get + { + return ( object )GetValue( ValueProperty ); + } + set + { + SetValue( ValueProperty, value ); + } + } + + private static void OnValueChanged( DependencyObject sender, DependencyPropertyChangedEventArgs args ) + { + ( ( ValueChangeHelper )sender ).RaiseValueChanged(); + } + #endregion + + public event EventHandler ValueChanged; + + #region Constructor + + public ValueChangeHelper(Action changeCallback) + { + if( changeCallback == null ) + throw new ArgumentNullException( "changeCallback" ); + + this.ValueChanged += ( s, args ) => changeCallback(); + } + + #endregion + + #region Methods + + public void UpdateValueSource( object sourceItem, string path ) + { + BindingBase binding = null; + if( sourceItem != null && path != null ) + { + binding = new Binding( path ) { Source = sourceItem }; + } + + this.UpdateBinding( binding ); + } + + public void UpdateValueSource( IEnumerable sourceItems, string path ) + { + BindingBase binding = null; + if( sourceItems != null && path != null ) + { + MultiBinding multiBinding = new MultiBinding(); + multiBinding.Converter = new BlankMultiValueConverter(); + + foreach( var item in sourceItems ) + { + multiBinding.Bindings.Add( new Binding( path ) { Source = item } ); + } + + binding = multiBinding; + } + + this.UpdateBinding( binding ); + } + + private void UpdateBinding( BindingBase binding ) + { + if( binding != null ) + { + BindingOperations.SetBinding( this, ValueChangeHelper.ValueProperty, binding ); + } + else + { + this.ClearBinding(); + } + } + + private void ClearBinding() + { + BindingOperations.ClearBinding( this, ValueChangeHelper.ValueProperty ); + } + + private void RaiseValueChanged() + { + if( this.ValueChanged != null ) + { + this.ValueChanged( this, EventArgs.Empty ); + } + } + + #endregion + + #region BlankMultiValueConverter private class + + private class BlankMultiValueConverter : IMultiValueConverter + { + public object Convert( object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture ) + { + // We will not use the result anyway. We just want the change notification to kick in. + // Return a new object to have a different value. + return new object(); + } + + public object[] ConvertBack( object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture ) + { + throw new InvalidOperationException(); + } + } + + #endregion + } +} diff --git a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/DateTimePicker/Themes/Generic.xaml b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/DateTimePicker/Themes/Generic.xaml index ebfa82fe..d4c797f2 100644 --- a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/DateTimePicker/Themes/Generic.xaml +++ b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/DateTimePicker/Themes/Generic.xaml @@ -19,10 +19,10 @@ + xmlns:conv="clr-namespace:Xceed.Wpf.Toolkit.Core.Converters"> @@ -37,6 +37,13 @@ + + + + + + + @@ -64,10 +71,15 @@ - + + + + + + @@ -80,6 +92,7 @@ + + diff --git a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Obselete/MaskedTextBox/Implementation/MaskedTextBox.cs b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Obselete/MaskedTextBox/Implementation/MaskedTextBox.cs new file mode 100644 index 00000000..ee15e423 --- /dev/null +++ b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Obselete/MaskedTextBox/Implementation/MaskedTextBox.cs @@ -0,0 +1,726 @@ +/************************************************************************ + + Extended WPF Toolkit + + Copyright (C) 2010-2012 Xceed Software Inc. + + This program is provided to you under the terms of the Microsoft Public + License (Ms-PL) as published at http://wpftoolkit.codeplex.com/license + + This program can be provided to you by Xceed Software Inc. under a + proprietary commercial license agreement for use in non-Open Source + projects. The commercial version of Extended WPF Toolkit also includes + priority technical support, commercial updates, and many additional + useful WPF controls if you license Xceed Business Suite for WPF. + + Visit http://xceed.com and follow @datagrid on Twitter. + + **********************************************************************/ + +using System; +using System.ComponentModel; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; + +namespace Xceed.Wpf.Toolkit.Obselete +{ + [Obsolete("Legacy implementation of MaskedTextBox. Use Xceed.Wpf.Toolkit.MaskedTextBox instead", false)] + public class MaskedTextBox : TextBox + { + #region Members + + /// + /// Flags if the Text and Value properties are in the process of being sync'd + /// + private bool _isSyncingTextAndValueProperties; + private bool _isInitialized; + private bool _convertExceptionOccurred = false; + + #endregion //Members + + #region Properties + + protected MaskedTextProvider MaskProvider + { + get; + set; + } + + #region IncludePrompt + + public static readonly DependencyProperty IncludePromptProperty = DependencyProperty.Register( "IncludePrompt", typeof( bool ), typeof( MaskedTextBox ), new UIPropertyMetadata( false, OnIncludePromptPropertyChanged ) ); + public bool IncludePrompt + { + get + { + return ( bool )GetValue( IncludePromptProperty ); + } + set + { + SetValue( IncludePromptProperty, value ); + } + } + + private static void OnIncludePromptPropertyChanged( DependencyObject o, DependencyPropertyChangedEventArgs e ) + { + MaskedTextBox maskedTextBox = o as MaskedTextBox; + if( maskedTextBox != null ) + maskedTextBox.OnIncludePromptChanged( ( bool )e.OldValue, ( bool )e.NewValue ); + } + + protected virtual void OnIncludePromptChanged( bool oldValue, bool newValue ) + { + ResolveMaskProvider( Mask ); + } + + #endregion //IncludePrompt + + #region IncludeLiterals + + public static readonly DependencyProperty IncludeLiteralsProperty = DependencyProperty.Register( "IncludeLiterals", typeof( bool ), typeof( MaskedTextBox ), new UIPropertyMetadata( true, OnIncludeLiteralsPropertyChanged ) ); + public bool IncludeLiterals + { + get + { + return ( bool )GetValue( IncludeLiteralsProperty ); + } + set + { + SetValue( IncludeLiteralsProperty, value ); + } + } + + private static void OnIncludeLiteralsPropertyChanged( DependencyObject o, DependencyPropertyChangedEventArgs e ) + { + MaskedTextBox maskedTextBox = o as MaskedTextBox; + if( maskedTextBox != null ) + maskedTextBox.OnIncludeLiteralsChanged( ( bool )e.OldValue, ( bool )e.NewValue ); + } + + protected virtual void OnIncludeLiteralsChanged( bool oldValue, bool newValue ) + { + ResolveMaskProvider( Mask ); + } + + #endregion //IncludeLiterals + + #region Mask + + public static readonly DependencyProperty MaskProperty = DependencyProperty.Register( "Mask", typeof( string ), typeof( MaskedTextBox ), new UIPropertyMetadata( "<>", OnMaskPropertyChanged ) ); + public string Mask + { + get + { + return ( string )GetValue( MaskProperty ); + } + set + { + SetValue( MaskProperty, value ); + } + } + + private static void OnMaskPropertyChanged( DependencyObject o, DependencyPropertyChangedEventArgs e ) + { + MaskedTextBox maskedTextBox = o as MaskedTextBox; + if( maskedTextBox != null ) + maskedTextBox.OnMaskChanged( ( string )e.OldValue, ( string )e.NewValue ); + } + + protected virtual void OnMaskChanged( string oldValue, string newValue ) + { + ResolveMaskProvider( newValue ); + UpdateText( 0 ); + } + + #endregion //Mask + + #region PromptChar + + public static readonly DependencyProperty PromptCharProperty = DependencyProperty.Register( "PromptChar", typeof( char ), typeof( MaskedTextBox ), new UIPropertyMetadata( '_', OnPromptCharChanged ) ); + public char PromptChar + { + get + { + return ( char )GetValue( PromptCharProperty ); + } + set + { + SetValue( PromptCharProperty, value ); + } + } + + private static void OnPromptCharChanged( DependencyObject o, DependencyPropertyChangedEventArgs e ) + { + MaskedTextBox maskedTextBox = o as MaskedTextBox; + if( maskedTextBox != null ) + maskedTextBox.OnPromptCharChanged( ( char )e.OldValue, ( char )e.NewValue ); + } + + protected virtual void OnPromptCharChanged( char oldValue, char newValue ) + { + ResolveMaskProvider( Mask ); + } + + #endregion //PromptChar + + #region SelectAllOnGotFocus + + public static readonly DependencyProperty SelectAllOnGotFocusProperty = DependencyProperty.Register( "SelectAllOnGotFocus", typeof( bool ), typeof( MaskedTextBox ), new PropertyMetadata( false ) ); + public bool SelectAllOnGotFocus + { + get + { + return ( bool )GetValue( SelectAllOnGotFocusProperty ); + } + set + { + SetValue( SelectAllOnGotFocusProperty, value ); + } + } + + #endregion //SelectAllOnGotFocus + + #region Text + + private static void OnTextChanged( DependencyObject o, DependencyPropertyChangedEventArgs e ) + { + MaskedTextBox inputBase = o as MaskedTextBox; + if( inputBase != null ) + inputBase.OnTextChanged( ( string )e.OldValue, ( string )e.NewValue ); + } + + protected virtual void OnTextChanged( string oldValue, string newValue ) + { + if( _isInitialized ) + SyncTextAndValueProperties( MaskedTextBox.TextProperty, newValue ); + } + + #endregion //Text + + #region Value + + public static readonly DependencyProperty ValueProperty = DependencyProperty.Register( "Value", typeof( object ), typeof( MaskedTextBox ), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnValueChanged ) ); + public object Value + { + get + { + return ( object )GetValue( ValueProperty ); + } + set + { + SetValue( ValueProperty, value ); + } + } + + private static void OnValueChanged( DependencyObject o, DependencyPropertyChangedEventArgs e ) + { + MaskedTextBox maskedTextBox = o as MaskedTextBox; + if( maskedTextBox != null ) + maskedTextBox.OnValueChanged( ( object )e.OldValue, ( object )e.NewValue ); + } + + protected virtual void OnValueChanged( object oldValue, object newValue ) + { + if( _isInitialized ) + SyncTextAndValueProperties( MaskedTextBox.ValueProperty, newValue ); + + RoutedPropertyChangedEventArgs args = new RoutedPropertyChangedEventArgs( oldValue, newValue ); + args.RoutedEvent = MaskedTextBox.ValueChangedEvent; + RaiseEvent( args ); + } + + #endregion //Value + + #region ValueType + + public static readonly DependencyProperty ValueTypeProperty = DependencyProperty.Register( "ValueType", typeof( Type ), typeof( MaskedTextBox ), new UIPropertyMetadata( typeof( String ), OnValueTypeChanged ) ); + public Type ValueType + { + get + { + return ( Type )GetValue( ValueTypeProperty ); + } + set + { + SetValue( ValueTypeProperty, value ); + } + } + + private static void OnValueTypeChanged( DependencyObject o, DependencyPropertyChangedEventArgs e ) + { + MaskedTextBox maskedTextBox = o as MaskedTextBox; + if( maskedTextBox != null ) + maskedTextBox.OnValueTypeChanged( ( Type )e.OldValue, ( Type )e.NewValue ); + } + + protected virtual void OnValueTypeChanged( Type oldValue, Type newValue ) + { + if( _isInitialized ) + SyncTextAndValueProperties( MaskedTextBox.TextProperty, Text ); + } + + #endregion //ValueType + + #endregion //Properties + + #region Constructors + + static MaskedTextBox() + { + TextProperty.OverrideMetadata( typeof( MaskedTextBox ), new FrameworkPropertyMetadata( OnTextChanged ) ); + } + + public MaskedTextBox() + { + CommandBindings.Add( new CommandBinding( ApplicationCommands.Paste, Paste ) ); //handle paste + CommandBindings.Add( new CommandBinding( ApplicationCommands.Cut, null, CanCut ) ); //surpress cut + } + + #endregion //Constructors + + #region Base Class Overrides + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + ResolveMaskProvider( Mask ); + UpdateText( 0 ); + } + + protected override void OnInitialized( EventArgs e ) + { + base.OnInitialized( e ); + + if( !_isInitialized ) + { + _isInitialized = true; + SyncTextAndValueProperties( ValueProperty, Value ); + } + } + + protected override void OnGotKeyboardFocus( KeyboardFocusChangedEventArgs e ) + { + if( SelectAllOnGotFocus ) + { + SelectAll(); + } + + base.OnGotKeyboardFocus( e ); + } + + protected override void OnPreviewKeyDown( KeyEventArgs e ) + { + if( !e.Handled ) + { + HandlePreviewKeyDown( e ); + } + + base.OnPreviewKeyDown( e ); + } + + protected override void OnPreviewTextInput( TextCompositionEventArgs e ) + { + if( !e.Handled ) + { + HandlePreviewTextInput( e ); + } + + base.OnPreviewTextInput( e ); + } + + #endregion //Base Class Overrides + + #region Events + + public static readonly RoutedEvent ValueChangedEvent = EventManager.RegisterRoutedEvent( "ValueChanged", RoutingStrategy.Bubble, typeof( RoutedPropertyChangedEventHandler ), typeof( MaskedTextBox ) ); + public event RoutedPropertyChangedEventHandler ValueChanged + { + add + { + AddHandler( ValueChangedEvent, value ); + } + remove + { + RemoveHandler( ValueChangedEvent, value ); + } + } + + #endregion //Events + + #region Methods + + #region Private + + private void UpdateText() + { + UpdateText( SelectionStart ); + } + + private void UpdateText( int position ) + { + MaskedTextProvider provider = MaskProvider; + if( provider == null ) + throw new InvalidOperationException(); + + Text = provider.ToDisplayString(); + SelectionLength = 0; + SelectionStart = position; + } + + private int GetNextCharacterPosition( int startPosition ) + { + int position = MaskProvider.FindEditPositionFrom( startPosition, true ); + return position == -1 ? startPosition : position; + } + + private void ResolveMaskProvider( string mask ) + { + //do not create a mask provider if the Mask is empty, which can occur if the IncludePrompt and IncludeLiterals properties + //are set prior to the Mask. + if( String.IsNullOrEmpty( mask ) ) + return; + + MaskProvider = new MaskedTextProvider( mask ) + { + IncludePrompt = this.IncludePrompt, + IncludeLiterals = this.IncludeLiterals, + PromptChar = this.PromptChar, + ResetOnSpace = false //should make this a property + }; + } + + private object ConvertTextToValue( string text ) + { + object convertedValue = null; + + Type dataType = ValueType; + + string valueToConvert = MaskProvider.ToString().Trim(); + + try + { + if( valueToConvert.GetType() == dataType || dataType.IsInstanceOfType( valueToConvert ) ) + { + convertedValue = valueToConvert; + } +#if !VS2008 + else if( String.IsNullOrWhiteSpace( valueToConvert ) ) + { + convertedValue = Activator.CreateInstance( dataType ); + } +#else + else if ( String.IsNullOrEmpty( valueToConvert ) ) + { + convertedValue = Activator.CreateInstance( dataType ); + } +#endif + else if( null == convertedValue && valueToConvert is IConvertible ) + { + convertedValue = Convert.ChangeType( valueToConvert, dataType ); + } + } + catch + { + //if an excpetion occurs revert back to original value + _convertExceptionOccurred = true; + return Value; + } + + return convertedValue; + } + + private string ConvertValueToText( object value ) + { + if( value == null ) + value = string.Empty; + + if( _convertExceptionOccurred ) + { + value = Value; + _convertExceptionOccurred = false; + } + + //I have only seen this occur while in Blend, but we need it here so the Blend designer doesn't crash. + if( MaskProvider == null ) + return value.ToString(); + + MaskProvider.Set( value.ToString() ); + return MaskProvider.ToDisplayString(); + } + + private void SyncTextAndValueProperties( DependencyProperty p, object newValue ) + { + //prevents recursive syncing properties + if( _isSyncingTextAndValueProperties ) + return; + + _isSyncingTextAndValueProperties = true; + + //this only occures when the user typed in the value + if( MaskedTextBox.TextProperty == p ) + { + if( newValue != null ) + SetValue( MaskedTextBox.ValueProperty, ConvertTextToValue( newValue.ToString() ) ); + } + + SetValue( MaskedTextBox.TextProperty, ConvertValueToText( newValue ) ); + + _isSyncingTextAndValueProperties = false; + } + + private void HandlePreviewTextInput( TextCompositionEventArgs e ) + { + if( !IsReadOnly ) + { + this.InsertText( e.Text ); + } + + e.Handled = true; + } + + private void HandlePreviewKeyDown( KeyEventArgs e ) + { + if( e.Key == Key.Delete ) + { + e.Handled = IsReadOnly + || HandleKeyDownDelete(); + } + else if( e.Key == Key.Back ) + { + e.Handled = IsReadOnly + || HandleKeyDownBack(); + } + else if( e.Key == Key.Space ) + { + if( !IsReadOnly ) + { + InsertText( " " ); + } + + e.Handled = true; + } + else if( e.Key == Key.Return || e.Key == Key.Enter ) + { + if( !IsReadOnly && AcceptsReturn ) + { + this.InsertText( "\r" ); + } + + // We don't want the OnPreviewTextInput to be triggered for the Return/Enter key + // when it is not accepted. + e.Handled = true; + } + else if( e.Key == Key.Escape ) + { + // We don't want the OnPreviewTextInput to be triggered at all for the Escape key. + e.Handled = true; + } + else if( e.Key == Key.Tab ) + { + if( AcceptsTab ) + { + if( !IsReadOnly ) + { + this.InsertText( "\t" ); + } + + e.Handled = true; + } + } + } + + private bool HandleKeyDownDelete() + { + ModifierKeys modifiers = Keyboard.Modifiers; + bool handled = true; + + if( modifiers == ModifierKeys.None ) + { + if( !RemoveSelectedText() ) + { + int position = SelectionStart; + + if( position < Text.Length ) + { + RemoveText( position, 1 ); + UpdateText( position ); + } + } + else + { + UpdateText(); + } + } + else if( modifiers == ModifierKeys.Control ) + { + if( !RemoveSelectedText() ) + { + int position = SelectionStart; + + RemoveTextToEnd( position ); + UpdateText( position ); + } + else + { + UpdateText(); + } + } + else if( modifiers == ModifierKeys.Shift ) + { + if( RemoveSelectedText() ) + { + UpdateText(); + } + else + { + handled = false; + } + } + else + { + handled = false; + } + + return handled; + } + + private bool HandleKeyDownBack() + { + ModifierKeys modifiers = Keyboard.Modifiers; + bool handled = true; + + if( modifiers == ModifierKeys.None || modifiers == ModifierKeys.Shift ) + { + if( !RemoveSelectedText() ) + { + int position = SelectionStart; + + if( position > 0 ) + { + int newPosition = position - 1; + + RemoveText( newPosition, 1 ); + UpdateText( newPosition ); + } + } + else + { + UpdateText(); + } + } + else if( modifiers == ModifierKeys.Control ) + { + if( !RemoveSelectedText() ) + { + RemoveTextFromStart( SelectionStart ); + UpdateText( 0 ); + } + else + { + UpdateText(); + } + } + else + { + handled = false; + } + + return handled; + } + + private void InsertText( string text ) + { + int position = SelectionStart; + MaskedTextProvider provider = MaskProvider; + + bool textRemoved = this.RemoveSelectedText(); + + position = GetNextCharacterPosition( position ); + + if( !textRemoved && Keyboard.IsKeyToggled( Key.Insert ) ) + { + if( provider.Replace( text, position ) ) + { + position += text.Length; + } + } + else + { + if( provider.InsertAt( text, position ) ) + { + position += text.Length; + } + } + + position = GetNextCharacterPosition( position ); + + this.UpdateText( position ); + } + + private void RemoveTextFromStart( int endPosition ) + { + RemoveText( 0, endPosition ); + } + + private void RemoveTextToEnd( int startPosition ) + { + RemoveText( startPosition, Text.Length - startPosition ); + } + + private void RemoveText( int position, int length ) + { + if( length == 0 ) + return; + + MaskProvider.RemoveAt( position, position + length - 1 ); + } + + private bool RemoveSelectedText() + { + int length = SelectionLength; + + if( length == 0 ) + return false; + + int position = SelectionStart; + + return MaskProvider.RemoveAt( position, position + length - 1 ); + } + + #endregion //Private + + #endregion //Methods + + #region Commands + + private void Paste( object sender, RoutedEventArgs e ) + { + if( IsReadOnly ) + return; + + object data = Clipboard.GetData( DataFormats.Text ); + if( data != null ) + { + string text = data.ToString().Trim(); + if( text.Length > 0 ) + { + int position = SelectionStart; + + MaskProvider.Set( text ); + + UpdateText( position ); + } + } + } + + private void CanCut( object sender, CanExecuteRoutedEventArgs e ) + { + e.CanExecute = false; + e.Handled = true; + } + + #endregion //Commands + } +} diff --git a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Properties/AssemblyInfo.cs b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Properties/AssemblyInfo.cs index ad13a5f6..f541a0f9 100644 --- a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Properties/AssemblyInfo.cs +++ b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/Properties/AssemblyInfo.cs @@ -75,6 +75,8 @@ using System.Windows.Markup; [assembly: XmlnsDefinition("http://schemas.xceed.com/wpf/xaml/toolkit", "Xceed.Wpf.Toolkit")] [assembly: XmlnsDefinition("http://schemas.xceed.com/wpf/xaml/toolkit", "Xceed.Wpf.Toolkit.Core.Converters" )] [assembly: XmlnsDefinition("http://schemas.xceed.com/wpf/xaml/toolkit", "Xceed.Wpf.Toolkit.Core.Input" )] +[assembly: XmlnsDefinition("http://schemas.xceed.com/wpf/xaml/toolkit", "Xceed.Wpf.Toolkit.Core.Utilities")] +[assembly: XmlnsDefinition("http://schemas.xceed.com/wpf/xaml/toolkit", "Xceed.Wpf.Toolkit.Chromes")] [assembly: XmlnsDefinition("http://schemas.xceed.com/wpf/xaml/toolkit", "Xceed.Wpf.Toolkit.Primitives")] [assembly: XmlnsDefinition("http://schemas.xceed.com/wpf/xaml/toolkit", "Xceed.Wpf.Toolkit.PropertyGrid")] [assembly: XmlnsDefinition("http://schemas.xceed.com/wpf/xaml/toolkit", "Xceed.Wpf.Toolkit.PropertyGrid.Attributes")] diff --git a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/PropertyGrid/Implementation/Converters/SelectedObjectConverter.cs b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/PropertyGrid/Implementation/Converters/SelectedObjectConverter.cs new file mode 100644 index 00000000..7b062a58 --- /dev/null +++ b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/PropertyGrid/Implementation/Converters/SelectedObjectConverter.cs @@ -0,0 +1,92 @@ +/************************************************************************ + + Extended WPF Toolkit + + Copyright (C) 2010-2012 Xceed Software Inc. + + This program is provided to you under the terms of the Microsoft Public + License (Ms-PL) as published at http://wpftoolkit.codeplex.com/license + + This program can be provided to you by Xceed Software Inc. under a + proprietary commercial license agreement for use in non-Open Source + projects. The commercial version of Extended WPF Toolkit also includes + priority technical support, commercial updates, and many additional + useful WPF controls if you license Xceed Business Suite for WPF. + + Visit http://xceed.com and follow @datagrid on Twitter. + + **********************************************************************/ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Data; +using System.Globalization; +using System.ComponentModel; +using System.Windows; +using System.Reflection; + +namespace Xceed.Wpf.Toolkit.PropertyGrid.Converters +{ + public class SelectedObjectConverter : IValueConverter + { + private const string ValidParameterMessage = @"parameter must be one of the following strings: 'Type', 'TypeName'"; + #region IValueConverter Members + + public object Convert( object value, Type targetType, object parameter, CultureInfo culture ) + { + if( parameter == null ) + throw new ArgumentNullException( "parameter" ); + + if( !( parameter is string ) ) + throw new ArgumentException( SelectedObjectConverter.ValidParameterMessage ); + + if( this.CompareParam(parameter, "Type") ) + { + return this.ConvertToType( value, culture ); + } + else if( this.CompareParam( parameter, "TypeName" ) ) + { + return this.ConvertToTypeName( value, culture ); + } + else + { + throw new ArgumentException( SelectedObjectConverter.ValidParameterMessage ); + } + } + + private bool CompareParam(object parameter, string parameterValue ) + { + return string.Compare( ( string )parameter, parameterValue, true ) == 0; + } + + private object ConvertToType( object value, CultureInfo culture ) + { + return ( value != null ) + ? value.GetType() + : null; + } + + private object ConvertToTypeName( object value, CultureInfo culture ) + { + if( value == null ) + return string.Empty; + + Type newType = value.GetType(); + + DisplayNameAttribute displayNameAttribute = newType.GetCustomAttributes( false ).OfType().FirstOrDefault(); + + return (displayNameAttribute == null) + ? newType.Name + : displayNameAttribute.DisplayName; + } + + public object ConvertBack( object value, Type targetType, object parameter, CultureInfo culture ) + { + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/PropertyGrid/Implementation/Editors/UpDownEditors.cs b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/PropertyGrid/Implementation/Editors/UpDownEditors.cs new file mode 100644 index 00000000..994ad9d8 --- /dev/null +++ b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/PropertyGrid/Implementation/Editors/UpDownEditors.cs @@ -0,0 +1,52 @@ +/************************************************************************ + + Extended WPF Toolkit + + Copyright (C) 2010-2012 Xceed Software Inc. + + This program is provided to you under the terms of the Microsoft Public + License (Ms-PL) as published at http://wpftoolkit.codeplex.com/license + + This program can be provided to you by Xceed Software Inc. under a + proprietary commercial license agreement for use in non-Open Source + projects. The commercial version of Extended WPF Toolkit also includes + priority technical support, commercial updates, and many additional + useful WPF controls if you license Xceed Business Suite for WPF. + + Visit http://xceed.com and follow @datagrid on Twitter. + + **********************************************************************/ + +using Xceed.Wpf.Toolkit.Primitives; +using System; +namespace Xceed.Wpf.Toolkit.PropertyGrid.Editors +{ + public class UpDownEditor : TypeEditor where TEditor : UpDownBase, new() + { + protected override void SetControlProperties() + { + Editor.BorderThickness = new System.Windows.Thickness( 0 ); + } + protected override void SetValueDependencyProperty() + { + ValueProperty = UpDownBase.ValueProperty; + } + } + + public class ByteUpDownEditor : UpDownEditor { } + + public class DecimalUpDownEditor : UpDownEditor { } + + public class DoubleUpDownEditor : UpDownEditor { } + + public class IntegerUpDownEditor : UpDownEditor { } + + public class LongUpDownEditor : UpDownEditor { } + + public class ShortUpDownEditor : UpDownEditor { } + + public class SingleUpDownEditor : UpDownEditor { } + + public class DateTimeUpDownEditor : UpDownEditor { } + +} diff --git a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/PropertyGrid/Implementation/IPropertyParent.cs b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/PropertyGrid/Implementation/IPropertyParent.cs new file mode 100644 index 00000000..18804b01 --- /dev/null +++ b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/PropertyGrid/Implementation/IPropertyParent.cs @@ -0,0 +1,36 @@ +/************************************************************************ + + Extended WPF Toolkit + + Copyright (C) 2010-2012 Xceed Software Inc. + + This program is provided to you under the terms of the Microsoft Public + License (Ms-PL) as published at http://wpftoolkit.codeplex.com/license + + This program can be provided to you by Xceed Software Inc. under a + proprietary commercial license agreement for use in non-Open Source + projects. The commercial version of Extended WPF Toolkit also includes + priority technical support, commercial updates, and many additional + useful WPF controls if you license Xceed Business Suite for WPF. + + Visit http://xceed.com and follow @datagrid on Twitter. + + **********************************************************************/ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Xceed.Wpf.Toolkit.PropertyGrid.Attributes; + +namespace Xceed.Wpf.Toolkit.PropertyGrid +{ + internal interface IPropertyParent + { + bool IsReadOnly { get; } + + object ValueInstance { get; } + + EditorDefinitionCollection EditorDefinitions { get; } + } +} diff --git a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/PropertyGrid/Implementation/PropertyGrid.cs b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/PropertyGrid/Implementation/PropertyGrid.cs index 5c0d65c3..c46bdb3e 100644 --- a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/PropertyGrid/Implementation/PropertyGrid.cs +++ b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/PropertyGrid/Implementation/PropertyGrid.cs @@ -30,20 +30,23 @@ using Xceed.Wpf.Toolkit.PropertyGrid.Commands; using System.Collections.Specialized; using System.Windows.Media; using Xceed.Wpf.Toolkit.PropertyGrid.Attributes; +using System.Collections.ObjectModel; namespace Xceed.Wpf.Toolkit.PropertyGrid { [TemplatePart( Name = PART_DragThumb, Type = typeof( Thumb ) )] - public class PropertyGrid : Control, ISupportInitialize + public class PropertyGrid : Control, ISupportInitialize, IPropertyParent { private const string PART_DragThumb = "PART_DragThumb"; #region Members private Thumb _dragThumb; - private List _propertyItemsCache; private bool _hasPendingSelectedObjectChanged; private int _initializationCount; + private PropertyItemCollection _properties; + private PropertyDefinitionCollection _propertyDefinitions; + private EditorDefinitionCollection _editorDefinitions; #endregion //Members @@ -68,7 +71,7 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid #region AutoGenerateProperties - public static readonly DependencyProperty AutoGeneratePropertiesProperty = DependencyProperty.Register( "AutoGenerateProperties", typeof( bool ), typeof( PropertyGrid ), new UIPropertyMetadata( true ) ); + public static readonly DependencyProperty AutoGeneratePropertiesProperty = DependencyProperty.Register( "AutoGenerateProperties", typeof( bool ), typeof( PropertyGrid ), new UIPropertyMetadata( true, OnAutoGeneratePropertiesChanged ) ); public bool AutoGenerateProperties { get @@ -81,8 +84,16 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid } } + private static void OnAutoGeneratePropertiesChanged( DependencyObject o, DependencyPropertyChangedEventArgs e ) + { + ( ( PropertyGrid )o ).UpdateProperties( true ); + } + #endregion //AutoGenerateProperties + + + #region ShowSummary public static readonly DependencyProperty ShowSummaryProperty = DependencyProperty.Register( "ShowSummary", typeof( bool ), typeof( PropertyGrid ), new UIPropertyMetadata( true ) ); @@ -102,26 +113,20 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid #region EditorDefinitions - public static readonly DependencyProperty EditorDefinitionsProperty = DependencyProperty.Register( "EditorDefinitions", typeof( EditorDefinitionCollection ), typeof( PropertyGrid ), new UIPropertyMetadata( null, OnEditorDefinitionsChanged) ); public EditorDefinitionCollection EditorDefinitions { get { - return ( EditorDefinitionCollection )GetValue( EditorDefinitionsProperty ); + return _editorDefinitions; } set { - SetValue( EditorDefinitionsProperty, value ); + EditorDefinitionCollection oldValue = _editorDefinitions; + _editorDefinitions = value; + this.OnEditorDefinitionsChanged( oldValue, value ); } } - private static void OnEditorDefinitionsChanged( DependencyObject o, DependencyPropertyChangedEventArgs e ) - { - PropertyGrid propertyGrid = o as PropertyGrid; - if( propertyGrid != null ) - propertyGrid.OnEditorDefinitionsChanged( ( EditorDefinitionCollection )e.OldValue, ( EditorDefinitionCollection )e.NewValue ); - } - protected virtual void OnEditorDefinitionsChanged( EditorDefinitionCollection oldValue, EditorDefinitionCollection newValue ) { if( oldValue != null ) @@ -130,7 +135,12 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid if( newValue != null ) newValue.CollectionChanged += new NotifyCollectionChangedEventHandler( OnEditorDefinitionsCollectionChanged ); - RefreshPropertyGrid(); + UpdateProperties( true ); + } + + private void OnEditorDefinitionsCollectionChanged( object sender, NotifyCollectionChangedEventArgs e ) + { + UpdateProperties( true ); } #endregion //EditorDefinitions @@ -159,8 +169,7 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid protected virtual void OnFilterChanged( string oldValue, string newValue ) { - if( Properties != null ) - Properties.Filter( newValue ); + Properties.Filter( newValue ); } #endregion //Filter @@ -206,7 +215,9 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid protected virtual void OnIsCategorizedChanged( bool oldValue, bool newValue ) { - InitializePropertyGrid( newValue ); + this.UpdateProperties( false ); + this.UpdateThumb(); + } #endregion //IsCategorized @@ -235,23 +246,19 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid protected virtual void OnNameColumnWidthChanged( double oldValue, double newValue ) { - ( ( TranslateTransform )_dragThumb.RenderTransform ).X = newValue; + if( _dragThumb != null ) + ( ( TranslateTransform )_dragThumb.RenderTransform ).X = newValue; } #endregion //NameColumnWidth #region Properties - private static readonly DependencyPropertyKey PropertiesPropertyKey = DependencyProperty.RegisterReadOnly( "Properties", typeof( PropertyItemCollection ), typeof( PropertyGrid ), new UIPropertyMetadata( null ) ); public PropertyItemCollection Properties { get { - return ( PropertyItemCollection )GetValue( PropertiesPropertyKey.DependencyProperty ); - } - private set - { - SetValue( PropertiesPropertyKey, value ); + return _properties; } } @@ -259,26 +266,20 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid #region PropertyDefinitions - public static readonly DependencyProperty PropertyDefinitionsProperty = DependencyProperty.Register( "PropertyDefinitions", typeof( PropertyDefinitionCollection ), typeof( PropertyGrid ), new UIPropertyMetadata( null, OnPropertyDefinitionsChanged ) ); public PropertyDefinitionCollection PropertyDefinitions { get { - return ( PropertyDefinitionCollection )GetValue( PropertyDefinitionsProperty ); + return _propertyDefinitions; } set { - SetValue( PropertyDefinitionsProperty, value ); + PropertyDefinitionCollection oldValue = _propertyDefinitions; + _propertyDefinitions = value; + this.OnPropertyDefinitionsChanged( oldValue, value ); } } - private static void OnPropertyDefinitionsChanged( DependencyObject o, DependencyPropertyChangedEventArgs e ) - { - PropertyGrid propertyGrid = o as PropertyGrid; - if( propertyGrid != null ) - propertyGrid.OnPropertyDefinitionsChanged( ( PropertyDefinitionCollection )e.OldValue, ( PropertyDefinitionCollection )e.NewValue ); - } - protected virtual void OnPropertyDefinitionsChanged( PropertyDefinitionCollection oldValue, PropertyDefinitionCollection newValue ) { if( oldValue != null ) @@ -287,11 +288,45 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid if( newValue != null ) newValue.CollectionChanged += new NotifyCollectionChangedEventHandler( OnPropertyDefinitionsCollectionChanged ); - RefreshPropertyGrid(); + UpdateProperties( true ); + } + + private void OnPropertyDefinitionsCollectionChanged( object sender, NotifyCollectionChangedEventArgs e ) + { + UpdateProperties( true ); } #endregion //PropertyDefinitions + #region IsReadOnly + + public static readonly DependencyProperty IsReadOnlyProperty = DependencyProperty.Register( "IsReadOnly", typeof( bool ), typeof( PropertyGrid ), new UIPropertyMetadata( false, OnIsReadOnlyChanged ) ); + public bool IsReadOnly + { + get + { + return ( bool )GetValue( IsReadOnlyProperty ); + } + set + { + SetValue( IsReadOnlyProperty, value ); + } + } + + private static void OnIsReadOnlyChanged( DependencyObject o, DependencyPropertyChangedEventArgs e ) + { + PropertyGrid propertyGrid = o as PropertyGrid; + if( propertyGrid != null ) + { + foreach( PropertyItem propertyItem in propertyGrid.Properties ) + { + propertyItem.Editor.IsEnabled = !propertyGrid.IsReadOnly; + } + } + } + + #endregion //ReadOnly + #region SelectedObject public static readonly DependencyProperty SelectedObjectProperty = DependencyProperty.Register( "SelectedObject", typeof( object ), typeof( PropertyGrid ), new UIPropertyMetadata( null, OnSelectedObjectChanged ) ); @@ -317,20 +352,15 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid protected virtual void OnSelectedObjectChanged( object oldValue, object newValue ) { // We do not want to process the change now if the grid is initializing (ie. BeginInit/EndInit). - _hasPendingSelectedObjectChanged = IsInitializing(); - - if( IsInitializing() ) - return; - - if( newValue == null ) - ResetPropertyGrid(); - else + if( _initializationCount != 0 ) { - SetSelectedObjectNameBinding( newValue ); - SelectedObjectType = newValue.GetType(); - _propertyItemsCache = GetObjectProperties( newValue ); - InitializePropertyGrid( IsCategorized ); + _hasPendingSelectedObjectChanged = true; + return; } + + this.UpdateProperties( true ); + + RaiseEvent( new RoutedPropertyChangedEventArgs( oldValue, newValue, PropertyGrid.SelectedObjectChangedEvent ) ); } #endregion //SelectedObject @@ -344,7 +374,7 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid { return ( Type )GetValue( SelectedObjectTypeProperty ); } - private set + set { SetValue( SelectedObjectTypeProperty, value ); } @@ -359,13 +389,6 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid protected virtual void OnSelectedObjectTypeChanged( Type oldValue, Type newValue ) { - if( newValue == null ) - SelectedObjectTypeName = string.Empty; - else - { - DisplayNameAttribute displayNameAttribute = newValue.GetCustomAttributes( false ).OfType().FirstOrDefault(); - SelectedObjectTypeName = displayNameAttribute == null ? newValue.Name : displayNameAttribute.DisplayName; - } } #endregion //SelectedObjectType @@ -379,7 +402,7 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid { return ( string )GetValue( SelectedObjectTypeNameProperty ); } - private set + set { SetValue( SelectedObjectTypeNameProperty, value ); } @@ -389,19 +412,31 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid #region SelectedObjectName - public static readonly DependencyProperty SelectedObjectNameProperty = DependencyProperty.Register( "SelectedObjectName", typeof( string ), typeof( PropertyGrid ), new UIPropertyMetadata( string.Empty, OnSelectedObjectNameChanged ) ); + public static readonly DependencyProperty SelectedObjectNameProperty = DependencyProperty.Register( "SelectedObjectName", typeof( string ), typeof( PropertyGrid ), new UIPropertyMetadata( string.Empty, OnSelectedObjectNameChanged, OnCoerceSelectedObjectName ) ); public string SelectedObjectName { get { return ( string )GetValue( SelectedObjectNameProperty ); } - private set + set { SetValue( SelectedObjectNameProperty, value ); } } + private static object OnCoerceSelectedObjectName( DependencyObject o, object baseValue ) + { + PropertyGrid propertyGrid = o as PropertyGrid; + if( propertyGrid != null ) + { + if( (propertyGrid.SelectedObject is FrameworkElement) && ( String.IsNullOrEmpty( ( String )baseValue ) )) + return ""; + } + + return baseValue; + } + private static void OnSelectedObjectNameChanged( DependencyObject o, DependencyPropertyChangedEventArgs e ) { PropertyGrid propertyGrid = o as PropertyGrid; @@ -411,7 +446,6 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid protected virtual void SelectedObjectNameChanged( string oldValue, string newValue ) { - } #endregion //SelectedObjectName @@ -443,10 +477,10 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid if( oldValue != null ) oldValue.IsSelected = false; - //if (newValue != null) - // newValue.IsSelected = true; + if( newValue != null ) + newValue.IsSelected = true; - RaiseEvent( new RoutedEventArgs( PropertyGrid.SelectedPropertyItemChangedEvent, newValue ) ); + RaiseEvent( new RoutedPropertyChangedEventArgs( oldValue, newValue, PropertyGrid.SelectedPropertyItemChangedEvent ) ); } #endregion //SelectedPropertyItem @@ -530,8 +564,11 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid public PropertyGrid() { + _properties = new PropertyItemCollection( new ObservableCollection() ); EditorDefinitions = new EditorDefinitionCollection(); PropertyDefinitions = new PropertyDefinitionCollection(); + + AddHandler( PropertyItem.ItemSelectionChangedEvent, new RoutedEventHandler( OnItemSelectionChanged ) ); CommandBindings.Add( new CommandBinding( PropertyGridCommands.ClearFilter, ClearFilter, CanClearFilter ) ); } @@ -560,6 +597,8 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid TranslateTransform _moveTransform = new TranslateTransform(); _moveTransform.X = NameColumnWidth; _dragThumb.RenderTransform = _moveTransform; + + this.UpdateThumb(); } protected override void OnPreviewKeyDown( KeyEventArgs e ) @@ -579,7 +618,23 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid #region Event Handlers - void DragThumb_DragDelta( object sender, DragDeltaEventArgs e ) + private void OnItemSelectionChanged( object sender, RoutedEventArgs args ) + { + PropertyItem item = ( PropertyItem )args.OriginalSource; + if( item.IsSelected ) + { + SelectedPropertyItem = item; + } + else + { + if( object.ReferenceEquals( item, SelectedPropertyItem ) ) + { + SelectedPropertyItem = null; + } + } + } + + private void DragThumb_DragDelta( object sender, DragDeltaEventArgs e ) { NameColumnWidth = Math.Max( 0, NameColumnWidth + e.HorizontalChange ); } @@ -602,123 +657,77 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid #region Methods - private void InitializePropertyGrid( bool isCategorized ) - { - LoadProperties( isCategorized ); - SetDragThumbMargin( isCategorized ); - } - - private void LoadProperties( bool isCategorized ) + private void UpdateProperties( bool regenerateItems ) { - if( _propertyItemsCache == null ) - return; + IEnumerable newProperties = null; + if( regenerateItems ) + { + newProperties = this.GeneratePropertyItems( this.SelectedObject ); - //clear any filters first - Filter = String.Empty; + string defaultPropertyName = PropertyGridUtilities.GetDefaultPropertyName( this.SelectedObject ); + this.SelectedPropertyItem = newProperties.FirstOrDefault( ( prop ) => prop.Name.Equals( defaultPropertyName ) ); + } - if( isCategorized ) - Properties = PropertyGridUtilities.GetCategorizedProperties( _propertyItemsCache ); - else - Properties = PropertyGridUtilities.GetAlphabetizedProperties( _propertyItemsCache ); + Properties.Update( newProperties, IsCategorized, Filter ); } - private List GetObjectProperties( object instance ) + private List GeneratePropertyItems(object instance) { var propertyItems = new List(); - if( instance == null ) - return propertyItems; - try + if( instance != null ) { - PropertyDescriptorCollection descriptors = PropertyGridUtilities.GetPropertyDescriptors( instance ); - - if( !AutoGenerateProperties ) + try { - List specificProperties = new List(); - if( PropertyDefinitions != null ) + PropertyDescriptorCollection descriptors = PropertyGridUtilities.GetPropertyDescriptors( instance ); + + if( !AutoGenerateProperties ) { - foreach( PropertyDefinition pd in PropertyDefinitions ) + List specificProperties = new List(); + if( PropertyDefinitions != null ) { - foreach( PropertyDescriptor descriptor in descriptors ) + foreach( PropertyDefinition pd in PropertyDefinitions ) { - if( descriptor.Name == pd.Name ) + foreach( PropertyDescriptor descriptor in descriptors ) { - specificProperties.Add( descriptor ); - break; + if( descriptor.Name == pd.Name ) + { + specificProperties.Add( descriptor ); + break; + } } } } + + descriptors = new PropertyDescriptorCollection( specificProperties.ToArray() ); } - descriptors = new PropertyDescriptorCollection( specificProperties.ToArray() ); + foreach( PropertyDescriptor descriptor in descriptors ) + { + if( descriptor.IsBrowsable ) + propertyItems.Add( PropertyGridUtilities.CreatePropertyItem( descriptor, this ) ); + } } - - foreach( PropertyDescriptor descriptor in descriptors ) + catch( Exception ) { - if( descriptor.IsBrowsable ) - propertyItems.Add( PropertyGridUtilities.CreatePropertyItem( descriptor, instance, this, descriptor.Name ) ); + //TODO: handle this some how } } - catch( Exception ) - { - //TODO: handle this some how - } return propertyItems; } - private void SetSelectedObjectNameBinding( object selectedObject ) + private void UpdateThumb() { - if( selectedObject is FrameworkElement ) + if( _dragThumb != null ) { - var binding = new Binding( "Name" ); - binding.Source = selectedObject; - binding.Mode = BindingMode.OneWay; - BindingOperations.SetBinding( this, PropertyGrid.SelectedObjectNameProperty, binding ); + if( IsCategorized ) + _dragThumb.Margin = new Thickness( 6, 0, 0, 0 ); + else + _dragThumb.Margin = new Thickness( -1, 0, 0, 0 ); } } - private void SetDragThumbMargin( bool isCategorized ) - { - if( _dragThumb == null ) - return; - - if( isCategorized ) - _dragThumb.Margin = new Thickness( 6, 0, 0, 0 ); - else - _dragThumb.Margin = new Thickness( -1, 0, 0, 0 ); - } - - private void ResetPropertyGrid() - { - SelectedObjectName = String.Empty; - SelectedObjectType = null; - _propertyItemsCache = null; - SelectedPropertyItem = null; - Properties = null; - } - - private void OnEditorDefinitionsCollectionChanged( object sender, NotifyCollectionChangedEventArgs e ) - { - RefreshPropertyGrid(); - } - - private void OnPropertyDefinitionsCollectionChanged( object sender, NotifyCollectionChangedEventArgs e ) - { - RefreshPropertyGrid(); - } - - private void RefreshPropertyGrid() - { - _propertyItemsCache = GetObjectProperties( SelectedObject ); - InitializePropertyGrid( IsCategorized ); - } - - private bool IsInitializing() - { - return _initializationCount != 0; - } - /// /// Updates all property values in the PropertyGrid with the data from the SelectedObject /// @@ -747,8 +756,8 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid } } - public static readonly RoutedEvent SelectedPropertyItemChangedEvent = EventManager.RegisterRoutedEvent( "SelectedPropertyItemChanged", RoutingStrategy.Bubble, typeof( RoutedEventHandler ), typeof( PropertyGrid ) ); - public event RoutedEventHandler SelectedPropertyItemChanged + public static readonly RoutedEvent SelectedPropertyItemChangedEvent = EventManager.RegisterRoutedEvent( "SelectedPropertyItemChanged", RoutingStrategy.Bubble, typeof( RoutedPropertyChangedEventHandler ), typeof( PropertyGrid ) ); + public event RoutedPropertyChangedEventHandler SelectedPropertyItemChanged { add { @@ -760,6 +769,19 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid } } + public static readonly RoutedEvent SelectedObjectChangedEvent = EventManager.RegisterRoutedEvent( "SelectedObjectChanged", RoutingStrategy.Bubble, typeof( RoutedPropertyChangedEventHandler ), typeof( PropertyGrid ) ); + public event RoutedPropertyChangedEventHandler SelectedObjectChanged + { + add + { + AddHandler( SelectedObjectChangedEvent, value ); + } + remove + { + RemoveHandler( SelectedObjectChangedEvent, value ); + } + } + #endregion //Events #region Interfaces @@ -786,12 +808,32 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid if( _hasPendingSelectedObjectChanged ) { //This will update SelectedObject, Type, Name based on the actual config. - OnSelectedObjectChanged( SelectedObject, SelectedObject ); + this.UpdateProperties( true ); + _hasPendingSelectedObjectChanged = false; } } #endregion + #region IPropertyParent Members + + bool IPropertyParent.IsReadOnly + { + get { return this.IsReadOnly; } + } + + object IPropertyParent.ValueInstance + { + get { return this.SelectedObject; } + } + + EditorDefinitionCollection IPropertyParent.EditorDefinitions + { + get { return this.EditorDefinitions; } + } + + #endregion + #endregion } diff --git a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/PropertyGrid/Implementation/PropertyGridUtilities.cs b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/PropertyGrid/Implementation/PropertyGridUtilities.cs index b8ffc753..2cf14c37 100644 --- a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/PropertyGrid/Implementation/PropertyGridUtilities.cs +++ b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/PropertyGrid/Implementation/PropertyGridUtilities.cs @@ -34,34 +34,17 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid { internal static T GetAttribute( PropertyDescriptor property ) where T : Attribute { - foreach( Attribute att in property.Attributes ) - { - var tAtt = att as T; - if( tAtt != null ) - return tAtt; - } - return null; + return property.Attributes.OfType().FirstOrDefault(); } - internal static PropertyItemCollection GetAlphabetizedProperties( List propertyItems ) - { - PropertyItemCollection propertyCollection = new PropertyItemCollection( propertyItems ); - propertyCollection.SortBy( "DisplayName", ListSortDirection.Ascending ); - return propertyCollection; - } - internal static PropertyItemCollection GetCategorizedProperties( List propertyItems ) + internal static string GetDefaultPropertyName( object instance ) { - PropertyItemCollection propertyCollection = new PropertyItemCollection( propertyItems ); - propertyCollection.GroupBy( "Category" ); - propertyCollection.SortBy( "Category", ListSortDirection.Ascending ); - - propertyCollection.SortBy( "PropertyOrder", ListSortDirection.Ascending ); - propertyCollection.SortBy( "DisplayName", ListSortDirection.Ascending ); - return propertyCollection; + AttributeCollection attributes = TypeDescriptor.GetAttributes( instance ); + DefaultPropertyAttribute defaultPropertyAttribute =( DefaultPropertyAttribute )attributes[ typeof( DefaultPropertyAttribute ) ]; + return defaultPropertyAttribute != null ? defaultPropertyAttribute.Name : null; } - internal static PropertyDescriptorCollection GetPropertyDescriptors( object instance ) { PropertyDescriptorCollection descriptors; @@ -83,33 +66,31 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid return descriptors; } - internal static PropertyItem CreatePropertyItem( PropertyDescriptor property, object instance, PropertyGrid grid, string bindingPath ) + internal static PropertyItem CreatePropertyItem( PropertyDescriptor property, IPropertyParent propertyParent ) { - return CreatePropertyItem(property, instance, grid, bindingPath, 0); + return CreatePropertyItem( property, propertyParent, 0 ); } - internal static PropertyItem CreatePropertyItem( PropertyDescriptor property, object instance, PropertyGrid grid, string bindingPath, int level ) + internal static PropertyItem CreatePropertyItem( PropertyDescriptor property, IPropertyParent propertyParent, int level ) { - PropertyItem propertyItem = new PropertyItem( instance, property, grid, bindingPath, level ); + PropertyItem propertyItem = new PropertyItem( property, propertyParent, level ); - var binding = new Binding( bindingPath ) + var binding = new Binding( property.Name ) { - Source = instance, + Source = propertyParent.ValueInstance, ValidatesOnExceptions = true, ValidatesOnDataErrors = true, Mode = propertyItem.IsReadOnly ? BindingMode.OneWay : BindingMode.TwoWay }; propertyItem.SetBinding( PropertyItem.ValueProperty, binding ); - propertyItem.Editor = PropertyGridUtilities.GetTypeEditor( propertyItem - , grid.EditorDefinitions - ); + propertyItem.Editor = PropertyGridUtilities.GetTypeEditor( propertyItem, propertyParent.EditorDefinitions ); return propertyItem; } - internal static FrameworkElement GetTypeEditor( PropertyItem propertyItem - , EditorDefinitionCollection editorDefinitions - ) + internal static FrameworkElement GetTypeEditor( + PropertyItem propertyItem, + EditorDefinitionCollection editorDefinitions ) { FrameworkElement editor = null; @@ -125,6 +106,7 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid if( editor == null ) editor = PropertyGridUtilities.CreateDefaultEditor( propertyItem ); + editor.IsEnabled = !propertyItem.PropertyParent.IsReadOnly; return editor; } @@ -188,6 +170,22 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid editor = new DoubleUpDownEditor(); else if( propertyItem.PropertyType == typeof( int ) || propertyItem.PropertyType == typeof( int? ) ) editor = new IntegerUpDownEditor(); + else if( propertyItem.PropertyType == typeof( short ) || propertyItem.PropertyType == typeof( short? ) ) + editor = new ShortUpDownEditor(); + else if( propertyItem.PropertyType == typeof( long ) || propertyItem.PropertyType == typeof( long? ) ) + editor = new LongUpDownEditor(); + else if( propertyItem.PropertyType == typeof( float ) || propertyItem.PropertyType == typeof( float? ) ) + editor = new SingleUpDownEditor(); + else if( propertyItem.PropertyType == typeof( byte ) || propertyItem.PropertyType == typeof( byte? ) ) + editor = new ByteUpDownEditor(); + else if( propertyItem.PropertyType == typeof( sbyte ) || propertyItem.PropertyType == typeof( sbyte? ) ) + editor = new UpDownEditor(); + else if( propertyItem.PropertyType == typeof( uint ) || propertyItem.PropertyType == typeof( uint? ) ) + editor = new UpDownEditor(); + else if( propertyItem.PropertyType == typeof( ulong ) || propertyItem.PropertyType == typeof( ulong? ) ) + editor = new UpDownEditor(); + else if( propertyItem.PropertyType == typeof( ushort ) || propertyItem.PropertyType == typeof( ushort? ) ) + editor = new UpDownEditor(); else if( propertyItem.PropertyType == typeof( DateTime ) || propertyItem.PropertyType == typeof( DateTime? ) ) editor = new DateTimeUpDownEditor(); else if( ( propertyItem.PropertyType == typeof( Color ) ) ) @@ -198,11 +196,16 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid editor = new TimeSpanEditor(); else if( propertyItem.PropertyType == typeof( FontFamily ) || propertyItem.PropertyType == typeof( FontWeight ) || propertyItem.PropertyType == typeof( FontStyle ) || propertyItem.PropertyType == typeof( FontStretch ) ) editor = new FontComboBoxEditor(); + else if( propertyItem.PropertyType == typeof( object ) ) + // If any type of object is possible in the property, default to the TextBoxEditor. + // Useful in some case (e.g., Button.Content). + // Can be reconsidered but was the legacy behavior on the PropertyGrid. + editor = new TextBoxEditor(); else { - Type listType = CollectionEditor.GetListItemType(propertyItem.PropertyType); + Type listType = CollectionEditor.GetListItemType( propertyItem.PropertyType ); - if(listType != null) + if( listType != null ) { if( !listType.IsPrimitive && !listType.Equals( typeof( String ) ) ) editor = new Xceed.Wpf.Toolkit.PropertyGrid.Editors.CollectionEditor(); @@ -210,7 +213,15 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid editor = new Xceed.Wpf.Toolkit.PropertyGrid.Editors.PrimitiveTypeCollectionEditor(); } else - editor = new TextBoxEditor(); + { + // If the type is not supported, check if there is a converter that supports + // string conversion to the object type. Use TextBox in theses cases. + // Otherwise, return a TextBlock editor since no valid editor exists. + TypeConverter typeConverter = propertyItem.PropertyDescriptor.Converter; + editor = ( typeConverter != null && typeConverter.CanConvertFrom( typeof( string ) ) ) + ? ( ITypeEditor )new TextBoxEditor() + : ( ITypeEditor )new TextBlockEditor(); + } } return editor.ResolveEditor( propertyItem ); diff --git a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/PropertyGrid/Implementation/PropertyItem.cs b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/PropertyGrid/Implementation/PropertyItem.cs index 1684b3e4..0ecdb32a 100644 --- a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/PropertyGrid/Implementation/PropertyItem.cs +++ b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/PropertyGrid/Implementation/PropertyItem.cs @@ -28,50 +28,73 @@ using System.Windows.Input; using System.Windows.Markup.Primitives; using Xceed.Wpf.Toolkit.PropertyGrid.Attributes; using Xceed.Wpf.Toolkit.PropertyGrid.Commands; +using System.Collections.ObjectModel; +using Xceed.Wpf.Toolkit.Core.Utilities; namespace Xceed.Wpf.Toolkit.PropertyGrid { - public class PropertyItem : Control + public class PropertyItem : Control, INotifyPropertyChanged, IPropertyParent { #region Members - private DependencyPropertyDescriptor _dpDescriptor; - private MarkupObject _markupObject; + private readonly DependencyPropertyDescriptor _dpDescriptor; + private readonly MarkupObject _markupObject; + private readonly NotifyPropertyChangedHelper _propertyChangedHelper; + + private string _displayName; + private string _description; + private string _category; + private int _categoryOrder; + private int _propertyOrder; #endregion //Members #region Properties - public string BindingPath - { - get; - private set; - } #region Category - public static readonly DependencyProperty CategoryProperty = DependencyProperty.Register( "Category", typeof( string ), typeof( PropertyItem ), new UIPropertyMetadata( string.Empty ) ); public string Category { get { - return ( string )GetValue( CategoryProperty ); + return _category; } set { - SetValue( CategoryProperty, value ); + _propertyChangedHelper.HandleEqualityChanged( () => Category, ref _category, value ); } } #endregion //Category + #region CategoryOrder + + public int CategoryOrder + { + get + { + return _categoryOrder; + } + set + { + _propertyChangedHelper.HandleEqualityChanged( () => CategoryOrder, ref _categoryOrder, value ); + } + } + + #endregion // CategoryOrder + #region Description public string Description { get { - return PropertyDescriptor.Description; + return _description; + } + set + { + _propertyChangedHelper.HandleEqualityChanged( () => Description, ref _description, value ); } } @@ -79,16 +102,15 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid #region DisplayName - public static readonly DependencyProperty DisplayNameProperty = DependencyProperty.Register( "DisplayName", typeof( string ), typeof( PropertyItem ), new UIPropertyMetadata( null ) ); public string DisplayName { get { - return ( string )GetValue( DisplayNameProperty ); + return _displayName; } set { - SetValue( DisplayNameProperty, value ); + _propertyChangedHelper.HandleEqualityChanged( () => DisplayName, ref _displayName, value ); } } @@ -129,18 +151,10 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid #region Instance - private object _instance; public object Instance { - get - { - return _instance; - } - private set - { - _instance = value; - _markupObject = MarkupWriter.GetMarkupObjectFor( _instance ); - } + get; + private set; } #endregion //Instance @@ -154,7 +168,7 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid { get { - var dependencyObject = Instance as DependencyObject; + var dependencyObject = PropertyParent.ValueInstance as DependencyObject; if( dependencyObject != null && _dpDescriptor != null ) return BindingOperations.GetBindingExpressionBase( dependencyObject, _dpDescriptor.DependencyProperty ) != null; @@ -203,7 +217,7 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid protected virtual void OnIsExpandedChanged( bool oldValue, bool newValue ) { - if( newValue && ( Properties == null || Properties.Count == 0 ) ) + if( newValue && Properties.Count == 0 ) { GetChildProperties(); } @@ -247,8 +261,10 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid public bool IsReadOnly { - get; - private set; + get + { + return PropertyDescriptor.IsReadOnly; + } } #region IsSelected @@ -275,75 +291,66 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid protected virtual void OnIsSelectedChanged( bool oldValue, bool newValue ) { - if( newValue ) - PropertyGrid.SelectedPropertyItem = this; + this.RaiseItemSelectionChangedEvent(); } #endregion //IsSelected #region Level - public static readonly DependencyProperty LevelProperty = DependencyProperty.Register( "Level", typeof( int ), typeof( PropertyItem ), new UIPropertyMetadata( 0 ) ); public int Level { - get - { - return ( int )GetValue( LevelProperty ); - } - set - { - SetValue( LevelProperty, value ); - } + get; + private set; } #endregion //Level #region Properties - public static readonly DependencyProperty PropertiesProperty = DependencyProperty.Register( "Properties", typeof( PropertyItemCollection ), typeof( PropertyItem ), new UIPropertyMetadata( null ) ); public PropertyItemCollection Properties { - get - { - return ( PropertyItemCollection )GetValue( PropertiesProperty ); - } - set - { - SetValue( PropertiesProperty, value ); - } + get; + private set; } #endregion //Properties #region PropertyDescriptor - private PropertyDescriptor _propertyDescriptor; public PropertyDescriptor PropertyDescriptor { - get - { - return _propertyDescriptor; - } - private set - { - _propertyDescriptor = value; - _dpDescriptor = DependencyPropertyDescriptor.FromProperty( _propertyDescriptor ); - } + get; + private set; } #endregion //PropertyDescriptor - public PropertyGrid PropertyGrid + #region PropertyParent + + internal IPropertyParent PropertyParent { get; private set; } + #endregion + + #region PropertyOrder + public int PropertyOrder { - get; - set; - } //maybe make a DP + get + { + return _propertyOrder; + } + set + { + _propertyChangedHelper.HandleEqualityChanged( () => PropertyOrder, ref _propertyOrder, value ); + } + } + + #endregion #region PropertyType @@ -357,12 +364,16 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid #endregion //PropertyType + #region ResetValueCommand + public ICommand ResetValueCommand { get; private set; } + #endregion + #region Value public static readonly DependencyProperty ValueProperty = DependencyProperty.Register( "Value", typeof( object ), typeof( PropertyItem ), new UIPropertyMetadata( null, OnValueChanged ) ); @@ -391,7 +402,7 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid { if( IsInitialized ) { - PropertyGrid.RaiseEvent( new PropertyValueChangedEventArgs( PropertyGrid.PropertyValueChangedEvent, this, oldValue, newValue ) ); + RaiseEvent( new PropertyValueChangedEventArgs( PropertyGrid.PropertyValueChangedEvent, this, oldValue, newValue ) ); } } @@ -406,7 +417,7 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid { get { - var dependencyObject = Instance as DependencyObject; + var dependencyObject = PropertyParent.ValueInstance as DependencyObject; if( _dpDescriptor != null && dependencyObject != null ) return DependencyPropertyHelper.GetValueSource( dependencyObject, _dpDescriptor.DependencyProperty ).BaseValueSource; @@ -418,6 +429,25 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid #endregion //Properties + #region Events + + public event PropertyChangedEventHandler PropertyChanged; + + #region ItemSelectionChanged + + internal static readonly RoutedEvent ItemSelectionChangedEvent = EventManager.RegisterRoutedEvent( + "ItemSelectedEvent", RoutingStrategy.Bubble, typeof( RoutedEventHandler ), typeof( PropertyItem ) ); + + // This method raises the Tap event + private void RaiseItemSelectionChangedEvent() + { + RaiseEvent( new RoutedEventArgs( PropertyItem.ItemSelectionChangedEvent ) ); + } + + #endregion + + #endregion + #region Constructors static PropertyItem() @@ -425,18 +455,26 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid DefaultStyleKeyProperty.OverrideMetadata( typeof( PropertyItem ), new FrameworkPropertyMetadata( typeof( PropertyItem ) ) ); } - public PropertyItem( object instance, PropertyDescriptor property, PropertyGrid propertyGrid, string bindingPath, int level ) + internal PropertyItem( PropertyDescriptor property, IPropertyParent propertyParent, int level ) { + _propertyChangedHelper = new NotifyPropertyChangedHelper( this, RaisePropertyChanged ); + Properties = new PropertyItemCollection( new ObservableCollection() ); + PropertyParent = propertyParent; PropertyDescriptor = property; - PropertyGrid = propertyGrid; - Instance = instance; - BindingPath = bindingPath; Level = level; - SetPropertyDescriptorProperties(); - ResolveParenthesisPropertyName(); - ResolveExpandableObject(); - ResolvePropertyOrder(); + + Name = PropertyDescriptor.Name; + Category = PropertyDescriptor.Category; + CategoryOrder = 0; + + Description = ResolveDescription(); + DisplayName = ResolveDisplayName(); + HasChildProperties = ResolveExpandableObject(); + PropertyOrder = ResolvePropertyOrder(); + + _dpDescriptor = DependencyPropertyDescriptor.FromProperty( PropertyDescriptor ); + _markupObject = MarkupWriter.GetMarkupObjectFor( PropertyParent.ValueInstance ); CommandBindings.Add( new CommandBinding( PropertyItemCommands.ResetValue, ExecuteResetValueCommand, CanExecuteResetValueCommand ) ); AddHandler( Mouse.PreviewMouseDownEvent, new MouseButtonEventHandler( PropertyItem_PreviewMouseDown ), true ); @@ -457,8 +495,8 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid private void ExecuteResetValueCommand( object sender, ExecutedRoutedEventArgs e ) { - if( PropertyDescriptor.CanResetValue( Instance ) ) - PropertyDescriptor.ResetValue( Instance ); + if( PropertyDescriptor.CanResetValue( PropertyParent.ValueInstance ) ) + PropertyDescriptor.ResetValue( PropertyParent.ValueInstance ); //TODO: notify UI that the ValueSource may have changed to update the icon } @@ -467,7 +505,7 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid { bool canExecute = false; - if( PropertyDescriptor.CanResetValue( Instance ) && !PropertyDescriptor.IsReadOnly ) + if( PropertyDescriptor.CanResetValue( PropertyParent.ValueInstance ) && !PropertyDescriptor.IsReadOnly ) { canExecute = true; } @@ -479,6 +517,14 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid #region Methods + private void RaisePropertyChanged( string propertyName ) + { + if( this.PropertyChanged != null ) + { + this.PropertyChanged( this, new PropertyChangedEventArgs( propertyName ) ); + } + } + private void GetChildProperties() { if( Value == null ) @@ -494,7 +540,7 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid { if( descriptor.IsBrowsable ) { - PropertyItem childPropertyItem = PropertyGridUtilities.CreatePropertyItem( descriptor, Instance, PropertyGrid, String.Format( "{0}.{1}", BindingPath, descriptor.Name ), Level + 1 ); + PropertyItem childPropertyItem = PropertyGridUtilities.CreatePropertyItem( descriptor, this, Level + 1 ); propertyItems.Add( childPropertyItem ); } } @@ -504,45 +550,89 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid //TODO: handle this some how } - Properties = PropertyGridUtilities.GetAlphabetizedProperties( propertyItems ); + Properties.Update( propertyItems, false, null ); } - private void ResolveParenthesisPropertyName() + private string ResolveDescription() { - var attribute = PropertyGridUtilities.GetAttribute( PropertyDescriptor ); + //We do not simply rely on the "Description" property of PropertyDescriptor + //since this value is cached bye PropertyDescriptor and the localized version + //(eg. LocalizedDescriptionAttribute) value can dynamicaly change + DescriptionAttribute descriptionAtt = GetAttribute(); + return ( descriptionAtt != null ) + ? descriptionAtt.Description + : PropertyDescriptor.Description; + } + + + + + + + + + private string ResolveDisplayName() + { + string displayName = PropertyDescriptor.DisplayName; + var attribute = GetAttribute(); if( (attribute != null) && attribute.NeedParenthesis ) { - DisplayName = "(" + DisplayName + ")"; + displayName = "(" + displayName + ")"; } + + return displayName; } - private void ResolveExpandableObject() + private bool ResolveExpandableObject() { - var attribute = PropertyGridUtilities.GetAttribute( PropertyDescriptor ); + bool isExpandable = false; + var attribute = GetAttribute(); if( attribute != null ) { - HasChildProperties = true; - IsReadOnly = true; + isExpandable = true; } + + return isExpandable; } - private void ResolvePropertyOrder() + private int ResolvePropertyOrder() { - var attrs = PropertyDescriptor.Attributes.OfType(); - if( attrs.Any() ) - PropertyOrder = attrs.First().Order; - else - PropertyOrder = 0; + var attribute = GetAttribute(); + + // Max Value. Properties with no order will be displayed last. + return ( attribute != null ) + ? attribute.Order + : int.MaxValue; } - private void SetPropertyDescriptorProperties() + private T GetAttribute() where T : Attribute { - Name = PropertyDescriptor.Name; - DisplayName = PropertyDescriptor.DisplayName; - Category = PropertyDescriptor.Category; - IsReadOnly = PropertyDescriptor.IsReadOnly; + return PropertyGridUtilities.GetAttribute( PropertyDescriptor ); } #endregion //Methods + + #region Interfaces + + #region IPropertyParent Members + + bool IPropertyParent.IsReadOnly + { + get { return PropertyParent.IsReadOnly; } + } + + object IPropertyParent.ValueInstance + { + get { return Value; } + } + + EditorDefinitionCollection IPropertyParent.EditorDefinitions + { + get { return PropertyParent.EditorDefinitions; } + } + + #endregion + + #endregion } } diff --git a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/PropertyGrid/Implementation/PropertyItemCollection.cs b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/PropertyGrid/Implementation/PropertyItemCollection.cs index 53961ee1..f18b064c 100644 --- a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/PropertyGrid/Implementation/PropertyItemCollection.cs +++ b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/PropertyGrid/Implementation/PropertyItemCollection.cs @@ -21,26 +21,23 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Windows.Data; +using System; +using System.Collections.Specialized; +using System.Diagnostics; namespace Xceed.Wpf.Toolkit.PropertyGrid { - public class PropertyItemCollection : ObservableCollection + public class PropertyItemCollection : ReadOnlyObservableCollection { - public PropertyItemCollection() - { - - } + private bool _preventNotification; - public PropertyItemCollection( List list ) - : base( list ) + public PropertyItemCollection(ObservableCollection editableCollection) + :base(editableCollection) { - + EditableCollection = editableCollection; } - public PropertyItemCollection( IEnumerable collection ) - : base( collection ) - { - } + public ObservableCollection EditableCollection { get; private set; } private ICollectionView GetDefaultView() { @@ -68,5 +65,52 @@ namespace Xceed.Wpf.Toolkit.PropertyGrid return property.DisplayName.ToLower().StartsWith( text.ToLower() ); }; } + + protected override void OnCollectionChanged( NotifyCollectionChangedEventArgs args ) + { + if( _preventNotification ) + return; + + base.OnCollectionChanged( args ); + } + + internal void Update( IEnumerable newItems, bool isCategorized, string filter ) + { + using( GetDefaultView().DeferRefresh() ) + { + _preventNotification = true; + + // Replace the collection content with the new items. + if( newItems != null ) + { + EditableCollection.Clear(); + foreach( var item in newItems ) + { + this.EditableCollection.Add( item ); + } + } + + // Clear view values + ICollectionView view = this.GetDefaultView(); + view.GroupDescriptions.Clear(); + view.SortDescriptions.Clear(); + view.Filter = null; + + // Update view values + if( isCategorized ) + { + GroupBy( "Category" ); + SortBy( "Category", ListSortDirection.Ascending ); + } + + SortBy( "PropertyOrder", ListSortDirection.Ascending ); + SortBy( "DisplayName", ListSortDirection.Ascending ); + + Filter( filter ); + + _preventNotification = false; + OnCollectionChanged( new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Reset ) ); + } + } } } diff --git a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/PropertyGrid/Themes/Generic.xaml b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/PropertyGrid/Themes/Generic.xaml index 2f8c6e0c..41db4dfd 100644 --- a/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/PropertyGrid/Themes/Generic.xaml +++ b/ExtendedWPFToolkitSolution_35/Src/WPFToolkit.Extended/PropertyGrid/Themes/Generic.xaml @@ -24,7 +24,8 @@ xmlns:conv="clr-namespace:Xceed.Wpf.Toolkit.Core.Converters" xmlns:pgconv="clr-namespace:Xceed.Wpf.Toolkit.PropertyGrid.Converters" xmlns:utilities="clr-namespace:Xceed.Wpf.Toolkit.Core.Utilities" - xmlns:commands="clr-namespace:Xceed.Wpf.Toolkit.PropertyGrid.Commands"> + xmlns:commands="clr-namespace:Xceed.Wpf.Toolkit.PropertyGrid.Commands" + xmlns:sys="clr-namespace:System;assembly=mscorlib"> @@ -36,6 +37,8 @@ + + @@ -358,7 +361,7 @@ - + @@ -392,18 +395,27 @@ - + + + +