# PropertyGrid content
A PropertyGrid is a two-column grid composed of Properties. Those Properties are organized under categories and sub-categories, like a conventional tree's nodes.
In SPG, a row, or a Property, corresponds to a .Net property, the identifier that you write containing a get and/or a set accessors.
However, in the SPG framework, a row or Property points to one of two kinds of data:
- Hosted by one of your classes
- Hosted internally by SPG
For example, you can specify a new row containing a boolean with an initial value of true, without having to bind to an actual property.
# Accessing a Property
Whatever data it contains, you can access any Property or category by two means:
- A Property identifier
- A Property enumerator
# Property identifiers
A Property identifier is a simple integer value attached to the Property. It is set on creation of the Property, but can be subsequently changed. You will usually set it as a unique identifier, but this is not mandatory.
Use the identifier to look for Properties:
mygrid.FindProperty(555);
555 is the identifier of the lost Property.
The identifier is also passed to all the callback methods. You can trigger actions according to its value by comparing it to known IDs.
# Property enumerators
If you have a PropertyEnumerator, you can access the underlying Property:
Property prop = propEnum.Property;
A PropertyEnumerator is returned to you:
- When you create a new property.
- When you find a property.
- By an event (the enumerator is given to you in the argument of the event handler, or of the virtual method).
You can then use it as a parameter of other methods like:
myGrid.EnableProperty(propEnum, false);
You can also use it to navigate through the hierarchy of Properties.
The basic design under an enumerator can be represented by this simple diagram:
Note
It is possible to store an enumerator for later use. However, a PropertyEnumerator is only valid in the view from which it originates. If the user switches, e.g., to the alphabetically sorted view, the PropertyEnumerator points to the right Property, but is no longer useful for navigating Properties.
# Filling modes
The PropertyGrid has three modes for populating the grid:
- Full Reflection Mode
- Dynamic Reflection Mode
- Full Dynamic Mode
# Full Reflection Mode
Full Reflection Mode should be familiar to you from MSPG. The content is set by using one of the following calls:
myGrid.SelectedObject = myTargetInstance;
myGrid.SelectedObjects = new object[] { myTargetInstance1, myTargetInstance2 };
All the target instance's public properties (with BrowsableAttribute set to true) are rendered in a two-level tree of root categories and Properties.
Notes:
When a target instance is already selected and you want to select a new one whose type is the same or contextually similar, you may wish to save the states of the expanded and selected Properties. The following code will accomplish this task:
myGrid.BeginUpdate();
var states = myGrid.SavePropertiesStates(PropertyStateFlags.All);
myGrid.SelectedObject = myNewTargetInstance;
myGrid.RestorePropertiesStates(states);
myGrid.EndUpdate();
The property states are saved and restored based on a hash code calculated for each property. If you are not satisfied with the algorithm used by SPG, you can redefine it by overriding the PropertyGrid.GetPropertyHasCode method.
# Customizing the grid content
Several techniques allow you to customize the grid content. Some are already available in .Net (the classes TypeConverter, ICustomTypeDescriptor and TypeDescriptionProvider should be familiar). Others are introduced by SPG.
In this mode, one call to SelectedObject(s) fills the grid, so at this stage you cannot customize each Property. You have 2 ways to customize properties appearance and behaviour:
- Attach attributes to the properties of the class assigned to SelectedObject. Some of these attributes exist in .Net and are also the ones used by MSPG. Others are specific to SPG.
- Use a fluent API to externally configure the properties without having to modify the class owning them.
# Refreshing the content
MSPG used to offer a single way to refresh the content of the grid. The Refresh method actually clears, recreates and repaints it. SPG proposes more flexibility:
Repainting:
In SPG, the Refresh method does what it usually does in a control. It just sends a paint message. This can be useful when the value of a non selected simple property has been changed externally (“simple” means that the property does not incur changes in other properties).
Resetting:
When a value change incurs a modification in the hierarchy of the grid (e.g. a TypeConverter publishes new properties), a paint message is not enough. In this case, you can call the PropertyGrid’s RefreshProperties method. It does what MSPG’s Refresh method does: clear, recreate and repaint.
Here is an example where a property change obliges the grid to be recreated:
myGrid.SelectedObject = myTargetInstance;
myTargetInstance.Font = null;
myGrid.RefreshProperties();
Note that you can recreate a single Property by calling the PropertyGrid.RecreateProperty method.
WARNING
Your stored PropertyEnumerator instances become invalid after this call.
Updating the inplace control:
Sometimes, your client application will change the value of the currently selected property. To make the current inplace control aware of this change, call the PropertyGrid’s RefreshInPlaceCtrl method.
# Lazy loading
By default, all properties of the target instance(s) are loaded. This differs from the Microsoft PropertyGrid which loads only visible properties when needed. This can sometimes be an issue in SPG when there are numerous properties or when there is an infinite cycle. In this case, you can mimic MSPG’s behavior by setting:
myGrid.LazyLoading = true;
# Dynamic Reflection Mode
Dynamic Reflection Mode gives full control over the dynamic content of new and existing grids. You can even use it to finish off the content drawn by Full Reflection Mode, which actually uses Dynamic Reflection Mode internally.
This mode enables you to place Properties anywhere in the grid. The following code examples use Append operations, only. However, Insert operations also work.
# > Root categories
A root category must exist in order to host other Properties:
PropertyEnumerator rootEnum = AppendRootCategory(id, "Label");
The returned enumerator is used to create child Properties.
# > Subcategories
WARNING
Not available when Full Reflection Mode is used.
Organize complex content by creating subcategories under root categories, or under other subcategories.
PropertyEnumerator subEnum = AppendSubCategory(parentEnum, id, "label");
The returned enumerator is used to create child Properties.
# > Properties
The following method appends a Property to the last position under a parent node:
public PropertyEnumerator AppendProperty(
PropertyEnumerator underCategory,
int id,
string propName,
object container,
string memberName,
string comment,
params Attribute[] attributes);
# > Public variables
With the same signature than AppendProperty, AppendVariable lets you target public fields of instances. This is convenient when you want to target classes where it’s impossible to add properties (you have no access to the code or the class is sealed).
Remarks:
- container specifies the property's target instance. You can combine different properties coming from different target instances.
- memberName specifies the Property display name.
- attributes are all the attributes you want to attach to the Property. See Attributes from SPG. Some attributes do not work in this mode, e.g. PropertyIdAttribute since the identifier is passed as a parameter, and SortedPropertyAttribute since calling AppendProperty controls the order of Properties at will.
# > Controlling Properties directly
In this mode, use some of the library methods to directly control the effect of attributes. For example, the attribute PropertyDisableAttribute can be replaced by:
EnableProperty(propEnum, false);
# Full Dynamic Mode
In Full Dynamic Mode, the library completely hosts the Properties and their values. Categories and subcategories are created as in Dynamic Reflection Mode. However, Property type and initial value are required, only. This mode uses neither target instance nor property name.
This is a convenient mode for filling a PropertyGrid instead of creating a specific class for storing highly dynamic data.
Example:
A third party supplies code for an application's settings. A global shortcut is missing, but is required in the grid. You add it manually using the code:
propEnum = AppendManagedProperty(categoryEnum, id, "Global shortcut",
typeof(Keys), Keys.F4, "");
Good to know
The term Managed Property has nothing to do with the notion of managed code in .Net and simply reflects that SPG will manage the underlying data.
# Customization with attributes
Under all three modes showcased above, attributes can be used to control the behavior or appearance of the PropertyGrid. They can be set at design-time traditionally through the metadata and at runtime through three different mechanisms:
- As arguments of the Append/Insert(Managed)Property methods.
- When the PropertyValueAttributesNeeded event is triggered.
- Through the PropertyValue’s SetAttribute and DeleteAttribute methods.
In the next sections, let's see the exhaustive list of attributes that SPG recognizes.
# > Attributes from .Net
CategoryAttribute defines the category in which a Property will appear.
BrowsableAttribute filters out attached Properties.
DefaultPropertyAttribute defines the property that is initially selected when the grid is filled.
DescriptionAttribute sets the comments text (at the bottom of the grid). To make this area visible:
CommentsVisibility = true;
DisplayName sets the Property label.
ParenthesizePropertyName puts brackets around the property display name.
MergablePropertyAttribute filter out decorated properties when SelectedObjects (note the final “s”) is used, if set to false.
# > Attributes from SPG
# PropertyIdAttribute:
By default, the Categories have a negative identifier decrementing from -1 and Properties a positive identifier incrementing from 1. Use PropertyIdAttribute to assign a specific identifier to any property directly published by the target instance. This attribute can also be used on descendant properties if the PropertyComparerDefaultSort comparer is used (see Using a PropertyComparer).
[PropertyId(100)]
public int MyInteger {
# SortedCategoryAttribute:
Used during the initial sorting process – see Sorting the Properties.
# SortedPropertyAttribute:
Used during the initial sorting process - see Sorting the Properties.
# ValueAddedToPropertyAttribute:
This attribute defines a logical link between several data in the client application. This supports a single inplace control enabling the user to edit multiple values contained in a same target instance. For example, the “Unit” inplace control comprises: a textbox for editing a number and a combo box for selecting the units, e.g. currency.
For an example, see FeelEditUnit.
# PropertyDisableAttribute:
Used with no arguments, this sets a disabled Property:
[PropertyDisable]
public int MyProperty {
Used with arguments, it disables child Properties of a complex property. For example, this disables the Width property of a Pen:
[PropertyDisableAttribute("Width")]
public Pen MyPen {
# ReadOnlyChildPropertiesAttribute:
Ensures that all child properties of a parent property decorated with this attribute will be set as ReadOnly.
[ReadOnlyChildProperties]
public Expandable Property
{
get { return _property; }
}
[TypeConverter(typeof(ExpandableObjectConverter))]
public class Expandable {
# PropertyHeightMultiplierAttribute:
Sets the number of lines occupied by a Property. This works for inplace controls with multiple lines, e.g. multiline textboxes:
[PropertyHeightMultiplier(3)]
[PropertyFeel(PropertyGrid.FeelMultilineEdit)]
public string Answer {
Note
Properties displaying a set of possible values, such as checkboxes or radiobuttons, don’t need this attribute because the number of lines is automatically set.
# PropertyManuallyDisabledAttribute:
Displays a check box to the left of Property label, so that users can toggle between "enabled" and "disabled".
A typical use might be an optional command line parameter. For example, -p is optional, and can also have a value like -p4:
[DisplayName("-p")]
[PropertyManuallyDisabled]
public int paramP {
# PropertyDropDownContentAttribute:
Displays a custom editor inside the dropdown part of a combobox. For example, this displays a custom alpha color editor:
[PropertyDropDownContent(typeof(AlphaColorPicker))]
public Color MyColor {
Optional arguments can be passed. In the following example, the alpha component is turned off by passing false to the AlphaColorPicker’s constructor:
[PropertyDropDownContent(typeof(AlphaColorPicker), false)]
public Color MyColor {
This also works for a child Property whose code is inaccessible. The following assigns the same editor to the Color property of a Pen:
[PropertyDropDownContent("Color", typeof(AlphaColorPicker))]
public Pen MyPen {
See FeelList and FeelEditList.
# DropDownListboxVisualsAttribute:
A listbox will normally use the colors and font set by the PropertyValue owner. If you don’t want this default behavior and prefer to use the default colors and font of the grid, use this attribute:
[DropDownListboxVisuals(false)]
public Browsers SupportedBrowsers { ... }
# UseFeelCacheAttribute:
The List feel can use a cache for properties showing a costly content that should not be recreated each time the listbox is displayed. To configure a property in such a way, mark it with the UseFeelCacheAttribute. The cache can be emptied by setting the PropertyValue’s FeelCache property to null.
# PropertyFeelAttribute:
Specifies the inplace control for a particular Property. A whole chapter is devoted to Feels so here is a simple example which links multiple checkboxes to the ParticipatingCountries property:
[PropertyFeel(PropertyGrid.FeelCheckbox)]
public Countries ParticipatingCountries {
This also works for a child Property whose code is inaccessible. For example, this sets an up/down button Feel for a pen's Width property:
[PropertyFeel("Width", PropertyGrid.FeelEditUpDown)]
public Pen MyPen {
# UITypeEditorTextBoxVisibilityAttribute:
When a UITypeEditor is used for a property, by default a textbox is always shown. This can be avoided by attaching this attribute to the property. If you use a custom look, the space left can be used to draw anything (the PropertyColorLook uses this feature to draw a large color box).
# PropertyHideAttribute:
Hides a Property. The Property is created, but remains invisible until shown. For example, this hides a Property unneeded on first launch of an evaluation copy (because the user has yet to be invited to purchase the software):
[PropertyHide]
public string RegistrationKey {
This also works for a child Property whose code is inaccessible. The following hides the unfriendly CompoundArray property of a pen:
[PropertyHide("CompoundArray")]
public Pen MyPen {
# PropertyLookAttribute:
Specifies the kind of custom drawing for a particular Property value. The following sets a custom Look for a pen:
[PropertyLook(typeof(PropertyPenLook))]
public Pen MyPen {
This also works for a child Property whose code is inaccessible. For example, this draws the pen's Color property with a Look displaying its alpha component:
[PropertyLook("Color", typeof(PropertyAlphaColorLook))]
public Pen MyPen {
# PropertyValidatorAttribute:
Assigns an object derived from PropertyValidatorBase to a Property so that its value is checked each time the user inputs new data. Here is an example:
[PropertyValidator(typeof(PropertyValidatorMinMax), 0, 100)]
public int Percentage {
The first parameter specifies the type of the validator class. The subsequent parameters are passed to the validator class constructor. There can be any number of these.
This also works for a child Property whose code is inaccessible. The following applies a validator to a Size property's Width and Height properties:
[PropertyValidator("Width", typeof(PropertyValidatorMinMax), 0, 20)]
[PropertyValidator("Height", typeof(PropertyValidatorMinMax), 0, 30)]
public Size MySize {
# ButtonSettingsAttribute:
Assigns a set of settings (ButtonText and FullWidth) on a Property that uses a button or a UITypeEditor without a textbox. Here is an example:
[ButtonSettingsAttribute("Add", false)]
public ICollection Contacts {
This also works for a child Property whose code may be inaccessible (use the attribute’s constructor which accepts a childPropertyName parameter).
# TrackBarSettingsAttribute:
Assigns a set of settings (SmallChange, LargeChange and Resolution) on a Property that uses a trackbar. Here is an example:
[TrackBarSettingsAttribute(0.1f, 0.1f, 1.0f)]
public float Value {
This also works for a child Property whose code may be inaccessible. The following code assigns some settings to a PointF Property and its X and Y properties:
[TypeConverter(typeof(PointFConverter))]
[PropertyFeel("X", VisualHint.SmartPropertyGrid.PropertyGrid.FeelTrackbarEdit)]
[PropertyValidator("X", typeof(PropertyValidatorMinMax), 0.0f, 10.0f)]
[TrackBarSettings("X", 0.1f, 0.1f, 1.0f)]
[PropertyFeel("Y", VisualHint.SmartPropertyGrid.PropertyGrid.FeelTrackbarEdit)]
[PropertyValidator("Y", typeof(PropertyValidatorMinMax), 0.0f, 10.0f)]
[TrackBarSettings("Y", 0.1f, 0.1f, 1.0f)]
[PropertyValidator(typeof(PropertyValidatorPointFMinMax), 0.0f, 10.0f, 0.0f, 10.0f)]
public PointF MyPoint {
# UpDownSettingsAttribute:
Assigns a set of settings (Increment) on a Property that uses an updown button. Here is an example:
[UpDownSettingsAttribute(0.1)]
public double Value {
This also works for a child Property whose code may be inaccessible. The following code assigns some settings to a PointF Property and its X and Y properties:
[TypeConverter(typeof(PointFConverter))]
[PropertyFeel("X", VisualHint.SmartPropertyGrid.PropertyGrid.FeelEditUpDown)]
[PropertyValidator("X", typeof(PropertyValidatorMinMax), 0.0f, 10.0f)]
[UpDownSettings("X", 0.1f)]
[PropertyFeel("Y", VisualHint.SmartPropertyGrid.PropertyGrid. FeelEditUpDown)]
[PropertyValidator("Y", typeof(PropertyValidatorMinMax), 0.0f, 10.0f)]
[UpDownSettings("Y", 0.1f)]
[PropertyValidator(typeof(PropertyValidatorPointFMinMax), 0.0f, 10.0f, 0.0f, 10.0f)]
public PointF MyPoint {
# PropertyValueDisplayedAsAttribute:
Specifies permitted values for a Property, typically one with a set of enumerated values returned by a TypeConverter (but no necessarily) and edited with an inplace control displaying these values. This enables you to avoid writing additional classes just for this one action. This example modifies the strings for a Boolean to Yes and No:
[PropertyValueDisplayedAs(new string[] { "Yes", "No" })]
public bool MyBoolean {
This also works for a child Property whose code is inaccessible. Suppose a Font property is to have Booleans as checkboxes and display no strings:
[PropertyValueDisplayedAsAttribute("Bold", new string[2] { "", "" })]
[PropertyValueDisplayedAsAttribute("Italic", new string[2] { "", "" })]
[PropertyValueDisplayedAsAttribute("Strikeout", new string[2] { "", "" })]
[PropertyValueDisplayedAsAttribute("Underline", new string[2] { "", "" })]
public Font MyFont {
When the attribute is used at runtime with methods like AppendProperty, it accepts more than a collection of strings. You can pass a collection of objects or a collection of pairs <object, string>. In the first case, the string representation will be given by the TypeConverter of each object, and in the second case the string representation for the key of the pair is given by the value of the pair.
The following example configures a Property at runtime. The value is an unnamed planet initialized with a position in the solar system. The attribute will give a name to the possible planets:
propEnum = AppendManagedProperty(rootCategory, _id, "List of Planets / no names",
typeof(Planet), new Planet(5), "",
new PropertyValueDisplayedAsAttribute(true, new object[] {
new Planet(1), "Mercury",
new Planet(2), "Venus",
new Planet(3), "Earth",
new Planet(4), "Mars",
new Planet(5), "Jupiter",
new Planet(6), "Saturn" }));
In this other example, planets have a name but there is no TypeConverter to publish the standard values. In this case we use a list of objects:
propEnum = AppendManagedProperty(listCategory, _id++, "List of Planets",
typeof(Planet), new Planet("Venus"), "",
new PropertyValueDisplayedAsAttribute(false, new object[] {
new Planet("Mercury"),
new Planet("Venus"),
new Planet("Earth"),
new Planet("Mars"),
new Planet("Jupiter"),
new Planet("Saturn") }));
# ShowChildPropertiesAttribute:
This determines whether or not a Property can be expanded to make child Properties visible.
Take the example of our pen. In MSPG, no Properties would be visible without attaching a TypeConverter that acts like ExpandableObjectConverter. However, in SPG only the following is required:
[ShowChildProperties(true)]
public Pen MyPen {
It's just as simple to make child Properties unviewable. For example, the Font class has a type converter which publishes child Properties so that the Font node can be expanded. We can prevent this:
[ShowChildProperties(false)]
public Font MyFont {
Good to know
Unlike the PropertyHide attribute, this attribute prevents the creation of child Properties. You can't display them later without recreating the Property in the grid.
# CollectionPropertySelectorAttribute:
Defines the attached property as an index or a key in a related collection. The property will therefore act as a selector of an item in the collection.
Typically, you will instruct SPG not to display the collection property, but only the selector.
Here is an example that shows how to use this attribute with a list and a dictionary:
public TestClass()
{
PlanetList = new List<Planet> {
new Planet("Mercury"),
new Planet("Venus"),
new Planet("Earth"),
new Planet("Mars"),
new Planet("Jupiter"),
new Planet("Saturn")};
MyPlanet1 = 2;
PlanetDictionary = new Dictionary<string, Planet> {
{ "Mercury", new Planet(1) },
{ "Venus", new Planet(2) },
{ "Earth", new Planet(3) },
{ "Mars", new Planet(4) },
{ "Jupiter", new Planet(5) },
{ "Saturn", new Planet(6) },
};
MyPlanet2 = "Earth";
}
[Browsable(false)]
public List<Planet> PlanetList { get; set; }
[DisplayName("Home planet")]
[CollectionPropertySelector(nameof(PlanetList))]
public int MyPlanet1 { get; set; }
[Browsable(false)]
public Dictionary<string, Planet> PlanetDictionary { get; set; }
[DisplayName("Secondary planet")]
[CollectionPropertySelector(nameof(PlanetDictionary))]
public string MyPlanet2 { get; set; }
Note
In order to get readable options in the dropdown list used by the property, the Planet class shall at least override the ToString method to return a readable representation of itself. You can also supply a TypeConverter with a ConvertTo method. In the case of the example, such a conversion is necessary in the list. It would be necessary in a dictionary only if the Planet is the key, which is not the case here.
# PropertyCreatedHandlerAttribute:
In order to make the CollectionPropertySelectorAttribute above work, it derives from PropertyCreatedHandlerAttribute. This attribute avoids writing a custom PropertyCreated event handler and detecting which property is in arguments to do some special stuff on it. Instead, you simply attach a derived class of this attribute to a property and override its OnPropertyCreated method to do your special stuff that is needed when the property has been added to the grid.
# More on modes
# Mixing the construction modes
It is possible to mix the three construction modes (see Filling modes) when filling the grid, by changing Modes between Properties. For example, you could fill the grid with SelectedObject(s), use Dynamic Reflection Mode to add Properties by targeting actual Properties, and finally use Full Dynamic Mode for additional “virtual” Properties.
Example:
This shows existing properties created using Dynamic Reflection Mode and uses Full Dynamic Mode to add a virtual Property for quick information:
PropertyEnumerator propEnum = AppendProperty(categoryEnum, 1, "Width", target,
"Width", "");
propEnum = AppendProperty(categoryEnum, 2, "Height", target, "Height", "");
propEnum = AppendManagedProperty(categoryEnum, 3, "Diagonal", typeof(double),
0.0, "");
EnableProperty(propEnum, false);
Note
This enables the end-user to quickly see the value of the diagonal formed by the width and the height of, e.g., a rectangle. Of course, it's up to the developer to set the right value in the Diagonal Property (see Handling the PropertyValue’s value).
# Hyperlinked Properties
If you already know the verbs in MSPG, SPG hyperlinked Properties will seem familiar. SPG has two kinds of hyperlinks.
# > Clickable static text
The first kind of hyperlink is like a verb (term introduced by Microsoft in its PropertyGrid) embedded in the grid and occupies a full row in the middle of other Properties. It has no value and is just clickable static text.
To create this kind of hyperlink, use the following code:
propEnum = AppendHyperLinkProperty(parentEnum, id, "Click me", "Comment");
# > Hyperlinked property value
The second kind of hyperlink tags the value of a Property as if it were a web hyperlink, with the value displayed underlined in blue. The user CTRL + clicks the link to activate it.
You can also attach a link format to the value. For example, this returns a mail directive for a Property created to receive an email address:
propEnum = AppendManagedProperty(parentEnum, id, "Email", typeof(string),
"webmaster@visualhint.com", "");
propEnum.Property.HyperLinkFormat = "mailto:{0}";
# Multiple values per Property
You can store multiple data items in a single Property. The additional values can be found by reflection or created on the fly.
This feature is primarily used by the Unit look and feel classes displaying an editable numerical value and unit on the same row. However, it can be used in other ways.
Add a new value to an existing Property as follows:
Property.AddValue(object key, object container, string memberName,
Attribute[] attributes);
Or:
Property.AddManagedValue(object key, Type valueType, object defaultValue,
Attribute[] attributes);
Most of the arguments are the same as for calls to AppendProperty and AppendManagedProperty.
Note
"key" is an identifier giving meaning to the additional value. For instance, if you create an inplace control making editable the three coordinates of a point in space, the keys must discriminate between X, Y and Z.
This identifier is used by the attached look, feel, and inplace control classes. For an example, see PropertyUnitLook.
# Multiple target instances per Property
SPG supports the SelectedObjects property familiar from MSPG. This produces Properties each pointing to the same set of target instances.
When not using the Full Reflection Mode, you can manually add a target instance to a Property:
PropertyEnumerator propEnum = AppendProperty(...);
AddTargetInstance(propEnum, targetInstance2, "PropertyName");
This creates a Property, and then links it to one called PropertyName in an object instance targetInstance2.
# Hierarchy of Properties
A Property may often become a parent for other Properties:
- In the Full Reflection Mode, e.g. when a TypeConverter publishes properties (this is the case for a Font).
- In Dynamic Reflection and Full Dynamic Mode, when you use one of three manual options.
# > Automatic hierarchy
In Dynamic Reflection and Full Dynamic Mode, creating a single Property enables the framework to discover any child Properties at runtime:
PropertyEnumerator fontEnum = AppendProperty(rootEnum, 1, "Font", target, "Font", "");
# > Hierarchy with no dependencies
In Dynamic Reflection and Full Dynamic Mode, you can create a hierarchy in which there are no dependencies between the values of parent and children.
Example:
In a typical task management application, we would have: a task whose value is its name, plus sub Properties for the start and end dates:
PropertyEnumerator propEnum = AppendManagedProperty(rootEnum, 1, "Task",
typeof(String), "Work on 2.0", "");
PropertyEnumerator propEnum2 = AppendManagedProperty(propEnum, 2, "Start date",
typeof(DateTime), new DateTime(2006, 7, 1), "");
propEnum2 = AppendManagedProperty(propEnum, 3, "End date", typeof(DateTime), new DateTime(2006, 8, 31), "");
# Property Tags
When creating a Property at runtime, you can attach a Tag to it, or to its value. A tag can be any ValueType, or a reference to any object:
propEnum.Property.Tag = 1000;
propEnum.Property.Value.Tag = "remind me later";
# Browse the content
Once content is in place, you can use enumerators to navigate your Properties. Four classes, derived from PropertyEnumerator, are available.
# Classes of enumerator
Class | Description |
---|---|
PropertyDeepEnumerator | A bidirectional enumerator, this browses all Properties, including the hidden ones, in display order. |
PropertySiblingEnumerator | A bidirectional enumerator, this browses a parent's direct children, including the hidden ones. It only browses one level. |
PropertyVisibleDeepEnumerator | As PropertyDeepEnumerator, except ignores hidden Properties. |
PropertyVisibleSiblingEnumerator | As PropertySibilingEnumerator, except ignores hidden Properties. |
Note
Hidden Properties are those explicitly hidden by ShowProperty, not temporarily invisible because of a collapsed parent.
# Returning an enumerator for browsing
Here are some different ways to get an enumerator to start browsing Properties:
Enumerator Class | Target Property | Action |
---|---|---|
Deep | A new Property. | Store the enumerator returned on creation (e.g. using AppendProperty or AppendManagedProperty). |
Deep | A Property with a known identifier. | PropertyGrid.FindProperty(…). It is possible to search from a particular starting Property. |
Deep | First Property in grid. | PropertyGrid.FirstProperty |
Deep | Last Property in grid. | PropertyGrid.LastProperty |
Visible Deep | First visible Property in grid. | PropertyGrid.FirstVisibleProperty |
Deep | Parent of a Property with known enumerator. | PropertyEnumerator.Parent |
Sibling | Child of a Property with known enumerator. | PropertyEnumerator.Children |
# Enumerator properties and methods
Useful PropertyEnumerator properties and methods include:
Action | Properties and methods |
---|---|
Return a count of a Property's direct children. | PropertyEnumerator.Count |
Return the collection limits. | Mainly used in loops. Also RightBound is returned by FindProperty when no property can be found. PropertyEnumerator.LeftBound PropertyEnumerator.RightBound Note: These enumerators don’t point to valid Properties. |
Clone an enumerator. | Depending on the desired class: GetDeepEnumerator() GetVisibleDeepEnumerator() GetSiblingEnumerator() GetVisibleSiblingEnumerator() Clone() |
Return whether or not the Property is at the root level. | PropertyEnumerator.HasParent |
Move an enumerator to another Property. | MoveFirst(), MoveNext(), etc. |
Return the enumerator's Property instance. | PropertyEnumerator.Property |
Limit the enumerator to the current level and below. | PropertyEnumerator.RestrictedToThisLevelAndUnder |
Limit the enumerator to the current property and below. | PropertyEnumerator.RestrictedToThisPropertyAndUnder |
# > Examples
Browse child Properties of a Property:
PropertyEnumerator childrenEnumerator = propEnum.Children;
while (childrenEnumerator != childrenEnumerator.RightBound)
{
// Do something here
childrenEnumerator.MoveNext();
}
Browse all visible Properties in reverse order:
PropertyEnumerator propEnum = FirstDisplayedProperty.MoveLast();
while (propEnum != propEnum.LeftBound)
{
// Do something here
vdEnumerator.MovePrev();
}
Browse from a Property to all its ancestors:
PropertyEnumerator currentEnum = propEnum.Parent;
while (currentEnum != currentEnum.RightBound)
{
// Do something here
currentEnum = currentEnum.Parent;
}