65.9K
CodeProject 正在变化。 阅读更多。
Home

PropertyGridCE - PropertyGrid 的移动版本

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (21投票s)

2008年6月23日

CPOL

6分钟阅读

viewsIcon

75626

downloadIcon

1073

一个有用的控件,模仿了 .NET Compact Framework 中 PropertyGrid 的大部分功能。

Screenshot - PropertyGridCE

引言

我在一些项目中使用过 PropertyGrid 控件,并认为它非常有用、灵活且看起来专业。但不幸的是,它不适用于 .NET Compact Framework。我猜原因可能是它太重量级了,无法在 Windows CE 这样的受限环境中提供所有功能。PropertyGrid 需要大量类、接口和特性的支持。

由于我需要开发我的桌面软件的 Pocket PC 版本,所以我不得不重新实现 PropertyGrid 及其相关元素的所有功能,并与真正的 .NET 等效项匹配。我成功了,但结果源代码让我非常不满意,因为它太复杂了。

基于此经验,我开发了这个新版本的控件;这次,我忽略了名称兼容性要求,并提出了一个更简单的解决方案,但保留了 PropertyGrid 的大部分出色功能,我将在后面进一步解释。

基本用法 - Person 类

提供的演示项目将允许您测试 PropertyGridCE 的所有功能。在 DemoClasses.cs 中定义了三个类,具有不同的功能,例如自定义特性。这是第一个也是最简单的类的声明:

Person Class Diagram

Person Properties

public class Person
{
    public enum Gender { Male, Female }

    #region private fields
    private string[] _Names = new string[3];
    private Gender _Gender;
    private DateTime _BirthDate;
    private int _Income;
    private System.Guid _Guid;
    #endregion

    #region Public Properties
    [Category("Name")]
    [DisplayName("First Name")]
    public string FirstName
    {
        set { _Names[0] = value; }
        get { return _Names[0]; }
    }

    [Category("Name")]
    [DisplayName("Mid Name")]
    public string MidName
    {
        set { _Names[1] = value; }
        get { return _Names[1]; }
    }

    [Category("Name")]
    [DisplayName("Last Name")]
    public string LastName
    {
        set { _Names[2] = value; }
        get { return _Names[2]; }
    }

    [Category("Characteristics")]
    [DisplayName("Gender")]
    public Gender PersonGender
    {
        set { _Gender = value; }
        get { return _Gender; }
    }

    [Category("Characteristics")]
    [DisplayName("Birth Date")]
    public DateTime BirthDate
    {
        set { _BirthDate = value; }
        get { return _BirthDate; }
    }

    [Category("Characteristics")]
    public int Income
    {
        set { _Income = value; }
        get { return _Income; }
    }   

    // multiple attributes at the same line
    [DisplayName("GUID"), 
        ReadOnly(true), 
        ParenthesizePropertyName(true)]   
    public string GuidStr
    {
        get { return _Guid.ToString(); }
    }

    // this property will not be displayed
    [Browsable(false)]  
    public System.Guid Guid
    {
        get { return _Guid; }
    }
    #endregion

    public Person()
    {
        // default values
        for (int i = 0; i > 3; i++)
            _Names[i] = "";
        _Gender = Gender.Male;
        _Guid = System.Guid.NewGuid();
    }
    public override string ToString()
    {
        return string.Format("{0} {1} {2}", 
            FirstName, MidName, 
            LastName).Trim().Replace("  ", " ");
    }
}

请注意,声明了两个名称相似的字段(private)和属性(public)。控件将只显示属性,而不显示字段。如果您使用的是 C# 3.0,您可以使用自动实现的属性来避免声明底层字段。

显示 Person 对象的属性非常简单;只需将其分配给控件的 SelectedObject 属性即可,如下所示:

PropertyGrid1.SelectedObject = thePerson;
//'thePerson' is an object of class Person

基本特性

Person 类的实现中,您会注意到有些属性带有特性(方括号内的那些);它们不会影响您的类行为,但会影响属性网格。这些特性与桌面 .NET Framework 中实现的特性类似。让我们详细了解它们。

  • Category:允许您为受影响的属性指定一个类别组。类别默认会在属性网格中显示,并带有灰色背景,正如您在第一个屏幕截图中所看到的。如果属性没有 Category 特性,它将属于一个空白的类别组,就像前一个屏幕截图中的 GUID 属性一样。建议始终为每个属性指定一个类别。
  • DisplayName:当您想显示一个与实际名称不同的属性名时,它会很有用。通常,当您需要增加可读性(例如,通过添加空格)或缩写名称时使用它。
  • ReadOnly:当设置为 true 时,将阻止编辑属性;它仅显示在属性网格中。
  • ParenthesizePropertyName:当设置为 true 时,将在名称前后加上括号。这用于强调某些重要属性,并强制它们显示在类别组的开头。
  • Browsable:当设置为 false 时,该属性将不会显示。当您有一个不想完全显示的属性时,它很有用,例如第一个示例中的 GUID 属性。

