From 1be50e54fa1e70de21ac9e00267c39d7cbb0bdd8 Mon Sep 17 00:00:00 2001 From: SKharchi Date: Mon, 10 May 2021 08:49:23 +0200 Subject: [PATCH 1/4] Introduces flattening of types without a known type editor. Types which do not have a known type editor are browsed and flattened into the highest level as if their properties belong in the class that is currently handled by the PropertyGrid. Furthermore some flatting options are available to decide in which category to place the flattened properties and how to name that category. While the flattening is off by default to keep the default behavior, the demo has been extented to do apply flattening. --- .../Data/Model/PropertyGridDemoModel.cs | 4 + .../Controls/PropertyGridDemoCtl.xaml | 2 +- .../Controls/PropertyGrid/PropertyGrid.cs | 101 +++++++++++++++--- .../Controls/PropertyGrid/PropertyResolver.cs | 2 + .../Data/Enum/Flattening.cs | 14 +++ .../HandyControl_Shared.projitems | 1 + 6 files changed, 107 insertions(+), 17 deletions(-) create mode 100644 src/Shared/HandyControl_Shared/Data/Enum/Flattening.cs diff --git a/src/Shared/HandyControlDemo_Shared/Data/Model/PropertyGridDemoModel.cs b/src/Shared/HandyControlDemo_Shared/Data/Model/PropertyGridDemoModel.cs index a6c48a915..70805a130 100644 --- a/src/Shared/HandyControlDemo_Shared/Data/Model/PropertyGridDemoModel.cs +++ b/src/Shared/HandyControlDemo_Shared/Data/Model/PropertyGridDemoModel.cs @@ -1,6 +1,7 @@ using System.ComponentModel; using System.Windows; using System.Windows.Media; +using HandyControl.Controls; namespace HandyControlDemo.Data { @@ -18,6 +19,9 @@ public class PropertyGridDemoModel [Category("Category1")] public Gender Enum { get; set; } + [Category("Category2")] + public DemoDataModel FlattenedType { get; set; } + public HorizontalAlignment HorizontalAlignment { get; set; } public VerticalAlignment VerticalAlignment { get; set; } diff --git a/src/Shared/HandyControlDemo_Shared/UserControl/Controls/PropertyGridDemoCtl.xaml b/src/Shared/HandyControlDemo_Shared/UserControl/Controls/PropertyGridDemoCtl.xaml index f803c23e4..baec242fd 100644 --- a/src/Shared/HandyControlDemo_Shared/UserControl/Controls/PropertyGridDemoCtl.xaml +++ b/src/Shared/HandyControlDemo_Shared/UserControl/Controls/PropertyGridDemoCtl.xaml @@ -10,7 +10,7 @@ - + diff --git a/src/Shared/HandyControl_Shared/Controls/PropertyGrid/PropertyGrid.cs b/src/Shared/HandyControl_Shared/Controls/PropertyGrid/PropertyGrid.cs index 3888b91cb..0160de723 100644 --- a/src/Shared/HandyControl_Shared/Controls/PropertyGrid/PropertyGrid.cs +++ b/src/Shared/HandyControl_Shared/Controls/PropertyGrid/PropertyGrid.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Windows; @@ -6,6 +7,7 @@ using System.Windows.Data; using System.Windows.Input; using HandyControl.Data; +using HandyControl.Data.Enum; using HandyControl.Interactivity; using HandyControl.Tools.Extension; @@ -102,6 +104,15 @@ public bool ShowSortButton set => SetValue(ShowSortButtonProperty, value); } + public static readonly DependencyProperty FlattenChildPropertiesProperty = DependencyProperty.Register( + "FlattenChildProperties", typeof(Flattening), typeof(PropertyGrid), new PropertyMetadata(Flattening.Off)); + + public Flattening FlattenChildProperties + { + get => (Flattening) GetValue(FlattenChildPropertiesProperty); + set => SetValue(FlattenChildPropertiesProperty, value); + } + public override void OnApplyTemplate() { if (_searchBar != null) @@ -122,15 +133,72 @@ public override void OnApplyTemplate() UpdateItems(SelectedObject); } + /// + /// Algorithmic helper class to temporarily link parent data to a PropertyDescriptorCollection + /// + private class ParentPropertyDescriptorCollection + { + public ParentPropertyDescriptorCollection(PropertyDescriptorCollection properties, string category) + { + Properties = properties; + Category = category; + } + + public PropertyDescriptorCollection Properties { get; } + public string Category { get; } + } + + private IEnumerable FlattenUnknownProperties(PropertyDescriptorCollection propertiesToFlatten, string parentCategory) + { + var browsableProperties = propertiesToFlatten.OfType() + .Where(item => PropertyResolver.ResolveIsBrowsable(item)).ToList(); + + var knownProperties = browsableProperties.Where(item => PropertyResolver.IsKnownEditorType(item.PropertyType)) + .Select(item => CreatePropertyItem(item, parentCategory)) + .Do(item => item.InitElement()); + + var unknownPropertiesCollections = browsableProperties.Where(item => !PropertyResolver.IsKnownEditorType(item.PropertyType)) + .Select(GetCategorizedChildProperties); + + return unknownPropertiesCollections + .Select(coll => FlattenUnknownProperties(coll.Properties, coll.Category)) + .Aggregate(knownProperties, (current, flattenedChildProperties) => current.Concat(flattenedChildProperties)); + } + + private ParentPropertyDescriptorCollection GetCategorizedChildProperties(PropertyDescriptor parentItem) + { + string category = null; + switch (FlattenChildProperties) + { + case Flattening.ParentCategory: + category = PropertyResolver.ResolveCategory(parentItem); + break; + case Flattening.ParentNameAsCategory: + category = parentItem.DisplayName; + break; + } + return new ParentPropertyDescriptorCollection(parentItem.GetChildProperties(), category); + } + private void UpdateItems(object obj) { if (obj == null || _itemsControl == null) return; - _dataView = CollectionViewSource.GetDefaultView(TypeDescriptor.GetProperties(obj.GetType()).OfType() - .Where(item => PropertyResolver.ResolveIsBrowsable(item)).Select(CreatePropertyItem) - .Do(item => item.InitElement())); + if (FlattenChildProperties == Flattening.Off) + { + _dataView = CollectionViewSource.GetDefaultView(TypeDescriptor.GetProperties(obj.GetType()) + .OfType() + .Where(item => PropertyResolver.ResolveIsBrowsable(item)) + .Select(item => CreatePropertyItem(item, null)) + .Do(item => item.InitElement())); + } + else + { + _dataView = CollectionViewSource.GetDefaultView(FlattenUnknownProperties(TypeDescriptor.GetProperties(obj.GetType()), null)); + } SortByCategory(null, null); + _itemsControl.ItemsSource = _dataView; } @@ -181,19 +249,20 @@ private void SearchBar_SearchStarted(object sender, FunctionEventArgs e) } } - protected virtual PropertyItem CreatePropertyItem(PropertyDescriptor propertyDescriptor) => new PropertyItem - { - Category = PropertyResolver.ResolveCategory(propertyDescriptor), - DisplayName = PropertyResolver.ResolveDisplayName(propertyDescriptor), - Description = PropertyResolver.ResolveDescription(propertyDescriptor), - IsReadOnly = PropertyResolver.ResolveIsReadOnly(propertyDescriptor), - DefaultValue = PropertyResolver.ResolveDefaultValue(propertyDescriptor), - Editor = PropertyResolver.ResolveEditor(propertyDescriptor), - Value = SelectedObject, - PropertyName = propertyDescriptor.Name, - PropertyType = propertyDescriptor.PropertyType, - PropertyTypeName = $"{propertyDescriptor.PropertyType.Namespace}.{propertyDescriptor.PropertyType.Name}" - }; + protected virtual PropertyItem CreatePropertyItem(PropertyDescriptor propertyDescriptor, string category) => + new PropertyItem + { + Category = category ?? PropertyResolver.ResolveCategory(propertyDescriptor), + DisplayName = PropertyResolver.ResolveDisplayName(propertyDescriptor), + Description = PropertyResolver.ResolveDescription(propertyDescriptor), + IsReadOnly = PropertyResolver.ResolveIsReadOnly(propertyDescriptor), + DefaultValue = PropertyResolver.ResolveDefaultValue(propertyDescriptor), + Editor = PropertyResolver.ResolveEditor(propertyDescriptor), + Value = SelectedObject, + PropertyName = propertyDescriptor.Name, + PropertyType = propertyDescriptor.PropertyType, + PropertyTypeName = $"{propertyDescriptor.PropertyType.Namespace}.{propertyDescriptor.PropertyType.Name}" + }; protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo) { diff --git a/src/Shared/HandyControl_Shared/Controls/PropertyGrid/PropertyResolver.cs b/src/Shared/HandyControl_Shared/Controls/PropertyGrid/PropertyResolver.cs index d28031fbb..6bd833443 100644 --- a/src/Shared/HandyControl_Shared/Controls/PropertyGrid/PropertyResolver.cs +++ b/src/Shared/HandyControl_Shared/Controls/PropertyGrid/PropertyResolver.cs @@ -104,6 +104,8 @@ public virtual PropertyEditorBase CreateDefaultEditor(Type type) => public virtual PropertyEditorBase CreateEditor(Type type) => new ReadOnlyTextPropertyEditor(); + public static bool IsKnownEditorType(Type type) => TypeCodeDic.ContainsKey(type) || type.IsSubclassOf(typeof(Enum)); + private enum EditorTypeCode { PlainText, diff --git a/src/Shared/HandyControl_Shared/Data/Enum/Flattening.cs b/src/Shared/HandyControl_Shared/Data/Enum/Flattening.cs new file mode 100644 index 000000000..1d9627a22 --- /dev/null +++ b/src/Shared/HandyControl_Shared/Data/Enum/Flattening.cs @@ -0,0 +1,14 @@ +// ----------------------------- +// Copyright (c) XION GmbH +// ----------------------------- + +namespace HandyControl.Data.Enum +{ + public enum Flattening : byte + { + Off, + Uncategorized, + ParentCategory, + ParentNameAsCategory + } +} diff --git a/src/Shared/HandyControl_Shared/HandyControl_Shared.projitems b/src/Shared/HandyControl_Shared/HandyControl_Shared.projitems index e7b9e0523..00eb0c4e1 100644 --- a/src/Shared/HandyControl_Shared/HandyControl_Shared.projitems +++ b/src/Shared/HandyControl_Shared/HandyControl_Shared.projitems @@ -80,6 +80,7 @@ + From 628397713dbd1da80eace2659d9804ed7f5b974d Mon Sep 17 00:00:00 2001 From: SKharchi Date: Mon, 10 May 2021 08:54:59 +0200 Subject: [PATCH 2/4] removed unnecessary dependency --- .../HandyControlDemo_Shared/Data/Model/PropertyGridDemoModel.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Shared/HandyControlDemo_Shared/Data/Model/PropertyGridDemoModel.cs b/src/Shared/HandyControlDemo_Shared/Data/Model/PropertyGridDemoModel.cs index 70805a130..d5a367c98 100644 --- a/src/Shared/HandyControlDemo_Shared/Data/Model/PropertyGridDemoModel.cs +++ b/src/Shared/HandyControlDemo_Shared/Data/Model/PropertyGridDemoModel.cs @@ -1,7 +1,6 @@ using System.ComponentModel; using System.Windows; using System.Windows.Media; -using HandyControl.Controls; namespace HandyControlDemo.Data { From b30c1c6eb75ad69be894624789993b2764d59f2d Mon Sep 17 00:00:00 2001 From: SKharchi Date: Mon, 10 May 2021 11:38:09 +0200 Subject: [PATCH 3/4] Hides the search bar element. Introduces a dependency property ShowSearchBar in order to hide the search bar. The search bar XAML mark up has been updated to adjust the visibility accordingly. Furthermore, multibinding is used to collapse the dockpanel visbility if the search bar and the sort buttons are both hidden. To support that the BooleanArr2VisibilityConverter has been extented to optionally return a visible state if any of the bindings are visible (instead of all). For that the ConverterParameter can be set to 'UseAny'. --- .../HandyControl_Net_40/Themes/Theme.xaml | 34 ++++++++++++------- .../Controls/PropertyGrid/PropertyGrid.cs | 9 +++++ .../Styles/Base/PropertyGridBaseStyle.xaml | 10 +++++- .../HandyControl_Shared/Themes/Theme.xaml | 8 ++++- 4 files changed, 46 insertions(+), 15 deletions(-) diff --git a/src/Net_40/HandyControl_Net_40/Themes/Theme.xaml b/src/Net_40/HandyControl_Net_40/Themes/Theme.xaml index da65710e7..c0c2456e0 100644 --- a/src/Net_40/HandyControl_Net_40/Themes/Theme.xaml +++ b/src/Net_40/HandyControl_Net_40/Themes/Theme.xaml @@ -9677,18 +9677,26 @@ - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + @@ -13299,4 +13307,4 @@