控制 PropertyGrid 的描述区域






3.88/5 (8投票s)
两种操作 PropertyGrid 描述区域高度的方法

引言
今年早些时候,在编写一个使用 .NET PropertyGrid
的 Windows Forms 应用程序时,我遇到了一个问题——条目描述对于描述区域来说太大了。我检查了 PropertyGrid
本身,看看是否能以某种方式调整描述区域的大小以适应大的描述,但令我失望的是,没有任何成员能够做到这一点。我谷歌了很多,但找不到任何关于如何更改描述区域高度的方法。失望之余,我放弃了;然而,几周后,在搜索更多 PropertyGrid
相关内容时,我偶然发现了 CodeProject 文章“PropertyGrid 工具”上的一篇消息板帖子,这给了我急需的希望。我将其作为起点,解决方案和本文就是这次努力的结果。
请注意:在我解决了这个问题之后,Geno Carman 写了 CodeProject 文章:“更改 PropertyGrid 的描述区域高度”,但由于我的解决方案采用了不同的方法,所以我提供了这篇文章。
第一个解决方案
我解决问题的第一个尝试来自消息板帖子。这个解决方案的核心部分围绕着 PropertyGrid
的 DocComment
(一个继承自 System.Windows.Forms.Control
的 .NET 内部类)的 private userSized
布尔字段。默认情况下,userSized
设置为“false
”,但我们将使用反射将其设置为“true
”。要获取对 PropertyGrid
的 DocComment
的引用,我们遍历 PropertyGrid.Controls
。Intellisense 不会显示它,但 PropertyGrid
有一个 ControlCollection
,因为它继承自 Control
。创建一个继承自 PropertyGrid
的新类。在其构造函数中输入以下内容:
foreach (Control control in this.Controls)
{
}
现在,我们将定位 DocComment
。一旦我们知道我们找到了 DocComment
,我们就可以开始工作了。我们将 DocComment
的引用及其 Type
保存到 private
字段中。将此添加到您的循环中
Type controlType = control.GetType();
if (controlType.Name == "DocComment")
{
this.docCommentType = controlType;
this.docComment = control;
}
然后,在设置字段之后,我们使用反射获取对 DocComment
的 Lines
属性的引用,并将其保存到 private
字段中以备将来使用。
this.linesProperty = this.docCommentType.GetProperty("Lines");
完成此操作后,我们最终获得了对 userSized
字段的引用,并将其赋值为 true
。我们只需要执行一次此操作,因此我们不保存对其的任何引用。将此插入到我们添加的最后一个语句之后。
FieldInfo userSizedField = this.docCommentType.BaseType.GetField(
"userSized",
BindingFlags.Instance | BindingFlags.NonPublic);
userSizedField.SetValue(this.docComment, true);
完成此操作后,将以下 else if
语句添加到 if
语句之后:
else if (controlType.Name == "PropertyGridView")
{
this.propertyGridView = control;
}
这会保存对 PropertyGrid
主组件 PropertyGridView
的引用。
现在让我们开始使用您之前保存的那些引用。我们将创建一个属性来公开我们正在向 PropertyGrid
添加的新功能。我稍后会解释这段代码。
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();
}
}
getter 是不言而喻的。我们返回一个名为 descriptionAreaLineCount
的 private
字段的值。该字段存储 DocComment
的 Lines
属性的值(出于优化目的,而不是每次都使用反射来获取值)。在 setter 中,我们首先验证提供的值不小于零,并确保设置该值所需的所有对象都可用。然后我们设置值,并检查高度变化是否会超过 PropertyGridView
。如果高度变化过大,我们会将其回滚。然后我们刷新 PropertyGrid
。
第一个解决方案就到此为止了。它有效,但可能会出现一些问题,例如在 PropertyGrid
的句柄创建之前设置行数对其没有影响(这可以通过在 OnCreateControl protected
方法中设置行数,或在设置行数之前调用 CreateControl
或 CreateHandle
(请参阅 MSDN 文档中的 CreateControl
)来解决,但如果这不是一个问题就更好了)。
然后我意识到...
在创建了刚才展示的解决方案之后,我意识到 DocComment
是一个 Control
的含义。我一直知道我无法直接访问它的完全派生形式(因为它对 .NET 库是内部的),但我意识到由于它继承自 System.Windows.Forms.Control
,Control
实现的**任何**东西都将直接对我可用——包括 Height
属性……为什么不直接使用它而不是 DocComment
的 Lines
属性呢?
更好的解决方案
我们可以改进第一个解决方案使其生效。我们所要做的就是在自定义 PropertyGrid
中添加一个名为 DescriptionAreaHeight
的属性。
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();
}
}
}
在 getter 中,我们只需要返回 DocComment
的高度,使用我们在构造函数中获得的引用。在 setter 中,我们计算高度差,确保我们不会让描述区域过高,设置描述区域的高度,通过差值更改 DocComment.Top
,并调整 PropertyGridView
的高度以反映差值,以便 DocComment
和 PropertyGridView
之间的“分割线”保持不变。
这就是本解决方案的全部内容!您可以随时设置 DescriptionAreaHeight
,而无需担心 PropertyGrid
的句柄是否已创建。最重要的是,唯一需要使用反射的地方就是将 userSized
设置为 true
。
结论
您现在可以以编程方式使描述区域足够大以满足您的需求,无论是原始像素还是行数。在我的例子中,我还使用 DescriptionAreaHeight
属性在应用程序关闭时保存描述区域的高度,并在应用程序再次启动时将其恢复。
在源代码中,两种解决方案是并发实现的。为了使演示工作,我还添加了一个名为 UserChangedDescriptionAreaSize
的事件,当用户手动更改描述区域高度时会触发该事件。要查看详细信息,您可以查看源代码和演示。
历史
- 2008 年 12 月 5 日:初始帖子