所有这些特性都声明在 System.ComponentModel 命名空间中,以保持与原始 PropertyGrid 控件在一定程度上的兼容性。

自定义属性 - Vehicle 类

虽然 PropertyGridCE 的最简单实现会公开一个类的所有属性(除了那些 Browsable 特性设置为 false 的属性),但 ICustomProperties 接口允许您有条件地公开某些属性。这需要几个步骤,如下面的示例所示:

Vehicle Class Diagram

Vehicle Properties

Vehicle Properties

// Sample class with custom properties
public class Vehicle : 
    PropertyGridCE.ICustomProperties
{
    public enum CarType 
     { Sedan, StationWagon, 
      Coupe, Roadster, Van, Pickup, Truck }
    public enum CarBrand 
      { Acura, Audi, BMW, 
    Citroen, Ford, GMC, Honda, Lexus,
         Mercedes, Mitsubishi, Nissan, 
    Porshe, Suzuki, 
    Toyota, VW, Volvo }

    #region Private fields
    private CarBrand _Brand;
    private CarType _CarType;
    private string _Model;
    private int _Year;
    private string _Plate;
    private int _Seats;
    private int _Volume;
    private int _Payload;
    #endregion

    #region Public Properties
    [Category("Classification")]
    public CarBrand Brand
    {
        get { return _Brand; }
        set { _Brand = value; }
    }

    [Category("Classification")]
    [ParenthesizePropertyName(true)]
    [DisplayName("Type")]
    public CarType TypeOfCar
    {
        get { return _CarType; }
        set { _CarType = value; }
    }

    [Category("Classification")]
    public string Model
    {
        get { return _Model; }
        set { _Model = value; }
    }

    [Category("Identification")]
    [DisplayName("Manuf.Year")]
    public int Year
    {
        get { return _Year; }
        set { _Year = value; }
    }

    [Category("Identification")]
    [DisplayName("License Plate")]
    public string Plate
    {
        get { return _Plate; }
        set { _Plate = value; }
    }

    [Category("Capacity")]
    public int Seats
    {
        get { return _Seats; }
        set { _Seats = value; }
    }

    // Just for Pickup and Truck
    [Category("Capacity")]
    [DisplayName("Volume (ft3)")]
    public int Volume
    {
        get { return _Volume; }
        set { _Volume = value; }
    }

    [Category("Capacity")]
    [DisplayName("Payload (pnd)")]
    public int Payload
    {
        get { return _Payload; }
        set { _Payload = value; }
    }
    #endregion

    #region ICustomProperties Members
    PropertyInfo[] PropertyGridCE.
    ICustomProperties.GetProperties()
    {
        List<PropertyInfo> props = 
        new List<PropertyInfo>();

        foreach 
    (System.Reflection.PropertyInfo 
            info in GetType().GetProperties())
        {
            if ((info.Name == "Volume" || 
                info.Name == "Payload") && 
                (this._CarType != 
        CarType.Pickup && 
                    this._CarType != 
            CarType.Truck))
                     continue;

            props.Add(info);
        }
        return props.ToArray();
    }
    #endregion
}

请注意,实现 PropertyGrideCE.ICustomProperties 所需的唯一方法是 GetProperties()。此方法应根据某些条件返回您要公开的所有属性名称的数组。在此示例中,如果汽车类型是 Pick UpTruck,则会公开 VolumePayload 属性。

自定义编辑器 - Place 类

自定义编辑器是此控件最强大的功能。您可以使用它来完成各种技巧。默认情况下,控件会为所有基本类提供编辑器:intfloatdouble 等,以及字符串和枚举,后者以 ComboBox 的形式显示。如果您有一个自定义类的对象作为属性,它将仅以只读形式显示内容,因为网格控件不知道如何编辑它。

必须使用 CustomEditor 特性声明自定义编辑器,如下面的示例所示。有两种自定义编辑器:一种派生自 Control,另一种派生自 FormPlace 类的实现展示了这两种。无论编辑器类型如何,它都必须继承 ICustomEditor 接口,我们将在稍后详细介绍。

