Dynamic Property Grid

This entry is part 1 of 5 in the series C#: Property Grid

There are a bunch of resources around for adding property grid to your .NET 4.5 project however I often find that they are really bits and pieces of the solution and very rarely an entire overview. There have been examples of using a class as your property grids object. But what if you are working with dynamic data in which a static class may not be the answer? I will provide an example below of just such a case which includes a parent type static class as well. If you have any questions please feel free to ask.

The first step is to create our type builder and to do that we must create the assembly, builder and module.

 AssemblyName assemblyName = new AssemblyName("ASSEMBLY_NAME_YOU_WANT");

AssemblyBuilder assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);

ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule"MODULE_NAME_YOU_WANT");

Here we need to define our type. Give it any name you want and what types it will extend. The third option is optional. If you have a parent type that you also want to add to the dynamic type this is where you do it.

 TypeBuilder typeBuilder = moduleBuilder.DefineType("TYPE_NAME_YOU_WANT", TypeAttributes.Public | 
                              TypeAttributes.Class | TypeAttributes.AutoClass | TypeAttributes.AnsiClass | 
                              TypeAttributes.BeforeFieldInit | TypeAttributes.AutoLayout, 
                              Type.GetType("NAMESPACE.CLASSNAME"));

If we used the parent type this is what it could look like. Up to you really.

 namespace AwesomeProject.PropertyGrid
{    
	public class MyName
	{
		public MyName() { }

