充分利用 .NET 框架的 PropertyGrid 控件

类别:.NET开发 点击:0 评论:0 推荐:
 

充分利用 .NET 框架的 PropertyGrid 控件

Mark Rideout
Microsoft Corporation

摘要:本文旨在帮助您了解 Microsoft .NET 框架中的 PropertyGrid 控件,以及如何针对您的应用程序自定义该控件。

适用于:
   Microsoft® .NET® 框架
   Microsoft® Visual Studio® .NET

目录

PropertyGrid 控件简介
创建 PropertyGrid 控件
何处使用 PropertyGrid 控件
选择对象
自定义 PropertyGrid 控件
显示复杂属性
为属性提供自定义 UI
小结

PropertyGrid 控件简介

如果您使用过 Microsoft® Visual Basic® 或 Microsoft Visual Studio .NET,那么您一定使用过属性浏览器来浏览、查看和编辑一个或多个对象的属性。.NET 框架 PropertyGrid 控件是 Visual Studio .NET 属性浏览器的核心。PropertyGrid 控件显示对象或类型的属性,并主要通过使用反射来检索项目的属性。(反射是在运行时提供类型信息的技术。)

下面的屏幕快照显示了 PropertyGrid 在窗体上的外观。

图 1:窗体上的 PropertyGrid

PropertyGrid 包含以下部分:

  • 属性
  • 可展开属性
  • 属性类别标题
  • 属性说明
  • 属性编辑器
  • 属性选项卡
  • 命令窗格(显示控件设计器提供的设计器操作)

创建 PropertyGrid 控件

要使用 Visual Studio .NET 创建 PropertyGrid 控件,需要将该控件添加到工具箱中,因为默认情况下并不包含该控件。在 Tools(工具)菜单中,选择 Customize Toolbox(自定义工具箱)。在对话框中选择 Framework Components(框架组件)选项卡,然后选择 PropertyGrid

如果您从命令行编译代码,请使用 /reference 选项并指定 System.Windows.Forms.dll。

以下代码显示了如何创建 PropertyGrid 控件并将其添加到窗体中。

' Visual Basic

Imports System
Imports System.Drawing
Imports System.ComponentModel
Imports System.Windows.Forms
Imports System.Globalization

Public Class OptionsDialog
   Inherits System.Windows.Forms.Form

   Private OptionsPropertyGrid As System.Windows.Forms.PropertyGrid

   Public Sub New()
      MyBase.New()

      OptionsPropertyGrid = New PropertyGrid()
      OptionsPropertyGrid.Size = New Size(300, 250)

      Me.Controls.Add(OptionsPropertyGrid)
      Me.Text = "选项对话框"
   End Sub
End Class


//C#

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 的类,其中包含映射到应用程序设置的所有属性。如果创建单独的类而不使用多个分散的变量,设置将更便于管理和维护。

' Visual Basic

Public Class AppSettings
    Private _saveOnClose As Boolean = True
    Private _greetingText As String = "欢迎使用应用程序!"
    Private _maxRepeatRate As Integer = 10
    Private _itemsInMRU As Integer = 4

    Private _settingsChanged As Boolean = False
    Private _appVersion As String = "1.0"

    Public Property SaveOnClose() As Boolean
        Get
            Return _saveOnClose
        End Get
        Set(ByVal Value As Boolean)
            SaveOnClose = Value
        End Set
    End Property

    Public Property GreetingText() As String
        Get
            Return _greetingText
        End Get
        Set(ByVal Value As String)
            _greetingText = Value
        End Set
    End Property

    Public Property ItemsInMRUList() As Integer
        Get
            Return _itemsInMRU
        End Get
        Set(ByVal Value As Integer)
            _itemsInMRU = Value
        End Set
    End Property

    Public Property MaxRepeatRate() As Integer
        Get
            Return _maxRepeatRate
        End Get
        Set(ByVal Value As Integer)
            _maxRepeatRate = Value
        End Set
    End Property

    Public Property SettingsChanged() As Boolean
        Get
            Return _settingsChanged
        End Get
        Set(ByVal Value As Boolean)
            _settingsChanged = Value
        End Set
    End Property

    Public Property AppVersion() As String
        Get
            Return _appVersion
        End Get
        Set(ByVal Value As String)
            _appVersion = Value
        End Set
    End Property
End Class


//C#

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 属性的值。

