Featured image of post PropertyGrid

PropertyGrid

一个功能强大、高度可扩展的 WPF 属性编辑器(PropertyGrid),专为现代 MVVM 应用设计。

🎯 SharpBoxesCore.Wpf.PropertyGrid

一个功能强大、高度可扩展的 WPF 属性编辑器(PropertyGrid),专为现代 MVVM 应用设计。支持自动 UI 生成、属性分组、描述显示、范围验证、滑块调节、自定义按钮、命令绑定、复合类型嵌套、集合编辑、动态刷新、防抖优化等高级功能,开箱即用,适用于配置面板、调试工具、设计器、参数设置等场景。


✨ 核心特性

功能 说明
🔹 自动属性发现 扫描对象的公共可读写属性
🔹 属性分组(Category) 支持 [Category("分组名")] 分类显示
🔹 属性排序 支持 [PropertyOrder(1)] 自定义排序
🔹 描述显示 鼠标悬停或聚焦时显示 [Description] 内容
🔹 显示名称 支持 [DisplayName("别名")] 自定义名称
🔹 枚举支持 显示 [Description] 文本,支持可空枚举
🔹 数值范围验证 支持 [Range(0, 100)] 输入限制
🔹 滑块调节 配合 [ShowSlider] 显示 Slider 控件
🔹 自定义按钮 支持 [AddButton] 添加操作按钮
🔹 命令绑定 按钮可绑定 ICommand,支持参数传递
🔹 降级机制 命令不存在时 fallback 到 ButtonClicked 事件
🔹 折叠面板 每个 Category 支持 Expander 折叠/展开
🔹 事件回调 支持 ButtonClickedPropertyValueChanged 事件
🔹 错误提示 边框变红,显示错误提示,不更新数据源
🔹 文件/文件夹选择 支持 [SelectFilePath] / [SelectFolder] 对话框
🔹 下拉框编辑 支持 [EnumerableProperty] 绑定数据源
🔹 复合类型嵌套 支持类属性展开为子属性面板
🔹 集合编辑 支持 List<T> / Dictionary<K,V> 弹窗编辑
🔹 动态刷新 数据源变化自动更新 UI
🔹 类型安全 编辑时保留原始类型,支持自动解析
🔹 防抖机制 高频更新时自动合并刷新,提升性能
🔹 高度自适应 支持滚动条,内容展开自动拉伸
🔹 搜索属性字段 支持搜索指定名称属性编辑
🔹 一键展开、折叠 支持一键展开、折叠所有属性

🚀 快速开始

1. 安装引用

.NET CLI

1
dotnet add package SharpBoxesCore.Wpf --version 1.1.2

PackageReference

1
<PackageReference Include="SharpBoxesCore.Wpf" Version="1.1.2" />

CPM

Directory.Packages.props

1
<PackageVersion Include="SharpBoxesCore.Wpf" Version="1.1.2" />

Project file

1
<PackageReference Include="SharpBoxesCore.Wpf" />

2. 在 XAML 中使用

1
2
3
4
5
6
7
<Window x:Class="YourApp.MainWindow"
        xmlns:local="clr-namespace:SharpBoxesCore.Wpf.PropertyGrid"
        ...>
    <Grid>
        <local:PropertyGrid x:Name="propertyGrid" Padding="10" />
    </Grid>
</Window>

3. 绑定数据对象

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        propertyGrid.SelectedObject = new SampleObject();
        
        // 订阅事件
        propertyGrid.ButtonClicked += OnButtonClicked;
        propertyGrid.PropertyValueChanged += OnPropertyValueChanged;
    }
}

