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, (s, e) => e.CanExecute = ShowSortButton)); CommandBindings.Add(new CommandBinding(ControlCommands.SortByName, SortByName, (s, e) => e.CanExecute = ShowSortButton)); } public virtual PropertyResolver PropertyResolver { get; } = new(); 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); } public static readonly DependencyProperty SelectedObjectProperty = DependencyProperty.Register( nameof(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(oldValue, newValue, SelectedObjectChangedEvent)); } public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register( nameof(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( nameof(MaxTitleWidth), typeof(double), typeof(PropertyGrid), new PropertyMetadata(ValueBoxes.Double0Box)); public double MaxTitleWidth { get => (double) GetValue(MaxTitleWidthProperty); set => SetValue(MaxTitleWidthProperty, value); } public static readonly DependencyProperty MinTitleWidthProperty = DependencyProperty.Register( nameof(MinTitleWidth), typeof(double), typeof(PropertyGrid), new PropertyMetadata(ValueBoxes.Double0Box)); public double MinTitleWidth { get => (double) GetValue(MinTitleWidthProperty); set => SetValue(MinTitleWidthProperty, value); } public static readonly DependencyProperty ShowSortButtonProperty = DependencyProperty.Register( nameof(ShowSortButton), typeof(bool), typeof(PropertyGrid), new PropertyMetadata(ValueBoxes.TrueBox)); public bool ShowSortButton { get => (bool) GetValue(ShowSortButtonProperty); set => SetValue(ShowSortButtonProperty, ValueBoxes.BooleanBox(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.GetType()).OfType() .Where(item => PropertyResolver.ResolveIsBrowsable(item)).Select(CreatePropertyItem) .Do(item => item.InitElement())); SortByCategory(null, null); _itemsControl.ItemsSource = _dataView; } private void SortByCategory(object sender, ExecutedRoutedEventArgs e) { if (_dataView == null) return; 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.DisplayNameProperty.Name, ListSortDirection.Ascending)); _dataView.GroupDescriptions.Add(new PropertyGroupDescription(PropertyItem.CategoryProperty.Name)); } } private void SortByName(object sender, ExecutedRoutedEventArgs e) { if (_dataView == null) return; 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 e) { if (_dataView == null) return; _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() { 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)))); } }