Click here to Skip to main content
15,860,859 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 250.6K   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

 
QuestionGratitude Pin
Genís Ortiz 202126-Feb-24 5:25
Genís Ortiz 202126-Feb-24 5:25 
QuestionSplitter position Pin
Zek Patafta16-Oct-16 18:21
Zek Patafta16-Oct-16 18:21 
QuestionHow to display DisplayName and Description in SomeOtherResource.resources Pin
Member 125017808-Jul-16 3:53
Member 125017808-Jul-16 3:53 
GeneralMy vote of 5 Pin
Code Artist20-Apr-16 3:53
professionalCode Artist20-Apr-16 3:53 
QuestionProblem getting it to work in my app Pin
Karrok8-Mar-16 1:23
Karrok8-Mar-16 1:23 
AnswerRe: Problem getting it to work in my app Pin
wout de zeeuw8-Mar-16 3:12
wout de zeeuw8-Mar-16 3:12 
QuestionLicense Pin
BMW74012-Jul-12 23:11
BMW74012-Jul-12 23:11 
GeneralMy vote of 5 Pin
cdinten11-Jun-12 3:10
cdinten11-Jun-12 3:10 
GeneralRe: My vote of 5 Pin
wout de zeeuw19-Jun-12 4:31
wout de zeeuw19-Jun-12 4:31 
GeneralFinally Pin
LW0027-Jan-10 4:40
LW0027-Jan-10 4:40 
GeneralRe: Finally Pin
wout de zeeuw27-Jan-10 5:05
wout de zeeuw27-Jan-10 5:05 
GeneralTab! Tab! Tab! Pin
Ryan McFarren20-Mar-09 10:48
Ryan McFarren20-Mar-09 10:48 
GeneralRe: Tab! Tab! Tab! Pin
wout de zeeuw20-Mar-09 17:42
wout de zeeuw20-Mar-09 17:42 
GeneralRe: Tab! Tab! Tab! Pin
D. de Kerf10-Nov-14 1:35
D. de Kerf10-Nov-14 1:35 
GeneralNew solution with greater flexibility Pin
Mizan Rahman22-Feb-09 4:12
Mizan Rahman22-Feb-09 4:12 
QuestionHow do I use the Enter key for tabbing through items. Pin
Paresh B Joshi16-May-08 7:26
Paresh B Joshi16-May-08 7:26 
AnswerRe: How do I use the Enter key for tabbing through items. Pin
xiwang10-Oct-08 0:42
xiwang10-Oct-08 0:42 
GeneralRe: How do I use the Enter key for tabbing through items. Pin
Paresh B Joshi13-Oct-08 4:26
Paresh B Joshi13-Oct-08 4:26 
GeneralDisplay collections as child nodes Pin
Len202025-Feb-08 9:53
Len202025-Feb-08 9:53 
GeneralFantastic Pin
Angelus1234519-Jul-07 1:23
Angelus1234519-Jul-07 1:23 
GeneralRe: Fantastic Pin
wout de zeeuw19-Jul-07 1:59
wout de zeeuw19-Jul-07 1:59 
GeneralGreat Pin
mileni21-Mar-07 23:31
mileni21-Mar-07 23:31 
GeneralRe: Great Pin
wout de zeeuw30-May-07 3:10
wout de zeeuw30-May-07 3:10 
GeneralRemoving Properties inherited from PictureBox Pin
khoonseng13-Dec-06 19:56
khoonseng13-Dec-06 19:56 
GeneralProperty sorting in VS2005 c++ Pin
khoonseng13-Dec-06 19:43
khoonseng13-Dec-06 19:43 

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.