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:
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:
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.
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:
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:
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.
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
.
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