目录
PropertyGrid 控件简介
创建 PropertyGrid 控件
何处使用 PropertyGrid 控件
选择对象
自定义 PropertyGrid 控件
显示复杂属性
为属性提供自定义 UI
PropertyGrid 控件简介
如果您使用过 Microsoft? Visual Basic? 或 Microsoft Visual Studio .NET,那么您一定使用过属性浏览器来浏览、查看和编辑一个或多个对象的属性。.NET 框架 PropertyGrid 控件是 Visual Studio .NET 属性浏览器的核心。PropertyGrid 控件显示对象或类型的属性,并主要通过使用反射来检索项目的属性。(反射是在运行时提供类型信息的技术。)
下面的屏幕快照显示了 PropertyGrid 在窗体上的外观。
PropertyGrid 包含以下部分:
创建 PropertyGrid 控件
要使用 Visual Studio .NET 创建 PropertyGrid 控件,需要将该控件添加到工具箱中,因为默认情况下并不包含该控件。在 Tools (工具)菜单中,选择 Customize Toolbox (自定义工具箱)。在对话框中选择 Framework Components (框架组件)选项卡,然后选择 PropertyGrid 。
如果您从命令行编译代码,请使用 /reference 选项并指定 System.Windows.Forms.dll。
以下代码显示了如何创建 PropertyGrid 控件并将其添加到窗体中。
using System; using System.Drawing; using System.ComponentModel; using System.Windows.Forms; using System.Globalization; public class OptionsDialog : System.Windows.Forms.Form { private System.Windows.Forms.PropertyGrid OptionsPropertyGrid; public OptionsDialog() { OptionsPropertyGrid = new PropertyGrid(); OptionsPropertyGrid.Size = new Size(300, 250); this.Controls.Add(OptionsPropertyGrid); this.Text = "选项对话框"; } [STAThread] static void Main() { Application.Run(new OptionsDialog()); } } using System; using System.Drawing; using System.ComponentModel; using System.Windows.Forms; using System.Globalization; public class OptionsDialog : System.Windows.Forms.Form { private System.Windows.Forms.PropertyGrid OptionsPropertyGrid; public OptionsDialog() { OptionsPropertyGrid = new PropertyGrid(); OptionsPropertyGrid.Size = new Size(300, 250); this.Controls.Add(OptionsPropertyGrid); this.Text = "选项对话框"; } [STAThread] static void Main() { Application.Run(new OptionsDialog()); } }
何处使用 PropertyGrid 控件
在应用程序中的很多地方,您都可以使用户与 PropertyGrid 进行交互,从而获得更丰富的编辑体验。例如,某个应用程序包含多个用户可以设置的“设置”或选项,其中一些可能十分复杂。您可以使用单选按钮、组合框或文本框来表示这些选项。但本文将逐步介绍如何使用 PropertyGrid 控件创建选项窗口来设置应用程序选项。上面所创建的 OptionsDialog 窗体即是选项窗口的开始。现在,我们创建一个名为 AppSettings 的类,其中包含映射到应用程序设置的所有属性。如果创建单独的类而不使用多个分散的变量,设置将更便于管理和维护。
public class AppSettings{ private bool saveOnClose = true; private string greetingText = "欢迎使用应用程序!"; private int itemsInMRU = 4; private int maxRepeatRate = 10; private bool settingsChanged = false; private string appVersion = "1.0"; public bool SaveOnClose { get { return saveOnClose; } set { saveOnClose = value;} } public string GreetingText { get { return greetingText; } set { greetingText = value; } } public int MaxRepeatRate { get { return maxRepeatRate; } set { maxRepeatRate = value; } } public int ItemsInMRUList { get { return itemsInMRU; } set { itemsInMRU = value; } } public bool SettingsChanged { get { return settingsChanged; } set { settingsChanged = value; } } public string AppVersion { get { return appVersion; } set { appVersion = value; } } }
选项窗口上的 PropertyGrid 将使用此类,因此请将类定义添加到应用程序项目中,在添加时可创建新文件或将其添加到现有窗体源代码的下方。
选择对象
要标识 PropertyGrid 显示的内容,请将 PropertyGrid.SelectedObject 属性设置为一个对象实例。然后,PropertyGrid 将完成其余的工作。每次设置 SelectedObject 时,PropertyGrid 都会刷新显示的属性。这提供了一种简单的方法来强制刷新属性,或在运行时切换对象。您还可以调用 PropertyGrid.Refresh 方法来刷新属性。
接下来,您需要更新 OptionsDialog 构造函数中的代码,以创建一个 AppSettings 对象,并将其设置为 PropertyGrid.SelectedObject 属性的值。
public OptionsDialog() { OptionsPropertyGrid = new PropertyGrid(); OptionsPropertyGrid.Size = new Size(300, 250); this.Controls.Add(OptionsPropertyGrid); this.Text = "选项对话框"; // 创建 AppSettings 类并在 PropertyGrid 中显示该类。 AppSettings appset = new AppSettings(); OptionsPropertyGrid.SelectedObject = appset; } public OptionsDialog() { OptionsPropertyGrid = new PropertyGrid(); OptionsPropertyGrid.Size = new Size(300, 250); this.Controls.Add(OptionsPropertyGrid); this.Text = "选项对话框"; // 创建 AppSettings 类并在 PropertyGrid 中显示该类。 AppSettings appset = new AppSettings(); OptionsPropertyGrid.SelectedObject = appset; }
编译并运行该应用程序。下面的屏幕快照显示了应用程序的外观。
图 2:PropertyGrid 中选定的 AppSettings 类
自定义 PropertyGrid 控件
您可以修改 PropertyGrid 的某些外观特征以满足自己的需要。可以更改某些属性的显示方式,甚至选择不显示某些属性。那么,如何对 PropertyGrid 进行自定义呢?
更改 PropertyGrid 的外观特征
PropertyGrid 的许多外观特征都可以自定义。下面列出了其中的一部分:
本示例中的选项窗口不需要工具栏,因此可以将 ToolbarVisible 设置为 false 。其余属性均保留默认设置。
更改属性的显示方式
要更改某些属性的显示方式,您可以对这些属性应用不同的特性。特性是用于为类型、字段、方法和属性等编程元素添加批注的声明标记,在运行时可以使用反射对其进行检索。下面列出了其中的一部分:
现在,我们将其中的一些特性应用于 AppSettings 类,以更改属性在 PropertyGrid 中的显示方式。
[DefaultPropertyAttribute("SaveOnClose")] public class AppSettings{ private bool saveOnClose = true; private string greetingText = "欢迎使用应用程序!"; private int maxRepeatRate = 10; private int itemsInMRU = 4; private bool settingsChanged = false; private string appVersion = "1.0"; [CategoryAttribute("文档设置"), DefaultValueAttribute(true)] public bool SaveOnClose { get { return saveOnClose; } set { saveOnClose = value;} } [CategoryAttribute("全局设置"), ReadOnlyAttribute(true), DefaultValueAttribute("欢迎使用应用程序!")] public string GreetingText { get { return greetingText; } set { greetingText = value; } } [CategoryAttribute("全局设置"), DefaultValueAttribute(4)] public int ItemsInMRUList { get { return itemsInMRU; } set { itemsInMRU = value; } } [DescriptionAttribute("以毫秒表示的文本重复率。"), CategoryAttribute("全局设置"), DefaultValueAttribute(10)] public int MaxRepeatRate { get { return maxRepeatRate; } set { maxRepeatRate = value; } } [BrowsableAttribute(false), DefaultValueAttribute(false)] public bool SettingsChanged { get { return settingsChanged; } set { settingsChanged = value; } } [CategoryAttribute("版本"), DefaultValueAttribute("1.0"), ReadOnlyAttribute(true)] public string AppVersion { get { return appVersion; } set { appVersion = value; } } }
将这些特性应用于 AppSettings 类后,编译并运行该应用程序。下面的屏幕快照显示了应用程序的外观。
图 3:PropertyGrid 中显示的带有类别和默认值的属性
使用此版本的选项窗口后,您会注意到以下几点:
显示复杂属性
到现在为止,选项窗口显示的都是简单的类型,如整数、布尔值和字符串。那么,如何显示更复杂的类型呢?如果应用程序需要跟踪窗口大小、文档字体或工具栏颜色等信息,该如何处理呢?.NET 框架提供的某些数据类型具有特殊的显示功能,能使这些类型在 PropertyGrid 中更具可用性。
对所提供类型的支持
首先,请更新 AppSettings 类,为窗口大小(Size 类型)、窗口字体(Font 类型)和工具栏颜色(Color 类型)添加新属性。
[DefaultPropertyAttribute("SaveOnClose")] public class AppSettings{ private bool saveOnClose = true; private string greetingText = "欢迎使用应用程序!"; private int maxRepeatRate = 10; private int itemsInMRU = 4; private bool settingsChanged = false; private string appVersion = "1.0"; private Size windowSize = new Size(100,100); private Font windowFont = new Font("宋体", 9, FontStyle.Regular); private Color toolbarColor = SystemColors.Control; [CategoryAttribute("文档设置"), DefaultValueAttribute(true)] public bool SaveOnClose { get { return saveOnClose; } set { saveOnClose = value;} } [CategoryAttribute("文档设置")] public Size WindowSize { get { return windowSize; } set { windowSize = value;} } [CategoryAttribute("文档设置")] public Font WindowFont { get {return windowFont; } set { windowFont = value;} } [CategoryAttribute("全局设置")] public Color ToolbarColor { get { return toolbarColor; } set { toolbarColor = value; } } [CategoryAttribute("全局设置"), ReadOnlyAttribute(true), DefaultValueAttribute("欢迎使用应用程序!")] public string GreetingText { get { return greetingText; } set { greetingText = value; } } [CategoryAttribute("全局设置"), DefaultValueAttribute(4)] public int ItemsInMRUList { get { return itemsInMRU; } set { itemsInMRU = value; } } [DescriptionAttribute("以毫秒表示的文本重复率。"), CategoryAttribute("全局设置"), DefaultValueAttribute(10)] public int MaxRepeatRate { get { return maxRepeatRate; } set { maxRepeatRate = value; } } [BrowsableAttribute(false), DefaultValueAttribute(false)] public bool SettingsChanged { get { return settingsChanged; } set { settingsChanged = value; } } [CategoryAttribute("版本"), DefaultValueAttribute("1.0"), ReadOnlyAttribute(true)] public string AppVersion { get { return appVersion; } set { appVersion = value; } } }
下面的屏幕快照显示了新属性在 PropertyGrid 中的外观。
图 4:显示在 PropertyGrid 中的 .NET 框架数据类型
请注意,WindowFont 属性带有一个省略号 (...) 按钮,按下该按钮将显示字体选择对话框。此外,还可以展开该属性以显示更多的 Font 属性。某些 Font 属性提供有关字体的值和详细信息的下拉列表。您可以展开 WindowSize 属性以显示 Size 类型的更多属性。最后,请注意,ToolbarColor 属性包含一个选定颜色的样本,以及一个用于选择不同颜色的自定义下拉列表。对于这些以及其他数据类型,.NET 框架提供了其他的类,可以使在 PropertyGrid 中的编辑更加容易。
对自定义类型的支持
现在,您需要在 AppSettings 类中添加另外两个属性,即 DefaultFileName 和 SpellCheckOptions 。 DefaultFileName 属性用于获取或设置字符串;SpellCheckOptions 属性用于获取或设置 SpellingOptions 类的实例。
SpellingOptions 类是一个新类,用于管理应用程序的拼写检查属性。对于何时创建单独的类以管理对象的属性,并没有严格的规定,而取决于您的整个类设计。将 SpellingOptions 类定义添加到应用程序项目中 - 可以添加到新文件中,也可以添加到窗体源代码的下方。
[DescriptionAttribute("展开以查看应用程序的拼写选项。")] public class SpellingOptions{ private bool spellCheckWhileTyping = true; private bool spellCheckCAPS = false; private bool suggestCorrections = true; [DefaultValueAttribute(true)] public bool SpellCheckWhileTyping { get { return spellCheckWhileTyping; } set { spellCheckWhileTyping = value; } } [DefaultValueAttribute(false)] public bool SpellCheckCAPS { get { return spellCheckCAPS; } set { spellCheckCAPS = value; } } [DefaultValueAttribute(true)] public bool SuggestCorrections { get { return suggestCorrections; } set { suggestCorrections = value; } } }
再次编译并运行选项窗口应用程序。下面的屏幕快照显示了应用程序的外观。
图 5:在 PropertyGrid 中显示的不带类型转换器的自定义数据类型
请注意 SpellcheckOptions 属性的外观。与 .NET 框架类型不同,它不展开或显示自定义的字符串表示。如果要在自己的复杂类型中提供与 .NET 框架类型相同的编辑体验,该如何处理呢?.NET 框架类型使用 TypeConverter 和 UITypeEditor 类提供大部分 PropertyGrid 编辑支持,您也可以使用这些类。
添加可展开属性支持
要使 PropertyGrid 能够展开 SpellingOptions 属性,您需要创建 TypeConverter 。TypeConverter 提供了从一种类型转换为另一种类型的方法。PropertyGrid 使用 TypeConverter 将对象类型转换为 String ,并使用该 String 在网格中显示对象值。在编辑过程中,TypeConverter 会将 String 转换回对象类型。.NET 框架提供的 ExpandableObjectConverter 类可以简化这一过程。
提供可展开对象支持
1. 创建一个从 ExpandableObjectConverter 继承而来的类。
public class SpellingOptionsConverter:ExpandableObjectConverter { }
2. 如果 destinationType 参数与使用此类型转换器的类(示例中的 SpellingOptions 类)的类型相同,则覆盖 CanConvertTo 方法并返回 true ;否则返回基类 CanConvertTo 方法的值。
public override bool CanConvertTo(ITypeDescriptorContext context, System.Type destinationType) { if (destinationType == typeof(SpellingOptions)) return true; return base.CanConvertTo(context, destinationType); }
3. 覆盖 ConvertTo 方法,并确保 destinationType 参数是一个 String ,并且值的类型与使用此类型转换器的类(示例中的 SpellingOptions 类)相同。如果其中任一情况为 false ,都将返回基类 ConvertTo 方法的值;否则,返回值对象的字符串表示。字符串表示需要使用唯一分隔符将类的每个属性隔开。由于整个字符串都将显示在 PropertyGrid 中,因此需要选择一个不会影响可读性的分隔符,逗号的效果通常比较好。
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, System.Type destinationType) { if (destinationType == typeof(System.String) && value is SpellingOptions){ SpellingOptions so = (SpellingOptions)value; return "在键入时检查:" + so.SpellCheckWhileTyping + ",检查大小写: " + so.SpellCheckCAPS + ",建议更正: " + so.SuggestCorrections; } return base.ConvertTo(context, culture, value, destinationType); }
4.(可选)通过指定类型转换器可以从字符串进行转换,您可以启用网格中对象字符串表示的编辑。要执行此操作,首先需要覆盖 CanConvertFrom 方法并返回 true (如果源 Type 参数为 String 类型);否则,返回基类 CanConvertFrom 方法的值。
public override bool CanConvertFrom(ITypeDescriptorContext context, System.Type sourceType) { if (sourceType == typeof(string)) return true; return base.CanConvertFrom(context, sourceType); } public override bool CanConvertFrom(ITypeDescriptorContext context, System.Type sourceType) { if (sourceType == typeof(string)) return true; return base.CanConvertFrom(context, sourceType); }
5. 要启用对象基类的编辑,同样需要覆盖 ConvertFrom 方法并确保值参数是一个 String 。如果不是 String ,将返回基类 ConvertFrom 方法的值;否则,返回基于值参数的类(示例中的 SpellingOptions 类)的新实例。您需要根据值参数解析类的每个属性的值。了解在 ConvertTo 方法中创建的分隔字符串的格式将有助于您的解析。
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { if (value is string) { try { string s = (string) value; int colon = s.IndexOf(':'); int comma = s.IndexOf(','); if (colon != -1 && comma != -1) { string checkWhileTyping = s.Substring(colon + 1 , (comma - colon - 1)); colon = s.IndexOf(':', comma + 1); comma = s.IndexOf(',', comma + 1); string checkCaps = s.Substring(colon + 1 , (comma - colon -1)); colon = s.IndexOf(':', comma + 1); string suggCorr = s.Substring(colon + 1); SpellingOptions so = new SpellingOptions(); so.SpellCheckWhileTyping =Boolean.Parse(checkWhileTyping); so.SpellCheckCAPS = Boolean.Parse(checkCaps); so.SuggestCorrections = Boolean.Parse(suggCorr); return so; } } catch { throw new ArgumentException( "无法将“" + (string)value + "”转换为 SpellingOptions 类型"); } } return base.ConvertFrom(context, culture, value); }
6. 现在已经有了一个类型转换器类,下面您需要确定使用该类的目标类。您可以通过将 TypeConverterAttribute 应用到目标类(示例中的 SpellingOptions 类)来执行此操作。
// 应用于 SpellingOptions 类的 TypeConverter 特性。
[TypeConverterAttribute(typeof(SpellingOptionsConverter)),
DescriptionAttribute(“展开以查看应用程序的拼写选项。")]
public class SpellingOptions{ ... }
再次编译并运行选项窗口应用程序。下面的屏幕快照显示了选项窗口目前的外观。
图 6:在 PropertyGrid 中显示的带有类型转换器的自定义数据类型
注意: 如果只需要可展开对象支持,而不需要自定义字符串表示,则只需将 TypeConverterAttribute 应用到类中。将 ExpandableObjectConverter 指定为类型转换器类型。
添加域列表和简单的下拉列表属性支持
对于基于 Enum 类型返回枚举的属性,PropertyGrid 会自动在下拉列表中显示枚举值。EnumConverter 也提供了这一功能。对于自己的属性,您可能希望为用户提供一个有效值列表(有时也称为选取列表或域列表),而其类型并不是基于 Enum 。如果域值在运行时之前未知,或者值可以更改,则属于这种情况。
修改选项窗口,提供一个用户可从中选择的默认文件名的域列表。您已经将 DefaultFileName 属性添加到 AppSettings 类。下一步是在 PropertyGrid 中显示属性的下拉列表,以提供域列表。
提供简单的下拉列表属性支持
1. 创建一个从类型转换器类继承而来的类。由于 DefaultFileName 属性属于 String 类型,因此可以从 StringConverter 中继承。如果属性类型的类型转换器不存在,则可以从 TypeConverter 继承;这里并不需要。
public class FileNameConverter: StringConverter { }
2. 覆盖 GetStandardValuesSupported 方法并返回 true ,表示此对象支持可以从列表中选取的一组标准值。
public override bool GetStandardValuesSupported(ITypeDescriptorContext context) { return true; }
3. 覆盖 GetStandardValues 方法并返回填充了标准值的 StandardValuesCollection 。创建 StandardValuesCollection 的方法之一是在构造函数中提供一个值数组。对于选项窗口应用程序,您可以使用填充了建议的默认文件名的 String 数组。
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) { return new StandardValuesCollection(new string[]{"新文件", "文件1", "文档1"}); }
4. (可选)如果希望用户能够键入下拉列表中没有包含的值,请覆盖 GetStandardValuesExclusive 方法并返回 false 。这从根本上将下拉列表样式变成了组合框样式。
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context) { return false; }
5. 拥有自己的用于显示下拉列表的类型转换器类后,您需要确定使用该类的目标。在本示例中,目标为 DefaultFileName 属性,因为类型转换器是针对该属性的。将 TypeConverterAttribute 应用到目标属性中。
// 应用到 DefaultFileName 属性的 TypeConverter 特性。 [TypeConverter(typeof(FileNameConverter)), CategoryAttribute("文档设置")] public string DefaultFileName { get{ return defaultFileName; } set{ defaultFileName = value; } }
再次编译并运行选项窗口应用程序。下面的屏幕快照显示了选项窗口目前的外观。请注意 DefaultFileName 属性的外观。
图 7:在 PropertyGrid 中显示下拉域列表