Click here to Skip to main content
Click here to Skip to main content

PropertyGrid Data Formatting

By , 29 Dec 2005
Rate this:
Please Sign up or sign in to vote.

Sample Image

Introduction

The application I'm currently working on relies heavily on the use of the .NET PropertyGrid to allow the user to view and modify data.

The PropertyGrid in its original format allows us to do most of the things we need. With little effort, it allows us to use data conversion, localization, grouping, and other forms of data presentation. Input formatting is one of the major drawbacks we have to overcome when using the PropertyGrid. This capability is almost totally absent from this control. Yet, we have a need to sometimes present data in a different format then it is stored.

The attached example shows how the PropertyGrid can be used to display password text fields and also formatted numeric string fields. In the example, the formatted properties are initially set to be blank values. To see the data formatting, you can simply start typing in the input field of the PropertyGrid.

Background

When presenting the data to the user, we ran into requirements to have a "password" field and formatted data input fields. With the password fields, the requirements are obvious. When the user types in this kind of a field, the typed characters are masked; typically, they are masked using the asterisk ("*") character. For the "formatted" input, the requirement is that the user must be able to input data using a predefined format only. This is mostly used when inputting numeric values with the decimal point separator embedded in them. For an additional twist, the decimal point indicator should be culture specific; so for US it is a period ("."), for most European customers, it would be a comma (",").

For example:

The format may look like this: "---.-- ". In this case, the user would be able to enter only numbers with three significant digits and, at the most, two decimal places. For the specific application we are working on, this type of formatting is very important. If the field does not contain any data, the format mask is displayed to inform the user how the data should be inputted. When the data is typed in the field, the entered text overwrites the format.

The .NET PropertyGrid with all its powers did not provide a straightforward solution to the above problems. I did some research on the Web to figure out how to implement password fields within the PropertyGrid control, but what I found were mostly questions and a few answers. So, I decided to do the work myself.

My first approach was to embed the editing control into the password property similar to the way date/time values are edited. In this case, the user clicks on the down arrow located at the right side of the edit field. This brings up the editor window into view; the user enters the data into the window and then presses the additional buttons to close the editor. This is a very simple and cheap approach. The users felt that this was too cumbersome and hard to use, so they requested that the password must be editable within the control's edit fields without using extra edit windows or controls.

A similar action was requested for the "formatted" field issue so they would work the same way.

To solve this problem, I decided that the easiest solution would be to create a custom grid control which derives from the .NET native PropertyGrid and then write all the necessary code to achieve this functionality for both of these requirements.

Using the code

First, derive the custom control class from the existing .NET PropertyGrid control. This is a trivial task, and in my case, the code looked like this, where CustomPropertyGrid is the name of my derived custom control. The private variables defined are used for different purposes which will be apparent from further code study. From that point, all that had to be done was to write code inside this class to create the desired functionality.

public class CustomPropertyGrid: System.Windows.Forms.PropertyGrid 
{
    private System.Windows.Forms.Control.ControlCollection gridControls = null;
    private TextBox m_TextBox = null;
    private int lastSelection;
    private string formatingString = "";
    private char formatSeparator = '.';

#region PROPERTIES
#endregion PROPERTIES

#region METHODS
#region CustomPropertyGrid Method
    //***************************************
    // Method Name: CustomPropertyGrid
    /// Krzysztof J. Stoj
    /// February 4, 2005
    //***************************************
    public CustomPropertyGrid() : base()
    { }
#endregion CustomPropertyGrid Method
}

The initial challenge was to find out what method to overwrite in the derived class to accomplish the desired effects. As it turned out, I only had to overwrite two methods: OnSelectedGridItemChanged(SelectedGridItemChangedEventArgs e) and OnSelectedObjectsChanged(EventArgs e). The first method sets the control in the mode allowing the password character masks, while the second one is used to reset the control to the default state and gather the necessary formatting data from the object being displayed within the grid. Of course, there are additional peripheral methods which aid the PropertyGrid's overwritten methods; and all these are described in this article.

As the user traverses the edit fields within the grid, there is a potential that they will end up on the password or formatted data field. Also, every time this happens, the OnSelectedGridItemChanged method is called. Because of this, we can gather all the information we need to perform data formatting when the method is called.

The first thing we need to do is to get the active control and selected item. This is done in the first few lines of the code in the method below. Once we have that, we called our own method SetupPropertyStyle. This method makes sure that we are on the password field. In such a case, it masks the field's input data; otherwise, it resets the mask to null. The method's logic is described later in this document.

