Click here to Skip to main content
15,887,585 members
Articles / Programming Languages / C#

PropertyGrid Utilities

Rate me:
Please Sign up or sign in to vote.
4.90/5 (44 votes)
7 Jan 2016CPOL4 min read 252.4K   7.8K   153   75
An article on sorting and globalization of properties in a PropertyGrid

Image 1

Image 2

Introduction

This article is on sorting and globalization of properties in a PropertyGrid.

Features:

  • Declarative (static) definition of property behaviour: property order, DisplayName, globalization of DisplayName, Description, and Category.
  • Dynamic definition of property behaviour using a callback. For each property, a property attributes provider can be set in which all attributes mentioned above can be overridden. Also, IsBrowsable and IsReadOnly can be modified at runtime.
  • Use the Tab key for tabbing through items.
  • Globalized tool tips of tool bar buttons.

It's easy to use and has ways to modify the place it looks for resources.

Background

I ripped code from two projects, fused and improved them. The sorting idea was ripped from Paul Tingey, his PropertySorter class. Globalization was ripped from Globalized Property Grid by Gerd Klevesaat. He, however, used inheritance from GlobalizationObject, which makes the mechanism not so flexible. Plus, his implementation was very slow (especially in combination with sorting), and didn't include Category globalization. The dynamic behaviour was developed by George Soules and me.

Using the Code

The example below basically shows most of the possibilities:

C#
using System;
using System.ComponentModel;

using PropertyGridUtils;

namespace MyNameSpace
{
    [TypeConverter(typeof(PropertiesDeluxeTypeConverter))]
    class SomeClassWithProperties {
        string lastName = "Zeeuw";
        string occupation = "Software engineer";

        int dynamicProperty = 3;
        string dynamicPropertyDisplayName = "Dynamic property";
        bool dynamicPropertyIsVisible = true;
        bool dynamicPropertyIsReadOnly = false;

        string propertyWithResourcesElseWhere = "Dummy";
        SomeClassWithProperties child;

        // DisplayName and Description are gotten from resource file
        // MyNameSpace.SomeClassWithProperties.resources.
        [PropertyOrder(0)]
        [GlobalizedProperty(CategoryId = "GlobalCategory")]
        public string LastName {
            get {
                return lastName;
            }
        }

        // DisplayName and Description are gotten from resource file
        // MyNameSpace.SomeClassWithProperties.resources.
        [PropertyOrder(1)]
        [GlobalizedProperty(CategoryId = "GlobalCategory")]
        public string Occupation {
            get {
                return occupation;
            }
        }

        // Behaviour of this property is partially defined runtime.
        [PropertyOrder(2)]
        [Description("This property is partially dynamic.")]
        [PropertyAttributesProvider("DynamicPropertyAttributesProvider")]
        public int DynamicProperty {
            get {
                return dynamicProperty;
            }
            set {
                dynamicProperty = value;
            }
        }

        public void DynamicPropertyAttributesProvider(PropertyAttributes attributes) {
            attributes.DisplayName = dynamicPropertyDisplayName;
            attributes.IsReadOnly = dynamicPropertyIsReadOnly;
            attributes.IsBrowsable = dynamicPropertyIsVisible;
        }

        [PropertyOrder(3)]
        [DisplayName("Dynamic property display name")]
        public string DynamicPropertyDisplayName {
            get {
                return dynamicPropertyDisplayName;
            }
            set {
                dynamicPropertyDisplayName = value;
            }
        }

        [PropertyOrder(4)]
        [DisplayName("Dynamic property visible")]
        public bool DynamicPropertyIsVisible {
            get {
                return dynamicPropertyIsVisible;
            }
            set {
                dynamicPropertyIsVisible = value;
            }
        }

        [PropertyOrder(5)]
        [DisplayName("Dynamic property readonly")]
        public bool DynamicPropertyIsReadOnly {
            get {
                return dynamicPropertyIsReadOnly;
            }
            set {
                dynamicPropertyIsReadOnly = value;
            }
        }

        // DisplayName and Description are gotten from resource file
        // SomeOtherResources.resources
        [PropertyOrder(6)]
        [GlobalizedProperty(
                            BaseName = "SomeOtherResources",
                            DisplayNameId = "Dummy.DisplayName",
                            DescriptionId = "Dummy.Description"
                            )]
        public string PropertyWithResourcesElseWhere {
            get {
                return propertyWithResourcesElseWhere;
            }
        }

        // Property with sub properties for testing
        // tabbing accross expanded/non expanded items.
        [PropertyOrder(7)]
        public SomeClassWithProperties Child {
            get {
                if (child == null) {
                    child = new SomeClassWithProperties();
                }
                return child;
            }
        }
     }
}

Static Usage

Property behaviour is defined using the attributes DisplayNameAttribute, DescriptionAttribute, GlobalizedPropertyAttribute, GlobalizedTypeAttribute, and PropertyOrderAttribute.

Usage without resources is simple, users can use the DisplayNameAttribute and DescriptionAttribute to directly set the DisplayName and Description. But when working like this, the properties are not globalized.

Using resource files enables globalization, but is slightly more complicated. By default, resources are gotten from a file namespace.class.resources. This can, if needed, be controlled with the GlobalizedPropertyAttribute or GlobalizedTypeAttribute. The resource for DisplayName is gotten from the ResourceManager from the resource string SomeProperty.DisplayName. Similarly, the Description is gotten from SomeProperty.Description. This can also be controlled through the GlobalizedPropertyAttribute though, so, e.g., description strings can be reused in multiple properties/classes. Category is, by default, gotten from SomeProperty.Category. To reuse a category string in a resource file, you can specify [GlobalizedProperty(CategoryId = "GlobalCategory")] (thanks to Paul Tingey for the idea).

Dynamic Usage

It is possible to define a PropertyAttributesProviderAttribute at a property. The method name mentioned there will be called by the PropertiesDeluxeTypeConverter with a PropertyAttributes object as the only parameter. The method can then change all attributes there like IsBrowsable, IsReadOnly, DisplayName, etc. The PropertyGrid must be refreshed (Refresh()) for the changes to take effect.

Tabbing Through Properties

To make use of the Tab key feature and globalized tool tips for the tool bar buttons, use CustomPropertyGrid. It also has a property ExpandOnTab which sets whether to expand an expandable item when Tab is pressed. Note that pressing the Enter key also achieves that.

Points of Interest

If no resource file is there for the neutral culture, globalization is disabled. But still, PropertiesDeluxeTypeConverter can then be used for sorting and setting the DisplayName of properties.

One could set up the resources to, e.g., use only one resource file. There is no protection from name clashes then though. The namespace could be prepended to the resource names, but then the names would get very long.

For adding languages for the tool tips, compile satellite assemblies containing the PropertyGridUtils.CustomPropertyGrid.<language>.resources file (don't forget signing and giving it the correct assembly version, see PropertyGridUtils\compileResources.bat). The assembly must be located in a <language> subdir, like the nl example.

New Status Bar Class (.NET 1.1 Only)

I also wrote a small status bar class because the standard .NET 1.1 status bar doesn't allow embedding of controls (the .NET 2.0 StatusStrip is more powerful). It's not much code so it's probably not worth devoting a separate article to it (and there's already one somewhere on CodeProject doing something similar). One feature that is in this version that I like is that both absolute and relative widths can be mixed in the status bar items. E.g., two items can be defined, one of absolute width 50 pixels, relative width 70%, and the other item 200 pixels, relative width 30%. If the total status bar width is 1000 pixels, then 200 + 50 = 250 pixels are fixed. The remaining 750 pixels are divided according to the relative sizes.

History

  • 7th April 2006: Migrated to .NET 2.0 (DisplayNameAttribute is now part of the .NET libraries)
  • 21st June 2005: Improved tabbing to go over all expanded items instead of items on one level (1.0.0.3). Also added a new status bar class.
  • 19th October 2004: Cooperated with George Soules to add dynamic behaviour. (Assembly version is now 1.0.0.2.)
  • 12th September 2004: Added Tab key feature and globalized tool tips for tool bar buttons
  • 21st July 2004: First version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Netherlands Netherlands
Wout works as a freelancer based in the Netherlands. His interests are .NET, 3D, DWG, DXF, OpenGL, Computational Geometry and playing Tank Ball!

One of his latest private projects is a commercial DWG DXF .NET library and small DWG DXF viewing application.

