写Form设计器尝试(二) PropertyGrid

如果要修改这个设计时Form的其它属性该怎么办呢?接下来的更简单了。
切换到
Form1的设计界面,往上面放一个SplitContainer, 再在这个SplitContainer的右panel上放一个PropertyGrid, 并将其Dock属性值设置成Fill
切换到代码编辑界面,将上篇中输入的代码的最后一行改成:

this .splitContainer1.Panel1.Controls.Add(view);
再加上一行:
this .propertyGrid1.SelectedObject  =  surface.ComponentContainer.Components[ 0 ];

运行项目,现在就可以编辑 那个设计时 Form 的其它属性了。


除了一点点拖放操作之外,只写了六行代码,就实现这样的功能,有意思吧?

我的这个专题的目标是写一个能用的窗体设计器,有什么用呢?比如您的程序发布后,用户觉得某个控件的位置需要调整,某个控件的字体颜色需要修改,没关系,用户自行修改就是了。更复杂的一点的应用是用户希望在某个单据中增加一个字段,没关系,用户或者实施人员自已加就是了,不要改代码,不要重新编译。甚至用户想增加一些处理,也可以由实施人员现场在设计器中写代码,系统能将其编译好,并在运行时调用。

相关章节:
Form设计器尝试()

下节预告: Form 设计器尝试 ( ) 在窗体上添加控件。
<think>我们使用PropertyGrid来显示和编辑字典数据,但是PropertyGrid通常用于对象的属性。因此,我们需要将字典转换为一个具有属性的对象。 步骤: 1. 创建一个类,例如`IniSection`,它代表一个INI节。 2. 为字典中的每个键值对,在类中创建对应的属性。但是,由于键是动态的,我们可以通过自定义TypeConverter或使用动态对象来实现,但这样比较复杂。 3. 另一种方法是使用一个自定义的`CustomTypeDescriptor`来动态生成属性,但这需要较多代码。 但是,考虑到您已经有一个固定的字典(虽然键是固定的,但每个节可能不同),我们可以为每个节创建一个类,但这样不够通用。 这里我们采用一个折中的方法:创建一个动态对象包装器,将字典包装成一个对象,然后通过PropertyGrid显示。但是PropertyGrid不支持动态对象(ExpandoObject)的显示。 因此,我们采用另一种方式:创建一个通用的类,它包含一个字典,并通过TypeConverter和自定义属性描述器来使PropertyGrid能够显示字典的键值对。 不过,有一个更简单的方法:使用`PropertyGrid`的内置功能,但需要将字典转换为一个属性集合。我们可以创建一个类,该类具有一个属性返回`PropertySpec`的集合。 这里我们使用`PropertySpec`和`PropertyBag`的方法,这是.NET WinForms中一个常见的技术。 但是,由于这个实现相对复杂,我们可以使用现成的组件。不过,如果不想使用第三方组件,我们可以自己实现一个简单的属性包。 参考方案:使用`PropertyBag`(自定义类)和`PropertySpec`(定义属性)。 由于时间关系,我提供一个使用`PropertyBag`的示例,这个类在MSDN上有示例代码。我们将使用一个现成的`PropertyBag`实现。 但是,请注意,这个实现需要一些辅助类。这里我将提供一个简化的版本。 步骤: 1. 定义`PropertySpec`类(用于定义属性的元数据) 2. 定义`PropertyBag`类(继承自`CustomTypeDescriptor`,并实现`ICustomTypeDescriptor`) 3. 将字典转换为`PropertyBag`,并为每个键值对添加一个`PropertySpec`。 由于代码较长,我将只展示关键部分。 但是,考虑到复杂度,如果您的字典结构是固定的,我建议直接创建一个类来表示这个节,这样更简单。 例如,对于ST04软管检测,我们可以创建一个类: ```csharp public class ST04软管检测 { [Category("参数"), Description("曝光1")] public int 曝光1 { get; set; } // ... 其他属性 } ``` 但是,这样不够灵活,因为每增加一个节就需要创建一个类。 因此,我采用动态生成属性的方法。这里我使用一个开源的`PropertyBag`实现(简化版)。 下面是一个完整的示例: ### 1. 定义PropertySpec ```csharp public class PropertySpec { public string Name { get; set; } public Type Type { get; set; } public string Category { get; set; } public string Description { get; set; } public object DefaultValue { get; set; } public object Value { get; set; } } ``` ### 2. 定义PropertyBag ```csharp // 由于代码较长,这里只给出关键部分,完整实现可参考MSDN示例 public class PropertyBag : ICustomTypeDescriptor { private ArrayList properties = new ArrayList(); public void AddProperty(PropertySpec property) { properties.Add(property); } // 实现ICustomTypeDescriptor接口的所有方法... // 这里省略了大部分接口方法的实现,只展示关键部分 public PropertyDescriptorCollection GetProperties() { return GetProperties(new Attribute[0]); } public PropertyDescriptorCollection GetProperties(Attribute[] attributes) { ArrayList props = new ArrayList(); foreach (PropertySpec property in properties) { List<Attribute> attrs = new List<Attribute>(); if (!string.IsNullOrEmpty(property.Category)) attrs.Add(new CategoryAttribute(property.Category)); if (!string.IsNullOrEmpty(property.Description)) attrs.Add(new DescriptionAttribute(property.Description)); props.Add(new PropertySpecDescriptor(property, attrs.ToArray())); } PropertyDescriptor[] propArray = (PropertyDescriptor[])props.ToArray(typeof(PropertyDescriptor)); return new PropertyDescriptorCollection(propArray); } // 其他接口方法留空或返回null // ... } // 自定义属性描述符 public class PropertySpecDescriptor : PropertyDescriptor { private PropertySpec property; public PropertySpecDescriptor(PropertySpec property, Attribute[] attrs) : base(property.Name, attrs) { this.property = property; } public override Type ComponentType => typeof(PropertyBag); public override bool IsReadOnly => false; public override Type PropertyType => property.Type; public override bool CanResetValue(object component) => false; public override object GetValue(object component) { return property.Value; } public override void ResetValue(object component) { } public override void SetValue(object component, object value) { property.Value = value; } public override bool ShouldSerializeValue(object component) { return false; } } ``` ### 3. 在窗体中使用 ```csharp public partial class MainForm : Form { public MainForm() { InitializeComponent(); LoadDataToPropertyGrid(); } private void LoadDataToPropertyGrid() { // 创建字典(使用您提供的数据) var st04 = new Dictionary<string, string> { {"曝光1", "1500"}, // ... 其他数据 }; // 创建PropertyBag PropertyBag bag = new PropertyBag(); // 将字典的每个键值对转换为PropertySpec foreach (var kvp in st04) { // 尝试推断类型 Type type = typeof(string); if (int.TryParse(kvp.Value, out int i)) type = typeof(int); else if (double.TryParse(kvp.Value, out double d)) type = typeof(double); bag.AddProperty(new PropertySpec { Name = kvp.Key, Value = Convert.ChangeType(kvp.Value, type), Type = type, Category = "ST04软管检测" }); } // 绑定到PropertyGrid propertyGrid1.SelectedObject = bag; } } ``` ### 注意 - 上述`PropertyBag`的实现是不完整的,需要实现`ICustomTypeDescriptor`的所有方法。这里为了简洁,只实现了必要的部分。在实际使用中,您可能需要参考完整的实现(例如,搜索“PropertyBag for PropertyGrid”)。 - 类型推断:我们尝试将字符串转换为int或double,如果失败则保留为字符串。 ### 更简单的方案:使用固定类 如果您的节是固定的,那么为每个节创建一个类会更简单: ```csharp [TypeConverter(typeof(ExpandableObjectConverter))] public class ST04软管检测 { [Category("曝光参数"), Description("曝光1")] public int 曝光1 { get; set; } = 1500; [Category("曝光参数"), Description("曝光2")] public int 曝光2 { get; set; } = 1500; // ... 其他属性 } // 在窗体中 propertyGrid1.SelectedObject = new ST04软管检测(); ``` ### 结论 对于动态字典,使用`PropertyBag`是可行的,但需要一些额外的工作。如果数据结构是固定的,建议使用固定类,这样更简单且易于维护。 §§相关问题§§ 1. 如何在PropertyGrid中实现动态添加或删除属性? 2. 如何为PropertyGrid中的属性添加下拉列表选择功能? 3. 如何将PropertyGrid的修改保存回原始字典? 4. 如何根据属性名称或类别对PropertyGrid中的属性进行分组和排序? 5. 如何为数值型属性在PropertyGrid中设置取值范围(如0-100)?</think>### 使用PropertyGrid显示和编辑字典数据的完整方案 要使用PropertyGrid控件显示您提供的字典数据,需要创建一个自定义类来包装字典键值对。以下是完整实现方案: ```csharp using System; using System.Collections.Generic; using System.ComponentModel; using System.Windows.Forms; namespace INI_Editor { public partial class MainForm : Form { public MainForm() { InitializeComponent(); LoadDataToPropertyGrid(); } private void LoadDataToPropertyGrid() { // 创建ST04软管检测数据 var st04 = new Dictionary<string, string> { {"曝光1", "1500"}, {"曝光2", "1500"}, {"增益", "0"}, {"间隙上限", "7"}, {"间隙下限", "3"}, {"上褶皱上限", "5.5"}, {"上褶皱下限", "3"}, {"下褶皱上限", "6.6"}, {"下褶皱下限", "4"}, {"备用一上限", "0"}, {"备用一下限", "0"}, {"导管有无上限", "130"}, {"导管有无下限", "100"}, {"金属锲有无上限", "200"}, {"金属锲有无下限", "170"}, {"备用上限", "0"}, {"备用下限", "0"} }; // 创建包装对象并绑定到PropertyGrid var config = new INIConfig(st04, "ST04软管检测"); propertyGrid1.SelectedObject = config; } // 保存按钮件 private void btnSave_Click(object sender, EventArgs e) { var config = (INIConfig)propertyGrid1.SelectedObject; var modifiedData = config.GetModifiedData(); // 这里可以保存到文件或数据库 MessageBox.Show($"已保存 {modifiedData.Count} 个修改的参数"); } } // 自定义配置类(支持PropertyGrid显示) [TypeConverter(typeof(ExpandableObjectConverter))] public class INIConfig { private readonly Dictionary<string, string> _originalData; private readonly Dictionary<string, string> _currentData; private readonly string _sectionName; public INIConfig(Dictionary<string, string> data, string sectionName) { _originalData = new Dictionary<string, string>(data); _currentData = new Dictionary<string, string>(data); _sectionName = sectionName; } // 获取修改后的数据 public Dictionary<string, string> GetModifiedData() { var modified = new Dictionary<string, string>(); foreach (var key in _currentData.Keys) { if (_currentData[key] != _originalData[key]) { modified.Add(key, _currentData[key]); } } return modified; } // 属性分组:曝光参数 [Category("1. 曝光参数")] [Description("相机曝光时间1 (单位: ms)")] public string 曝光1 { get => _currentData["曝光1"]; set => _currentData["曝光1"] = value; } [Category("1. 曝光参数")] [Description("相机曝光时间2 (单位: ms)")] public string 曝光2 { get => _currentData["曝光2"]; set => _currentData["曝光2"] = value; } [Category("1. 曝光参数")] [Description("图像增益值 (0-100)")] public string 增益 { get => _currentData["增益"]; set => _currentData["增益"] = value; } // 属性分组:间隙检测 [Category("2. 间隙检测")] [Description("允许的最大间隙尺寸 (单位: mm)")] public string 间隙上限 { get => _currentData["间隙上限"]; set => _currentData["间隙上限"] = value; } [Category("2. 间隙检测")] [Description("允许的最小间隙尺寸 (单位: mm)")] public string 间隙下限 { get => _currentData["间隙下限"]; set => _currentData["间隙下限"] = value; } // 属性分组:褶皱检测 [Category("3. 褶皱检测")] [Description("上表面允许的最大褶皱尺寸 (单位: mm)")] public string 上褶皱上限 { get => _currentData["上褶皱上限"]; set => _currentData["上褶皱上限"] = value; } [Category("3. 褶皱检测")] [Description("上表面允许的最小褶皱尺寸 (单位: mm)")] public string 上褶皱下限 { get => _currentData["上褶皱下限"]; set => _currentData["上褶皱下限"] = value; } [Category("3. 褶皱检测")] [Description("下表面允许的最大褶皱尺寸 (单位: mm)")] public string 下褶皱上限 { get => _currentData["下褶皱上限"]; set => _currentData["下褶皱上限"] = value; } [Category("3. 褶皱检测")] [Description("下表面允许的最小褶皱尺寸 (单位: mm)")] public string 下褶皱下限 { get => _currentData["下褶皱下限"]; set => _currentData["下褶皱下限"] = value; } // 属性分组:导管检测 [Category("4. 导管检测")] [Description("导管存在性检测上限值")] public string 导管有无上限 { get => _currentData["导管有无上限"]; set => _currentData["导管有无上限"] = value; } [Category("4. 导管检测")] [Description("导管存在性检测下限值")] public string 导管有无下限 { get => _currentData["导管有无下限"]; set => _currentData["导管有无下限"] = value; } // 属性分组:金属锲检测 [Category("5. 金属锲检测")] [Description("金属锲存在性检测上限值")] public string 金属锲有无上限 { get => _currentData["金属锲有无上限"]; set => _currentData["金属锲有无上限"] = value; } [Category("5. 金属锲检测")] [Description("金属锲存在性检测下限值")] public string 金属锲有无下限 { get => _currentData["金属锲有无下限"]; set => _currentData["金属锲有无下限"] = value; } // 备用参数组 [Category("9. 备用参数")] [Description("备用参数组1上限")] public string 备用一上限 { get => _currentData["备用一上限"]; set => _currentData["备用一上限"] = value; } [Category("9. 备用参数")] [Description("备用参数组1下限")] public string 备用一下限 { get => _currentData["备用一下限"]; set => _currentData["备用一下限"] = value; } [Category("9. 备用参数")] [Description("备用参数组2上限")] public string 备用上限 { get => _currentData["备用上限"]; set => _currentData["备用上限"] = value; } [Category("9. 备用参数")] [Description("备用参数组2下限")] public string 备用下限 { get => _currentData["备用下限"]; set => _currentData["备用下限"] = value; } // 显示节名(只读属性) [Category("配置信息")] [Description("当前配置节名称")] [ReadOnly(true)] public string 节名称 => _sectionName; // 显示参数数量(只读属性) [Category("配置信息")] [Description("当前参数总数")] [ReadOnly(true)] public int 参数数量 => _currentData.Count; } } ``` ### 窗体设计(Designer.cs部分) ```csharp partial class MainForm { private PropertyGrid propertyGrid1; private Button btnSave; private Button btnReset; private void InitializeComponent() { // PropertyGrid控件 propertyGrid1 = new PropertyGrid(); propertyGrid1.Location = new System.Drawing.Point(10, 10); propertyGrid1.Size = new System.Drawing.Size(600, 500); propertyGrid1.HelpVisible = true; // 显示描述信息 propertyGrid1.ToolbarVisible = true; // 显示工具栏 propertyGrid1.PropertySort = PropertySort.Categorized; // 按分类排序 // 保存按钮 btnSave = new Button(); btnSave.Text = "保存修改"; btnSave.Location = new System.Drawing.Point(400, 520); btnSave.Size = new System.Drawing.Size(100, 30); btnSave.Click += btnSave_Click; // 重置按钮 btnReset = new Button(); btnReset.Text = "重置修改"; btnReset.Location = new System.Drawing.Point(280, 520); btnReset.Size = new System.Drawing.Size(100, 30); btnReset.Click += (s, e) => LoadDataToPropertyGrid(); // 添加控件到窗体 Controls.Add(propertyGrid1); Controls.Add(btnSave); Controls.Add(btnReset); // 窗体设置 Text = "INI参数编辑器 - PropertyGrid版"; Size = new System.Drawing.Size(630, 580); } } ``` ### 使用说明 1. **界面特点**: - 左侧属性树:按分类组织的参数列表 - 右侧属性值:可直接编辑的参数值 - 底部描述区:显示参数的详细说明 - 工具栏按钮:支持按字母或分类排序 2. **功能特点**: - 参数按功能分组显示(曝光参数、间隙检测等) - 每个参数都有详细描述说明 - 只读属性显示配置节信息 - 支持保存和重置功能 - 自动检测修改的参数 3. **操作方式**: - 直接在属性网格中修改参数值 - 点击"保存修改"按钮保存变更 - 点击"重置修改"按钮恢复原始值 - 使用工具栏按钮切换排序方式 ### 高级优化建议 1. **添加类型转换器**(支持数值范围验证): ```csharp // 在属性上添加类型转换器 [TypeConverter(typeof(NumericRangeConverter))] public string 增益 { ... } // 自定义类型转换器 public class NumericRangeConverter : TypeConverter { public override bool IsValid(ITypeDescriptorContext context, object value) { if (int.TryParse(value.ToString(), out int num)) { return num >= 0 && num <= 100; } return false; } } ``` 2. **添加下拉选择框**(针对特定参数): ```csharp [TypeConverter(typeof(GainOptionsConverter))] public string 增益 { ... } // 自定义选项转换器 public class GainOptionsConverter : StringConverter { public override bool GetStandardValuesSupported(ITypeDescriptorContext context) => true; public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) { return new StandardValuesCollection(new[] { "0", "25", "50", "75", "100" }); } } ``` 3. **添加自定义编辑器**(针对布尔值参数): ```csharp [Editor(typeof(BooleanEditor), typeof(UITypeEditor))] public string 导管有无 { ... } // 自定义布尔编辑器 public class BooleanEditor : UITypeEditor { public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) => UITypeEditorEditStyle.DropDown; public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) { // 实现下拉框编辑 } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值