🎯 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 折叠/展开 |
| 🔹 事件回调 |
支持 ButtonClicked 和 PropertyValueChanged 事件 |
| 🔹 错误提示 |
边框变红,显示错误提示,不更新数据源 |
| 🔹 文件/文件夹选择 |
支持 [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 效果预览

- 分组折叠/展开
- 滑块与文本框联动
- 按钮自动布局
- 错误输入红色提示
- 描述信息实时显示
- 复合对象嵌套展开
- 集合点击“编辑…”弹窗
- 滚动条支持长内容
🔧 高级功能详解
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}");
};
|
✅ 所有编辑操作(包括集合、文件选择)均触发此事件
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 输入 |
✅ 后续计划支持:DatePicker、ColorPicker、IEditableObject 撤销
如果你喜欢这篇文章,欢迎点赞、收藏、分享!
也可以关注我的博客,获取更多 WPF / .NET 技术干货 😊