Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

PropertyGridCE - Mobile version of PropertyGrid

0.00/5 (No votes)
14 Oct 2008 2  
A useful control that mimics most of the PropertyGrid functionality in the .NET Compact Framework.

Screenshot - PropertyGridCE

Introduction

I have used the PropertyGrid control in some projects and have concluded that is extremely useful, flexible, and professional looking. But unfortunately, it is not available for the .NET Compact Framework. I guess the reason is that it is too heavy to provide all its functionality in a constrained environment like Windows CE. PropertyGrid will require the support of lots of classes, interfaces, and attributes.

Since I needed to develop a Pocket PC version of my desktop software, I had to reproduce all the functionality of the PropertyGrid and related elements, matching the name of the real .NET equivalents. I did it with success, but the resulting source code didn't satisfy me at all because it was too complex.

Based on this experience, I developed this new implementation of my control; this time, I ignored the name-compatibility requirements and produced a simpler solution, but keeping most of the great PropertyGrid features, as I will explain further.

Basic Usage - Person Class

The supplied demo project will allow you to test all the PropertyGridCE capabilities. There are three classes defined in DemoClasses.cs, with different features, like custom attributes. Here is the declaration of the first and simplest one:

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("  ", " ");
    }
}

Notice that there are two fields (private) and properties (public) declared with similar names. The control will show just the properties, not the fields. If you are using C# 3.0, you can avoid declaring the underlying fields by using auto-implemented properties

To show the properties of a Person object is quiet simple; just assign it to the control's SelectedObject property, as shown:

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

Basic Attributes

In the Person class implementation, you will notice there are some properties that have attributes (those with square brackets); they won't have any effect on your class behaviour, but will do with the property grid. These attributes are similar to those implemented in the desktop .NET Framework. Let's see them in detail.

  • Category: Lets you specify a category group for the affected property. A category appears by default at the property grid with a gray background, as you can see in the first screenshot. If the property doesn't have a Category attribute, it will belong to a blank category group, as with the GUID property in the previous screenshot. It is recommended to always specify a category for each property.
  • DisplayName: Will be useful when you want to display a property name different from the real one. Usually, it is used when you have to increment readability with white spaces, or abbreviate the name.
  • ReadOnly: When set to true, will prevent the property from being edited; it will be just shown in the property grid.
  • ParenthesizePropertyName: When set to true, will display the names between parenthesis. This is used to enhance some important properties, and force them to show at the beginning of a category group.
  • Browsable: When set to false, the property will not be shown. It is useful when you have a property that you don't want to show at all, like the GUID property in the first example.

All these attributes are declared in the System.ComponentModel namespace, to keep some degree of compatibility with the original PropertyGrid control.

Custom Properties - Vehicle Class

While the simplest implementation of PropertyGridCE exposes all the properties of a class (with the exception of those with the Browsable attribute set to false), the ICustomProperties interface will allow to conditionally expose some properties. There are a few steps to accomplish this, as in the following example:

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
}

Notice that the unique method needed to implement the PropertyGrideCE.ICustomProperties is GetProperties(). This method should return all the property names you want to expose as an array, depending on some conditions. In this example, if the car type is a Pick Up or Truck, the Volume and Payload properties will be exposed.

Custom Editors - Place Class

Custom editors is the most powerful feature of this control. There are several tricks you can do with it. By default, the control will provide an editor for all the fundamental classes: int, float, double, etc., and also for strings and enumerations, the latter as a ComboBox. If you have a custom class' object as a property, it will show the contents but just as readonly, because the grid control doesn't know how to edit it.

A custom editor must de declared by using the CustomEditor attribute, as shown in the following example. There are two kinds of custom editors: derived from Control, and derived from Form. The Place class implementation shows both. Despite the kind of editor, it has to inherit the ICustomEditor interface, as we will see in detail later.

The attribute declaration can be done in two places: before the class declaration, as with the CountryInfo class, or before the property itself, like with the Picture property. The first will have effect in every property of the class, the second will have effect only in the specific property; other properties of the same class will remain unaffected.

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] = "";
    }
}

The ICustomEditor interface provides some elements to allow the PropertyGridCE control to interact with the editor control. There are events, properties, and methods. It is declared as:

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);
}

As I mentioned, there are two examples of custom editor implementations in the Place class; the first one, CountryEditor, is a control editor. It asks you for a country with two ComboBoxes: one for Continent, and one for Country, as you can see in the screenshot. To avoid source code clutter, I will show just the partial implementation:

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
}

First, you have to declare the Style property; in this case, it has the DropDown value for this kind of control editor. Then, you have to implement the Value property; this will allow the PropertyGridCE control to set and get the value inside the editor control.

The Init method will pass the suggested place to show the control, it is the same as the rectangle containing the value at the grid. You should resize or relocate your control as pertinent. Also, you can do some private initialization here.

The ValueChanged event also is important to tell the PropertyGridCE control that the value of the property has been changed so it can update the selected object property properly. It has the same implementation as the example.

Also, it will be important to control the LostFocus event to allow the control to update the property value and close when the user taps the stylus outside the control. You will find an example in the full source code provided.

The second example of a custom editor is PictureEditor; it is a form editor, so it will occupy all the screen workable area. As in the previous example, I will show the class partially for abbreviation purposes:

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
}

Notice this time, the Style property returns the Modal value, because it is a form control. Also, the rect parameter in the Init method is ignored.

Final Comments

Future releases will consider showing members of properties, including collections, collapse/expand option, and keypad support.

The sample application has been built with Visual Studio 2008. You cannot load the solution with Visual Studio 2005 directly, but as it is designed for C# 2.0 and .NET 2.0 and up, you can create a new solution and attach the project file without problems. I have tested the application with an emulator and a real Pocket PC 2003, but you can use it in other platforms.

To use this control, you just need to add the PropertyGridCE.cs file into your project, no extra DLL is needed.

History

  • June 23, 2008: First edition.
  • October 15, 2008: Moved to the Mobile section.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here