' Visual Basic

   Public Sub New()
      MyBase.New()

      OptionsPropertyGrid = New PropertyGrid()
      OptionsPropertyGrid.Size = New Size(300, 250)

      Me.Controls.Add(OptionsPropertyGrid)
      Me.Text = "选项对话框"

      ' 创建 AppSettings 类并在 PropertyGrid 中显示该类。
      Dim appset as AppSettings = New AppSettings()
      OptionsPropertyGrid.SelectedObject = appset
   End Sub


//C#

   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 的许多外观特征都可以自定义。下面列出了其中的一部分:

  • 通过 HelpBackColorHelpForeColorHelpVisible 属性可以更改背景颜色、更改字体颜色或隐藏说明窗格。
  • 通过 ToolbarVisible 属性可以隐藏工具栏,通过 BackColor 属性可以更改工具栏的颜色,通过 LargeButtons 属性可以显示大工具栏按钮。
  • 使用 PropertySort 属性可以按字母顺序对属性进行排序和分类。
  • 通过 BackColor 属性可以更改拆分器的颜色。
  • 通过 LineColor 属性可以更改网格线和边框。

本示例中的选项窗口不需要工具栏,因此可以将 ToolbarVisible 设置为 false。其余属性均保留默认设置。

更改属性的显示方式

要更改某些属性的显示方式,您可以对这些属性应用不同的特性。特性是用于为类型、字段、方法和属性等编程元素添加批注的声明标记,在运行时可以使用反射对其进行检索。下面列出了其中的一部分:

  • DescriptionAttribute - 设置显示在属性下方说明帮助窗格中的属性文本。这是一种为活动属性(即具有焦点的属性)提供帮助文本的有效方法。可以将此特性应用于 MaxRepeatRate 属性。
  • CategoryAttribute - 设置属性在网格中所属的类别。当您需要将属性按类别名称分组时,此特性非常有用。如果没有为属性指定类别,该属性将被分配给杂项类别。可以将此特性应用于所有属性。
  • BrowsableAttribute – 表示是否在网格中显示属性。此特性可用于在网格中隐藏属性。默认情况下,公共属性始终显示在网格中。可以将此特性应用于 SettingsChanged 属性。
  • ReadOnlyAttribute – 表示属性是否为只读。此特性可用于禁止在网格中编辑属性。默认情况下,带有 get 和 set 访问函数的公共属性在网格中是可以编辑的。可以将此特性应用于 AppVersion 属性。
  • DefaultValueAttribute – 表示属性的默认值。如果希望为属性提供默认值,然后确定该属性值是否与默认值相同,则可使用此特性。可以将此特性应用于所有属性。
  • DefaultPropertyAttribute – 表示类的默认属性。在网格中选择某个类时,将首先突出显示该类的默认属性。可以将此特性应用于 AppSettings 类。

现在,我们将其中的一些特性应用于 AppSettings 类,以更改属性在 PropertyGrid 中的显示方式。

' Visual Basic

<DefaultPropertyAttribute("SaveOnClose")> _
Public Class AppSettings
    Private _saveOnClose As Boolean = True
    Private _greetingText As String = "欢迎使用应用程序!"
    Private _maxRepeatRate As Integer = 10
    Private _itemsInMRU As Integer = 4

    Private _settingsChanged As Boolean = False
    Private _appVersion As String = "1.0"

    <CategoryAttribute("文档设置"), _
     DefaultValueAttribute(True)> _
    Public Property SaveOnClose() As Boolean
        Get
            Return _saveOnClose
        End Get
        Set(ByVal Value As Boolean)
            SaveOnClose = Value
        End Set
    End Property

    <CategoryAttribute("全局设置"), _
    ReadOnlyAttribute(True), _
    DefaultValueAttribute("欢迎使用应用程序!")> _
    Public Property GreetingText() As String
        Get
            Return _greetingText
        End Get
        Set(ByVal Value As String)
            _greetingText = Value
        End Set
    End Property

    <CategoryAttribute("全局设置"), _
    DefaultValueAttribute(4)> _
    Public Property ItemsInMRUList() As Integer
        Get
            Return _itemsInMRU
        End Get
        Set(ByVal Value As Integer)
            _itemsInMRU = Value
        End Set
    End Property

    <DescriptionAttribute("以毫秒表示的文本重复率。"), _
    CategoryAttribute("全局设置"), _
    DefaultValueAttribute(10)> _
    Public Property MaxRepeatRate() As Integer
        Get
            Return _maxRepeatRate
        End Get
        Set(ByVal Value As Integer)
            _maxRepeatRate = Value
        End Set
    End Property

    <BrowsableAttribute(False),
     DefaultValueAttribute(False)> _
    Public Property SettingsChanged() As Boolean
        Get
            Return _settingsChanged
        End Get
        Set(ByVal Value As Boolean)
            _settingsChanged = Value
        End Set
    End Property

    <CategoryAttribute("版本"), _
    DefaultValueAttribute("1.0"), _
    ReadOnlyAttribute(True)> _
    Public Property AppVersion() As String
        Get
            Return _appVersion
        End Get
        Set(ByVal Value As String)
            _appVersion = Value
        End Set
    End Property
