Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

Gain Control Over PropertyGrid’s Description Area

3.88/5 (8 votes)
4 Dec 2008CPOL5 min read 40.1K   889  
Two ways to manipulate PropertyGrid's description area height
DescriptionAreaResize_src

Introduction

Earlier this year, while writing a Windows Forms application which uses .NET’s PropertyGrid, I ran into a problem—entry descriptions were too large for the description area. I inspected the PropertyGrid itself to see if I could somehow resize the description area to fit large descriptions, but to my chagrin, there was no member capable of doing so. I Googled a lot, but I could not find anything on how to change the height of the description area. Disappointed, I gave up; however, a few weeks later, while searching on more PropertyGrid related stuff, I stumbled across a message board post on the CodeProject article “PropertyGrid Utilities” which gave me some much needed hope. I used it as a starting point and the solution and this article are the results of that endeavor.

Please note: after I solved the problem, Geno Carman wrote The Code Project article: “Change the Height of a PropertyGrid’s Description Area”, but since my solution takes a different approach, I am providing this article. 

The First Solution

My first attempt to resolve the issue came from the message board post. The central part of this solution revolves around the private userSized boolean field of the PropertyGrid’s DocComment (a .NET internal class inheriting from System.Windows.Forms.Control).  By default, userSized is set to “false”, but we will set this to “true” using reflection. To get a reference to the PropertyGrid’s DocComment, we iterate through PropertyGrid.Controls. Intellisense won't show it, but PropertyGrid does have a ControlCollection because it is derived from Control. Create a new class which inherits from PropertyGrid. Enter the following in its constructor: 

C#
foreach (Control control in this.Controls)
{

} 

Now, we will locate the DocComment.  Once we know we have found the DocComment, we will get to work. We'll save both the reference to the DocComment and its Type to private fields. Add this inside your loop:

C#
Type controlType = control.GetType();
if (controlType.Name == "DocComment")
{
    this.docCommentType = controlType;
    this.docComment = control;
} 

Then, just following setting the fields, we obtain a reference using reflection to DocComment’s Lines property and save it to a private field for later use.

C#
this.linesProperty = this.docCommentType.GetProperty("Lines"); 

Once we do this, we finally acquire a reference to the userSized field and assign it a value of true. We only have to do this once, so we don't save any references to it. Insert this after the last statement we added:

C#
FieldInfo userSizedField = this.docCommentType.BaseType.GetField(
    "userSized", 
    BindingFlags.Instance | BindingFlags.NonPublic);
userSizedField.SetValue(this.docComment, true); 

After you do that, add the following else if statement after the if statement: 

C#
else if (controlType.Name == "PropertyGridView")
{
    this.propertyGridView = control;
}

This saves a reference to the main component of the PropertyGrid, the PropertyGridView.

Let’s now begin to use those references you saved earlier. We will create a property to expose the new functionality we are adding to PropertyGrid.  I'll explain this code in a moment.

C#
public int DescriptionAreaLineCount
{
    get 
    { 
        return this.descriptionAreaLineCount; 
    }

    set
    {
        if (value < 0)
        {
            throw new ArgumentException(
                "The value cannot be less than zero.");
        }

        if (this.docCommentType == null ||
            this.docComment == null ||
            this.propertyGridView == null ||
            this.linesProperty == null)
        {
            throw new TypeLoadException(
                "Not all of the objects required to set the field were found.");
        }

        try
        {
            int oldDocCommentHeight = this.docComment.Height;
            int oldValue = this.DescriptionAreaLineCount;
            this.linesProperty.SetValue(this.docComment, value, null);
            int difference = this.docComment.Height - oldDocCommentHeight;
            if (this.docComment.Top - difference > this.propertyGridView.Top)
            {
                this.sizeChangeIsFromUser = false;
                this.propertyGridView.Height -= difference;
                this.docComment.Top -= difference;
                this.descriptionAreaLineCount = value;
                this.sizeChangeIsFromUser = true;
            }
            else
            {
                this.linesProperty.SetValue(this.docComment, oldValue, null);
            }
        }
        catch (TargetInvocationException)
        {
        }

        this.Refresh();
    }
}

The getter is self-explanatory. We return the value of a private field named descriptionAreaLineCount. This field stores the value of the DocComment’s Lines property (for optimization purposes, instead of using reflection every time to get the value).  In the setter, we start by validating that the value supplied is not less than zero and also make sure all the objects needed to set the value are available. Then we set the value and check that the height change will not go above PropertyGridView. If the height change is too much, we roll it back. We then refresh the PropertyGrid

This wraps up the first solution.  It works, but there are issues that can arise in it, like the fact that setting the line count before the PropertyGrid’s handle is created has no effect upon it (this can be worked around by setting the line count in the OnCreateControl protected method or invoking CreateControl or CreateHandle (see MSDN documentation on CreateControl) before setting the line count, but it would be nice if this was not an issue).

Then I Realized...

After creating the solution I just showed you, I realized the implications of DocComment being a Control.  I had known that I could not directly access its fully derived form (because it is internal to the .NET library), but I realized that since it inherited from System.Windows.Forms.Control, anything that Control implemented would be directly available to me—including the Height property…why not just use it instead of DocComment's Lines property?    

The Better Solution

We can improve on our first solution to make this work.  All we do is add a property called DescriptionAreaHeight to the custom PropertyGrid.

C#
public int DescriptionAreaHeight
{
    get 
    { 
        return this.docComment.Height; 
    }

    set
    {
        int difference = value - this.docComment.Height;
        if (this.docComment.Top - difference > this.propertyGridView.Top)
        {
            this.docComment.Height = value;
            this.docComment.Top -= difference;
            this.propertyGridView.Height -= difference;
            this.Refresh();
        }
    }
}

In the getter, all we have to do is return the height of the DocComment using the reference we obtained in the constructor. In the setter, we calculate the difference in height, make sure we are not going to make the description area go too high, set the description area height, change DocComment.Top by the difference, and adjust the PropertyGridView height to reflect the difference so the “splitter” between the DocComment and the PropertyGridView stays the same.

That’s all for this solution!  You can set DescriptionAreaHeight anytime you want without worrying if the PropertyGrid’s handle has been created yet. And best of all, the only place reflection is needed is to set userSized to true.

Conclusion

You can now programmatically make the description area large enough for your needs, either in raw pixels or in numbers of lines. In my case, I also ended up using the DescriptionAreaHeight property to save the height of the description area when my application closes and restore it when it is launched again. 

In the source code, both solutions are implemented concurrently. To make the demo work, I also added an event named UserChangedDescriptionAreaSize that occurs when the user manually changes the description area height. To see the details, you can check out the source code and demo.

History

  • 5th December, 2008: Initial post

License

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