/************************************************************************************* Extended WPF Toolkit Copyright (C) 2007-2013 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 For more features, controls, and fast professional support, pick up the Plus Edition at http://xceed.com/wpf_toolkit Stay informed: follow @datagrid on Twitter or Like http://facebook.com/datagrids ***********************************************************************************/ using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Data; using System.Windows.Input; using Xceed.Wpf.Toolkit.PropertyGrid.Commands; using System.Collections.Specialized; using System.Windows.Media; using Xceed.Wpf.Toolkit.PropertyGrid.Attributes; using System.Collections.ObjectModel; using System.Collections; using Xceed.Wpf.Toolkit.Core.Utilities; using System.Reflection; using System.Linq.Expressions; using System.Windows.Markup; namespace Xceed.Wpf.Toolkit.PropertyGrid { [TemplatePart( Name = PART_DragThumb, Type = typeof( Thumb ) )] [TemplatePart( Name = PART_PropertyItemsControl, Type = typeof( PropertyItemsControl ) )] [StyleTypedProperty( Property = "PropertyContainerStyle", StyleTargetType = typeof( PropertyItemBase ) )] public class PropertyGrid : Control, ISupportInitialize, IPropertyContainer, INotifyPropertyChanged { private const string PART_DragThumb = "PART_DragThumb"; internal const string PART_PropertyItemsControl = "PART_PropertyItemsControl"; private static readonly ComponentResourceKey SelectedObjectAdvancedOptionsMenuKey = new ComponentResourceKey( typeof( PropertyGrid ), "SelectedObjectAdvancedOptionsMenu" ); #region Members private Thumb _dragThumb; private bool _hasPendingSelectedObjectChanged; private int _initializationCount; private ContainerHelperBase _containerHelper; private PropertyDefinitionCollection _propertyDefinitions; private EditorDefinitionCollection _editorDefinitions; private WeakEventListener _propertyDefinitionsListener; private WeakEventListener _editorDefinitionsListener; #endregion //Members #region Properties #region AdvancedOptionsMenu public static readonly DependencyProperty AdvancedOptionsMenuProperty = DependencyProperty.Register( "AdvancedOptionsMenu", typeof( ContextMenu ), typeof( PropertyGrid ), new UIPropertyMetadata( null ) ); public ContextMenu AdvancedOptionsMenu { get { return ( ContextMenu )GetValue( AdvancedOptionsMenuProperty ); } set { SetValue( AdvancedOptionsMenuProperty, value ); } } #endregion //AdvancedOptionsMenu #region AutoGenerateProperties public static readonly DependencyProperty AutoGeneratePropertiesProperty = DependencyProperty.Register( "AutoGenerateProperties", typeof( bool ), typeof( PropertyGrid ), new UIPropertyMetadata( true ) ); public bool AutoGenerateProperties { get { return ( bool )GetValue( AutoGeneratePropertiesProperty ); } set { SetValue( AutoGeneratePropertiesProperty, value ); } } #endregion //AutoGenerateProperties #region ShowDescriptionByTooltip public static readonly DependencyProperty ShowDescriptionByTooltipProperty = DependencyProperty.Register( "ShowDescriptionByTooltip", typeof( bool ), typeof( PropertyGrid ), new UIPropertyMetadata( false ) ); public bool ShowDescriptionByTooltip { get { return ( bool )GetValue( ShowDescriptionByTooltipProperty ); } set { SetValue( ShowDescriptionByTooltipProperty, value ); } } #endregion //ShowDescriptionByTooltip #region ShowSummary public static readonly DependencyProperty ShowSummaryProperty = DependencyProperty.Register( "ShowSummary", typeof( bool ), typeof( PropertyGrid ), new UIPropertyMetadata( true ) ); public bool ShowSummary { get { return ( bool )GetValue( ShowSummaryProperty ); } set { SetValue( ShowSummaryProperty, value ); } } #endregion //ShowSummary #region EditorDefinitions public EditorDefinitionCollection EditorDefinitions { get { return _editorDefinitions; } set { if( _editorDefinitions != value ) { EditorDefinitionCollection oldValue = _editorDefinitions; _editorDefinitions = value; this.OnEditorDefinitionsChanged( oldValue, value ); } } } protected virtual void OnEditorDefinitionsChanged( EditorDefinitionCollection oldValue, EditorDefinitionCollection newValue ) { if( oldValue != null ) CollectionChangedEventManager.RemoveListener( oldValue, _editorDefinitionsListener ); if( newValue != null ) CollectionChangedEventManager.AddListener( newValue, _editorDefinitionsListener ); this.Notify( this.PropertyChanged, () => this.EditorDefinitions ); } private void OnEditorDefinitionsCollectionChanged( object sender, NotifyCollectionChangedEventArgs e ) { _containerHelper.NotifyEditorDefinitionsCollectionChanged(); } #endregion //EditorDefinitions #region Filter public static readonly DependencyProperty FilterProperty = DependencyProperty.Register( "Filter", typeof( string ), typeof( PropertyGrid ), new UIPropertyMetadata( null, OnFilterChanged ) ); public string Filter { get { return ( string )GetValue( FilterProperty ); } set { SetValue( FilterProperty, value ); } } private static void OnFilterChanged( DependencyObject o, DependencyPropertyChangedEventArgs e ) { PropertyGrid propertyGrid = o as PropertyGrid; if( propertyGrid != null ) propertyGrid.OnFilterChanged( ( string )e.OldValue, ( string )e.NewValue ); } protected virtual void OnFilterChanged( string oldValue, string newValue ) { // The Filter property affects the resulting FilterInfo of IPropertyContainer. Raise an event corresponding // to this property. this.Notify( this.PropertyChanged, () => ( ( IPropertyContainer )this ).FilterInfo ); } #endregion //Filter #region FilterWatermark public static readonly DependencyProperty FilterWatermarkProperty = DependencyProperty.Register( "FilterWatermark", typeof( string ), typeof( PropertyGrid ), new UIPropertyMetadata( "Search" ) ); public string FilterWatermark { get { return ( string )GetValue( FilterWatermarkProperty ); } set { SetValue( FilterWatermarkProperty, value ); } } #endregion //FilterWatermark #region HideInheritedProperties public static readonly DependencyProperty HideInheritedPropertiesProperty = DependencyProperty.Register( "HideInheritedProperties", typeof( bool ), typeof( PropertyGrid ), new UIPropertyMetadata( false ) ); public bool HideInheritedProperties { get { return ( bool )GetValue( HideInheritedPropertiesProperty ); } set { SetValue( HideInheritedPropertiesProperty, value ); } } #endregion //HideInheritedProperties #region IsCategorized public static readonly DependencyProperty IsCategorizedProperty = DependencyProperty.Register( "IsCategorized", typeof( bool ), typeof( PropertyGrid ), new UIPropertyMetadata( true, OnIsCategorizedChanged ) ); public bool IsCategorized { get { return ( bool )GetValue( IsCategorizedProperty ); } set { SetValue( IsCategorizedProperty, value ); } } private static void OnIsCategorizedChanged( DependencyObject o, DependencyPropertyChangedEventArgs e ) { PropertyGrid propertyGrid = o as PropertyGrid; if( propertyGrid != null ) propertyGrid.OnIsCategorizedChanged( ( bool )e.OldValue, ( bool )e.NewValue ); } protected virtual void OnIsCategorizedChanged( bool oldValue, bool newValue ) { this.UpdateThumb(); } #endregion //IsCategorized #region IsMiscCategoryLabelHidden public static readonly DependencyProperty IsMiscCategoryLabelHiddenProperty = DependencyProperty.Register( "IsMiscCategoryLabelHidden", typeof( bool ), typeof( PropertyGrid ), new UIPropertyMetadata( false ) ); public bool IsMiscCategoryLabelHidden { get { return ( bool )GetValue( IsMiscCategoryLabelHiddenProperty ); } set { SetValue( IsMiscCategoryLabelHiddenProperty, value ); } } #endregion //IsMiscCategoryLabelHidden #region NameColumnWidth public static readonly DependencyProperty NameColumnWidthProperty = DependencyProperty.Register( "NameColumnWidth", typeof( double ), typeof( PropertyGrid ), new UIPropertyMetadata( 150.0, OnNameColumnWidthChanged ) ); public double NameColumnWidth { get { return ( double )GetValue( NameColumnWidthProperty ); } set { SetValue( NameColumnWidthProperty, value ); } } private static void OnNameColumnWidthChanged( DependencyObject o, DependencyPropertyChangedEventArgs e ) { PropertyGrid propertyGrid = o as PropertyGrid; if( propertyGrid != null ) propertyGrid.OnNameColumnWidthChanged( ( double )e.OldValue, ( double )e.NewValue ); } protected virtual void OnNameColumnWidthChanged( double oldValue, double newValue ) { if( _dragThumb != null ) ( ( TranslateTransform )_dragThumb.RenderTransform ).X = newValue; } #endregion //NameColumnWidth #region Properties public IList Properties { get { return _containerHelper.Properties; } } #endregion //Properties #region PropertyContainerStyle /// /// Identifies the PropertyContainerStyle dependency property /// public static readonly DependencyProperty PropertyContainerStyleProperty = DependencyProperty.Register( "PropertyContainerStyle", typeof( Style ), typeof( PropertyGrid ), new UIPropertyMetadata( null, OnPropertyContainerStyleChanged ) ); /// /// Gets or sets the style that will be applied to all PropertyItemBase instances displayed in the property grid. /// public Style PropertyContainerStyle { get { return ( Style )GetValue( PropertyContainerStyleProperty ); } set { SetValue( PropertyContainerStyleProperty, value ); } } private static void OnPropertyContainerStyleChanged( DependencyObject o, DependencyPropertyChangedEventArgs e ) { var owner = o as PropertyGrid; if( owner != null ) owner.OnPropertyContainerStyleChanged( ( Style )e.OldValue, ( Style )e.NewValue ); } protected virtual void OnPropertyContainerStyleChanged( Style oldValue, Style newValue ) { } #endregion //PropertyContainerStyle #region PropertyDefinitions public PropertyDefinitionCollection PropertyDefinitions { get { return _propertyDefinitions; } set { if( _propertyDefinitions != value ) { PropertyDefinitionCollection oldValue = _propertyDefinitions; _propertyDefinitions = value; this.OnPropertyDefinitionsChanged( oldValue, value ); } } } protected virtual void OnPropertyDefinitionsChanged( PropertyDefinitionCollection oldValue, PropertyDefinitionCollection newValue ) { if( oldValue != null ) CollectionChangedEventManager.RemoveListener( oldValue, _propertyDefinitionsListener ); if( newValue != null ) CollectionChangedEventManager.AddListener( newValue, _propertyDefinitionsListener ); this.Notify( this.PropertyChanged, () => this.PropertyDefinitions ); } private void OnPropertyDefinitionsCollectionChanged( object sender, NotifyCollectionChangedEventArgs e ) { _containerHelper.NotifyPropertyDefinitionsCollectionChanged(); if( this.IsLoaded ) { this.UpdateContainerHelper(); } } #endregion //PropertyDefinitions #region IsReadOnly public static readonly DependencyProperty IsReadOnlyProperty = DependencyProperty.Register( "IsReadOnly", typeof( bool ), typeof( PropertyGrid ), new UIPropertyMetadata( false ) ); public bool IsReadOnly { get { return ( bool )GetValue( IsReadOnlyProperty ); } set { SetValue( IsReadOnlyProperty, value ); } } #endregion //ReadOnly #region SelectedObject public static readonly DependencyProperty SelectedObjectProperty = DependencyProperty.Register( "SelectedObject", typeof( object ), typeof( PropertyGrid ), new UIPropertyMetadata( null, OnSelectedObjectChanged ) ); public object SelectedObject { get { return ( object )GetValue( SelectedObjectProperty ); } set { SetValue( SelectedObjectProperty, value ); } } private static void OnSelectedObjectChanged( DependencyObject o, DependencyPropertyChangedEventArgs e ) { PropertyGrid propertyInspector = o as PropertyGrid; if( propertyInspector != null ) propertyInspector.OnSelectedObjectChanged( ( object )e.OldValue, ( object )e.NewValue ); } 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). if( _initializationCount != 0 ) { _hasPendingSelectedObjectChanged = true; return; } this.UpdateContainerHelper(); RaiseEvent( new RoutedPropertyChangedEventArgs( oldValue, newValue, PropertyGrid.SelectedObjectChangedEvent ) ); } #endregion //SelectedObject #region SelectedObjectType public static readonly DependencyProperty SelectedObjectTypeProperty = DependencyProperty.Register( "SelectedObjectType", typeof( Type ), typeof( PropertyGrid ), new UIPropertyMetadata( null, OnSelectedObjectTypeChanged ) ); public Type SelectedObjectType { get { return ( Type )GetValue( SelectedObjectTypeProperty ); } set { SetValue( SelectedObjectTypeProperty, value ); } } private static void OnSelectedObjectTypeChanged( DependencyObject o, DependencyPropertyChangedEventArgs e ) { PropertyGrid propertyGrid = o as PropertyGrid; if( propertyGrid != null ) propertyGrid.OnSelectedObjectTypeChanged( ( Type )e.OldValue, ( Type )e.NewValue ); } protected virtual void OnSelectedObjectTypeChanged( Type oldValue, Type newValue ) { } #endregion //SelectedObjectType #region SelectedObjectTypeName public static readonly DependencyProperty SelectedObjectTypeNameProperty = DependencyProperty.Register( "SelectedObjectTypeName", typeof( string ), typeof( PropertyGrid ), new UIPropertyMetadata( string.Empty ) ); public string SelectedObjectTypeName { get { return ( string )GetValue( SelectedObjectTypeNameProperty ); } set { SetValue( SelectedObjectTypeNameProperty, value ); } } #endregion //SelectedObjectTypeName #region SelectedObjectName 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 ); } 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; if( propertyGrid != null ) propertyGrid.SelectedObjectNameChanged( ( string )e.OldValue, ( string )e.NewValue ); } protected virtual void SelectedObjectNameChanged( string oldValue, string newValue ) { } #endregion //SelectedObjectName #region SelectedPropertyItem private static readonly DependencyPropertyKey SelectedPropertyItemPropertyKey = DependencyProperty.RegisterReadOnly( "SelectedPropertyItem", typeof( PropertyItemBase ), typeof( PropertyGrid ), new UIPropertyMetadata( null, OnSelectedPropertyItemChanged ) ); public static readonly DependencyProperty SelectedPropertyItemProperty = SelectedPropertyItemPropertyKey.DependencyProperty; public PropertyItemBase SelectedPropertyItem { get { return ( PropertyItemBase )GetValue( SelectedPropertyItemProperty ); } internal set { SetValue( SelectedPropertyItemPropertyKey, value ); } } private static void OnSelectedPropertyItemChanged( DependencyObject o, DependencyPropertyChangedEventArgs e ) { PropertyGrid propertyGrid = o as PropertyGrid; if( propertyGrid != null ) propertyGrid.OnSelectedPropertyItemChanged( ( PropertyItemBase )e.OldValue, ( PropertyItemBase )e.NewValue ); } protected virtual void OnSelectedPropertyItemChanged( PropertyItemBase oldValue, PropertyItemBase newValue ) { if( oldValue != null ) oldValue.IsSelected = false; if( newValue != null ) newValue.IsSelected = true; this.SelectedProperty = ( newValue != null ) ? _containerHelper.ItemFromContainer( newValue ) : null; RaiseEvent( new RoutedPropertyChangedEventArgs( oldValue, newValue, PropertyGrid.SelectedPropertyItemChangedEvent ) ); } #endregion //SelectedPropertyItem #region SelectedProperty /// /// Identifies the SelectedProperty dependency property /// public static readonly DependencyProperty SelectedPropertyProperty = DependencyProperty.Register( "SelectedProperty", typeof( object ), typeof( PropertyGrid ), new UIPropertyMetadata( null, OnSelectedPropertyChanged ) ); /// /// Gets or sets the selected property or returns null if the selection is empty. /// public object SelectedProperty { get { return ( object )GetValue( SelectedPropertyProperty ); } set { SetValue( SelectedPropertyProperty, value ); } } private static void OnSelectedPropertyChanged( DependencyObject sender, DependencyPropertyChangedEventArgs args ) { PropertyGrid propertyGrid = sender as PropertyGrid; if( propertyGrid != null ) { propertyGrid.OnSelectedPropertyChanged( ( object )args.OldValue, ( object )args.NewValue ); } } private void OnSelectedPropertyChanged( object oldValue, object newValue ) { // Do not update the SelectedPropertyItem if the Current SelectedPropertyItem // item is the same as the new SelectedProperty. There may be // duplicate items and the result could be to change the selection to the wrong item. object currentSelectedProperty = _containerHelper.ItemFromContainer( this.SelectedPropertyItem ); if( !object.Equals( currentSelectedProperty, newValue ) ) { this.SelectedPropertyItem = _containerHelper.ContainerFromItem( newValue ); } } #endregion //SelectedProperty #region ShowAdvancedOptions public static readonly DependencyProperty ShowAdvancedOptionsProperty = DependencyProperty.Register( "ShowAdvancedOptions", typeof( bool ), typeof( PropertyGrid ), new UIPropertyMetadata( false ) ); public bool ShowAdvancedOptions { get { return ( bool )GetValue( ShowAdvancedOptionsProperty ); } set { SetValue( ShowAdvancedOptionsProperty, value ); } } #endregion //ShowAdvancedOptions #region ShowPreview public static readonly DependencyProperty ShowPreviewProperty = DependencyProperty.Register( "ShowPreview", typeof( bool ), typeof( PropertyGrid ), new UIPropertyMetadata( false ) ); public bool ShowPreview { get { return ( bool )GetValue( ShowPreviewProperty ); } set { SetValue( ShowPreviewProperty, value ); } } #endregion //ShowPreview #region ShowSearchBox public static readonly DependencyProperty ShowSearchBoxProperty = DependencyProperty.Register( "ShowSearchBox", typeof( bool ), typeof( PropertyGrid ), new UIPropertyMetadata( true ) ); public bool ShowSearchBox { get { return ( bool )GetValue( ShowSearchBoxProperty ); } set { SetValue( ShowSearchBoxProperty, value ); } } #endregion //ShowSearchBox #region ShowSortOptions public static readonly DependencyProperty ShowSortOptionsProperty = DependencyProperty.Register( "ShowSortOptions", typeof( bool ), typeof( PropertyGrid ), new UIPropertyMetadata( true ) ); public bool ShowSortOptions { get { return ( bool )GetValue( ShowSortOptionsProperty ); } set { SetValue( ShowSortOptionsProperty, value ); } } #endregion //ShowSortOptions #region ShowTitle public static readonly DependencyProperty ShowTitleProperty = DependencyProperty.Register( "ShowTitle", typeof( bool ), typeof( PropertyGrid ), new UIPropertyMetadata( true ) ); public bool ShowTitle { get { return ( bool )GetValue( ShowTitleProperty ); } set { SetValue( ShowTitleProperty, value ); } } #endregion //ShowTitle #region UpdateTextBoxSourceOnEnterKey public static readonly DependencyProperty UpdateTextBoxSourceOnEnterKeyProperty = DependencyProperty.Register( "UpdateTextBoxSourceOnEnterKey", typeof( bool ), typeof( PropertyGrid ), new UIPropertyMetadata( true ) ); public bool UpdateTextBoxSourceOnEnterKey { get { return ( bool )GetValue( UpdateTextBoxSourceOnEnterKeyProperty ); } set { SetValue( UpdateTextBoxSourceOnEnterKeyProperty, value ); } } #endregion //UpdateTextBoxSourceOnEnterKey #endregion //Properties #region Constructors static PropertyGrid() { DefaultStyleKeyProperty.OverrideMetadata( typeof( PropertyGrid ), new FrameworkPropertyMetadata( typeof( PropertyGrid ) ) ); } public PropertyGrid() { _propertyDefinitionsListener = new WeakEventListener( this.OnPropertyDefinitionsCollectionChanged ); _editorDefinitionsListener = new WeakEventListener( this.OnEditorDefinitionsCollectionChanged); UpdateContainerHelper(); EditorDefinitions = new EditorDefinitionCollection(); PropertyDefinitions = new PropertyDefinitionCollection(); this.PropertyValueChanged += this.PropertyGrid_PropertyValueChanged; AddHandler( PropertyItemBase.ItemSelectionChangedEvent, new RoutedEventHandler( OnItemSelectionChanged ) ); AddHandler( PropertyItemsControl.PreparePropertyItemEvent, new PropertyItemEventHandler( OnPreparePropertyItemInternal ) ); AddHandler( PropertyItemsControl.ClearPropertyItemEvent, new PropertyItemEventHandler( OnClearPropertyItemInternal ) ); CommandBindings.Add( new CommandBinding( PropertyGridCommands.ClearFilter, ClearFilter, CanClearFilter ) ); } #endregion //Constructors #region Base Class Overrides public override void OnApplyTemplate() { base.OnApplyTemplate(); if( _dragThumb != null ) _dragThumb.DragDelta -= DragThumb_DragDelta; _dragThumb = GetTemplateChild( PART_DragThumb ) as Thumb; if( _dragThumb != null ) _dragThumb.DragDelta += DragThumb_DragDelta; _containerHelper.ChildrenItemsControl = GetTemplateChild( PART_PropertyItemsControl ) as PropertyItemsControl; //Update TranslateTransform in code-behind instead of XAML to remove the //output window error. //When we use FindAncesstor in custom control template for binding internal elements property //into its ancestor element, Visual Studio displays data warning messages in output window when //binding engine meets unmatched target type during visual tree traversal though it does the proper //binding when it receives expected target type during visual tree traversal //ref : http://www.codeproject.com/Tips/124556/How-to-suppress-the-System-Windows-Data-Error-warn TranslateTransform _moveTransform = new TranslateTransform(); _moveTransform.X = NameColumnWidth; _dragThumb.RenderTransform = _moveTransform; this.UpdateThumb(); } protected override void OnPreviewKeyDown( KeyEventArgs e ) { var textBox = e.OriginalSource as TextBox; //hitting enter on textbox will update value of underlying source if UpdateTextBoxSourceOnEnterKey is true if( (this.SelectedPropertyItem != null) && (e.Key == Key.Enter) && this.UpdateTextBoxSourceOnEnterKey && (textBox != null) && !textBox.AcceptsReturn ) { BindingExpression be = textBox.GetBindingExpression( TextBox.TextProperty ); if( be != null ) be.UpdateSource(); } } protected override void OnPropertyChanged( DependencyPropertyChangedEventArgs e ) { base.OnPropertyChanged( e ); // First check that the raised property is actually a real CLR property. // This could be something else like a Attached DP. if( ReflectionHelper.IsPublicInstanceProperty( GetType(), e.Property.Name ) ) { this.Notify( this.PropertyChanged, e.Property.Name ); } } #endregion //Base Class Overrides #region Event Handlers private void OnItemSelectionChanged( object sender, RoutedEventArgs args ) { PropertyItemBase item = ( PropertyItemBase )args.OriginalSource; if( item.IsSelected ) { SelectedPropertyItem = item; } else { if( object.ReferenceEquals( item, SelectedPropertyItem ) ) { SelectedPropertyItem = null; } } } private void OnPreparePropertyItemInternal( object sender, PropertyItemEventArgs args ) { _containerHelper.PrepareChildrenPropertyItem( args.PropertyItem, args.Item ); args.Handled = true; } private void OnClearPropertyItemInternal( object sender, PropertyItemEventArgs args ) { _containerHelper.ClearChildrenPropertyItem( args.PropertyItem, args.Item ); args.Handled = true; } private void DragThumb_DragDelta( object sender, DragDeltaEventArgs e ) { NameColumnWidth = Math.Max( 0, NameColumnWidth + e.HorizontalChange ); } private void PropertyGrid_PropertyValueChanged( object sender, PropertyValueChangedEventArgs e ) { var modifiedPropertyItem = e.OriginalSource as PropertyItem; if( modifiedPropertyItem != null ) { if( modifiedPropertyItem.WillRefreshPropertyGrid ) this.UpdateContainerHelper(); var parentPropertyItem = modifiedPropertyItem.ParentNode as PropertyItem; if( ( parentPropertyItem != null ) && parentPropertyItem.IsExpandable ) { //Rebuild Editor for parent propertyItem if one of its sub-propertyItem have changed. this.RebuildEditor( parentPropertyItem ); } } } #endregion //Event Handlers #region Commands private void ClearFilter( object sender, ExecutedRoutedEventArgs e ) { Filter = String.Empty; } private void CanClearFilter( object sender, CanExecuteRoutedEventArgs e ) { e.CanExecute = !String.IsNullOrEmpty( Filter ); } #endregion //Commands #region Methods private void RebuildEditor( PropertyItem propertyItem ) { ObjectContainerHelperBase objectContainerHelperBase = propertyItem.ContainerHelper as ObjectContainerHelperBase; //Re-build the editor to update this propertyItem FrameworkElement editor = objectContainerHelperBase.GenerateChildrenEditorElement( propertyItem ); if( editor != null ) { // Tag the editor as generated to know if we should clear it. ContainerHelperBase.SetIsGenerated( editor, true ); propertyItem.Editor = editor; //Update Source of binding and Validation of PropertyItem to update var be = propertyItem.GetBindingExpression( PropertyItem.ValueProperty ); if( be != null ) { be.UpdateSource(); propertyItem.SetRedInvalidBorder( be ); } } } private void UpdateContainerHelper() { // Keep a backup of the template element and initialize the // new helper with it. ItemsControl childrenItemsControl = null; if( _containerHelper != null ) { childrenItemsControl = _containerHelper.ChildrenItemsControl; _containerHelper.ClearHelper(); if( _containerHelper is ObjectContainerHelperBase ) { // If the actual AdvancedOptionMenu is the default menu for selected object, // remove it. Otherwise, it is a custom menu provided by the user. // This "default" menu is only valid for the SelectedObject[s] case. Otherwise, // it is useless and we must remove it. //var defaultAdvancedMenu = ( ContextMenu )this.FindResource( PropertyGrid.SelectedObjectAdvancedOptionsMenuKey ); //if( this.AdvancedOptionsMenu == defaultAdvancedMenu ) //{ this.AdvancedOptionsMenu = null; //} } } _containerHelper = new ObjectContainerHelper( this, SelectedObject ); ( ( ObjectContainerHelper )_containerHelper ).GenerateProperties(); _containerHelper.ChildrenItemsControl = childrenItemsControl; // Since the template will bind on this property and this property // will be different when the property parent is updated. this.Notify( this.PropertyChanged, () => this.Properties ); } private void UpdateThumb() { if( _dragThumb != null ) { if( IsCategorized ) _dragThumb.Margin = new Thickness( 6, 0, 0, 0 ); else _dragThumb.Margin = new Thickness( -1, 0, 0, 0 ); } } /// /// Override this call to control the filter applied based on the /// text input. /// /// /// protected virtual Predicate CreateFilter( string filter ) { return null; } /// /// Updates all property values in the PropertyGrid with the data from the SelectedObject /// public void Update() { _containerHelper.UpdateValuesFromSource(); } #endregion //Methods #region Events #region PropertyChanged Event public event PropertyChangedEventHandler PropertyChanged; #endregion #region PropertyValueChangedEvent Routed Event public static readonly RoutedEvent PropertyValueChangedEvent = EventManager.RegisterRoutedEvent( "PropertyValueChanged", RoutingStrategy.Bubble, typeof( PropertyValueChangedEventHandler ), typeof( PropertyGrid ) ); public event PropertyValueChangedEventHandler PropertyValueChanged { add { AddHandler( PropertyValueChangedEvent, value ); } remove { RemoveHandler( PropertyValueChangedEvent, value ); } } #endregion #region SelectedPropertyItemChangedEvent Routed Event public static readonly RoutedEvent SelectedPropertyItemChangedEvent = EventManager.RegisterRoutedEvent( "SelectedPropertyItemChanged", RoutingStrategy.Bubble, typeof( RoutedPropertyChangedEventHandler ), typeof( PropertyGrid ) ); public event RoutedPropertyChangedEventHandler SelectedPropertyItemChanged { add { AddHandler( SelectedPropertyItemChangedEvent, value ); } remove { RemoveHandler( SelectedPropertyItemChangedEvent, value ); } } #endregion #region SelectedObjectChangedEventRouted Routed Event 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 #region IsPropertyBrowsable Event public event IsPropertyBrowsableHandler IsPropertyBrowsable; #endregion #region PreparePropertyItemEvent Attached Routed Event /// /// Identifies the PreparePropertyItem event. /// This attached routed event may be raised by the PropertyGrid itself or by a /// PropertyItemBase containing sub-items. /// public static readonly RoutedEvent PreparePropertyItemEvent = EventManager.RegisterRoutedEvent( "PreparePropertyItem", RoutingStrategy.Bubble, typeof( PropertyItemEventHandler ), typeof( PropertyGrid ) ); /// /// This event is raised when a property item is about to be displayed in the PropertyGrid. /// This allow the user to customize the property item just before it is displayed. /// public event PropertyItemEventHandler PreparePropertyItem { add { AddHandler( PropertyGrid.PreparePropertyItemEvent, value ); } remove { RemoveHandler( PropertyGrid.PreparePropertyItemEvent, value ); } } /// /// Adds a handler for the PreparePropertyItem attached event /// /// the element to attach the handler /// the handler for the event public static void AddPreparePropertyItemHandler( UIElement element, PropertyItemEventHandler handler ) { element.AddHandler( PropertyGrid.PreparePropertyItemEvent, handler ); } /// /// Removes a handler for the PreparePropertyItem attached event /// /// the element to attach the handler /// the handler for the event public static void RemovePreparePropertyItemHandler( UIElement element, PropertyItemEventHandler handler ) { element.RemoveHandler( PropertyGrid.PreparePropertyItemEvent, handler ); } internal static void RaisePreparePropertyItemEvent( UIElement source, PropertyItemBase propertyItem, object item ) { source.RaiseEvent( new PropertyItemEventArgs( PropertyGrid.PreparePropertyItemEvent, source, propertyItem, item ) ); } #endregion #region ClearPropertyItemEvent Attached Routed Event /// /// Identifies the ClearPropertyItem event. /// This attached routed event may be raised by the PropertyGrid itself or by a /// PropertyItemBase containing sub items. /// public static readonly RoutedEvent ClearPropertyItemEvent = EventManager.RegisterRoutedEvent( "ClearPropertyItem", RoutingStrategy.Bubble, typeof( PropertyItemEventHandler ), typeof( PropertyGrid ) ); /// /// This event is raised when an property item is about to be remove from the display in the PropertyGrid /// This allow the user to remove any attached handler in the PreparePropertyItem event. /// public event PropertyItemEventHandler ClearPropertyItem { add { AddHandler( PropertyGrid.ClearPropertyItemEvent, value ); } remove { RemoveHandler( PropertyGrid.ClearPropertyItemEvent, value ); } } /// /// Adds a handler for the ClearPropertyItem attached event /// /// the element to attach the handler /// the handler for the event public static void AddClearPropertyItemHandler( UIElement element, PropertyItemEventHandler handler ) { element.AddHandler( PropertyGrid.ClearPropertyItemEvent, handler ); } /// /// Removes a handler for the ClearPropertyItem attached event /// /// the element to attach the handler /// the handler for the event public static void RemoveClearPropertyItemHandler( UIElement element, PropertyItemEventHandler handler ) { element.RemoveHandler( PropertyGrid.ClearPropertyItemEvent, handler ); } internal static void RaiseClearPropertyItemEvent( UIElement source, PropertyItemBase propertyItem, object item ) { source.RaiseEvent( new PropertyItemEventArgs( PropertyGrid.ClearPropertyItemEvent, source, propertyItem, item ) ); } #endregion #endregion //Events #region Interfaces #region ISupportInitialize Members public override void BeginInit() { base.BeginInit(); _initializationCount++; } public override void EndInit() { base.EndInit(); if( --_initializationCount == 0 ) { if( _hasPendingSelectedObjectChanged ) { //This will update SelectedObject, Type, Name based on the actual config. this.UpdateContainerHelper(); _hasPendingSelectedObjectChanged = false; } _containerHelper.OnEndInit(); } } #endregion #region IPropertyContainer Members FilterInfo IPropertyContainer.FilterInfo { get { return new FilterInfo() { Predicate = this.CreateFilter(this.Filter), InputString = this.Filter }; } } ContainerHelperBase IPropertyContainer.ContainerHelper { get { return _containerHelper; } } bool IPropertyContainer.IsSortedAlphabetically { get { return true; } } bool? IPropertyContainer.IsPropertyVisible( PropertyDescriptor pd ) { var handler = this.IsPropertyBrowsable; //If anyone is registered to PropertyGrid.IsPropertyBrowsable event if( handler != null ) { var isBrowsableArgs = new IsPropertyBrowsableArgs( pd ); handler( this, isBrowsableArgs ); return isBrowsableArgs.IsBrowsable; } return null; } #endregion #endregion } #region PropertyValueChangedEvent Handler/Args public delegate void PropertyValueChangedEventHandler( object sender, PropertyValueChangedEventArgs e ); public class PropertyValueChangedEventArgs : RoutedEventArgs { public object NewValue { get; set; } public object OldValue { get; set; } public PropertyValueChangedEventArgs( RoutedEvent routedEvent, object source, object oldValue, object newValue ) : base( routedEvent, source ) { NewValue = newValue; OldValue = oldValue; } } #endregion #region PropertyItemCreatedEvent Handler/Args public delegate void PropertyItemEventHandler( object sender, PropertyItemEventArgs e ); public class PropertyItemEventArgs : RoutedEventArgs { public PropertyItemBase PropertyItem { get; private set; } public object Item { get; private set; } public PropertyItemEventArgs( RoutedEvent routedEvent, object source, PropertyItemBase propertyItem, object item ) : base( routedEvent, source ) { this.PropertyItem = propertyItem; this.Item = item; } } #endregion #region isPropertyBrowsableEvent Handler/Args public delegate void IsPropertyBrowsableHandler( object sender, IsPropertyBrowsableArgs e ); public class IsPropertyBrowsableArgs : RoutedEventArgs { #region Constructors public IsPropertyBrowsableArgs( PropertyDescriptor pd ) { this.PropertyDescriptor = pd; } #endregion #region Properties #region IsBrowsable Property public bool? IsBrowsable { get; set; } #endregion #region PropertyDescriptor Property public PropertyDescriptor PropertyDescriptor { get; private set; } #endregion #endregion } #endregion }