Comments and Discussions

 
GeneralAttibute fuction in other class Pin
praveenqwe6-Nov-06 17:46
praveenqwe6-Nov-06 17:46 
GeneralRe: Attibute fuction in other class Pin
wout de zeeuw7-Nov-06 0:48
wout de zeeuw7-Nov-06 0:48 
GeneralGreat! Pin
Querulant26-Jul-06 10:57
Querulant26-Jul-06 10:57 
GeneralRe: Great! Pin
wout de zeeuw20-Aug-06 0:38
wout de zeeuw20-Aug-06 0:38 
GeneralCategory Localisation doesn't work when using multiple SelectedObjects Pin
Jerleth.15-May-06 0:53
Jerleth.15-May-06 0:53 
GeneralRe: Category Localisation doesn't work when using multiple SelectedObjects Pin
wout de zeeuw15-May-06 7:07
wout de zeeuw15-May-06 7:07 
GeneralRe: Category Localisation doesn't work when using multiple SelectedObjects Pin
Stuart Wells4-Jul-06 5:24
Stuart Wells4-Jul-06 5:24 
AnswerSolution: Category Localisation doesn't work when using multiple SelectedObjects Pin
Niels.Bos9-May-07 2:44
Niels.Bos9-May-07 2:44 
Stuart, I think I may have a solution for you. My project is based on the code here, but different in many respects, so please let me know if I need to make any changes;)
My problem existed on two levels. The first was duplicate order numbers. Though after fixing problem 2, this one seems to have disappeared for me. Please let me know if this fixes the problem for anyone else.
After solution one, I had another problem when I multi-selected two classes derived from one and the same base class. In one of the inherited classes I overrided one of my ordered properties with an PropertyOrder somewhere in the middle (start or end works fine for some reason). In this override I added the [Browsable(false)]. Now most of my combined properties were gone with multi-selection.
After many tries with PropertyDescriptorCollection overloads, IEquatable/IComparable overloads, trying to get a hook into the PropertyGrid itself and quite a few other advanced tests it seemed nothing would even influence it. I had noticed that a simple removal of the PropertyOrder in my baseclass solved the problem though. This got me to thinking. I traced the flow through my PropertiesDeluxeTypeConverter class, checking the GetProperties() code. I noticed that for each item in the multiselect array, the GetProperties was called. I also noticed that my context.Instance gave me access to the array of selected objects. With a single object, context.Instance would not be an array. Bingo! I figured to skip any property descriptors for the current object, if it were non-IsBrowsable in even one of the other objects in the array of selected objects!
First I changed my base call from
PropertyDescriptorCollection baseProps = base.GetProperties(context, value, attributes);
to
PropertyDescriptorCollection baseProps = base.GetProperties(context, value, null);
This way my non-IsBrowsable attributes were no longer filtered. We filter in our own loop anyways. My final code looks like this: (note I use GetProperties from Type to avoid infinite loops on the PropertiesDeluxeTypeConverter typeconverter.
	public class ExpandableSortableObjectConverter : ExpandableObjectConverter<br />
	{<br />
		public override PropertyDescriptorCollection GetProperties(<br />
			ITypeDescriptorContext context,<br />
			object value,<br />
			Attribute[] attributes)<br />
		{<br />
			// Get the collection of properties<br />
			PropertyDescriptorCollection baseProps =<br />
				base.GetProperties(context, value, null);<br />
			PropertyDescriptorCollection deluxeProps =<br />
				new PropertyDescriptorCollection(null);<br />
<br />
			// For each property use a property descriptor of <br />
			// our own that has custom behaviour.<br />
			ArrayList orderedPropertyAttributesList = new ArrayList();<br />
			foreach (PropertyDescriptor oProp in baseProps)<br />
			{<br />
				PropertyAttributes propertyAttributes = GetPropertyAttributes(oProp, value);<br />
				bool browsable = propertyAttributes.IsBrowsable && context != null && context.Instance != null;<br />
				if (browsable && context.Instance is Array)<br />
				{<br />
					foreach (object obj in (Array)context.Instance)<br />
					{<br />
						Type type = obj.GetType();<br />
						PropertyInfo[] propInfos = type.GetProperties();<br />
						foreach (PropertyInfo propInfo in propInfos)<br />
						{<br />
							if (propInfo.PropertyType != oProp.PropertyType || propInfo.Name != oProp.Name)<br />
								continue;<br />
							BrowsableAttribute[] browsableAttribs  = (BrowsableAttribute[])propInfo.GetCustomAttributes(typeof(BrowsableAttribute), true);<br />
							if (browsableAttribs.Length > 0 && !browsableAttribs[0].Browsable)<br />
							{<br />
								browsable = false;<br />
								break;<br />
							}//if<br />
						}//foreach<br />
						if (!browsable)<br />
							break;<br />
					}//foreach<br />
				}//if<br />
				if (browsable)<br />
				{<br />
					orderedPropertyAttributesList.Add(propertyAttributes);<br />
					PropertyDescriptorEx oPropEx = new PropertyDescriptorEx(oProp, propertyAttributes);<br />
					deluxeProps.Add(oPropEx);<br />
				}//if<br />
			}//foreach<br />
            bool sortAlpha = false;  // Sort alphabetically?<br />
            if (context != null)<br />
            {<br />
                PropertyInfo propInfoOwnerGrid = context.GetType().GetProperty("OwnerGrid");<br />
                _PropertyGridEx grid = propInfoOwnerGrid.GetValue(context, null) as _PropertyGridEx;<br />
                Debug.Assert(grid != null);<br />
                if (grid.PropertySort == System.Windows.Forms.PropertySort.Alphabetical)<br />
                    sortAlpha = true;<br />
            }<br />
            if (sortAlpha)<br />
                orderedPropertyAttributesList.Sort(new AlphabeticalPropertyComparer());<br />
            else<br />
                orderedPropertyAttributesList.Sort();<br />
			//<br />
			// Build a string list of the ordered names<br />
			//<br />
			ArrayList propertyNames = new ArrayList();<br />
			foreach (PropertyAttributes propertyAttributes in orderedPropertyAttributesList)<br />
				propertyNames.Add(propertyAttributes.Name);<br />
			//<br />
			// Pass in the ordered list for the PropertyDescriptorCollection to sort by.<br />
			// (Sorting by passing a custom IComparer somehow doesn't work.<br />
			//<br />
			return deluxeProps.Sort((string[])propertyNames.ToArray(typeof(string)));<br />
		}<br />


Hope it helps! If anyone has any comments or suggestions, please let me knowSmile | :)
GeneralRe: Solution: Category Localisation doesn't work when using multiple SelectedObjects Pin
Len202025-Feb-08 9:48
Len202025-Feb-08 9:48 
GeneralLocalize the child property's names in PropertyGrid. Pin
krzychub7-Jan-06 6:55
krzychub7-Jan-06 6:55 
GeneralRe: Localize the child property's names in PropertyGrid. Pin
wout de zeeuw7-Jan-06 7:41
wout de zeeuw7-Jan-06 7:41 
GeneralRe: Localize the child property's names in PropertyGrid. Pin
krzychub7-Jan-06 9:09
krzychub7-Jan-06 9:09 
GeneralRe: Localize the child property's names in PropertyGrid. Pin
wout de zeeuw7-Jan-06 23:42
wout de zeeuw7-Jan-06 23:42 
QuestionHow to control the height of the PropertyGrid comment area Pin
dsk303719-Dec-05 9:12
dsk303719-Dec-05 9:12 
AnswerRe: How to control the height of the PropertyGrid comment area Pin
gedri25-Dec-06 6:27
gedri25-Dec-06 6:27 
Generalto show/replace it in the Properties View Pin
gan.gary17-Dec-05 15:43
gan.gary17-Dec-05 15:43 
GeneralRe: to show/replace it in the Properties View Pin
wout de zeeuw17-Dec-05 22:31
wout de zeeuw17-Dec-05 22:31 
GeneralRe: to show/replace it in the Properties View Pin
gan.gary23-Dec-05 15:45
gan.gary23-Dec-05 15:45 
GeneralPerformance Pin
Tuakisan5-Dec-05 19:47
Tuakisan5-Dec-05 19:47 
GeneralPerformance Pin
Tuakisan15-Dec-05 23:20
Tuakisan15-Dec-05 23:20 
GeneralRe: Performance Pin
wout de zeeuw16-Dec-05 8:44
wout de zeeuw16-Dec-05 8:44 
QuestionLicensing? Pin
Eugene Polonsky19-Aug-05 8:36
Eugene Polonsky19-Aug-05 8:36 
AnswerRe: Licensing? Pin
Steve Maier19-Aug-05 9:13
professionalSteve Maier19-Aug-05 9:13 
GeneralRe: Licensing? Pin
Eugene Polonsky19-Aug-05 9:47
Eugene Polonsky19-Aug-05 9:47 
GeneralRe: Licensing? Pin
Steve Maier19-Aug-05 9:56
professionalSteve Maier19-Aug-05 9:56 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.