特性声明可以在两个位置完成:在类声明之前,如 CountryInfo 类,或者在属性本身之前,如 Picture 属性。前者将影响该类的所有属性,后者仅影响特定属性;同一类的其他属性将保持不变。

Place Class Diagram

Place Properties

Place Properties

// Sample class with properties 
// with custom editors
public class Place
{
    [CustomEditor(typeof(CountryEditor))]
    public struct CountryInfo
    {
        public enum Continent 
            { Africa=1, America=2, 
    Asia=3, Europe=4, Oceania=5 }

        public static readonly 
    CountryInfo[] Countries = {
            // African countries
            new CountryInfo
         ( 1, "AO", "ANGOLA" ),
            new CountryInfo
         ( 1, "CM", "CAMEROON" ),
            // American countries
            new CountryInfo
         ( 2, "BO", "BOLIVIA" ),
            new CountryInfo
         ( 2, "PE", "PERU" ),
            // Asian countries
            new CountryInfo
         ( 3, "JP", "JAPAN" ),
            new CountryInfo
         ( 3, "MN", "MONGOLIA" ),
            // European countries
            new CountryInfo
        ( 4, "DE", "GERMANY" ),
            new CountryInfo
        ( 4, "NL", "NETHERLANDS" ),
            // Oceanian countries
            new CountryInfo
         ( 5, "AU", "AUSTRALIA" ),
            new CountryInfo
        ( 5, "NZ", "NEW ZEALAND" )
        };

        public Continent Contin;
        public string Abrev;
        public string Name;

        public override string ToString()
        {
             return Name;
        }
        public CountryInfo(int _continent,
             string _abrev, string _name)
        {
            this.Contin = 
               (Continent)_continent;
            this.Abrev = _abrev;
            this.Name = _name;
        }
    }

    #region Private fields
    private string[] _Address = 
        new string[4];
    public CountryInfo _Country;
    private Image _Picture;
    public int _CurrentValue;
    public int _Floors;
    #endregion

    #region Public properties
    [Category("Address")]
    public string Street
    {
        get { return _Address[0]; }
        set { _Address[0] = value; }
    }
    [Category("Address")]
    public string City
    {
        get { return _Address[1]; }
        set { _Address[1] = value; }
    }
    [Category("Address")]
    public string Province
    {
        get { return _Address[2]; }
        set { _Address[2] = value; }
    }
    [Category("Address")]
    public string Postal
    {
        get { return _Address[3]; }
        set { _Address[3] = value; }
    }
    [Category("Address")]
    public CountryInfo Country
    {
        get { return _Country; }
        set { _Country = value; }
    }

    [Category("Characteristics")]
    [CustomEditor(typeof(PictureEditor))]
    public Image Picture
    {
        get { return _Picture; }
        set { _Picture = value; }
    }
    [Category("Characteristics")]
    public int Floors
    {
        get { return _Floors; }
        set { _Floors = value; }
    }
    [Category("Characteristics")]
    public int CurrentValue
    {
        get { return _CurrentValue; }
        set { _CurrentValue = value; }
    }
    #endregion

    public Place()
    {
        for (int i = 0; i < 
       _Address.Length; i++)
            _Address[i] = "";
    }
}

ICustomEditor 接口提供了一些元素,允许 PropertyGridCE 控件与编辑器控件进行交互。它有事件、属性和方法。它的声明如下:

public interface ICustomEditor
{
    /// <summary>
    /// Fired when edited value was changed
    /// </summary>
    event EventHandler ValueChanged;
    /// <summary>
    /// Returns the editor style
    /// </summary>
    EditorStyle Style { get; }
    /// <summary>
    /// Get or set the edited value
    /// </summary>
    object Value { get; set; }
    /// <summary>
    /// Initializes editor control
    /// </summary>
    /// <param name="rect">Bounds of the right cell in the property grid</param>
    void Init(Rectangle rect);
}

如前所述,Place 类中有两个自定义编辑器实现的示例;第一个是 CountryEditor,它是一个控件编辑器。它通过两个 ComboBox 请求您输入国家/地区:一个用于 Continent,一个用于 Country,如您在屏幕截图中所见。为避免源代码混乱,我将仅显示部分实现:

public partial class CountryEditor : 
       UserControl, PropertyGridCE.ICustomEditor
{
    #region Private fields
    private Place.CountryInfo Info;

    private EventHandlerList ValueChangedColl = new EventHandlerList();
    private EventArgs EventArguments = new EventArgs();
    private object ID = new object();
    #endregion

    #region ICustomEditor Members
    event EventHandler PropertyGridCE.ICustomEditor.ValueChanged
    {
        add { ValueChangedColl.AddHandler(ID, value); }
        remove { ValueChangedColl.RemoveHandler(ID, value); }
    }
    PropertyGridCE.EditorStyle PropertyGridCE.ICustomEditor.Style
    {
        get { return PropertyGridCE.EditorStyle.DropDown; }
    }
    object PropertyGridCE.ICustomEditor.Value
    {
        get { return Info; }
        set
        {
            if (value is Place.CountryInfo)
            {
                Info = (Place.CountryInfo)value;
                this.ComboContinent.SelectedItem = Info.Contin;
                this.ComboCountry.SelectedItem = Info;
            }
        }
    }
    void PropertyGridCE.ICustomEditor.Init(Rectangle rect)
    {
        this.Location = rect.Location;

        ComboContinent.Items.Clear();
        foreach (FieldInfo info in typeof(
                 Place.CountryInfo.Continent).GetFields(
                 BindingFlags.Public | BindingFlags.Static))
        {
            ComboContinent.Items.Add(Enum.Parse(
                  typeof(Place.CountryInfo.Continent), info.Name, false));
        }
        ComboContinent.SelectedIndex = 0;

        ComboContinent.SelectedIndexChanged += 
        new EventHandler(ComboContinent_SelectedIndexChanged);
        ComboCountry.SelectedIndexChanged += 
        new EventHandler(ComboCountry_SelectedIndexChanged);
    }
    #endregion
}

首先,您必须声明 Style 属性;在这种情况下,对于这种控件编辑器,它具有 DropDown 值。然后,您必须实现 Value 属性;这将允许 PropertyGridCE 控件设置和获取编辑器控件中的值。

Init 方法将传递建议的显示控件的位置,它与网格中值所在的矩形相同。您应该根据需要调整控件的大小或位置。此外,您可以在此处执行一些私有初始化。

ValueChanged 事件也很重要,用于告知 PropertyGridCE 控件属性的值已更改,以便它可以正确更新选定的对象属性。它的实现与示例相同。

此外,控制 LostFocus 事件也很重要,以允许控件在用户将触笔移出控件外部时更新属性值并关闭。您可以在提供的完整源代码中找到一个示例。

第二个自定义编辑器示例是 PictureEditor;它是一个窗体编辑器,因此它将占用所有屏幕可用工作区。与前面的示例一样,为简化起见,我将部分显示该类:

public partial class PictureEditor : Form, PropertyGridCE.ICustomEditor
{
    #region Private fields
    private EventHandlerList ValueChangedColl = new EventHandlerList();
    private EventArgs EventArguments = new EventArgs();
    private object ID = new object();
    private Size PictureBoxSize;
    #endregion

    #region ICustomEditor Members
    event EventHandler PropertyGridCE.ICustomEditor.ValueChanged
    {
        add { ValueChangedColl.AddHandler(ID, value); }
        remove { ValueChangedColl.RemoveHandler(ID, value); }
    }
    PropertyGridCE.EditorStyle PropertyGridCE.ICustomEditor.Style
    {
        get { return PropertyGridCE.EditorStyle.Modal; }
    }
    object PropertyGridCE.ICustomEditor.Value
    {
        get { return PictureBox1.Image; }
        set
        {
            PictureBox1.Image = (Image)value;
            ScaleView();
        }
    }
    void PropertyGridCE.ICustomEditor.Init(Rectangle rect)
    {
        PictureBoxSize = PictureBox1.Size;
    }
    #endregion
}

请注意,这次 Style 属性返回 Modal 值,因为这是一个窗体控件。此外,Init 方法中的 rect 参数将被忽略。

最终评论

未来的版本将考虑显示属性成员,包括集合、折叠/展开选项和键盘支持。

示例应用程序已使用 Visual Studio 2008 构建。您无法直接使用 Visual Studio 2005 加载解决方案,但由于它设计用于 C# 2.0 和 .NET 2.0 及更高版本,您可以创建一个新解决方案并无问题地附加项目文件。我已在模拟器和真实的 Pocket PC 2003 上测试了该应用程序,但您可以在其他平台上使用它。

要使用此控件,您只需将 PropertyGridCE.cs 文件添加到您的项目中,无需额外的 DLL。

历史

  • 2008年6月23日:初版。
  • 2008年10月15日:移至移动设备部分。
© . All rights reserved.