End Class


//C#
[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 中显示的带有类别和默认值的属性

使用此版本的选项窗口后,您会注意到以下几点:

  • 显示窗口时,将首先突出显示 SaveOnClose 属性。
  • 选中 MaxRepeatRate 属性时,说明帮助窗格中将显示“以毫秒表示的文本重复率”。
  • SaveOnClose 属性显示在“文档设置”类别下。其他属性分别显示在“全局设置”和“版本”类别下。
  • SettingsChanged 属性将不再显示。
  • AppVersion 属性为只读。只读属性以灰显文本显示。
  • 如果 SaveOnClose 属性包含的值不是 true,该值将以粗体显示。PropertyGrid 使用粗体文本表示包含非默认值的属性。

显示复杂属性

到现在为止,选项窗口显示的都是简单的类型,如整数、布尔值和字符串。那么,如何显示更复杂的类型呢?如果应用程序需要跟踪窗口大小、文档字体或工具栏颜色等信息,该如何处理呢?.NET 框架提供的某些数据类型具有特殊的显示功能,能使这些类型在 PropertyGrid 中更具可用性。

对所提供类型的支持

首先,请更新 AppSettings 类,为窗口大小(Size 类型)、窗口字体(Font 类型)和工具栏颜色(Color 类型)添加新属性。

' Visual Basic

<DefaultPropertyAttribute("SaveOnClose")> _
Public Class AppSettings
    Private _saveOnClose As Boolean = True
    Private _greetingText As String = "欢迎使用应用程序!"
    Private _maxRepeatRate As Integer = 10
    Private _itemsInMRU As Integer = 4

    Private _settingsChanged As Boolean = False
    Private _appVersion As String = "1.0"

    Private _windowSize As Size = New Size(100, 100)
    Private _windowFont As Font = New Font("宋体", 9, FontStyle.Regular)
    Private _toolbarColor As Color = SystemColors.Control

    <CategoryAttribute("文档设置"), _
     DefaultValueAttribute(True)> _
    Public Property SaveOnClose() As Boolean
        Get
            Return _saveOnClose
        End Get
        Set(ByVal Value As Boolean)
            SaveOnClose = Value
        End Set
    End Property

    <CategoryAttribute("文档设置")> _
    Public Property WindowSize() As Size
        Get
            Return _windowSize
        End Get
        Set(ByVal Value As Size)
            _windowSize = Value
        End Set
    End Property

    <CategoryAttribute("文档设置")> _
    Public Property WindowFont() As Font
        Get
            Return _windowFont
        End Get
        Set(ByVal Value As Font)
            _windowFont = Value
        End Set
    End Property

    <CategoryAttribute("全局设置")> _
    Public Property ToolbarColor() As Color
        Get
            Return _toolbarColor
        End Get
        Set(ByVal Value As Color)
            _toolbarColor = Value
        End Set
    End Property

    <CategoryAttribute("全局设置"), _
    ReadOnlyAttribute(True), _
    DefaultValueAttribute("欢迎使用应用程序!")> _
    Public Property GreetingText() As String
        Get
            Return _greetingText
        End Get
        Set(ByVal Value As String)
            _greetingText = Value
        End Set
    End Property

    <CategoryAttribute("全局设置"), _
    DefaultValueAttribute(4)> _
    Public Property ItemsInMRUList() As Integer
        Get
            Return _itemsInMRU
        End Get
        Set(ByVal Value As Integer)
            _itemsInMRU = Value
        End Set
    End Property

    <DescriptionAttribute("以毫秒表示的文本重复率。"), _
    CategoryAttribute("全局设置"), _
    DefaultValueAttribute(10)> _
    Public Property MaxRepeatRate() As Integer
        Get
            Return _maxRepeatRate
        End Get
        Set(ByVal Value As Integer)
            _maxRepeatRate = Value
        End Set
    End Property

    <BrowsableAttribute(False),
     DefaultValueAttribute(False)> _
    Public Property SettingsChanged() As Boolean
        Get
            Return _settingsChanged
        End Get
        Set(ByVal Value As Boolean)
            _settingsChanged = Value
        End Set
    End Property

    <CategoryAttribute("版本"), _
    DefaultValueAttribute("1.0"), _
    ReadOnlyAttribute(True)> _
    Public Property AppVersion() As String
        Get
            Return _appVersion
        End Get
        Set(ByVal Value As String)
            _appVersion = Value
        End Set
    End Property
End Class


//C#

[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 类中添加另外两个属性,即 DefaultFileNameSpellCheckOptionsDefaultFileName 属性用于获取或设置字符串;SpellCheckOptions 属性用于获取或设置 SpellingOptions 类的实例。

SpellingOptions 类是一个新类,用于管理应用程序的拼写检查属性。对于何时创建单独的类以管理对象的属性,并没有严格的规定,而取决于您的整个类设计。将 SpellingOptions 类定义添加到应用程序项目中 - 可以添加到新文件中,也可以添加到窗体源代码的下方。

' Visual Basic

<DescriptionAttribute("展开以查看应用程序的拼写选项。")> _
Public Class SpellingOptions
    Private _spellCheckWhileTyping As Boolean = True
    Private _spellCheckCAPS As Boolean = False
    Private _suggestCorrections As Boolean = True

    <DefaultValueAttribute(True)> _
    Public Property SpellCheckWhileTyping() As Boolean
        Get
            Return _spellCheckWhileTyping
        End Get
        Set(ByVal Value As Boolean)
            _spellCheckWhileTyping = Value
        End Set
    End Property

    <DefaultValueAttribute(False)> _
    Public Property SpellCheckCAPS() As Boolean
        Get
            Return _spellCheckCAPS
        End Get
        Set(ByVal Value As Boolean)
            _spellCheckCAPS = Value
        End Set
    End Property

    <DefaultValueAttribute(True)> _
    Public Property SuggestCorrections() As Boolean
        Get
            Return _suggestCorrections
        End Get
        Set(ByVal Value As Boolean)
            _suggestCorrections = Value
        End Set
    End Property
End Class


//C#

[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 框架类型使用 TypeConverterUITypeEditor 类提供大部分 PropertyGrid 编辑支持,您也可以使用这些类。

添加可展开属性支持

要使 PropertyGrid 能够展开 SpellingOptions 属性,您需要创建 TypeConverterTypeConverter 提供了从一种类型转换为另一种类型的方法。PropertyGrid 使用 TypeConverter 将对象类型转换为 String,并使用该 String 在网格中显示对象值。在编辑过程中,TypeConverter 会将 String 转换回对象类型。.NET 框架提供的 ExpandableObjectConverter 类可以简化这一过程。

提供可展开对象支持

  1. 创建一个从 ExpandableObjectConverter 继承而来的类。
    ' Visual Basic
    
    Public Class SpellingOptionsConverter
        Inherits ExpandableObjectConverter
    End Class
    
    
    //C#
    
    public class SpellingOptionsConverter:ExpandableObjectConverter 
    {  }
  2. 如果 destinationType 参数与使用此类型转换器的类(示例中的 SpellingOptions 类)的类型相同,则覆盖 CanConvertTo 方法并返回 true;否则返回基类 CanConvertTo 方法的值。
    ' Visual Basic
    
    Public Overloads Overrides Function CanConvertTo( _
                                  ByVal context As ITypeDescriptorContext, _
                                  ByVal destinationType As Type) As Boolean
        If (destinationType Is GetType(SpellingOptions)) Then
            Return True
        End If
        Return MyBase.CanConvertTo(context, destinationType)
    End Function
    //C#
    
    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 中,因此需要选择一个不会影响可读性的分隔符,逗号的效果通常比较好。
    ' Visual Basic
    
    Public Overloads Overrides Function ConvertTo( _
                                  ByVal context As ITypeDescriptorContext, _
                                  ByVal culture As CultureInfo, _
                                  ByVal value As Object, _
                                  ByVal destinationType As System.Type) _
                         As Object
        If (destinationType Is GetType(System.String) _
            AndAlso TypeOf value Is SpellingOptions) Then
    
            Dim so As SpellingOptions = CType(value, SpellingOptions)
    
            Return "在键入时检查: " & so.SpellCheckWhileTyping & _
                   ",检查大小写: " & so.SpellCheckCAPS & _
                   ",建议更正: " & so.SuggestCorrections
        End If
        Return MyBase.ConvertTo(context, culture, value, destinationType)
    End Function
    //C#
    
    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 方法的值。
    ' Visual Basic
    
    Public Overloads Overrides Function CanConvertFrom( _
                               ByVal context As ITypeDescriptorContext, _
                               ByVal sourceType As System.Type) As Boolean
        If (sourceType Is GetType(String)) Then
            Return True
        End If
        Return MyBase.CanConvertFrom(context, sourceType)
    End Function
    //C#
    
    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 方法中创建的分隔字符串的格式将有助于您的解析。
    ' Visual Basic
    
    Public Overloads Overrides Function ConvertFrom( _
                                  ByVal context As ITypeDescriptorContext, _
                                  ByVal culture As CultureInfo, _
                                  ByVal value As Object) As Object
    
        If (TypeOf value Is String) Then
            Try
                Dim s As String = CStr(value)
                Dim colon As Integer = s.IndexOf(":")
                Dim comma As Integer = s.IndexOf(",")
    
                If (colon <> -1 AndAlso comma <> -1) Then
                    Dim checkWhileTyping As String = s.Substring(colon + 1, _
                                                    (comma - colon - 1))
    
                    colon = s.IndexOf(":", comma + 1)
                    comma = s.IndexOf(",", comma + 1)
    
                    Dim checkCaps As String = s.Substring(colon + 1, _
                                                    (comma - colon - 1))
    
                    colon = s.IndexOf(":", comma + 1)
    
                    Dim suggCorr As String = s.Substring(colon + 1)
    
                    Dim so As SpellingOptions = New SpellingOptions()
    
                    so.SpellCheckWhileTyping = Boolean.Parse(checkWhileTyping)
                    so.SpellCheckCAPS = Boolean.Parse(checkCaps)
                    so.SuggestCorrections = Boolean.Parse(suggCorr)
    
                    Return so
                End If
            Catch
                Throw New ArgumentException( _
                    "无法将“" & CStr(value) & _ 
                                      "”转换为 SpellingOptions 类型")
    
            End Try
        End If
        Return MyBase.ConvertFrom(context, culture, value)
    End Function
    //C#
    
    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 类)来执行此操作。
    ' Visual Basic
    
    ' 应用于 SpellingOptions 类的 TypeConverter 特性。
    <TypeConverter(GetType(SpellingOptionsConverter)), _
    DescriptionAttribute("展开以查看应用程序的拼写选项。")> _
    Public Class SpellingOptions
       ...
    End Class
    
    
    //C#
    
    // 应用于 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 继承;这里并不需要。
    ' Visual Basic
    
    Public Class FileNameConverter
        Inherits StringConverter
    End Class
    
    
    //C#
    
    public class FileNameConverter: StringConverter 
    {  } 
  2. 覆盖 GetStandardValuesSupported 方法并返回 true,表示此对象支持可以从列表中选取的一组标准值。
    ' Visual Basic
    
    Public Overloads Overrides Function GetStandardValuesSupported( _
                        ByVal context As ITypeDescriptorContext) As Boolean
        Return True
    End Function
    
    
    //C#
    
    public override bool GetStandardValuesSupported(
                               ITypeDescriptorContext context) 
    {
        return true;
    }
  3. 覆盖 GetStandardValues 方法并返回填充了标准值的 StandardValuesCollection。创建 StandardValuesCollection 的方法之一是在构造函数中提供一个值数组。对于选项窗口应用程序,您可以使用填充了建议的默认文件名的 String 数组。
    ' Visual Basic
    
    Public Overloads Overrides Function GetStandardValues( _
                         ByVal context As ITypeDescriptorContext) _
                      As StandardValuesCollection
    
        Return New StandardValuesCollection(New String() {"新文件", _
                                                          "文件1", _
                                                          "文档1"})
    End Function 
    
    
    //C#
    
    public override StandardValuesCollection
                         GetStandardValues(ITypeDescriptorContext context) 
    {
        return new StandardValuesCollection(new string[]{"新文件", 
                                                         "文件1", 
                                                         "文档1"});
    } 
  4. (可选)如果希望用户能够键入下拉列表中没有包含的值,请覆盖 GetStandardValuesExclusive 方法并返回 false。这从根本上将下拉列表样式变成了组合框样式。
    ' Visual Basic
    
    Public Overloads Overrides Function GetStandardValuesExclusive( _
                   ByVal context As ITypeDescriptorContext) As Boolean
        Return False
    End Function
    
    
    //C#
    
    public override bool GetStandardValuesExclusive(
                               ITypeDescriptorContext context) 
    {
        return false;
    }
  5. 拥有自己的用于显示下拉列表的类型转换器类后,您需要确定使用该类的目标。在本示例中,目标为 DefaultFileName 属性,因为类型转换器是针对该属性的。将 TypeConverterAttribute 应用到目标属性中。
    ' Visual Basic
    
    ' 应用到 DefaultFileName 属性的 TypeConverter 特性。
    <TypeConverter(GetType(FileNameConverter)), _
     CategoryAttribute("文档设置")> _
    Public Property DefaultFileName() As String
        Get
            Return _defaultFileName
        End Get
        Set(ByVal Value As String)
            _defaultFileName = Value
        End Set
    End Property
    
    
    //C#
    
    // 应用到 DefaultFileName 属性的 TypeConverter 特性。
     [TypeConverter(typeof(FileNameConverter)), 
     CategoryAttribute("文档设置")]
    public string DefaultFileName
    {
        get{ return defaultFileName; }
        set{ defaultFileName = value; }
    }

再次编译并运行选项窗口应用程序。下面的屏幕快照显示了选项窗口目前的外观。请注意 DefaultFileName 属性的外观。

图 7:在 PropertyGrid 中显示下拉域列表

为属性提供自定义 UI

如上所述,.NET 框架类型使用 TypeConverterUITypeEditor 类(以及其他类)来提供 PropertyGrid 编辑支持。有关如何使用 TypeConverter,请参阅对自定义类型的支持一节;您也可以使用 UITypeEditor 类来自定义 PropertyGrid

您可以在 PropertyGrid 中提供小图形表示和属性值,类似于为 ImageColor 类提供的内容。要在自定义中执行此操作,请从 UITypeEditor 继承,覆盖 GetPaintValueSupported 并返回 true。然后,覆盖 UITypeEditor.PaintValue 方法,并在自己的方法中使用 PaintValueEventArgs.Graphics 参数绘制图形。最后,将 Editor 特性应用到使用 UITypeEditor 类的类或属性。

下面的屏幕快照显示了结果外观。

图 8:在 PropertyGrid 中显示属性的自定义图形

您也可以提供自己的下拉列表控件,这与 Control.Dock 属性用来为用户提供靠接选择的控件类似。要执行此操作,请从 UITypeEditor 继承,覆盖 GetEditStyle,然后返回一个 UITypeEditorEditStyle 枚举值,例如 DropDown。您的自定义下拉列表控件必须从 ControlControl 的派生类(例如 UserControl)继承而来。然后,覆盖 UITypeEditor.EditValue 方法。使用 IServiceProvider 参数调用 IServiceProvider.GetService 方法,以获取一个 IWindowsFormsEditorService 实例。最后,调用 IWindowsFormsEditorService.DropDownControl 方法来显示您的自定义下拉列表控件。请记住将 Editor 特性应用到使用 UITypeEditor 类的类或属性中。

下面的屏幕快照显示了结果外观。

图 9:在 PropertyGrid 中显示属性的自定义下拉列表控件

除了使用 TypeEditorUITypeEditor 类外,还可以自定义 PropertyGrid 以显示其他属性选项卡。属性选项卡从 PropertyTab 类继承而来。如果您使用过 Microsoft Visual C#™ .NET 中的属性浏览器,那么就可能看到过自定义的 PropertyTabEvents 选项卡(带有闪电图形的按钮)就是一个自定义的 PropertyTab。下面的屏幕快照显示了自定义 PropertyTab 的另一个示例。可以使用 PropertyTab 编辑按钮的边界点,以创建自定义的按钮形状。

图 10:在 PropertyGrid 中显示自定义选项卡

有关使用 UITypeEditor 类自定义 PropertyGrid 的详细信息,以及上述自定义用户界面代码示例,请参阅 Shawn Burke 的文章 Make Your Components Really RAD with Visual Studio .NET Property Browser(英文)。

小结

.NET 框架提供的 ProperyGrid 控件具有丰富的编辑功能,您可以使用这些编辑功能来改善您的用户界面。PropertyGrid 的自定义非常简单,您可以在任何应用程序中使用这一控件。此外,由于 Visual Studio .NET 属性浏览器是建立在 PropertyGrid 的基础之上的,因此您可以使用这些技术提供更丰富的设计时体验。

本文地址:http://com.8s8s.com/it/it43778.htm