In the second part of the OnSelectedGridItemChanged overwritten method, we grab a hold of an actual edit control (TextBox) for the property and attach some event handlers to it. Please note the interesting method used to determine if the control is actually the one we are interested in. Since the type reported by the PropertyGrid for this control is an internal (.NET) type, we have to do a hard coded string comparison. Of course, this approach is very static and will stop working if .NET changes the name of this type under us, but such is life. If that happens, we would have to modify our code and change the type name we are looking for to the new one.

So first of all, the SetupPropertyStyle method called at the beginning will mark the field editing properties to allow for the input masking. Also, in the same overwritten method, we are extracting the TextBox control so later on we are able to modify how its input is treated.

Once we have the TextBox control and we determine that the property requires special treatment of its input, we subscribe to the TextChanged event of the TextBox control. In other cases, the extracted control will not be used. Subscribing to the event allows us to respond to each key stroke within the TextBox control and to modify its contents at will. The actual implementation of the event handler is shown below. In order to determine if the currently edited property needs special treatment (is it a password field or maybe a formatted data field), I have introduced a custom attribute which I can use to decorate certain properties of my display object. The OnSelectedGridItemChanged method logic below shows the reference to that attribute, which is called PropertyViewFormatAttribute. This custom attribute will tell me if I'm dealing with a "password" type field or a "formatted" input type field. I have attached example code, which shows clearly how the property is implemented and used within the displayed object class.

protected override void 
         OnSelectedGridItemChanged(SelectedGridItemChangedEventArgs e)
{
    Control  activeControl;
    GridItem selectedItem;

    activeControl = base.ActiveControl;
    selectedItem  = base.SelectedGridItem;
    if( activeControl != null && selectedItem != null )
    {
        this.SetupPropertyStyle( activeControl, selectedItem );
    }
    
    if (m_TextBox != null ) 
    {
        m_TextBox.TextChanged -= 
             new EventHandler(m_TextBox_TextChanged);
    }

    if( m_TextBox == null )
    {
        foreach (Control control in base.ActiveControl.Controls) 
        {
            if (control.GetType().ToString() == 
                "System.Windows.Forms.PropertyGridInternal." + 
                "PropertyGridView+GridViewEdit" )
            {
                if (control is TextBox) 
                {
                    m_TextBox = control as TextBox; 
                    break;    
                }
            }
        }
    }

    foreach( System.Attribute attribute in 
             selectedItem.PropertyDescriptor.Attributes )
    {
      if( attribute.GetType() == typeof(PropertyViewFormatAttribute) )
      {
        if( ( ((PropertyViewFormatAttribute)attribute).AttributeFlags & 
          PropertyViewAttributeEnum.PropertyViewAttributes.FormattedField)
          == PropertyViewAttributeEnum.PropertyViewAttributes.FormattedField )
        {
          if( m_TextBox != null )
          {
            lastSelection = m_TextBox.SelectionStart;
            m_TextBox.TextChanged += 
             new EventHandler(m_TextBox_TextChanged);
          }
          break;
        }
      }
    }

    base.OnSelectedGridItemChanged (e);
}

The OnSelectedObjectsChanged(EventArgs e) method is much simpler; its main task is to restore the PropertyGrid style to the default values. It is called when a new object is assigned to the PropertyGrid control. This method also inquires about any data formatting information the displayed object might want to use for the "formatted" fields. In order to do that, a simple interface is implemented (the implementation is also shown in the attached sample project ). If the object passed to the PropertyGrid implements this interface, then the formatting data is obtained from the object for later use. The interface, as it is currently implemented, allows the PropertyGrid to get the format mask string and separator character from the SelectedObject. This information is kept inside the PropertyGrid object and used when the "formatted" field data is being edited.

This method is implemented like this:

protected override void OnSelectedObjectsChanged(EventArgs e)
{

    if( this.SelectedObject is IPropertyGrid )
    {
        this.formatingString = 
          ((IPropertyGrid)this.SelectedObject).FormatString;
        this.formatSeparator = 
          ((IPropertyGrid)this.SelectedObject).FormatSeparator;
    }

    if( gridControls != null )
        this.ResetPropertyStyle( gridControls );
    
    base.OnSelectedObjectsChanged (e);
}

The first line of code gets the formatting string and the format separator (remember: "." for US, and "," for Europe) from the SelectedObject. In order to do this, the displayed object must implement the IPropertyGrid interface.

The ResetPropertyStyle method basically clears the edit field setting applied by the SetupPropertyStyle method so the new object can be displayed without the password character masks in place.

Now, what I need to show you is how the setting and resetting of the property values (performed in SetupPropertyStyle and ResetPropertyStyle methods respectively) works. The first method sets the editing field to be a password type field and it looks like this:

