PropertyGridLib 1.2.0
dotnet add package PropertyGridLib --version 1.2.0
NuGet\Install-Package PropertyGridLib -Version 1.2.0
<PackageReference Include="PropertyGridLib" Version="1.2.0" />
<PackageVersion Include="PropertyGridLib" Version="1.2.0" />
<PackageReference Include="PropertyGridLib" />
paket add PropertyGridLib --version 1.2.0
#r "nuget: PropertyGridLib, 1.2.0"
#:package PropertyGridLib@1.2.0
#addin nuget:?package=PropertyGridLib&version=1.2.0
#tool nuget:?package=PropertyGridLib&version=1.2.0
PropertyGridLib
WPF PropertyGrid 控件库 — 仿 WinForms PropertyGrid,基于 HandyControl UI 库。
版本 1.2.0 | .NET Framework 4.8+ / .NET 6/7/8-windows | HandyControl 3.5.1 | 作者:WangShuo
📦 相关仓库
| 仓库 | 地址 |
|---|---|
| 本库(PropertyGridLib) | NuGet:Install-Package PropertyGridLib -Version 1.2.0 |
| Demo 项目 | https://github.com/1wangshuo/PropertyGridDemo |
✨ v1.2.0 新特性
- ✅ 🔌 宿主自定义编辑器扩展机制 —
PropertyGrid.RegisterEditor<TAttr>(DataTemplate)一行注册,宿主侧定义 Attribute + DataTemplate 即可让 PropertyGrid 渲染自定义编辑器;框架对此 Attribute 一无所知。内置CustomEditors静态字典 +PropertyItem.CustomEditorTemplate/CustomEditorAttribute属性,EditorTemplateSelector优先返回宿主模板,完全覆盖框架内置编辑器的渲染路径 - ✅ 🎨 颜色编辑器 — 自动识别
Color/Color?/SolidColorBrush类型,显示为颜色预览块 +...按钮(紧凑居左,与 FilePath/Dictionary 编辑器风格统一);点击弹出 HandyControlColorPicker(StaysOpen=true + 手动外部点击检测,支持拖动 Slider 不丢失焦点,点"确定"才应用值);预览块显示#RRGGBB(不透明时省略 Alpha 前缀) - ✅ 📁 字典嵌套字典自动弹窗 — 字典值为集合(
IDictionary/IList)时不再内联,改为显示"N 项 + ..."按钮,点击弹出二级编辑器(模态覆盖父级,UI 不会无限拉长);字典值为复杂对象时仍内嵌 PropertyGrid 就地编辑 - ✅ 🌳 FormulaTree 类型提示 —
FormulaTreeNode新增FormulaType/TypeHint属性,TreeView 弹窗叶子节点右侧显示淡蓝色斜体类型名(如(int)(DateTime)),宿主可通过ShowTypeHint=False关闭;Category 前缀改为罗马数字(I. II. III.),切换中英文时分类顺序稳定 - ✅ 🔤 全局字体同步 —
PropertyGrid.FontSize/FontFamily动态变化时通过DependencyPropertyDescriptor实时更新GlobalFontSize/GlobalFontFamily,所有 Dialog(字典、集合、文件夹、颜色)打开时自动读取最新值 - ✅ 🌓 HandyControl 主题切换修复 —
SetAppSkin改为直接改Theme.Skin属性(HC 官方推荐方式),白→黑→白双向切换均正常,不再因替换 MergedDictionaries 导致重复 key 冲突
✨ v1.1.2 新特性
- ✅ 命令按钮编辑器
[Button]— 属性标记后渲染为按钮,点击可反射调用宿主方法(Action)并触发ButtonClicked事件;新增静态PropertyGrid.GlobalButtonClicked全局事件,主网格与集合/字典内嵌网格中的按钮点击都能被宿主统一捕获 - ✅ 字典编辑器(IDictionary) — 自动识别字典属性;新增
DictionaryEditorDialog(弹窗)与DictionaryEditorControl(内联,当SelectedObject直接是字典时);键、值均可为简单类型或复杂对象(内嵌 PropertyGrid,可无限嵌套),两者都是对象时左右各占 50% - ✅ 修复字典 "..." 按钮无响应 的历史缺陷(字典曾被当作集合但 EditCollection 只处理 IList)
- ✅ 修复嵌套网格中按钮"失效"(内嵌 PropertyGrid 的按钮事件无法上抵宿主、且不刷新)
- ✅ 新增 .NET Core 3.0 / 3.1 目标框架(连同 net48 / net6 / net7 / net8-windows)
✨ v1.1.1 新特性
- ✅ 集合编辑器内联显示 — 当
PropertyGrid.SelectedObject设为IList类型时,自动内联显示集合编辑器(无需弹出对话框) - ✅ 集合编辑器 UserControl 化 —
CollectionEditorControl/SimpleCollectionEditorControl可独立作为 UserControl 嵌入任意容器 - ✅ FormulaBindingControl 独立使用 — 新增
TreeItemsSource/FormulaTreeProvider依赖属性,支持脱离 PropertyGrid 直接使用 - ✅ 简单类型自动检测 — 内联模式自动区分简单类型(string/int/double 等)和复杂对象,选择对应编辑器
✨ v1.1.0 新特性
- ✅ 完全移除 System.Windows.Forms 依赖 - 纯 WPF 实现
- ✅ 多框架支持 - .NET Framework 4.8 / .NET 6/7/8-windows
- ✅ HandyControl 主题集成 - 所有对话框自动跟随深色/浅色主题
- ✅ 自定义文件夹浏览器 - 纯 WPF 实现,树形目录浏览
- ✅ 文件夹浏览器支持手动输入路径自动定位 - 输入合法路径自动在树上选中
- ✅ 完整中英文多语言 - 所有新增组件支持语言切换
- ✅ 现代化序列化 - JSON 替代 BinaryFormatter
- ✅ 零依赖冲突 - System.Text.Json 向上兼容到 16.0+
目录
- 安装
- 快速开始
- 命名空间
- 标准 .NET 特性
- 自定义特性
- 🎨 颜色编辑器
- 📁 字典编辑器(IDictionary)
- 🔌 宿主自定义编辑器扩展机制
- 🌳 FormulaTree 类型提示
- 编辑器类型总览
- PropertyGrid 集合模式(内联显示)
- 集合编辑器 UserControl 独立使用
- FormulaBindingControl 独立使用
- FormulaBound<T> — 公式绑定泛型类型
- TypeConverter 下拉列表
- 多语言 / 本地化系统
- API 参考
- 依赖项
- 更新日志
安装
NuGet 包管理器
Install-Package PropertyGridLib -Version 1.2.0
.NET CLI
dotnet add package PropertyGridLib --version 1.2.0
PackageReference (PackageReference)
<PackageReference Include="PropertyGridLib" Version="1.2.0" />
安装后,NuGet 会自动拉取依赖项 HandyControl 3.5.1。
🎯 单 DLL 部署
PropertyGridLib 使用 Costura.Fody 将 HandyControl.dll、System.Text.Json.dll 等依赖嵌入合并到主 DLL 内。
PropertyGridLib.dll ← 唯一需要部署的 DLL(内部已嵌入 HandyControl 等)
宿主项目只需引用这一个 DLL,无需额外附带 HandyControl.dll。运行时通过 AssemblyResolve 事件自动从内嵌资源加载依赖。
⚠️ 若宿主项目也直接引用了 HandyControl(版本可能不同),两个内嵌的 HandyControl 会并存。建议宿主统一使用 PropertyGridLib 内嵌的版本,或在宿主 csproj 中显式指定 HandyControl 版本以覆盖内嵌。
快速开始
1. 添加命名空间
xmlns:pg="clr-namespace:PropertyGridLib;assembly=PropertyGridLib"
2. 在 XAML 中使用
<pg:PropertyGrid x:Name="propertyGrid"
Width="400"
Height="600"
ShowSearchBar="True"
ShowDescription="True"/>
3. 绑定对象
propertyGrid.SelectedObject = new MyConfig();
4. 重置与刷新
propertyGrid.ResetSelectedToDefault(); // 重置当前选中属性
propertyGrid.ResetAllToDefault(); // 重置所有属性
propertyGrid.RefreshProperties(); // 刷新属性列表
命名空间
| 命名空间 | 内容 |
|---|---|
PropertyGridLib |
PropertyGrid 控件、IPropertyLocalization 接口 |
PropertyGridLib.Controls |
PropertyItem、IPropertyItem、PropertyCategory、EditorTemplateSelector、FormulaBound<T>、FormulaTreeNode、IFormulaTreeProvider、IMultiSelectProvider、MultiSelectControl、FormulaBindingControl、CollectionEditorControl、SimpleCollectionEditorControl、DictionaryEditorControl、ColorEditControl、PropertyButtonClickEventArgs、CustomEditorType 枚举 |
PropertyGridLib.Attributes |
FilePathAttribute、DirectoryPathAttribute、CollectionEditorAttribute、NumberSliderAttribute、FormulaEditorAttribute、MultiSelectAttribute、ButtonAttribute |
PropertyGridLib.Dialogs |
FolderBrowserDialog、CollectionEditorDialog、SimpleCollectionEditorDialog、DictionaryEditorDialog |
PropertyGridLib.Localization |
LocalizationManager、LocalizationProxy、LocalizedExtension、Language 枚举 |
PropertyGridLib.Converters |
BoolToVisibilityConverter、InverseBoolConverter、ObjectToTypeConverter、ColorToBrushConverter 等值转换器 |
标准 .NET 特性
PropertyGrid 自动识别以下标准 .NET 特性:
| 特性 | 说明 | 示例 |
|---|---|---|
[Category("分类名")] |
属性分组显示 | [Category("外观")] |
[DisplayName("显示名")] |
自定义属性显示名称 | [DisplayName("字体大小")] |
[Description("描述")] |
底部描述栏显示 | [Description("文字的字体大小")] |
[DefaultValue(value)] |
默认值,控制重置按钮显示 | [DefaultValue(14)] |
[ReadOnly(true)] |
只读属性,不可编辑 | [ReadOnly(true)] |
[Browsable(false)] |
隐藏属性,不在列表中显示 | [Browsable(false)] |
自定义特性
FilePathAttribute — 文件路径选择
标记属性为文件路径,显示浏览按钮,点击弹出文件选择对话框。支持多后缀筛选。
using PropertyGridLib.Attributes;
// 单后缀
[FilePath("*.log")]
public string LogFilePath { get; set; } = "";
// 多后缀
[FilePath("*.json", "*.xml", "*.txt")]
public string ConfigFilePath { get; set; } = "";
可选属性:
| 属性 | 类型 | 说明 |
|---|---|---|
Filters |
string[] |
文件后缀列表(构造函数参数) |
Filter |
string |
完整过滤器字符串(如 "*.json;*.xml") |
InitialDirectory |
string |
初始目录 |
Title |
string |
对话框标题 |
Multiselect |
bool |
是否允许多选 |
DirectoryPathAttribute — 目录路径选择
标记属性为目录路径,显示浏览按钮,点击弹出文件夹选择对话框(纯 WPF 实现,支持 HandyControl 主题)。
[DirectoryPath]
public string DataDirectory { get; set; } = "";
可选属性:
| 属性 | 类型 | 说明 |
|---|---|---|
InitialDirectory |
string |
初始目录 |
Title |
string |
对话框标题 |
特性:
- 树形目录浏览
- 支持路径直接输入
- 自动跟随 HandyControl 深色/浅色主题
- 完整的中英文多语言支持
CollectionEditorAttribute — 集合编辑器
标记集合类型属性使用编辑器对话框。简单类型(string、int 等)使用简易编辑器,复杂对象使用完整编辑器(含属性面板)。
// 简单类型集合 — 弹出简易编辑器(输入框 + 列表 + 增删排序)
[CollectionEditor]
public List<string> Tags { get; set; } = new List<string> { "A", "B" };
// 复杂对象集合 — 弹出完整编辑器(列表 + 属性面板 + 增删复制排序)
[CollectionEditor]
public List<RobotConfig> Robots { get; set; } = new List<RobotConfig>();
可选属性:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
CanAdd |
bool |
true |
是否允许添加新项 |
CanRemove |
bool |
true |
是否允许删除项 |
CanCopy |
bool |
true |
是否允许复制项 |
CanSort |
bool |
true |
是否允许上移/下移排序 |
集合编辑器自动区分简单类型和复杂对象。无需手动指定,控件会根据
List<T>的T类型自动选择合适的编辑器。
NumberSliderAttribute — 数值滑块
将数值属性显示为滑块编辑器,支持 int、double、float、decimal 等数值类型。
// 整数滑块
[NumberSlider(0, 100, 5)] // min, max, step
public int Volume { get; set; } = 50;
// 小数滑块(默认显示只读数值标签)
[NumberSlider(0.0, 1.0, 0.1)]
public double Brightness { get; set; } = 0.8;
// 滑块 + 可编辑数字框(hc:NumericUpDown,可键入小数等精确值;min/max/step 与滑块共用,
// 双向同步;显示后取代只读数值标签)
[NumberSlider(0, 100, 5, ShowNumberBox = true)]
public double Opacity { get; set; } = 50;
参数说明:
| 参数 | 类型 | 说明 |
|---|---|---|
minimum |
double |
最小值 |
maximum |
double |
最大值 |
step |
double |
步长(默认 1) |
ShowNumberBox |
bool |
是否显示可编辑数字输入框(默认 false;启用后与滑块双向同步并取代数值标签) |
FormulaEditorAttribute — 公式绑定
标记属性支持公式绑定编辑器。标记后,属性编辑器下方会额外显示一行公式绑定区(链接图标 + 公式文本框 + Popup 树选择器)。
// 需要实现 IFormulaTreeProvider 接口
[FormulaEditor(typeof(MyFormulaTreeProvider))]
public FormulaBound<string> NameFormula { get; set; } = new FormulaBound<string> { Value = "默认值" };
IFormulaTreeProvider 接口:
using PropertyGridLib.Controls;
public class MyFormulaTreeProvider : IFormulaTreeProvider
{
public List<FormulaTreeNode> GetFormulaTree(PropertyItem propertyItem)
{
return new List<FormulaTreeNode>
{
new FormulaTreeNode
{
Header = "流程A",
Children = new List<FormulaTreeNode>
{
new FormulaTreeNode
{
Header = "任务1",
Children = new List<FormulaTreeNode>
{
new FormulaTreeNode { Header = "属性X", Formula = "&{流程A.任务1.属性X}" }
}
}
}
}
};
}
}
FormulaTreeNode 属性:
| 属性 | 类型 | 说明 |
|---|---|---|
Header |
string |
节点显示文本 |
Formula |
string |
选中后填入的公式字符串(仅叶子节点需要设置) |
Children |
List<FormulaTreeNode> |
子节点列表 |
FormulaType |
Type? |
叶子节点的数值类型(如 typeof(int)),用于 TreeView 弹窗显示类型提示 |
TypeHint |
string |
自动从 FormulaType 生成的类型提示(如 (int)、(string)) |
MultiSelectAttribute — 多选列表
标记属性为多选列表编辑器,弹出 CheckBox 列表供用户多选。属性类型应为 List<string>,Provider 动态返回可选项。
// 需要实现 IMultiSelectProvider 接口
[MultiSelect(typeof(MyMultiSelectProvider))]
public List<string> SelectedOptions { get; set; } = new List<string>();
IMultiSelectProvider 接口:
using PropertyGridLib.Controls;
public class MyMultiSelectProvider : IMultiSelectProvider
{
public List<string> GetAvailableItems(PropertyItem propertyItem)
{
return new List<string> { "选项A", "选项B", "选项C", "选项D" };
}
}
ButtonAttribute — 命令按钮
标记属性为命令按钮。被标记的属性不再显示值编辑器,而是渲染为一个按钮;点击后:① 通过反射调用宿主对象上 Action 指定的方法(若指定);② 触发 PropertyItem.ButtonClicked 与 PropertyGrid.ButtonClicked / 静态 PropertyGrid.GlobalButtonClicked 事件。
using PropertyGridLib.Attributes;
// 仅触发事件(文本回退到 DisplayName,随多语言切换)
[Button]
public object GreetButton { get; set; }
// 指定按钮文本 + 点击时反射调用的宿主方法(无参或单参)
[Category("操作")]
[DisplayName("测试连接")]
[Button("测试连接", nameof(TestConnection))]
public object TestConnectionButton { get; set; }
public void TestConnection() { /* 点击后由库反射调用 */ }
宿主侧统一响应(推荐订阅静态全局事件,可捕获集合/字典内嵌网格中的按钮):
PropertyGrid.GlobalButtonClicked += (s, e) =>
{
// e.PropertyName / e.PropertyItem / e.Owner
Console.WriteLine($"{e.PropertyName} 被点击");
};
// 静态事件,窗口关闭时记得取消订阅:PropertyGrid.GlobalButtonClicked -= handler;
参数说明:
| 参数 | 类型 | 说明 |
|---|---|---|
text |
string |
按钮文本;为空时回退到 DisplayName |
action |
string |
点击时反射调用的宿主方法名(无参或单参);为空时仅触发事件 |
建议将按钮属性声明为
object/string占位类型(值可为 null)。按钮默认始终可点,仅当属性显式标注[ReadOnly(true)]时禁用。
🎨 颜色编辑器
自动检测
PropertyGrid 自动识别以下类型,无需任何特性:
| 类型 | 处理方式 |
|---|---|
Color |
值类型,双向编辑 |
Color? |
可空,支持 null |
SolidColorBrush |
取 Brush.Color 编辑,写回时重建 SolidColorBrush |
UI 布局
┌──────────────────┐ ┌──────┐
│ ■ #FF0000 │ │ ... │
└──────────────────┘ └──────┘
颜色预览块 + HEX 编辑按钮
- 居左占满宽度,与 FilePath/Dictionary 编辑器风格统一
- 预览块:16×16 颜色方块 +
#AARRGGBBHEX 文本 - 不透明颜色(Alpha=255)时省略 Alpha 前缀,显示
#RRGGBB - 深色/浅色主题自动适配
交互流程
点击 ... 按钮弹出 HandyControl ColorPicker:
| 操作 | 行为 |
|---|---|
| 拖动 Slider / 选色块 | ColorPicker 内部预览,不修改 PropertyItem.Value |
| 点"确定" | 写入 PropertyItem.Value → 关闭弹窗 |
| 点"取消" | 不改值 → 关闭弹窗 |
| 点弹窗外面 | 不改值 → 关闭弹窗 |
关键技术点
StaysOpen=true+ 手动Window.PreviewMouseDown检测外部点击(避免 HC ColorPicker 内部 Slider 拖动导致 Popup 误判关闭)IsInVisualTree()从 OriginalSource 向上遍历 VisualTree 判定点击来源Freeze()创建 SolidColorBrush 防止绑定循环警告DependencyPropertyDescriptor监听FontSize/FontFamily变化,ColorPicker 弹窗自动同步宿主字体
FormulaBound 属性的颜色提示
当属性带 [FormulaEditor] 且 ShowTypeHint=True 时,公式绑定区右侧显示淡蓝色斜体类型提示(如 (string)、(int)),颜色与公式输入框区分。
📁 字典编辑器(IDictionary)
字典属性(Dictionary<TKey,TValue> 等 IDictionary)会被自动识别,显示为 "N 项 + ..." 按钮,点击弹出 DictionaryEditorDialog。无需任何特性;若把 [CollectionEditor] 标在字典属性上,也会路由到字典编辑器。
// 简单键值:键、值都用文本框编辑(自动类型转换)
public Dictionary<string, int> ScoreMap { get; set; } = new Dictionary<string, int>();
// 复杂值:值区显示内嵌 PropertyGrid,可继续编辑其内部的集合/字典(无限嵌套)
public Dictionary<string, RobotConfig> Robots { get; set; } = new Dictionary<string, RobotConfig>();
// 复杂键:键区也显示内嵌 PropertyGrid(键类型需重写 Equals/GetHashCode)
public Dictionary<ServerEndpoint, string> Endpoints { get; set; } = new Dictionary<ServerEndpoint, string>();
// ===== 嵌套字典:值为字典时自动弹窗 =====
// 框架检测到值类型是 IDictionary → 显示 "N 项 ..." 按钮,点击弹出二级字典编辑器
public Dictionary<string, Dictionary<string, int>> NestedConfig { get; set; } = new()
{
{ "白天模式", new Dictionary<string, int> { { "人员检测", 6 }, { "车辆检测", 5 } } },
{ "夜间模式", new Dictionary<string, int> { { "人员检测", 8 }, { "车辆检测", 7 } } }
};
// ===== 字典值为列表:同样弹窗 =====
public Dictionary<string, List<string>> ZoneAlerts { get; set; } = new()
{
{ "大门", new List<string> { "2024-03-15 检测到人员" } }
};
嵌套值的三种渲染模式
| 值类型 | 检测 | UI 表现 | 编辑方式 |
|---|---|---|---|
| 简单类型(string/int/double 等) | IsSimpleType |
TextBox | 就地文本编辑 |
| 集合类型(IDictionary / IList) | IsDictionaryType / IsListType |
"N 项 ..."按钮 | 点击弹窗(模态覆盖父级) |
| 其他复杂对象 | 排除以上两种 | 内嵌 PropertyGrid | 就地编辑(可继续嵌套其属性) |
内联模式
当 PropertyGrid.SelectedObject 直接设为字典时,自动内联显示 DictionaryEditorControl(就地编辑、实时写回,无弹窗),与集合内联模式一致。
propertyGrid.SelectedObject = new Dictionary<string, int> { { "A", 1 }, { "B", 2 } };
独立使用
DictionaryEditorControl(PropertyGridLib.Controls)可作为 UserControl 嵌入任意容器,Items 为 IDictionary,直接操作源字典。
⚠️ 复杂键为可变对象时,就地编辑会改变其哈希——编辑器在"确定/切换条目"时会
Clear + 重新 Add重建哈希;复杂键类型务必重写Equals/GetHashCode。另注意System.Text.Json不支持非字符串字典键,故复杂键字典不宜作为会被深拷贝(Reset)的[Serializable]模型属性。
🔌 宿主自定义编辑器扩展机制
PropertyGridLib 提供通用扩展点,宿主只需 3 步即可让自定义编辑器生效,框架对此 Attribute 一无所知。
完整流程
宿主侧: [MyUnitEditor("GB,MB,KB", "MB")] 属性
→ PropertyGrid.RegisterEditor<MyUnitEditorAttribute>(template) // 注册
→ PropertyItem.DetectCustomEditor 发现 Attribute
→ CustomEditorTemplate = template, CustomEditorAttribute = attr // 存起来
→ EditorTemplateSelector.SelectTemplate 优先返回 CustomEditorTemplate // 渲染
→ DataTemplate 里 NumericUpDown + ComboBox
→ ComboBox Loaded 从 CustomEditorAttribute 读参数 // 宿主解析参数
Step 1 — 宿主定义 Attribute
// 纯宿主侧,框架对此类型一无所知
[AttributeUsage(AttributeTargets.Property)]
public class MyUnitEditorAttribute : Attribute
{
public string Units { get; }
public string DefaultUnit { get; set; }
public MyUnitEditorAttribute(string units) => Units = units;
}
Step 2 — 宿主定义 DataTemplate
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/PropertyGridLib;component/Themes/Generic.xaml"/>
<ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/SkinDefault.xaml"/>
<ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/Theme.xaml"/>
</ResourceDictionary.MergedDictionaries>
<DataTemplate x:Key="MyUnitEditorTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<hc:NumericUpDown Grid.Column="0"
Value="{Binding ValueString, ...}"
Margin="0,0,4,0"/>
<ComboBox Grid.Column="1"
Loaded="UnitCombo_Loaded"
SelectionChanged="UnitCombo_SelectionChanged"/>
</Grid>
</DataTemplate>
</ResourceDictionary>
</Application.Resources>
Step 3 — 宿主注册
// MainWindow 构造函数中(必须在 PropertyGrid.SelectedObject 赋值前注册)
PropertyGridLib.PropertyGrid.RegisterEditor<MyUnitEditorAttribute>(
(DataTemplate)FindResource("MyUnitEditorTemplate"));
Step 4 — 使用
[MyUnitEditor("GB,MB,KB", "MB")]
public double FileSize { get; set; } = 256;
模板内读 Attribute 参数
宿主在 DataTemplate 的 code-behind 中,从 PropertyItem.CustomEditorAttribute 拿到宿主 Attribute 实例,再读参数:
private void UnitCombo_Loaded(object sender, RoutedEventArgs e)
{
if (sender is ComboBox combo && combo.DataContext is PropertyItem pi)
{
var attr = pi.CustomEditorAttribute as MyUnitEditorAttribute;
if (attr == null) return;
var units = (attr.Units ?? "").Split(',').Select(s => s.Trim()).ToList();
combo.ItemsSource = units;
combo.SelectedItem = !string.IsNullOrEmpty(attr.DefaultUnit)
? attr.DefaultUnit
: units.FirstOrDefault();
}
}
API 参考
| 成员 | 类型 | 说明 |
|---|---|---|
PropertyGrid.CustomEditors |
Dictionary<Type, DataTemplate> |
静态注册字典,宿主可直接操作或通过 RegisterEditor 泛型方法 |
PropertyGrid.RegisterEditor<TAttribute>(template) |
static void |
泛型注册方法,一行搞定 |
PropertyItem.CustomEditorTemplate |
DataTemplate? |
宿主注册的编辑器模板,非 null 时 EditorTemplateSelector 优先使用 |
PropertyItem.CustomEditorAttribute |
Attribute? |
宿主 Attribute 实例,模板 code-behind 可读取参数 |
优先级
宿主注册 高于 框架内置。DetectCustomEditor 最前面先查 PropertyGrid.CustomEditors,找到就返回,内置的 Slider/Color/FilePath/Dictionary 都排在后面。宿主甚至可以覆盖框架内置编辑器的行为 — 比如给带 [NumberSlider] 的属性也注册一个自己的模板。
🌳 FormulaTree 类型提示
FormulaTreeNode 支持在 TreeView 弹窗中显示叶子节点的类型信息,帮助用户判断绑定源的类型是否匹配目标属性。
宿主设置类型
var provider = new DemoFormulaTreeProvider();
// 叶子节点设置 FormulaType
new FormulaTreeNode
{
Header = "任务A.值",
Formula = "&{Flow2,TaskA,Value}",
FormulaType = typeof(int) // ← 宿主指定类型
}
PropertyGrid 控制显示
<pg:PropertyGrid ShowTypeHint="False" .../>
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
ShowTypeHint |
bool |
true |
是否显示类型提示(FormulaTree 弹窗 + 公式绑定区) |
效果
├─ ▼ 流程2
│ 任务A.值 (int) ← 淡蓝色斜体,靠右
│ 任务B.计数 (double)
│ 任务C.启用状态 (bool)
- 非叶子节点(分类容器)不填
FormulaType,不显示类型提示 TypeHint自动从FormulaType生成,宿主也可直接覆盖TypeHint属性- 颜色
InfoBrush+Italic+ 比正常小 2 号,与节点 Header 区分
编辑器类型总览
控件根据属性类型和特性自动选择编辑器模板:
| 编辑器 | 触发条件 | 显示效果 |
|---|---|---|
| TextBox | string 类型 |
文本输入框 |
| CheckBox | bool / bool? |
复选框开关 |
| NumericUpDown | int, double, float, decimal, long, short, byte |
数字输入框(带增减按钮) |
| ComboBox | enum |
枚举下拉选择 |
| DateTimePicker | DateTime / DateTime? |
日期时间选择器 |
| Slider | [NumberSlider] 特性 |
滑块 + 数值标签 |
| Color | Color / Color? / SolidColorBrush |
颜色预览块 + "..." 按钮(弹出 HC ColorPicker) |
| FilePath | [FilePath] 特性 |
文本框 + 浏览按钮 |
| DirectoryPath | [DirectoryPath] 特性 |
文本框 + 浏览按钮 |
| Collection | [CollectionEditor] 特性或 IList 类型 |
文本框 + 编辑按钮(弹出对话框) |
| Dictionary | IDictionary 类型(自动识别) |
"N 项" + 编辑按钮;值为集合时显示 "N 项 ..." 弹窗 |
| DropDown | TypeConverter 标准值 |
下拉列表(支持排他/可编辑模式) |
| MultiSelect | [MultiSelect] 特性 |
摘要文本 + 弹出 CheckBox 列表 |
| Button | [Button] 特性 |
命令按钮(点击触发宿主方法/事件) |
| Expandable | 复杂对象(非简单类型、非集合) | 可展开子属性 |
| Formula | [FormulaEditor] 特性或 FormulaBound<T> |
值编辑器 + 公式绑定区 |
| 宿主自定义 | PropertyGrid.CustomEditors 注册的 Attribute |
宿主提供的 DataTemplate |
PropertyGrid 集合模式(内联显示)
当 PropertyGrid.SelectedObject 设为 IList 类型(如 List<string>、List<CustomType>)时,PropertyGrid 自动切换为集合编辑模式,内联显示集合编辑器,无需弹出对话框。
// 简单类型列表 — 自动显示 SimpleCollectionEditorControl
propertyGrid.SelectedObject = new List<string> { "A", "B", "C" };
// 复杂对象列表 — 自动显示 CollectionEditorControl(含内嵌 PropertyGrid)
propertyGrid.SelectedObject = new List<MyConfig> { new MyConfig() };
自动类型检测:
- 简单类型(
string、int、double、float、decimal、long、bool、DateTime、enum等)→SimpleCollectionEditorControl - 复杂对象类型 →
CollectionEditorControl(左侧列表 + 右侧属性面板) string不会被识别为集合;IDictionary会走独立的字典编辑器(见上节)
集合编辑器 UserControl 独立使用
CollectionEditorControl 和 SimpleCollectionEditorControl 现在可以作为 UserControl 独立嵌入任意容器,不再仅限于对话框弹出。
CollectionEditorControl(复杂对象列表)
xmlns:controls="clr-namespace:PropertyGridLib.Controls;assembly=PropertyGridLib"
<controls:CollectionEditorControl x:Name="editor" Items="{Binding MyList}" />
editor.Items = new List<MyConfig>
{
new MyConfig { Name = "Item1", Value = 100 },
new MyConfig { Name = "Item2", Value = 200 }
};
SimpleCollectionEditorControl(简单类型列表)
<controls:SimpleCollectionEditorControl x:Name="simpleEditor" Items="{Binding MyStringList}" />
simpleEditor.Items = new List<string> { "Apple", "Banana", "Cherry" };
// ElementType 会自动从集合泛型参数推断,也可手动指定
simpleEditor.ElementType = typeof(int);
simpleEditor.Items = new List<int> { 1, 2, 3 };
两个控件都直接操作传入的
IList实例,每次增删改操作后立即同步回源集合。
FormulaBindingControl 独立使用
FormulaBindingControl 支持脱离 PropertyGrid 独立使用,通过设置 TreeItemsSource 或 FormulaTreeProvider 提供公式树数据。
方式一:FormulaTreeProvider(推荐)
<Window.Resources>
<local:MyFormulaTreeProvider x:Key="myProvider"/>
</Window.Resources>
<controls:FormulaBindingControl
FormulaTreeProvider="{StaticResource myProvider}"
FormulaString="{Binding MyFormula, Mode=TwoWay}" />
方式二:TreeItemsSource(直接绑定树节点)
<controls:FormulaBindingControl
TreeItemsSource="{Binding MyTreeNodes}"
FormulaString="{Binding MyFormula, Mode=TwoWay}" />
public List<FormulaTreeNode> MyTreeNodes { get; } = new List<FormulaTreeNode>
{
new FormulaTreeNode
{
Header = "传感器",
Children =
{
new FormulaTreeNode { Header = "温度", Formula = "&{Sensors,Temperature,Value}" },
new FormulaTreeNode { Header = "湿度", Formula = "&{Sensors,Humidity,Value}" }
}
}
};
数据源优先级: TreeItemsSource > FormulaTreeProvider > PropertyItem.DataContext(PropertyGrid 内嵌用法)
FormulaBound<T> — 公式绑定泛型类型
FormulaBound<T> 是一个包装类型,同时持有值和公式字符串。用于数据绑定场景,让属性既能存储实际值,又能记录绑定公式。
using PropertyGridLib.Controls;
public class MyConfig
{
// string 类型公式绑定
[FormulaEditor(typeof(MyProvider))]
public FormulaBound<string> Name { get; set; } = new FormulaBound<string> { Value = "默认", Formula = "" };
// int 类型也支持公式绑定
[FormulaEditor(typeof(MyProvider))]
public FormulaBound<int> Count { get; set; } = new FormulaBound<int> { Value = 42 };
}
FormulaBound<T> 属性:
| 属性 | 类型 | 说明 |
|---|---|---|
Value |
T |
实际值 |
Formula |
string |
绑定公式字符串(如 &{流程.任务.属性}) |
PropertyGrid 自动检测
FormulaBound<T>类型,使用内部类型T选择编辑器,同时显示公式绑定区。即使不标记[FormulaEditor],FormulaBound<T>也会自动启用公式绑定输入框。
TypeConverter 下拉列表
当属性的 TypeConverter 提供标准值(GetStandardValuesSupported 返回 true),PropertyGrid 自动显示为下拉列表。
using System.ComponentModel;
// 自定义 TypeConverter 提供标准值
public class ComPortListConverter : TypeConverter
{
public override bool GetStandardValuesSupported(ITypeDescriptorContext context) => true;
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context) => true; // true=排他(不可手动输入),false=可输入
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
return new StandardValuesCollection(new[] { "COM1", "COM2", "COM3", "COM4" });
}
}
// 使用
[TypeConverter(typeof(ComPortListConverter))]
public string ComPort { get; set; } = "COM1";
多语言 / 本地化系统
PropertyGridLib 内置中英文双语支持,提供三个层面的本地化能力:
1. 类库内部字符串
通过 LocalizationManager 管理搜索框占位符、对话框标题、按钮文本等 47 个内置字符串。
using PropertyGridLib.Localization;
// 切换语言
LocalizationManager.CurrentLanguage = Language.EnUS; // 英文
LocalizationManager.CurrentLanguage = Language.ZhCN; // 中文(默认)
// 获取本地化字符串
var text = LocalizationManager.GetString("SearchPlaceholder");
var count = LocalizationManager.GetString("SelectedCount", 5);
在 XAML 中使用标记扩展绑定:
xmlns:loc="clr-namespace:PropertyGridLib.Localization;assembly=PropertyGridLib"
<TextBlock Text="{loc:Localized SearchPlaceholder}"/>
2. 属性名 / 描述 / 分类动态翻译
实现 IPropertyLocalization 接口,让数据对象提供动态的多语言翻译。语言切换时,PropertyGrid 自动刷新所有属性。
using PropertyGridLib;
using PropertyGridLib.Localization;
public class MyConfig : IPropertyLocalization
{
[Category("外观")]
[DisplayName("名称")]
[Description("对象的显示名称")]
public string Name { get; set; } = "Test";
// 返回 null 时回退到 [DisplayName] 特性值
public string GetDisplayName(string propertyName, Language language)
{
if (language == Language.ZhCN) return null; // 中文用特性值
return propertyName switch
{
"Name" => "Name",
_ => null
};
}
// 返回 null 时回退到 [Description] 特性值
public string GetDescription(string propertyName, Language language)
{
if (language == Language.ZhCN) return null;
return propertyName switch
{
"Name" => "Display name of the object",
_ => null
};
}
// 返回 null 时回退到 [Category] 特性值
public string GetCategory(string propertyName, Language language)
{
if (language == Language.ZhCN) return null;
return propertyName switch
{
"Name" => "Appearance",
_ => null
};
}
}
3. 语言切换流程
用户调用 LocalizationManager.CurrentLanguage = Language.EnUS
↓
触发 LanguageChanged 事件
↓
PropertyGrid.OnLanguageChanged() 遍历所有属性
↓
PropertyItem.UpdateLocalization() 递归更新 DisplayName/Description/Category
↓
刷新描述栏 + 重新分组过滤
内置本地化 Key 列表(部分常用,共 47 个):
| Key | 中文 | English |
|---|---|---|
| SearchPlaceholder | 搜索属性... | Search properties... |
| SelectBindingSource | 选择绑定源 | Select Binding Source |
| SelectItems | 选择项 | Select Items |
| SelectMultiple | 选择多项 | Select Multiple |
| ResetToDefault | 重置为初始值 | Reset to default |
| FormulaPlaceholder | 公式绑定... | Formula binding... |
| SelectFile | 选择文件 | Select File |
| SelectFolder | 选择文件夹 | Select Folder |
| AllFiles | 所有文件 | All Files |
| Files | 文件 | Files |
| ItemsCount | [{0} 项] | [{0} items] |
| Misc | 杂项 | Miscellaneous |
| NotSelected | (未选择) | (None) |
| SelectedCount | 已选 {0} 项 | {0} items selected |
| CollectionEditorTitle | 集合编辑器 | Collection Editor |
| CollectionItems | 集合项 | Collection Items |
| Properties | 属性 | Properties |
| Add | 添加 | Add |
| Copy | 复制 | Copy |
| Remove | 删除 | Remove |
| OK | 确定 | OK |
| Cancel | 取消 | Cancel |
| EditCollectionTitle | 编辑集合 | Edit Collection |
| InputPlaceholder | 输入新值... | Enter new value... |
| MoveUp | 上移 | Move Up |
| MoveDown | 下移 | Move Down |
| Tip | 提示 | Tip |
| Error | 错误 | Error |
| AddFailed | 添加失败:{0} | Add failed: {0} |
| CopyFailed | 复制失败:{0} | Copy failed: {0} |
| ConvertFailed | 转换失败:{0} | Convert failed: {0} |
| SelectItemToDelete | 请先选择要删除的项 | Please select an item to delete first |
| SelectItemToCopy | 请先选择要复制的项 | Please select an item to copy first |
| NoDefaultConstructor | 类型 {0} 没有无参构造函数 | Type {0} has no parameterless constructor |
| DictionaryEditorTitle | 字典编辑器 | Dictionary Editor |
| DictEntries | 字典条目 | Entries |
| DictKey | 键 (Key) | Key |
| DictValue | 值 (Value) | Value |
| DuplicateKey | 键 "{0}" 已存在,请使用唯一的键 | Key "{0}" already exists. Keys must be unique. |
API 参考
PropertyGrid 类
命名空间: PropertyGridLib
依赖属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
SelectedObject |
object |
null |
要编辑的目标对象 |
SearchText |
string |
"" |
搜索文本(自动过滤) |
ShowDescription |
bool |
true |
是否显示底部描述栏 |
ShowSearchBar |
bool |
true |
是否显示搜索栏 |
SelectedPropertyItem |
IPropertyItem |
null |
当前选中的属性项 |
GroupedCategories |
IEnumerable |
null |
分组后的分类列表(供绑定) |
IsLoading |
bool |
false |
是否正在加载(切换对象时显示动画) |
FormulaTreeProvider |
IFormulaTreeProvider |
null |
全局公式树提供者(可选) |
ShowTypeHint |
bool |
true |
是否显示 FormulaTree 类型提示和公式绑定区类型提示 |
静态成员
| 成员 | 类型 | 说明 |
|---|---|---|
GlobalFontFamily |
FontFamily |
全局字体(默认 Consolas),宿主可设置;PropertyGrid.FontFamily 变化时自动同步 |
GlobalFontSize |
double |
全局字号(默认 12),宿主可设置;PropertyGrid.FontSize 变化时自动同步 |
CustomEditors |
Dictionary<Type, DataTemplate> |
宿主自定义编辑器注册字典,AttributeType → DataTemplate |
RegisterEditor<TAttribute>(template) |
static void |
泛型注册方法,一行搞定宿主自定义编辑器 |
公共方法
| 方法 | 说明 |
|---|---|
RefreshProperties() |
刷新属性列表(重新反射对象) |
ResetSelectedToDefault() |
重置当前选中属性到初始值 |
ResetAllToDefault() |
重置所有属性到初始值 |
事件
| 事件 | 说明 |
|---|---|
ButtonClicked(实例) |
本网格内 [Button] 属性被点击时触发(参数 PropertyButtonClickEventArgs) |
GlobalButtonClicked(静态) |
任意 PropertyGrid 实例(含集合/字典内嵌网格、弹窗网格)的 [Button] 被点击时触发;宿主订阅一次即可全局响应,窗口关闭时应取消订阅 |
PropertyGrid 在内部监听 LocalizationManager.LanguageChanged 事件,语言切换时自动刷新所有属性的本地化文本。
集合/字典模式行为: 当
SelectedObject为IList(排除string)时内联显示集合编辑器;为IDictionary时内联显示字典编辑器;两者均自动隐藏正常属性列表。
ColorEditControl 类
命名空间: PropertyGridLib.Controls
颜色编辑器 UserControl,内嵌 HandyControl ColorPicker 弹窗。PropertyGrid 自动检测 Color / Color? / SolidColorBrush 类型,宿主无需手动使用此类。
| 成员 | 类型 | 说明 |
|---|---|---|
PART_ColorPreview |
Border |
颜色预览块(16×16),颜色随 ColorPicker.SelectedColor 同步 |
PART_ColorHexText |
TextBlock |
HEX 文本,显示 #RRGGBB 或 #AARRGGBB |
PART_EditButton |
Button |
点击弹出 ColorPicker 弹窗 |
PART_ColorPicker |
hc:ColorPicker |
弹窗内的 ColorPicker(StaysOpen=true,Confirmed/Canceled 事件处理) |
CollectionEditorControl 类
命名空间: PropertyGridLib.Controls
复杂对象列表编辑器 UserControl。左侧列表 + 右侧内嵌 PropertyGrid + 增删复制排序按钮。
| 依赖属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
Items |
IList |
null |
绑定的集合数据(直接操作源集合) |
SimpleCollectionEditorControl 类
命名空间: PropertyGridLib.Controls
简单类型列表编辑器 UserControl。列表 + 输入框 + 增删排序按钮。
| 依赖属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
Items |
IList |
null |
绑定的集合数据(直接操作源集合) |
ElementType |
Type |
typeof(string) |
元素类型(通常自动从集合泛型参数推断) |
DictionaryEditorControl 类
命名空间: PropertyGridLib.Controls
字典编辑器 UserControl。左侧条目列表 + 右侧键/值编辑区(键、值各自可为文本框或内嵌 PropertyGrid)。直接操作传入的 IDictionary 实例,增删改即时写回。
| 依赖属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
Items |
IDictionary |
null |
绑定的字典数据(直接操作源字典) |
弹窗版为
PropertyGridLib.Dialogs.DictionaryEditorDialog(构造传入IDictionary,ShowDialog()返回 true 表示写回)。
FormulaBindingControl 独立使用属性
命名空间: PropertyGridLib.Controls
除 PropertyGrid 内嵌用法外,新增以下依赖属性支持独立使用:
| 依赖属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
TreeItemsSource |
IEnumerable |
null |
直接设置公式树节点(FormulaTreeNode 集合),优先级最高 |
FormulaTreeProvider |
IFormulaTreeProvider |
null |
设置公式树提供者,优先级次之 |
FormulaString |
string |
"" |
公式字符串(支持 TwoWay 绑定) |
PropertyItem 类
命名空间: PropertyGridLib.Controls
| 属性 | 类型 | 说明 |
|---|---|---|
Name |
string |
属性名(代码中的名称) |
DisplayName |
string |
显示名称 |
Category |
string |
分类 |
Description |
string |
描述 |
Value |
object |
属性值(可读写) |
ValueString |
string |
属性值字符串表示 |
PropertyType |
Type |
属性类型 |
EffectivePropertyType |
Type |
有效类型(FormulaBound<T> 时返回 T;Color 类型时省略 Alpha 显示) |
IsReadOnly |
bool |
是否只读 |
IsBrowsable |
bool |
是否可浏览 |
IsExpandable |
bool |
是否可展开子属性 |
IsExpanded |
bool |
是否已展开 |
HasDefaultValue |
bool |
是否有默认值 |
IsDefault |
bool |
当前值是否等于初始值 |
IsModified |
bool |
是否已修改 |
CustomEditor |
CustomEditorType |
自定义编辑器类型(仅框架内置编辑器设置,宿主扩展为 null) |
CustomEditorTemplate |
DataTemplate? |
宿主注册的自定义编辑器模板,非 null 时 EditorTemplateSelector 优先使用 |
CustomEditorAttribute |
Attribute? |
宿主自定义编辑器对应的 Attribute 实例,模板 code-behind 可读取参数 |
FileFilter |
string |
文件过滤器 |
EnumValues |
Array |
枚举值列表 |
StandardValues |
List<object> |
下拉列表标准值 |
IsStandardValuesExclusive |
bool |
下拉列表是否排他 |
SliderMinimum/Maximum/Step |
double |
滑块参数 |
SliderShowNumberBox |
bool |
滑块旁是否显示可编辑数字框(取代数值标签) |
IsFormulaEnabled |
bool |
是否启用公式绑定 |
FormulaString |
string |
公式字符串 |
FormulaTreeProvider |
IFormulaTreeProvider |
公式树提供者 |
MultiSelectProvider |
IMultiSelectProvider |
多选列表提供者 |
ChildProperties |
List<PropertyItem> |
子属性列表 |
ResetCommand |
ICommand |
重置命令 |
BrowseFileCommand |
ICommand |
浏览文件命令 |
BrowseDirectoryCommand |
ICommand |
浏览目录命令 |
EditCollectionCommand |
ICommand |
编辑集合命令 |
ButtonText |
string |
按钮文本([Button];为空时回退到 DisplayName) |
ButtonClickCommand |
ICommand |
按钮点击命令([Button]) |
CustomEditorType 枚举
| 值 | 说明 |
|---|---|
None |
无自定义编辑器(按类型自动选择) |
FilePath |
文件路径选择器 |
DirectoryPath |
目录路径选择器 |
Collection |
集合编辑器 |
Expandable |
可展开对象 |
Slider |
数值滑块 |
DropDown |
TypeConverter 下拉列表 |
Formula |
公式绑定 |
MultiSelect |
多选列表 |
Button |
命令按钮([Button]) |
Dictionary |
字典编辑器(IDictionary) |
Color |
颜色编辑器(Color / SolidColorBrush) |
FormulaTreeNode 类
命名空间: PropertyGridLib.Controls
| 属性 | 类型 | 说明 |
|---|---|---|
Header |
string |
节点显示文本 |
Formula |
string |
选中后填入的公式字符串(仅叶子节点需要设置) |
Children |
List<FormulaTreeNode> |
子节点列表 |
FormulaType |
Type? |
叶子节点的数值类型(如 typeof(int)),用于 TreeView 弹窗显示类型提示 |
TypeHint |
string |
自动从 FormulaType 生成的类型提示(如 (int)、(string)),宿主也可直接覆盖 |
LocalizationManager 类
命名空间: PropertyGridLib.Localization
| 成员 | 类型 | 说明 |
|---|---|---|
CurrentLanguage |
Language |
当前语言(默认 ZhCN) |
LanguageChanged |
event EventHandler |
语言切换事件 |
GetString(key) |
string |
获取本地化字符串 |
GetString(key, args) |
string |
获取本地化字符串(带格式化参数) |
依赖项
| 依赖 | 版本 | 说明 |
|---|---|---|
| .NET Framework | 4.8+ | 支持的目标框架之一 |
| .NET | 6.0/7.0/8.0-windows | 支持的现代框架 |
| HandyControl | 3.5.1 | 官方 HandyControl UI 控件库 |
| System.Text.Json | 6.0.0+ | 仅 .NET Framework 4.8 需要(.NET 6+ 内置) |
版本兼容性:
- System.Text.Json 向上兼容到 16.0+
- 你的项目可以使用更高版本的 System.Text.Json,不会产生冲突
- .NET 6/7/8 使用内置版本,无需额外引用
更新日志
v1.2.0(2026-09-07)
✨ 新增功能
- 🔌 宿主自定义编辑器扩展机制:
PropertyGrid.CustomEditors静态字典 +RegisterEditor<TAttr>(template)泛型方法;宿主只需定义 Attribute + DataTemplate + 一行注册,PropertyGrid 即可渲染自定义编辑器;框架对此 Attribute 一无所知。新增PropertyItem.CustomEditorTemplate/CustomEditorAttribute属性,EditorTemplateSelector优先返回宿主模板,宿主甚至可覆盖框架内置编辑器 - 🎨 颜色编辑器:自动识别
Color/Color?/SolidColorBrush类型(内置IsColorType检测在CanExpand之前拦截,防止 Color struct 被误判为可展开对象);显示为颜色预览块(16×16 +#RRGGBBHEX 文本)+ "..." 按钮(紧凑居左,与 FilePath/Dictionary 编辑器风格统一);点击弹出 HandyControlColorPicker(StaysOpen=true+ 手动Window.PreviewMouseDown+IsInVisualTree外部点击检测,支持拖动 Slider 不丢失焦点;"确定"才写入值并关闭,"取消"不改值);HEX 显示:不透明时省略 Alpha 前缀 - 📁 字典嵌套字典/列表自动弹窗:字典值为集合(
IDictionary/IList)时不再内联显示内嵌 PropertyGrid,改为显示 "N 项 + ..." 按钮,点击弹出二级 DictionaryEditorDialog / SimpleCollectionEditorDialog(模态覆盖父级,UI 不会无限拉长);字典值为复杂对象时仍内嵌 PropertyGrid 就地编辑(三种模式互斥) - 🌳 FormulaTree 类型提示:
FormulaTreeNode新增FormulaType/TypeHint属性,TreeView 弹窗叶子节点右侧显示淡蓝色斜体类型名(如(int)、(DateTime));PropertyGrid 新增ShowTypeHint依赖属性(默认 true)控制是否显示;公式绑定区同样显示类型提示 - 🔤 全局字体动态同步:
PropertyGrid.FontSize/FontFamily通过DependencyPropertyDescriptor实时监听,变化时自动更新GlobalFontSize/GlobalFontFamily;所有 Dialog(字典、集合、文件夹、颜色)打开时读取最新全局值,FontSize/FontFamily 跟随宿主变化 - 📁 FolderBrowserDialog 主题修复:所有文字控件显式指定
Foreground="{DynamicResource PrimaryTextBrush}"+Background="{DynamicResource RegionBrush}",深色主题下不再出现白字黑底问题;TreeView/TreeViewItem 选中/悬停触发器适配深浅色主题 - 🎨 ColorPicker 弹窗图标适配:FolderBrowserDialog 文件夹图标从 emoji 改为 Material Design 风格 Path 图标(
Fill="{DynamicResource PrimaryBrush}"),选中时Foreground自动变白
🐛 缺陷修复
- HandyControl 主题切换失效:原
SetAppSkin用is HandyControl.Themes.Theme匹配不到 App.xaml 里通过 URI 加载的 Theme.xaml(那是 ResourceDictionary,不是 Theme 实例),导致替换 MergedDictionaries 时 idx=-1 每次 Add 新 Theme 到末尾,旧的还留着,第二次切换资源冲突——现改为直接改Theme.Skin属性(HC 官方推荐方式),白→黑→白双向切换均正常 - 颜色编辑器选完值不生效:原 XAML 双向绑定
ValueString用ColorToBrushConverter,但 WPF 调ConvertBack时targetType=object(因为PropertyItem.Value是 object),类型检查全挂;现改 code-behind,在Confirmed事件里根据EffectivePropertyType做正确类型转换(Color/SolidColorBrush/Color?) - ColorPicker 弹窗 Slider 拖动时消失:
StaysOpen=false时 HC ColorPicker 内部 Slider 的 MouseCapture 状态变化导致 Popup 误判"鼠标在外部"——现改为StaysOpen=true+ 手动Window.PreviewMouseDown+IsInVisualTree检测外部点击
📦 新增公共类型
PropertyGridLib.Controls.ColorEditControl— 颜色编辑器 UserControl(弹出 HC ColorPicker)PropertyGridLib.Controls.ColorToBrushConverter— Color ↔ SolidColorBrush 双向转换器PropertyGridLib.Controls.FormulaTreeNode.FormulaType/TypeHint— FormulaTree 类型提示
🔄 框架侧删除
- ❌
PropertyGridLib.Attributes.UnitEditorAttribute(框架内置的单位编辑器特性) - ❌
CustomEditorType.Unit枚举值 - ❌
PropertyItem.UnitOptions/SelectedUnit/UnitDisplayValue属性 - ❌ Generic.xaml 里的 UnitTemplate
- 原因:宿主扩展机制
RegisterEditor<TAttr>(template)可完全替代,框架内置编辑器与宿主扩展功能重复
🔄 向后兼容性
- ✅ 现有公共 API 全部保留(新增成员不影响旧代码)
- ⚠️
PropertyGrid.FontSize/FontFamily现在会自动同步到全局值(之前只在 OnApplyTemplate 同步一次),若宿主依赖"全局值独立于实例值"的行为需注意 - ⚠️ 字典编辑器嵌套值的渲染模式变化:集合类型值从"内嵌 PropertyGrid"改为"弹窗",UI 更紧凑但编辑交互从就地改为弹窗;复杂对象值仍保持内嵌模式
v1.1.2(2026-09-04)
✨ 新增功能
- 命令按钮编辑器
[Button]:属性标记[Button("文本", nameof(方法))]后渲染为按钮;点击时①反射调用宿主对象上的Action方法(支持无参或单参),②触发PropertyItem.ButtonClicked与PropertyGrid.ButtonClicked事件;Text为空时回退到DisplayName(随多语言切换)。按钮默认始终可点,仅显式[ReadOnly(true)]时禁用 - 全局按钮事件
PropertyGrid.GlobalButtonClicked(静态):任何 PropertyGrid 实例(包括集合/字典编辑器内嵌的网格、弹窗内的网格)触发[Button]都会引发,宿主订阅一次即可跨窗口/跨层级统一响应 - 字典编辑器(IDictionary):自动识别
IDictionary/IDictionary<,>属性(无需特性,[CollectionEditor]标在字典上也会路由到字典编辑器);提供弹窗DictionaryEditorDialog与内联DictionaryEditorControl(当SelectedObject直接是字典时自动内联);键、值各自支持简单类型(文本框 + 类型转换)或复杂对象(内嵌 PropertyGrid,可继续向下嵌套集合/字典);键值均为对象时左右各占 50%;支持增/删/改、重复键校验、实时写回原字典 - 多框架:新增 .NET Core 3.0 / 3.1 目标(实际目标:net48 / netcoreapp3.0 / netcoreapp3.1 / net6.0-windows / net7.0-windows / net8.0-windows)
🐛 缺陷修复
- 字典 "..." 按钮无响应:此前字典被
ICollection识别为集合,但EditCollection仅处理IList,导致点击编辑按钮无反应——现按IDictionary路由到字典编辑器 - 嵌套网格中按钮失效:集合/字典编辑器内嵌 PropertyGrid 的按钮点击无法上抵宿主、且 Action 修改不刷新——现通过全局事件 + 点击后网格自刷新解决
- 字典编辑器描述栏白条:内嵌网格
ShowDescription不一致导致底部描述栏折叠后露出 80px 白色空行——现键/值网格统一显示描述栏
📦 新增公共类型
PropertyGridLib.Attributes.ButtonAttributePropertyGridLib.Controls.PropertyButtonClickEventArgsPropertyGridLib.Controls.DictionaryEditorControl(内联字典编辑器 UserControl,Items为IDictionary)PropertyGridLib.Dialogs.DictionaryEditorDialogCustomEditorType新增枚举值:Button、Dictionary
🔄 向后兼容性
- ✅ 现有公共 API 与行为全部保留,仅新增成员
- ⚠️
IPropertyItem接口新增了ButtonText/ButtonClickCommand/ButtonClicked(库内仅PropertyItem实现该接口;若宿主自行实现过该接口,需补充这几个成员) - ⚠️ .NET Core 3.0/3.1 目标仍为 Windows 专用(WPF 不支持 Linux/macOS);因其 TFM 无
-windows后缀,跨平台项目误装后运行时才会报错,请注意
v1.1.1(2026-09-01)
✨ 新增功能
- PropertyGrid 集合模式(内联显示):当
SelectedObject为IList类型时,自动切换为集合编辑模式,内联显示编辑器(无需弹出对话框) - 集合编辑器 UserControl 化:
CollectionEditorControl(复杂对象列表)和SimpleCollectionEditorControl(简单类型列表)可作为独立 UserControl 嵌入任意容器 - FormulaBindingControl 独立使用:新增
TreeItemsSource/FormulaTreeProvider依赖属性,支持脱离 PropertyGrid 直接设置公式树数据源
🔧 技术改进
- PropertyGrid 模板新增
PART_CollectionEditorHost/PART_NormalHost双区域切换机制 - 集合元素类型自动检测:简单类型(primitives、enum、string、decimal、DateTime、TimeSpan、Guid)→ SimpleCollectionEditor,复杂类型 → CollectionEditor
FormulaBindingControl.LoadTreeItems()三级优先级:TreeItemsSource → FormulaTreeProvider → PropertyItem DataContext- 集合编辑器控件直接操作源
IList实例,每次操作后立即同步
📦 新增公共类型
PropertyGridLib.Controls.CollectionEditorControl— 复杂对象列表编辑器 UserControlPropertyGridLib.Controls.SimpleCollectionEditorControl— 简单类型列表编辑器 UserControl
🔄 向后兼容性
- ✅ 所有现有公共 API 保持不变
- ✅ PropertyGrid 正常对象绑定行为不受影响
- ✅ FormulaBindingControl 在 PropertyGrid 内嵌用法不受影响
许可证
MIT License
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net6.0-windows7.0 is compatible. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net7.0-windows7.0 is compatible. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net8.0-windows7.0 is compatible. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp3.0 is compatible. netcoreapp3.1 is compatible. |
| .NET Framework | net48 is compatible. net481 was computed. |
-
.NETCoreApp 3.0
- HandyControl (>= 3.5.1)
-
.NETCoreApp 3.1
- HandyControl (>= 3.5.1)
-
.NETFramework 4.8
- HandyControl (>= 3.5.1)
- System.Text.Json (>= 6.0.0)
-
net6.0-windows7.0
- HandyControl (>= 3.5.1)
-
net7.0-windows7.0
- HandyControl (>= 3.5.1)
-
net8.0-windows7.0
- HandyControl (>= 3.5.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.