PropertyGridCE - PropertyGrid 的移动版本






4.94/5 (21投票s)
一个有用的控件,模仿了 .NET Compact Framework 中 PropertyGrid 的大部分功能。
引言
我在一些项目中使用过 PropertyGrid
控件,并认为它非常有用、灵活且看起来专业。但不幸的是,它不适用于 .NET Compact Framework。我猜原因可能是它太重量级了,无法在 Windows CE 这样的受限环境中提供所有功能。PropertyGrid
需要大量类、接口和特性的支持。
由于我需要开发我的桌面软件的 Pocket PC 版本,所以我不得不重新实现 PropertyGrid
及其相关元素的所有功能,并与真正的 .NET 等效项匹配。我成功了,但结果源代码让我非常不满意,因为它太复杂了。
基于此经验,我开发了这个新版本的控件;这次,我忽略了名称兼容性要求,并提出了一个更简单的解决方案,但保留了 PropertyGrid
的大部分出色功能,我将在后面进一步解释。
基本用法 - Person 类
提供的演示项目将允许您测试 PropertyGridCE
的所有功能。在 DemoClasses.cs 中定义了三个类,具有不同的功能,例如自定义特性。这是第一个也是最简单的类的声明:
|
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
接口允许您有条件地公开某些属性。这需要几个步骤,如下面的示例所示:
|
// 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 Up 或 Truck,则会公开 Volume
和 Payload
属性。
自定义编辑器 - Place 类
自定义编辑器是此控件最强大的功能。您可以使用它来完成各种技巧。默认情况下,控件会为所有基本类提供编辑器:int
、float
、double
等,以及字符串和枚举,后者以 ComboBox
的形式显示。如果您有一个自定义类的对象作为属性,它将仅以只读形式显示内容,因为网格控件不知道如何编辑它。
必须使用 CustomEditor
特性声明自定义编辑器,如下面的示例所示。有两种自定义编辑器:一种派生自 Control
,另一种派生自 Form
。Place
类的实现展示了这两种。无论编辑器类型如何,它都必须继承 ICustomEditor
接口,我们将在稍后详细介绍。
特性声明可以在两个位置完成:在类声明之前,如 CountryInfo
类,或者在属性本身之前,如 Picture
属性。前者将影响该类的所有属性,后者仅影响特定属性;同一类的其他属性将保持不变。
|
// 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日:移至移动设备部分。