📦 示例:完整数据模型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public class SampleObject : INotifyPropertyChanged
{
    [Category("基本信息")]
    [DisplayName("姓名")]
    [Description("用户的全名")]
    [EnumerableProperty("AvaliableNames", true)]
    public string Name { get; set; } = "张三";

    [Category("数值设置")]
    [DisplayName("音量")]
    [Description("调节音量大小")]
    [Range(0, 100)]
    [ShowSlider(10, true)]
    public int Volume { get; set; } = 75;

    [Category("高级")]
    [DisplayName("数据模式")]
    public DataMode Mode { get; set; } = DataMode.Development;

    [Category("文件")]
    [DisplayName("配置文件")]
    [SelectFilePath(Title = "选择配置文件", Filter = "JSON Files|*.json")]
    public string ConfigFile { get; set; } = "";

    [Category("路径")]
    [DisplayName("工作目录")]
    [SelectFolder(Title = "选择工作目录")]
    public string WorkDir { get; set; } = "";

    [Category("集合")]
    public List<string> Hobbies { get; set; } = new() { "阅读", "音乐" };

    [Category("字典")]
    public Dictionary<string, int> Scores { get; set; } = new()
    {
        ["数学"] = 90,
        ["英语"] = 85
    };

    [Category("嵌套对象")]
    public Address HomeAddress { get; set; } = new();

    [Category("操作")]
    [AddButton("ResetCommand", "重置")]
    [AddButton("LogCommand", "记录当前值")]
    public int RetryCount { get; set; } = 3;

    // 命令定义
    public ICommand ResetCommand => new RelayCommand<string>(_ => RetryCount = 0);
    public ICommand LogCommand => new RelayCommand<string>(action => 
        Debug.WriteLine($"日志: {action}, Count={RetryCount}"));

    // 下拉数据源
    public ObservableCollection<string> AvaliableNames { get; } = 
        new() { "Alice", "Bob", "Charlie" };

    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

🎨 UI 效果预览

PropertyGrid 预览图

  • 分组折叠/展开
  • 滑块与文本框联动
  • 按钮自动布局
  • 错误输入红色提示
  • 描述信息实时显示
  • 复合对象嵌套展开
  • 集合点击“编辑…”弹窗
  • 滚动条支持长内容

🔧 高级功能详解

1. 枚举显示 [Description]

1
2
3
4
5
6
7
8
public enum DataMode
{
    [Description("开发模式")]
    Development,

    [Description("生产模式")]
    Production
}

✅ 显示为“开发模式”而非 Development


2. 滑块调节 [ShowSlider]

1
2
3
[Range(0, 100)]
[ShowSlider(5, true)]
public int Brightness { get; set; }

✅ 自动生成 Slider + TextBox 联动控件


3. 自定义按钮与命令绑定

1
2
3
[AddButton("SaveCommand", "保存", "另存为")]
[AddButton("DeleteCommand", "删除")]
public string FileName { get; set; }
  • ✅ 存在命令 → 绑定执行
  • ❌ 不存在 → 触发 ButtonClicked 事件
1
2
3
4
propertyGrid.ButtonClicked += (s, e) =>
{
    MessageBox.Show($"按钮点击: {e.PropertyName} - {e.ButtonText}");
};

4. 数值范围验证

1
2
[Range(1, 1000)]
public int Count { get; set; }

✅ 输入非法时:

  • 边框变红
  • 显示错误提示
  • 不更新数据源

5. 下拉框 [EnumerableProperty]

1
2
[EnumerableProperty("AvaliableNames", true)]
public string Theme { get; set; }

✅ 显示下拉框,支持自定义输入,数据源动态刷新


6. 文件/文件夹选择

1
2
3
4
5
[SelectFilePath(Title = "选择日志")]
public string LogFile { get; set; }

[SelectFolder]
public string BackupDir { get; set; }

✅ 使用 Ookii.Dialogs.Wpf 提供专业对话框


7. 复合类型(嵌套对象)

1
2
3
4
5
6
7
8
public class Address
{
    public string Street { get; set; }
    public string City { get; set; }
}

[DisplayName("家庭地址")]
public Address HomeAddress { get; set; }

✅ 自动展开为子属性面板,支持 INotifyPropertyChanged 动态刷新


8. 集合编辑(List / Dictionary)

1
2
public List<string> Hobbies { get; set; }
public Dictionary<string, int> Scores { get; set; }

✅ 点击“编辑…”弹出 ListEditor / DictEditor,支持类型保留编辑


🧩 事件系统

PropertyValueChanged — 属性更改事件

1
2
3
4
5
6
7
propertyGrid.PropertyValueChanged += (s, e) =>
{
    Console.WriteLine($"属性更改: {e.PropertyName}");
    Console.WriteLine($"  类型: {e.PropertyType.Name}");
    Console.WriteLine($"  旧值: {e.OldValue}");
    Console.WriteLine($"  新值: {e.NewValue}");
};

✅ 所有编辑操作(包括集合、文件选择)均触发此事件

ButtonClicked — 按钮点击事件(fallback)

1
2
3
4
propertyGrid.ButtonClicked += (s, e) =>
{
    if (e.ButtonText == "重置") ResetLogic();
};

✅ 用于未绑定 ICommand 的按钮


🛠️ 自定义 Attribute 说明

Attribute 用途 示例
[Category("分组")] 属性分组 Category("网络")
[DisplayName("别名")] 自定义显示名 DisplayName("IP地址")
[Description("说明")] 描述信息 Description("服务器IP")
[PropertyOrder(1)] 排序优先级 数字越小越靠前
[Range(0,100)] 数值范围 支持 int/double/float
[ShowSlider(10)] 显示滑块 TickFrequency=10
[AddButton("Cmd", "按钮")] 添加操作按钮 支持多标签
[EnumerableProperty("Source", true)] 下拉框 绑定数据源
[SelectFilePath] 文件选择 打开 OpenFileDialog
[SelectFolder] 文件夹选择 打开 FolderBrowserDialog

⚙️ 高级配置

防抖机制(Debounce)

默认启用,防止高频刷新:

1
propertyGrid.DebounceEnabled = true; // 默认 true

可调整延迟时间(内部使用 DispatcherTimer

手动触发属性更改

1
propertyGrid.RaisePropertyValueChanged("Name", "旧值", "新值");

用于代码修改属性后通知外部系统


📚 已知限制

限制 说明
❌ 不支持集合增删项 ListEditor 仅支持修改值
❌ 不支持复杂对象列表编辑 仅支持简单类型列表
❌ 不支持 DateTime 专用控件 可用 TextBox 输入

✅ 后续计划支持:DatePickerColorPickerIEditableObject 撤销


如果你喜欢这篇文章,欢迎点赞、收藏、分享!
也可以关注我的博客,获取更多 WPF / .NET 技术干货 😊


Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy