Tuesday, August 10, 2010

Property Grid - C#


This is one of the most powerful controls in .NET. We had a strange requirement to show properties of a class that user may (or may not) edit. Each of these properties had its own complications behind them. We came up with a variety of ways to display, until it dawned that we can use Property Grid control (as in Forms Designer view). It was very easy to manipulate. I will walk you through with some manipulations on Property Grid. Please let me know if any of these can be done better or if you'd like to see more of these

  1. Simple Property Grid
  2. Multi-Line Edit box for a property
  3. Combo/List-box for a property
  4. DialogBox for a property
  5. Add description
  6. Localization
  7. Dynamic construction
Personally I don't prefer to document a lot. Code should speak more than comments. "If the code is not obvious then refactor code to make it obvious", that's my philosophy. If there are some concepts I'll share a link with brief description. Please read the documentation before you consume.




Simple Property Grid
In order to associate an object to property grid we may have to set some attributes on the object. If your design does not allow to decorate objects, you can create a new "Displayable" class
and decorate it. This way you don't sway from your design. In this sample we will use "Browsable" and "Description" attributes.

Browsable - Indicates that the property will be displayed in the propety Grid. If the property does not have a setter (or has a private setter) it will not be editable. So make sure you have right getter's and setter's
Description - Gives a brief message about the property. I'd advice aginst hard-coding this attribute, especially if your application needs to support multiple languages. Look into "Localization" article of mine for more information.
 public class Employee : Person
{
public Employee(string name, int age, string empId, string company, string dsgn, string address, string city, string zip)
:
base(name, age, address, city, zip)
{
this.Company = company;
this.Id = empId;
this.Designation = dsgn;
}
[
Browsable(true)]
[
Description("Employer ID")]
public string Id { get; private set; }
[
Browsable(true)]
[
Description("Company employed in")]
public string Company { get; private set; }
[
Browsable(true)]
[
Description("Position in the company")]
public string Designation { get; private set; }
}
    public partial class SimplePropertyGrid : Form
{
public SimplePropertyGrid()
{
InitializeComponent();
}

private void SimplePropertyGrid_Load(object sender, EventArgs e)
{
// load sample data for association with property Grid
Employee emp = new Employee("Chuck W", 50, "E050918", "Thomson Reuters", "Architect",
"17400, Medina Rd", "Plymouth", "55447");
// bind object with property grid
propertyGrid.SelectedObject = emp;
}

private void rbShowHeader_CheckedChanged(object sender, EventArgs e)
{

}

private void rbShow_CheckedChanged(object sender, EventArgs e)
{
propertyGrid.ToolbarVisible = rbShow.Checked ?
true : false;
}
}




MultiLine Edit box in PropertyGrid
In order to achieve this we may have to do some more tweaking on attributes and this means some more concepts. In order to show a multi-line control we have to create a new class that's derived from UITypeEditor and override methods to show a control of our choice. UITypeEditor is a class used to design value editors. You can read more about it here

Editor - Associates a class that will show up the value editor for this property. In this case we will use a class of type UITypeEditor. We need to override

GetEditStyle method will say PropertyGrid the style for associated property. In this case it's a drop down. This can have other values like a Dialog or None.

EditValue method will bridge values to and from StudentMultiLineEditor into PropertyGrid
class StudentMultiLineEditor : UITypeEditor
{
public override UITypeEditorEditStyle GetEditStyle(System.ComponentModel.ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.DropDown;
}

public override object EditValue(System.ComponentModel.ITypeDescriptorContext context,
IServiceProvider provider, object value)
{
IWindowsFormsEditorService edSvc =
(IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
if (edSvc == null)
{
return null;
}

RichTextBox richTextBox = new RichTextBox();
richTextBox.Multiline = true;
richTextBox.Height = 100;
string sText = value as string;
if (string.IsNullOrEmpty(sText) == false)
{
richTextBox.Text = sText;
}
edSvc.DropDownControl(richTextBox);
return richTextBox.Text;
//return base.EditValue(context, provider, value);
}
}

Take a look at the Remarks property
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Drawing.Design;
using PropertyGridManip.MultiLine;

namespace PropertyGridManip.Business
{
public class Student : Person
{
public Student(string name, int age, string stdId, string school, string specialization
, string address, string city, string zip)
: base(name, age, address, city, zip)
{
this.School = school;
this.Id = stdId;
this.Specialization = specialization;
}

[Browsable(true)]
[Description("Student number")]
public string Id { get; private set; }
[Browsable(true)]
[Description("School/College Name")]
public string School { get; private set; }
[Browsable(true)]
[Description("Course specialization")]
public string Specialization { get; private set; }
[Browsable(true)]
[Description("Remarks about the student")]
[Editor(typeof(StudentMultiLineEditor), typeof(UITypeEditor))]
public string Remarks { get; set; }
}
}
Here's how the screen looks

ListBox in Property Grid
This is very similar to showing a multi-line edit control. We may have to do a some event handling on ListBox/Combobox to make sure control closes on selection. This code does not need any explanation more than what you see for Edit Control in property grid.

    public class ListTypeUIEditor : UITypeEditor
{
private ListBox lb = new ListBox();
private IWindowsFormsEditorService edSvc = null;

public override UITypeEditorEditStyle GetEditStyle(System.ComponentModel.ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.DropDown;
}

public override object EditValue(System.ComponentModel.ITypeDescriptorContext context, IServiceProvider provider,
object value)
{
edSvc =
(IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
if (edSvc == null)
return null;

lb.Click += new EventHandler(lb_Click);
lb.DataSource = GetListOfSpecializations();
string selectedValue = value as string;
if (string.IsNullOrEmpty(selectedValue))
{
lb.SelectedItem = selectedValue;
}
edSvc.DropDownControl(lb);

return lb.SelectedItem;
}

void lb_Click(object sender, EventArgs e)
{
if (edSvc != null)
{
edSvc.CloseDropDown();
}
}

private string[] GetListOfSpecializations()
{
String[] specialization = { "Computers", "Maths", "Biology", "Chemistry", "Physics", "history", "Geography" };
return specialization;
}
}
Make "Specialization" property in Student to be public, else this binding may not work
    public class CollegeStudent : Student
{
public CollegeStudent(string name, int age, string stdId, string school, string specialization
, string address, string city, string zip)
: base(name, age, stdId, school, specialization, address, city, zip)
{
}

[Browsable(true)]
[Description("Course specialization")]
[Editor(typeof(ListTypeUIEditor), typeof(UITypeEditor))]
public override string Specialization
{
get
{
return base.Specialization;
}
set
{
base.Specialization = value;
}
}
    }