private void SetupPropertyStyle(Control thisControl, GridItem viewItem)
{
    bool passwordField;
    System.ComponentModel.PropertyDescriptor viewProperty;
    System.ComponentModel.AttributeCollection attributes;
    System.Windows.Forms.Control.ControlCollection controls = 
                                        thisControl.Controls;

    gridControls = controls;
    viewProperty = viewItem.PropertyDescriptor;
    attributes = viewProperty.Attributes;
    passwordField = false;

    foreach( System.Attribute attribute in attributes )
    {
      if( attribute.GetType() == typeof(PropertyViewFormatAttribute) )
      {
        if( ( ((PropertyViewFormatAttribute)attribute).AttributeFlags & 
          PropertyViewAttributeEnum.PropertyViewAttributes.PasswordField)
          == PropertyViewAttributeEnum.PropertyViewAttributes.PasswordField )
        {
          passwordField = true;
          break;
        }
      }
    }

    if( passwordField )
    {
        foreach( Control control in controls )
        {
            System.Type baseType = control.GetType().BaseType;

            System.Reflection.PropertyInfo passwordCharProperty = 
                          baseType.GetProperty( "PasswordChar" );
            if( passwordCharProperty != null )
                passwordCharProperty.SetValue( control, '*', null );
        }
    }
    else
    {
        foreach( Control control in controls )
        {
            System.Type baseType = control.GetType().BaseType;

            System.Reflection.PropertyInfo passwordCharProperty = 
                          baseType.GetProperty( "PasswordChar" );
            if( passwordCharProperty != null )
                passwordCharProperty.SetValue( control, null, null );
        }
    }
}

In the SetupPropertyStyle method, all the necessary data elements are obtained up front. These are the property attributes, the property descriptor, and all the controls. Again, using the PropertyViewFormatAttribute, we determine if the current property needs any special formatting. These attributes will be attached in our data object class to the properties which are either password fields or require special formatting. In any case, we know that we will be dealing with the editing control which for the PropertyGrid is TextBox. Knowing this, and if the property is marked as a password field, then using .NET reflection capabilities, we get access to the PasswordChar property of the TextBox control we are dealing with. Then, we simply set this property to the password mask ("*") character. From now on, all the inputs into this control will use the PasswordChar as a mask.

That's all that needs to be done to get the "password" fields to mask their input. If the property displayed does not need any formatting, then the password mask is reset so we can deal with "normal" fields within the same grid as well.

private void ResetPropertyStyle(
        System.Windows.Forms.Control.ControlCollection thisControls)
{
    foreach( Control control in thisControls )
    {
        System.Type baseType = control.GetType().BaseType;

        System.Reflection.PropertyInfo passwordCharProperty = 
                      baseType.GetProperty( "PasswordChar" );
        if( passwordCharProperty != null )
            passwordCharProperty.SetValue( control, null, null );
    }
}

To reset the masking at the beginning of the display process, the ResetPropertyStyle method is used. This method simply walks through all the controls passed to it and if the control has the PasswordChar property (again obtained using the reflection methods), then the property value is set to null, thus removing the mask. At this point, we don't care if the property has a Password or Formatting attribute set or not; we are just trying to establish the starting point (default) value here, in case the previously displayed object had formatted data fields.

Finally, I have a few words about the TextChanged event handler that allows for the input of the formatted data. The event handler code looks like this:

private void m_TextBox_TextChanged(object sender, EventArgs e)
{
    System.Text.StringBuilder newText;
    string oldText = this.m_TextBox.Text;
    int stringLen = this.m_TextBox.Text.Length;
    int currentSelection = this.m_TextBox.SelectionStart;
    

    //
    // KJS. Get the caller type.
    //
    System.Type senderType = sender.GetType();

    //
    // KJS. It was established that the caller has "Modified" property.
    //      This property is set only when the data was modified, 
    //      not when the data is originally setup.
    //      Since this is very usefull information
    //      we are looking for this property.
    System.Reflection.PropertyInfo property =  
        senderType.GetProperty( "Modified" );
    
    //
    // KJS. Now get the property value
    //
    bool mod = (bool)property.GetValue( sender, null );

    //
    // KJS. Proceed with formatting only if data was actually changed
    //      which is indicated by set "Modified" property.
    //
    if( !mod )
        return;

    newText = new System.Text.StringBuilder(formatingString.Length);

    //
    // KJS. The string can only be as loong as the format allows
    //
    for( int i = 0, j = 0; i < formatingString.Length; i++ )
    {
        if( i < stringLen )
        {
            if( oldText[i] >= '0' && oldText[i] <= '9' )
            {
                if( formatingString[i] == formatSeparator )
                {
                    newText.Insert( j, formatingString[i] );
                    j++;
                    newText.Insert( j, oldText[i] );
                    if( this.lastSelection < currentSelection )
                        currentSelection++;
                    else
                        currentSelection--;
                }
                else
                {
                    newText.Insert( j, oldText[i] );
                }
            }
            else
                newText.Insert( j, formatingString[i] );
        }
        else
            newText.Insert( j, formatingString[i] );
        j++;
    }
    this.m_TextBox.Text = newText.ToString();
    if( currentSelection < 0 )
        currentSelection = 0;
    this.m_TextBox.Select(currentSelection, 0);
    this.lastSelection = currentSelection;
}

