Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.ComponentModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Media;

Expand All @@ -15,7 +15,7 @@ public class PropertyGridDemoModel
[Category("Category2")]
public bool Boolean { get; set; }

[Category("Category1")]
[Category("Category2")]
public Gender Enum { get; set; }

public HorizontalAlignment HorizontalAlignment { get; set; }
Expand All @@ -27,6 +27,8 @@ public class PropertyGridDemoModel

public enum Gender
{
[Description("Boy/Man/Male")]
Male,
[Description("Girl/Woman/Female")]
Female
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<UserControl x:Class="HandyControlDemo.UserControl.PropertyGridDemo"
<UserControl x:Class="HandyControlDemo.UserControl.PropertyGridDemo"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:hc="https://handyorg.github.io/handycontrol"
Background="{DynamicResource RegionBrush}"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
Expand All @@ -10,7 +11,23 @@
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<hc:PropertyGrid Width="500" SelectedObject="{Binding DemoModel}"/>
<hc:PropertyGrid Width="500" SelectedObject="{Binding DemoModel}">
<hc:PropertyGrid.CategoryOrder>
<x:Array Type="{x:Type sys:String}">
<sys:String>Category2</sys:String>
<sys:String>Category1</sys:String>
</x:Array>
</hc:PropertyGrid.CategoryOrder>
<hc:PropertyGrid.PropertyOrder>
<x:Array Type="{x:Type sys:String}">
<sys:String>Boolean</sys:String>
<sys:String>Integer</sys:String>
<sys:String>Enum</sys:String>
<sys:String>String</sys:String>
<sys:String>ImageSource</sys:String>
</x:Array>
</hc:PropertyGrid.PropertyOrder>
</hc:PropertyGrid>
<StackPanel hc:TitleElement.TitleWidth="168" Grid.Row="1" Margin="20,16,17,10">
<TextBox hc:TitleElement.Title="String" hc:TitleElement.TitlePlacement="Left" Style="{StaticResource TextBoxExtend}" Text="{Binding DemoModel.String,Mode=OneWay}" IsReadOnly="True"/>
<TextBox hc:TitleElement.Title="Enum" hc:TitleElement.TitlePlacement="Left" Style="{StaticResource TextBoxExtend}" Text="{Binding DemoModel.Enum,Mode=OneWay}" IsReadOnly="True" Margin="0,6,0,0"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
using System;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls.Primitives;
using HandyControl.Data;

namespace HandyControl.Controls;

Expand All @@ -9,8 +12,27 @@ public class EnumPropertyEditor : PropertyEditorBase
public override FrameworkElement CreateElement(PropertyItem propertyItem) => new System.Windows.Controls.ComboBox
{
IsEnabled = !propertyItem.IsReadOnly,
ItemsSource = Enum.GetValues(propertyItem.PropertyType)
ItemsSource = CreateEnumItems(propertyItem.PropertyType),
DisplayMemberPath = nameof(EnumItem.Description),
SelectedValuePath = nameof(EnumItem.Value)
};

public override DependencyProperty GetDependencyProperty() => Selector.SelectedValueProperty;

private static IEnumerable<EnumItem> CreateEnumItems(Type enumType)
{
foreach (Enum value in Enum.GetValues(enumType))
{
var fieldInfo = enumType.GetField(value.ToString());
var description = fieldInfo?.GetCustomAttributes(typeof(DescriptionAttribute), true) is DescriptionAttribute[] { Length: > 0 } attributes
? attributes[0].Description
: value.ToString();

yield return new EnumItem
{
Description = description,
Value = value
};
}
}
}
102 changes: 101 additions & 1 deletion src/Shared/HandyControl_Shared/Controls/PropertyGrid/PropertyGrid.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
Expand Down Expand Up @@ -102,6 +103,62 @@ public bool ShowSortButton
set => SetValue(ShowSortButtonProperty, ValueBoxes.BooleanBox(value));
}

public static readonly DependencyProperty CategoryOrderProperty = DependencyProperty.Register(
nameof(CategoryOrder), typeof(IEnumerable<string>), typeof(PropertyGrid),
new PropertyMetadata(default(IEnumerable<string>), OnCategoryOrderChanged));

private static void OnCategoryOrderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var ctl = (PropertyGrid) d;
ctl.OnCategoryOrderChanged();
}

protected virtual void OnCategoryOrderChanged()
{
if (_dataView == null) return;

UpdateCategoryOrder();

if (_dataView.GroupDescriptions.OfType<PropertyGroupDescription>().Any(group => group.PropertyName == PropertyItem.CategoryProperty.Name))
{
SortByCategory(null, null);
}
}

public IEnumerable<string> CategoryOrder
{
get => (IEnumerable<string>) GetValue(CategoryOrderProperty);
set => SetValue(CategoryOrderProperty, value);
}

public static readonly DependencyProperty PropertyOrderProperty = DependencyProperty.Register(
nameof(PropertyOrder), typeof(IEnumerable<string>), typeof(PropertyGrid),
new PropertyMetadata(default(IEnumerable<string>), OnPropertyOrderChanged));

private static void OnPropertyOrderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var ctl = (PropertyGrid) d;
ctl.OnPropertyOrderChanged();
}

protected virtual void OnPropertyOrderChanged()
{
if (_dataView == null) return;

UpdatePropertyOrder();

if (_dataView.GroupDescriptions.OfType<PropertyGroupDescription>().Any(group => group.PropertyName == PropertyItem.CategoryProperty.Name))
{
SortByCategory(null, null);
}
}

public IEnumerable<string> PropertyOrder
{
get => (IEnumerable<string>) GetValue(PropertyOrderProperty);
set => SetValue(PropertyOrderProperty, value);
}

public override void OnApplyTemplate()
{
if (_searchBar != null)
Expand Down Expand Up @@ -130,10 +187,51 @@ private void UpdateItems(object obj)
.Where(item => PropertyResolver.ResolveIsBrowsable(item)).Select(CreatePropertyItem)
.Do(item => item.InitElement()));

UpdateCategoryOrder();
UpdatePropertyOrder();

SortByCategory(null, null);
_itemsControl.ItemsSource = _dataView;
}

private void UpdateCategoryOrder()
{
if (_dataView?.SourceCollection == null) return;

var categoryOrderDic = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
var index = 0;

foreach (var category in CategoryOrder ?? Enumerable.Empty<string>())
{
if (string.IsNullOrWhiteSpace(category) || categoryOrderDic.ContainsKey(category)) continue;
categoryOrderDic[category] = index++;
}

foreach (var item in _dataView.SourceCollection.OfType<PropertyItem>())
{
item.CategoryOrder = categoryOrderDic.TryGetValue(item.Category, out var order) ? order : int.MaxValue;
}
}

private void UpdatePropertyOrder()
{
if (_dataView?.SourceCollection == null) return;

var propertyOrderDic = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
var index = 0;

foreach (var propertyName in PropertyOrder ?? Enumerable.Empty<string>())
{
if (string.IsNullOrWhiteSpace(propertyName) || propertyOrderDic.ContainsKey(propertyName)) continue;
propertyOrderDic[propertyName] = index++;
}

foreach (var item in _dataView.SourceCollection.OfType<PropertyItem>())
{
item.PropertyOrder = propertyOrderDic.TryGetValue(item.PropertyName, out var order) ? order : int.MaxValue;
}
}

private void SortByCategory(object sender, ExecutedRoutedEventArgs e)
{
if (_dataView == null) return;
Expand All @@ -142,7 +240,9 @@ private void SortByCategory(object sender, ExecutedRoutedEventArgs e)
{
_dataView.GroupDescriptions.Clear();
_dataView.SortDescriptions.Clear();
_dataView.SortDescriptions.Add(new SortDescription(PropertyItem.CategoryOrderProperty.Name, ListSortDirection.Ascending));
_dataView.SortDescriptions.Add(new SortDescription(PropertyItem.CategoryProperty.Name, ListSortDirection.Ascending));
_dataView.SortDescriptions.Add(new SortDescription(PropertyItem.PropertyOrderProperty.Name, ListSortDirection.Ascending));
_dataView.SortDescriptions.Add(new SortDescription(PropertyItem.DisplayNameProperty.Name, ListSortDirection.Ascending));
_dataView.GroupDescriptions.Add(new PropertyGroupDescription(PropertyItem.CategoryProperty.Name));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
Expand Down Expand Up @@ -89,6 +89,24 @@ public string Category
set => SetValue(CategoryProperty, value);
}

public static readonly DependencyProperty CategoryOrderProperty = DependencyProperty.Register(
nameof(CategoryOrder), typeof(int), typeof(PropertyItem), new PropertyMetadata(default(int)));

Comment on lines +92 to +94
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency with the rest of the codebase (many int dependency properties use ValueBoxes.Int0Box, e.g. PasswordBoxAttach.PasswordLengthProperty), consider using ValueBoxes.Int0Box as the default metadata value here instead of default(int) to reduce boxing/allocations.

Copilot uses AI. Check for mistakes.
public int CategoryOrder
{
get => (int) GetValue(CategoryOrderProperty);
set => SetValue(CategoryOrderProperty, value);
}

public static readonly DependencyProperty PropertyOrderProperty = DependencyProperty.Register(
nameof(PropertyOrder), typeof(int), typeof(PropertyItem), new PropertyMetadata(default(int)));

Comment on lines +101 to +103
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency with the rest of the codebase (many int dependency properties use ValueBoxes.Int0Box), consider using ValueBoxes.Int0Box as the default metadata value here instead of default(int) to reduce boxing/allocations.

Copilot uses AI. Check for mistakes.
public int PropertyOrder
{
get => (int) GetValue(PropertyOrderProperty);
set => SetValue(PropertyOrderProperty, value);
}

public static readonly DependencyProperty EditorProperty = DependencyProperty.Register(
nameof(Editor), typeof(PropertyEditorBase), typeof(PropertyItem), new PropertyMetadata(default(PropertyEditorBase)));

Expand Down