		[Browsable(true)]
		[Category(##MYCATEGORY##)]
		[ReadOnly(false)]
		[Description(##MYDESCRIPTION##)]
		public DropDownList MyAwesomeProperty 
		{
			get;
			set;
		}
	}
}

If the property in your parent type is using a custom editor than you need to add it using the following.

 [Editor(typeof(CustomEditor), typeof(UITypeEditor))]

Here is where we need to dynamically add to our type builder. You probably have some sort of call to database which returns your options. So just loop through them and add to the builder.
Create the private field. Field Type can be anything. String, DateTime, Int32, Double. It could even be a type you create which I will explain later.

 FieldBuilder fieldBuilder = typeBuilder.DefineField(##FIELDNAME##, ##FIELDTYPE##, FieldAttributes.Private);

Now we create the public property.

 PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(##FIELDNAME##, System.Reflection.PropertyAttributes.None, ##FIELDTYPE##, new[] { ##FIELDTYPE## });

Next we need to define the required set of property attributes for this property we created.

 MethodAttributes propertyAttributes = MethodAttributes.Public | 
                  MethodAttributes.SpecialName | 
                  MethodAttributes.HideBySig;

This part is really important. Let’s say we created our own type. Like a dropdown list or check box list. We need to define is TypeDescriptor here.

 TypeDescriptor.AddAttributes(typeof(##CLASS##), new EditorAttribute(typeof(##CUSTOMEDITOR##), typeof(System.Drawing.Design.UITypeEditor)));

Next we define the getter.

 MethodBuilder getter = typeBuilder.DefineMethod("get_" + ##PROPERTYNAME##, propertyAttributes, ##FIELDTYPE##, Type.EmptyTypes);

ILGenerator getterIlGen = getter.GetILGenerator();

getterIlGen.Emit(Oppres.Ldarg_0);

getterIlGen.Emit(Oppres.Ldfld, fieldBuilder);

getterIlGen.Emit(Oppres.Ret);

Next we define the setter.

 MethodBuilder setter = typeBuilder.DefineMethod("set_" + ##PROPERTYNAME##, propertyAttributes, null, new Type[] { ##FIELDTYPE## });

ILGenerator setterIlGen = setter.GetILGenerator();

setterIlGen.Emit(Oppres.Ldarg_0);

setterIlGen.Emit(Oppres.Ldarg_1);

setterIlGen.Emit(Oppres.Stfld, fieldBuilder);

setterIlGen.Emit(Oppres.Ret);

Bind the setter and getter to the property builder

 propertyBuilder.SetGetMethod(getter);
propertyBuilder.SetSetMethod(setter);

At this point we can also set the category attribute.

 propertyBuilder.SetCustomAttribute(new CustomAttributeBuilder(
typeof(CategoryAttribute).GetConstructor(
new Type[] { typeof(string) }), new object[] { ##CATEGORYNAME## }));

At this point we can also set the description attribute.

 propertyBuilder.SetCustomAttribute(new CustomAttributeBuilder(
typeof(DescriptionAttribute).GetConstructor(
new Type[] { typeof(string) }), new object[] { ##DESCRIPTION##}));

At this point we can also set the read only attribute.

 propertyBuilder.SetCustomAttribute(new CustomAttributeBuilder(typeof(ReadOnlyAttribute).GetConstructor(new Type[] { typeof(bool) }), new object[] { ##ISREADONLY## }));

Next we need to create and instantiate the dynamic type.

 Type type = typeBuilder.CreateType();
dynamicType = Activator.CreateInstance(type, new object[] { });

Here is where we can set the values for each property we added.

 foreach (PropertyInfo pi in properties)
{
      pi.SetValue(dynamicType, ##THEVALUE##, null);
}
PROPERTY_GRID.SelectedObject = dynamicType;

Side Notes:

If you want to change readonly attributes adding all the fields and before rendering to the property grid you can do that after the Activator.CreateInstance line.

 ReadOnlyAttribute attrib = (ReadOnlyAttribute)TypeDescriptor.GetProperties(dynamicType)["PROPERTY_NAME"].Attributes[typeof(ReadOnlyAttribute)];
FieldInfo isReadOnly = attrib.GetType().GetField("isReadOnly", BindingFlags.NonPublic | BindingFlags.Instance);
isReadOnly.SetValue(attrib, true);


If you want o collapse all categories there is a property on the property grid for that but there isn’t one for selectively only having one category show with the remaining closed. I can’t recall the url where I got some assistance on this so if anyone knows it please link it in the comments.

 GridItem root = pgRequest.SelectedGridItem;

if (root != null)
{
      do
      {
            root = root.Parent;

            if (root == null)
                  break;
      } while (root.GridItemType != GridItemType.Root);

      if (root != null)
      {
            foreach (GridItem category in root.GridItems)
            {
                  //really you can do anything you want here this is just an example.
                  //The important part is the Expanded = false.
                  if (category.Label == "CATEGORY_TO_CLOSE")
                  {
                        category.Expanded = false;
                        break;
                  }
            }
      }
}

PropertyGrid: DropDown Editor

This entry is part 2 of 5 in the series C#: Property Grid

You may want to add a dropdown to your property grid. If you do then the below code is what you will need to create the editor.

Build the DropDown Editor

 public class DropDownEditor : System.Drawing.Design.UITypeEditor
{
      private IWindowsFormsEditorService es = null;
      public override UITypeEditorEditStyle GetEditStyle(System.ComponentModel.ITypeDescriptorContext context)
      {
            return UITypeEditorEditStyle.DropDown;
      }

      public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
      {
            if (provider != null)
            {
                  // This service is in charge of popping our ListBox.
                  es = ((IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService)));

                  if (es != null && value is DropDownList)
                  {
                        dynamic property = (DropDownList)value;
                        var list = new ListBox();
                        list.Click += ListBox_Click;

                        if (property.Values != null)
                        {
                              foreach (object item in property.Values)
                              {
                                    var propertyInfo = item.GetType().GetProperty(property.DisplayMember);
                                    list.Items.Add(propertyInfo.GetValue(item, null));
                              }
                        }
                        // Drop the list control.
                        es.DropDownControl(list);

                        if (list.SelectedItem != null && list.SelectedIndices.Count == 1)
                        {
                              property.SelectedItem = list.SelectedItem;
                              value = property;
                        }
                  }
            }

            return value;
      }

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

PropertyGrid: DropDown Property

This entry is part 3 of 5 in the series C#: Property Grid

You may want to add a dropdown to your property grid. If you do then the below code is what you will need to create the property. You will also need the editor.

Build a DropDown Property Type

 public class DropDownList : INotifyPropertyChanged
{
      private dynamic _values;
      private dynamic selectedItem;
      public event PropertyChangedEventHandler PropertyChanged;
      public event PropertyValueChangedEventHandler PropertyValueChanged;

      public DropDownList(String name, PropertyGrid pg)
      {
            PropertyName = name;
            PG = pg;
      }

      private String PropertyName { get; set; }
      private PropertyGrid PG { get; set; }

      public dynamic Values
      {
            get
            {
                  return _values;
            }
            set
            {
                  if (value != null)
                        _values = value;
            }
      }

      public string ValueMember { get; set; }
      public string DisplayMember { get; set; }

      [Browsable(false)]
      public dynamic SelectedItem
      {
            get
            {
                  return selectedItem;
            }
            set
            {
                  String oldValue = selectedItem;
                  selectedItem = value;

                  if (PropertyChanged != null)
                        PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));

                  if (PG != null)
                  {
                        if (PropertyValueChanged != null)
                              PropertyValueChanged(this, new PropertyValueChangedEventArgs(PG.SelectedGridItem, oldValue));
                  }
            }
      }

      public override string ToString()
      {
            return SelectedItem;
      }
}

PropertyGrid: CheckBoxList Editor

This entry is part 4 of 5 in the series C#: Property Grid

You may want to add a checkboxlist to your property grid. If you do then the below code is what you will need to create the editor.

Build the CheckBox List Editor

 public class CheckBoxListEditor : System.Drawing.Design.UITypeEditor
{
      private IWindowsFormsEditorService es = null;
      
      public override UITypeEditorEditStyle GetEditStyle(System.ComponentModel.ITypeDescriptorContext context)
      {
            return UITypeEditorEditStyle.DropDown;
      }

      public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
      {
            if (provider != null)
            {
                  // This service is in charge of popping our ListBox.
                  es = ((IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService)));

                  if (es != null && value is CheckBoxList)
                  {
                        var property = (CheckBoxList)value;
                        var cl = new CheckedListBox();
                        cl.CheckOnClick = true;

                        Dictionary<String, Boolean> vals = new Dictionary<string, bool>();

                        foreach (KeyValuePair<String, Boolean> kvp in property.Values)
                        {
                              vals[kvp.Key] = kvp.Value;
                              cl.Items.Add(kvp.Key, kvp.Value);
                        }

                        // Drop the list control.
                        es.DropDownControl(cl);

                        if (cl.SelectedItem != null )
                        {
                              vals[cl.SelectedItem.ToString()] = !vals[cl.SelectedItem.ToString()];
                              property.Values = vals;
                              property.SelectedItem = String.Join(", ", (from v in vals where v.Value == true select v.Key).ToList());
                              value = property;
                        }
                  }
            }

            return value;
      }
}

PropertyGrid: CheckBoxList Property

This entry is part 5 of 5 in the series C#: Property Grid

Build a CheckBoxList Property Type

 

 

public class CheckBoxList : INotifyPropertyChanged
{
	private dynamic _values;
	private dynamic selectedItem;
	public event PropertyChangedEventHandler PropertyChanged;
	public event PropertyValueChangedEventHandler PropertyValueChanged;

	public CheckBoxList(String name, PropertyGrid pg)
	{
		PropertyName = name;
		PG = pg;
	}

	private String PropertyName { get; set; }
	private PropertyGrid PG { get; set; }

	public dynamic Values
	{
		get
		{
			return _values;
		}
		set
		{
			if (value != null)
			_values = value;
		}
	}

	public string ValueMember { get; set; }
	public string DisplayMember { get; set; }

	[Browsable(false)]
	public dynamic SelectedItem
	{
		get
		{
			return selectedItem;
		}
		set
		{
			String oldValue = selectedItem;
			selectedItem = value;
	
			if (PropertyChanged != null)
				PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));

			if (PG != null)
			{
				if (PropertyValueChanged != null)
					PropertyValueChanged(this, new PropertyValueChangedEventArgs(PG.SelectedGridItem, oldValue));
			}
		}
	}

	public override string ToString()
	{
		return SelectedItem;
	}
}