This method does the input formatting only for the properties which are marked to take the "formatted" input using our custom attributes. Again, .NET reflection is used to obtain all the necessary pieces of information. The biggest challenge when writing this method was to find out how to prevent data from being formatted when the TextBox value was originally set. At this point, the event is triggered automatically and the event handler method is called. Even if the field didn't require special formatting, the displayed data is modified by the code shown above. The solution is to find the property of the sender object which indicates if the data was just set or actually modified by the user. Of course in our case, I'm only interested in the actual events generated by the user and not by the initial value setting. Using the debugger and studying the reflected properties of the sender object, I found that the Modified boolean property on the sender contains the information I needed; it is set to true when the value is modified by the user and reset to false when initially set. The rest of the code in this method deals with data formatting using the format string and the separator character obtained in the OnSelectedObjectsChanged method and it should be self explanatory.

Points of Interest

An additional benefit gained while implementing the formatted fields is that we were able to change the PropertyGrid navigation mode to suit our customer's needs. For example the "Tab" key is used to navigate from one property edit field to another, which is not supported by the native .NET control. This was important for our customer and made the property grid easier for them to use. The implementation of this functionality, however, is not covered in this article. This was an additional challenge since the PropertyGrid categories needed to be taken into account. Also, one needs to be aware of the state (expanded or collapsed) of the categories. If someone is interested in this topic, I can respond privately or cover this in an additional article at a later time.

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

About the Author

kstoj
Web Developer
United States United States
Krzysztof "Kriss" Stoj works in the Software Development group for Itron Inc.
He is currently responsible for the UI development of the new meter reading system. He is also responsible for overlooking the localization aspects of the product.

Comments and Discussions

 
GeneralSerious disadvantage PinmemberRudolf Jan Heijink11-Apr-07 9:23 
GeneralRe: Serious disadvantage Pinmemberterwin14-May-07 3:36 
QuestionMaskedTextBox? PinmemberMark Beckwith19-Sep-06 8:25 
QuestionCapturing Click/Double Click event [modified] Pinmemberideru11-Jun-06 19:18 
GeneralBug Report PinmemberSteve Sugden28-Feb-06 5:25 
GeneralRe: Bug Report Pinmemberkstoj28-Feb-06 5:32 
GeneralRe: Bug Report PinmemberSteve Sugden28-Feb-06 5:35 
GeneralRe: Bug Report PinmemberMarkus Ulbricht6-Mar-06 22:41 
GeneralRe: Bug Report Pinmemberkstoj8-Mar-06 6:44 
GeneralNavigation Pinmembergatemaster9927-Feb-06 0:49 
GeneralRe: Navigation Pinmemberkstoj27-Feb-06 6:22 
GeneralRe: Navigation Pinmembertribbles9-May-06 13:22 
GeneralRe: Navigation Pinmemberkstoj9-May-06 13:43 
GeneralRe: Navigation Pinmembertribbles9-May-06 14:27 
GeneralDisplaying events in PropertyGrid Pinmemberarno rouschen3-Jan-06 20:50 
GeneralRe: Displaying events in PropertyGrid Pinmemberkstoj4-Jan-06 5:39 
GeneralRe: Displaying events in PropertyGrid PinmemberManju631-Jul-07 22:13 
GeneralRe: Displaying events in PropertyGrid PinmemberSivaVissamsetti6-Nov-07 19:58 
General.NET 2.0 has PropGrid Password Field Attribute PinmemberTonster10130-Dec-05 2:21 
GeneralRe: .NET 2.0 has PropGrid Password Field Attribute Pinmemberkstoj30-Dec-05 5:18 
GeneralRe: .NET 2.0 has PropGrid Password Field Attribute PinmemberBengie7-Apr-06 5:41 
GeneralRe: .NET 2.0 has PropGrid Password Field Attribute Pinmembernomats6-Sep-07 1:51 

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

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

| Advertise | Privacy | Mobile
Web04 | 2.8.140415.2 | Last Updated 29 Dec 2005
Article Copyright 2005 by kstoj
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid