Merge pull request #409 from HandyOrg/add-PropertyEditor

Add property editor
This commit is contained in:
NaBian authored and GitHub committed 2020-06-19 01:16:46 +08:00
commit e57340f1c2
50 files changed
+1080 -11

No files matched your search

@@ -0,0 +1,14 @@
using System.Windows;
namespace HandyControl.Controls
{
public class DatePropertyEditor : PropertyEditorBase
{
public override FrameworkElement CreateElement(PropertyItem propertyItem) => new DateTimePicker
{
IsEnabled = !propertyItem.IsReadOnly
};
public override DependencyProperty GetDependencyProperty() => System.Windows.Controls.DatePicker.SelectedDateProperty;
}
}
@@ -0,0 +1,14 @@
using System.Windows;
namespace HandyControl.Controls
{
public class DateTimePropertyEditor : PropertyEditorBase
{
public override FrameworkElement CreateElement(PropertyItem propertyItem) => new DateTimePicker
{
IsEnabled = !propertyItem.IsReadOnly
};
public override DependencyProperty GetDependencyProperty() => DateTimePicker.SelectedDateTimeProperty;
}
}
@@ -0,0 +1,17 @@
using System;
using System.Windows;
using System.Windows.Controls.Primitives;
namespace HandyControl.Controls
{
public class EnumPropertyEditor : PropertyEditorBase
{
public override FrameworkElement CreateElement(PropertyItem propertyItem) => new System.Windows.Controls.ComboBox
{
IsEnabled = !propertyItem.IsReadOnly,
ItemsSource = Enum.GetValues(propertyItem.PropertyType)
};
public override DependencyProperty GetDependencyProperty() => Selector.SelectedValueProperty;
}
}
@@ -0,0 +1,31 @@
using System.Windows;
namespace HandyControl.Controls
{
public class NumberPropertyEditor : PropertyEditorBase
{
public NumberPropertyEditor(double minimum, double maximum)
{
Minimum = minimum;
Maximum = maximum;
}
public double Minimum { get; set; }
public double Maximum { get; set; }
public override FrameworkElement CreateElement(PropertyItem propertyItem)
{
var numericUpDown = new NumericUpDown
{
IsReadOnly = propertyItem.IsReadOnly,
Minimum = Minimum,
Maximum = Maximum
};
numericUpDown.SetBinding(NumericUpDown.ValueProperty, CreateBinding(propertyItem));
return numericUpDown;
}
}
}
@@ -0,0 +1,31 @@
using System.Windows;
namespace HandyControl.Controls
{
public class NumberPropertyEditor : PropertyEditorBase
{
public NumberPropertyEditor()
{
}
public NumberPropertyEditor(double minimum, double maximum)
{
Minimum = minimum;
Maximum = maximum;
}
public double Minimum { get; set; }
public double Maximum { get; set; }
public override FrameworkElement CreateElement(PropertyItem propertyItem) => new NumericUpDown
{
IsReadOnly = propertyItem.IsReadOnly,
Minimum = Minimum,
Maximum = Maximum
};
public override DependencyProperty GetDependencyProperty() => NumericUpDown.ValueProperty;
}
}
@@ -0,0 +1,14 @@
using System.Windows;
namespace HandyControl.Controls
{
public class PlainTextPropertyEditor : PropertyEditorBase
{
public override FrameworkElement CreateElement(PropertyItem propertyItem) => new System.Windows.Controls.TextBox
{
IsReadOnly = propertyItem.IsReadOnly
};
public override DependencyProperty GetDependencyProperty() => System.Windows.Controls.TextBox.TextProperty;
}
}
@@ -0,0 +1,28 @@
using System.Windows;
using System.Windows.Data;
namespace HandyControl.Controls
{
public abstract class PropertyEditorBase : FrameworkElement
{
public abstract FrameworkElement CreateElement(PropertyItem propertyItem);
public virtual void CreateBinding(PropertyItem propertyItem, FrameworkElement element) =>
BindingOperations.SetBinding(element, GetDependencyProperty(),
new Binding($"({propertyItem.PropertyName})")
{
Source = propertyItem.Value,
Mode = GetBindingMode(propertyItem),
UpdateSourceTrigger = GetUpdateSourceTrigger(propertyItem),
Converter = GetConverter(propertyItem)
});
public abstract DependencyProperty GetDependencyProperty();
public virtual BindingMode GetBindingMode(PropertyItem propertyItem) => propertyItem.IsReadOnly ? BindingMode.OneWay : BindingMode.TwoWay;
public virtual UpdateSourceTrigger GetUpdateSourceTrigger(PropertyItem propertyItem) => UpdateSourceTrigger.PropertyChanged;
protected virtual IValueConverter GetConverter(PropertyItem propertyItem) => null;
}
}
@@ -0,0 +1,20 @@
using System.Windows;
using System.Windows.Data;
using HandyControl.Tools;
namespace HandyControl.Controls
{
public class ReadOnlyTextPropertyEditor : PropertyEditorBase
{
public override FrameworkElement CreateElement(PropertyItem propertyItem) => new System.Windows.Controls.TextBox
{
IsReadOnly = true
};
public override DependencyProperty GetDependencyProperty() => System.Windows.Controls.TextBox.TextProperty;
public override BindingMode GetBindingMode(PropertyItem propertyItem) => BindingMode.OneWay;
protected override IValueConverter GetConverter(PropertyItem propertyItem) => ResourceHelper.GetResource<IValueConverter>("Object2StringConverter");
}
}
@@ -0,0 +1,19 @@
using System.Windows;
namespace HandyControl.Controls
{
public class PlainTextPropertyEditor : PropertyEditorBase
{
public override FrameworkElement CreateElement(PropertyItem propertyItem)
{
var textbox = new System.Windows.Controls.TextBox
{
IsReadOnly = propertyItem.IsReadOnly
};
textbox.SetBinding(System.Windows.Controls.TextBox.TextProperty, CreateBinding(propertyItem));
return textbox;
}
}
}
@@ -0,0 +1,18 @@
using System.Windows;
using System.Windows.Controls.Primitives;
using HandyControl.Tools;
namespace HandyControl.Controls
{
public class SwitchPropertyEditor : PropertyEditorBase
{
public override FrameworkElement CreateElement(PropertyItem propertyItem) => new ToggleButton
{
Style = ResourceHelper.GetResource<Style>("ToggleButtonSwitch"),
HorizontalAlignment = HorizontalAlignment.Left,
IsEnabled = !propertyItem.IsReadOnly
};
public override DependencyProperty GetDependencyProperty() => ToggleButton.IsCheckedProperty;
}
}
@@ -0,0 +1,14 @@
using System.Windows;
namespace HandyControl.Controls
{
public class TimePropertyEditor : PropertyEditorBase
{
public override FrameworkElement CreateElement(PropertyItem propertyItem) => new DateTimePicker
{
IsEnabled = !propertyItem.IsReadOnly
};
public override DependencyProperty GetDependencyProperty() => TimePicker.SelectedTimeProperty;
}
}
@@ -0,0 +1,189 @@
using System;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using HandyControl.Data;
using HandyControl.Interactivity;
using HandyControl.Tools.Extension;
namespace HandyControl.Controls
{
[TemplatePart(Name = ElementItemsControl, Type = typeof(ItemsControl))]
[TemplatePart(Name = ElementSearchBar, Type = typeof(SearchBar))]
public class PropertyGrid : Control
{
private const string ElementItemsControl = "PART_ItemsControl";
private const string ElementSearchBar = "PART_SearchBar";
private ItemsControl _itemsControl;
private ICollectionView _dataView;
private SearchBar _searchBar;
private string _searchKey;
public PropertyGrid()
{
CommandBindings.Add(new CommandBinding(ControlCommands.SortByCategory, SortByCategory));
CommandBindings.Add(new CommandBinding(ControlCommands.SortByName, SortByName));
}
public virtual PropertyResolver PropertyResolver { get; } = new PropertyResolver();
public static readonly RoutedEvent SelectedObjectChangedEvent =
EventManager.RegisterRoutedEvent("SelectedObjectChanged", RoutingStrategy.Bubble,
typeof(RoutedPropertyChangedEventHandler<object>), typeof(PropertyGrid));
public event RoutedPropertyChangedEventHandler<object> SelectedObjectChanged
{
add => AddHandler(SelectedObjectChangedEvent, value);
remove => RemoveHandler(SelectedObjectChangedEvent, value);
}
public static readonly DependencyProperty SelectedObjectProperty = DependencyProperty.Register(
"SelectedObject", typeof(object), typeof(PropertyGrid), new PropertyMetadata(default, OnSelectedObjectChanged));
private static void OnSelectedObjectChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var ctl = (PropertyGrid)d;
ctl.OnSelectedObjectChanged(e.OldValue, e.NewValue);
}
public object SelectedObject
{
get => GetValue(SelectedObjectProperty);
set => SetValue(SelectedObjectProperty, value);
}
protected virtual void OnSelectedObjectChanged(object oldValue, object newValue)
{
UpdateItems(newValue);
RaiseEvent(new RoutedPropertyChangedEventArgs<object>(oldValue, newValue, SelectedObjectChangedEvent));
}
public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register(
"Description", typeof(string), typeof(PropertyGrid), new PropertyMetadata(default(string)));
public string Description
{
get => (string)GetValue(DescriptionProperty);
set => SetValue(DescriptionProperty, value);
}
public static readonly DependencyProperty MaxTitleWidthProperty = DependencyProperty.Register(
"MaxTitleWidth", typeof(double), typeof(PropertyGrid), new PropertyMetadata(default(double)));
public double MaxTitleWidth
{
get => (double) GetValue(MaxTitleWidthProperty);
set => SetValue(MaxTitleWidthProperty, value);
}
public static readonly DependencyProperty MinTitleWidthProperty = DependencyProperty.Register(
"MinTitleWidth", typeof(double), typeof(PropertyGrid), new PropertyMetadata(default(double)));
public double MinTitleWidth
{
get => (double) GetValue(MinTitleWidthProperty);
set => SetValue(MinTitleWidthProperty, value);
}
public override void OnApplyTemplate()
{
if (_searchBar != null)
{
_searchBar.SearchStarted -= SearchBar_SearchStarted;
}
base.OnApplyTemplate();
_itemsControl = GetTemplateChild(ElementItemsControl) as ItemsControl;
_searchBar = GetTemplateChild(ElementSearchBar) as SearchBar;
if (_searchBar != null)
{
_searchBar.SearchStarted += SearchBar_SearchStarted;
}
UpdateItems(SelectedObject);
}
private void UpdateItems(object obj)
{
if (obj == null || _itemsControl == null) return;
_dataView = CollectionViewSource.GetDefaultView(TypeDescriptor.GetProperties(obj).OfType<PropertyDescriptor>()
.Where(item => PropertyResolver.ResolveIsBrowsable(item)).Select(CreatePropertyItem)
.Do(item => item.InitElement()));
SortByCategory(null, null);
_itemsControl.ItemsSource = _dataView;
}
private void SortByCategory(object sender, ExecutedRoutedEventArgs e)
{
using (_dataView.DeferRefresh())
{
_dataView.GroupDescriptions.Clear();
_dataView.SortDescriptions.Clear();
_dataView.SortDescriptions.Add(new SortDescription(PropertyItem.CategoryProperty.Name, ListSortDirection.Ascending));
_dataView.SortDescriptions.Add(new SortDescription(PropertyItem.PropertyNameProperty.Name, ListSortDirection.Ascending));
_dataView.GroupDescriptions.Add(new PropertyGroupDescription(PropertyItem.CategoryProperty.Name));
}
}
private void SortByName(object sender, ExecutedRoutedEventArgs e)
{
using (_dataView.DeferRefresh())
{
_dataView.GroupDescriptions.Clear();
_dataView.SortDescriptions.Clear();
_dataView.SortDescriptions.Add(new SortDescription(PropertyItem.PropertyNameProperty.Name, ListSortDirection.Ascending));
}
}
private void SearchBar_SearchStarted(object sender, FunctionEventArgs<string> e)
{
_searchKey = e.Info;
if (string.IsNullOrEmpty(_searchKey))
{
foreach (UIElement item in _dataView)
{
item.Show();
}
}
else
{
foreach (PropertyItem item in _dataView)
{
item.Show(item.PropertyName.ToLower().Contains(_searchKey) || item.DisplayName.ToLower().Contains(_searchKey));
}
}
}
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 override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
base.OnRenderSizeChanged(sizeInfo);
TitleElement.SetTitleWidth(this, new GridLength(Math.Max(MinTitleWidth, Math.Min(MaxTitleWidth, ActualWidth / 3))));
}
}
}
@@ -0,0 +1,136 @@
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
namespace HandyControl.Controls
{
public class PropertyItem : Control
{
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
"Value", typeof(object), typeof(PropertyItem), new PropertyMetadata(default(object)));
public object Value
{
get => GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
public static readonly DependencyProperty DisplayNameProperty = DependencyProperty.Register(
"DisplayName", typeof(string), typeof(PropertyItem), new PropertyMetadata(default(string)));
public string DisplayName
{
get => (string)GetValue(DisplayNameProperty);
set => SetValue(DisplayNameProperty, value);
}
public static readonly DependencyProperty PropertyNameProperty = DependencyProperty.Register(
"PropertyName", typeof(string), typeof(PropertyItem), new PropertyMetadata(default(string)));
public string PropertyName
{
get => (string)GetValue(PropertyNameProperty);
set => SetValue(PropertyNameProperty, value);
}
public static readonly DependencyProperty PropertyTypeProperty = DependencyProperty.Register(
"PropertyType", typeof(Type), typeof(PropertyItem), new PropertyMetadata(default(Type)));
public Type PropertyType
{
get => (Type) GetValue(PropertyTypeProperty);
set => SetValue(PropertyTypeProperty, value);
}
public static readonly DependencyProperty PropertyTypeNameProperty = DependencyProperty.Register(
"PropertyTypeName", typeof(string), typeof(PropertyItem), new PropertyMetadata(default(string)));
public string PropertyTypeName
{
get => (string)GetValue(PropertyTypeNameProperty);
set => SetValue(PropertyTypeNameProperty, value);
}
public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register(
"Description", typeof(string), typeof(PropertyItem), new PropertyMetadata(default(string)));
public string Description
{
get => (string)GetValue(DescriptionProperty);
set => SetValue(DescriptionProperty, value);
}
public static readonly DependencyProperty IsReadOnlyProperty = DependencyProperty.Register(
"IsReadOnly", typeof(bool), typeof(PropertyItem), new PropertyMetadata(default(bool)));
public bool IsReadOnly
{
get => (bool)GetValue(IsReadOnlyProperty);
set => SetValue(IsReadOnlyProperty, value);
}
public static readonly DependencyProperty DefaultValueProperty = DependencyProperty.Register(
"DefaultValue", typeof(object), typeof(PropertyItem), new PropertyMetadata(default(object)));
public object DefaultValue
{
get => GetValue(DefaultValueProperty);
set => SetValue(DefaultValueProperty, value);
}
public static readonly DependencyProperty CategoryProperty = DependencyProperty.Register(
"Category", typeof(string), typeof(PropertyItem), new PropertyMetadata(default(string)));
public string Category
{
get => (string)GetValue(CategoryProperty);
set => SetValue(CategoryProperty, value);
}
public static readonly DependencyProperty EditorProperty = DependencyProperty.Register(
"Editor", typeof(PropertyEditorBase), typeof(PropertyItem), new PropertyMetadata(default(PropertyEditorBase)));
public PropertyEditorBase Editor
{
get => (PropertyEditorBase)GetValue(EditorProperty);
set => SetValue(EditorProperty, value);
}
public static readonly DependencyProperty EditorElementProperty = DependencyProperty.Register(
"EditorElement", typeof(FrameworkElement), typeof(PropertyItem), new PropertyMetadata(default(FrameworkElement)));
public FrameworkElement EditorElement
{
get => (FrameworkElement)GetValue(EditorElementProperty);
set => SetValue(EditorElementProperty, value);
}
public static readonly DependencyProperty IsSelectedProperty = DependencyProperty.Register(
"IsSelected", typeof(bool), typeof(PropertyItem), new PropertyMetadata(default(bool)));
public bool IsSelected
{
get => (bool)GetValue(IsSelectedProperty);
set => SetValue(IsSelectedProperty, value);
}
public static readonly DependencyProperty IsExpandedEnabledProperty = DependencyProperty.Register(
"IsExpandedEnabled", typeof(bool), typeof(PropertyItem), new PropertyMetadata(default(bool)));
public bool IsExpandedEnabled
{
get => (bool)GetValue(IsExpandedEnabledProperty);
set => SetValue(IsExpandedEnabledProperty, value);
}
public PropertyDescriptor PropertyDescriptor { get; set; }
public virtual void InitElement()
{
if (Editor == null) return;
EditorElement = Editor.CreateElement(this);
Editor.CreateBinding(this, EditorElement);
}
}
}
@@ -0,0 +1,9 @@
using System.Windows.Controls;
namespace HandyControl.Controls
{
public class PropertyItemsControl : ItemsControl
{
protected override bool IsItemItsOwnContainerOverride(object item) => item is PropertyItem;
}
}
@@ -0,0 +1,116 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using HandyControl.Properties.Langs;
namespace HandyControl.Controls
{
public class PropertyResolver
{
private static readonly Dictionary<Type, EditorTypeCode> TypeCodeDic = new Dictionary<Type, EditorTypeCode>
{
[typeof(string)] = EditorTypeCode.PlainText,
[typeof(sbyte)] = EditorTypeCode.SByteNumber,
[typeof(byte)] = EditorTypeCode.ByteNumber,
[typeof(short)] = EditorTypeCode.Int16Number,
[typeof(ushort)] = EditorTypeCode.UInt16Number,
[typeof(int)] = EditorTypeCode.Int32Number,
[typeof(uint)] = EditorTypeCode.UInt32Number,
[typeof(long)] = EditorTypeCode.Int64Number,
[typeof(ulong)] = EditorTypeCode.UInt64Number,
[typeof(float)] = EditorTypeCode.SingleNumber,
[typeof(double)] = EditorTypeCode.DoubleNumber,
[typeof(bool)] = EditorTypeCode.Switch,
[typeof(DateTime)] = EditorTypeCode.DateTime
};
public string ResolveCategory(PropertyDescriptor propertyDescriptor)
{
var category = propertyDescriptor.Category;
if (string.IsNullOrEmpty(category))
{
category = Lang.Miscellaneous;
}
return category;
}
public string ResolveDisplayName(PropertyDescriptor propertyDescriptor)
{
var displayName = propertyDescriptor.DisplayName;
if (string.IsNullOrEmpty(displayName))
{
displayName = propertyDescriptor.Name;
}
return displayName;
}
public string ResolveDescription(PropertyDescriptor propertyDescriptor) => propertyDescriptor.Description;
public bool ResolveIsBrowsable(PropertyDescriptor propertyDescriptor) => propertyDescriptor.IsBrowsable;
public bool ResolveIsDisplay(PropertyDescriptor propertyDescriptor) => propertyDescriptor.IsLocalizable;
public bool ResolveIsReadOnly(PropertyDescriptor propertyDescriptor) => propertyDescriptor.IsReadOnly;
public object ResolveDefaultValue(PropertyDescriptor propertyDescriptor)
{
var defaultValueAttribute = propertyDescriptor.Attributes.OfType<DefaultValueAttribute>().FirstOrDefault();
return defaultValueAttribute?.Value;
}
public PropertyEditorBase ResolveEditor(PropertyDescriptor propertyDescriptor)
{
var editorAttribute = propertyDescriptor.Attributes.OfType<EditorAttribute>().FirstOrDefault();
var editor = editorAttribute == null || string.IsNullOrEmpty(editorAttribute.EditorTypeName)
? CreateDefaultEditor(propertyDescriptor.PropertyType)
: CreateEditor(Type.GetType(editorAttribute.EditorTypeName));
return editor;
}
public virtual PropertyEditorBase CreateDefaultEditor(Type type) =>
TypeCodeDic.TryGetValue(type, out var editorType)
? editorType switch
{
EditorTypeCode.PlainText => new PlainTextPropertyEditor(),
EditorTypeCode.SByteNumber => new NumberPropertyEditor(sbyte.MinValue, sbyte.MaxValue),
EditorTypeCode.ByteNumber => new NumberPropertyEditor(byte.MinValue, byte.MaxValue),
EditorTypeCode.Int16Number => new NumberPropertyEditor(short.MinValue, short.MaxValue),
EditorTypeCode.UInt16Number => new NumberPropertyEditor(ushort.MinValue, ushort.MaxValue),
EditorTypeCode.Int32Number => new NumberPropertyEditor(int.MinValue, int.MaxValue),
EditorTypeCode.UInt32Number => new NumberPropertyEditor(uint.MinValue, uint.MaxValue),
EditorTypeCode.Int64Number => new NumberPropertyEditor(long.MinValue, long.MaxValue),
EditorTypeCode.UInt64Number => new NumberPropertyEditor(ulong.MinValue, ulong.MaxValue),
EditorTypeCode.SingleNumber => new NumberPropertyEditor(float.MinValue, float.MaxValue),
EditorTypeCode.DoubleNumber => new NumberPropertyEditor(double.MinValue, double.MaxValue),
EditorTypeCode.Switch => new SwitchPropertyEditor(),
EditorTypeCode.DateTime => new DateTimePropertyEditor(),
_ => new ReadOnlyTextPropertyEditor()
}
: type.IsSubclassOf(typeof(Enum))
? (PropertyEditorBase) new EnumPropertyEditor()
: new ReadOnlyTextPropertyEditor();
public virtual PropertyEditorBase CreateEditor(Type type) => new ReadOnlyTextPropertyEditor();
private enum EditorTypeCode
{
PlainText,
SByteNumber,
ByteNumber,
Int16Number,
UInt16Number,
Int32Number,
UInt32Number,
Int64Number,
UInt64Number,
SingleNumber,
DoubleNumber,
Switch,
DateTime
}
}
}