65.9K
CodeProject 正在变化。 阅读更多。
Home

控制 PropertyGrid 的描述区域

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.88/5 (8投票s)

2008年12月5日

CPOL

5分钟阅读

viewsIcon

40686

downloadIcon

921

两种操作 PropertyGrid 描述区域高度的方法

DescriptionAreaResize_src

引言

今年早些时候,在编写一个使用 .NET PropertyGrid 的 Windows Forms 应用程序时,我遇到了一个问题——条目描述对于描述区域来说太大了。我检查了 PropertyGrid 本身,看看是否能以某种方式调整描述区域的大小以适应大的描述,但令我失望的是,没有任何成员能够做到这一点。我谷歌了很多,但找不到任何关于如何更改描述区域高度的方法。失望之余,我放弃了;然而,几周后,在搜索更多 PropertyGrid 相关内容时,我偶然发现了 CodeProject 文章“PropertyGrid 工具”上的一篇消息板帖子,这给了我急需的希望。我将其作为起点,解决方案和本文就是这次努力的结果。

请注意:在我解决了这个问题之后,Geno Carman 写了 CodeProject 文章:“更改 PropertyGrid 的描述区域高度”,但由于我的解决方案采用了不同的方法,所以我提供了这篇文章。

第一个解决方案

我解决问题的第一个尝试来自消息板帖子。这个解决方案的核心部分围绕着 PropertyGridDocComment(一个继承自 System.Windows.Forms.Control 的 .NET 内部类)的 private userSized 布尔字段。默认情况下,userSized 设置为“false”,但我们将使用反射将其设置为“true”。要获取对 PropertyGridDocComment 的引用,我们遍历 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;
} 

然后,在设置字段之后,我们使用反射获取对 DocCommentLines 属性的引用,并将其保存到 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 是不言而喻的。我们返回一个名为 descriptionAreaLineCountprivate 字段的值。该字段存储 DocCommentLines 属性的值(出于优化目的,而不是每次都使用反射来获取值)。在 setter 中,我们首先验证提供的值不小于零,并确保设置该值所需的所有对象都可用。然后我们设置值,并检查高度变化是否会超过 PropertyGridView。如果高度变化过大,我们会将其回滚。然后我们刷新 PropertyGrid

第一个解决方案就到此为止了。它有效,但可能会出现一些问题,例如在 PropertyGrid 的句柄创建之前设置行数对其没有影响(这可以通过在 OnCreateControl protected 方法中设置行数,或在设置行数之前调用 CreateControlCreateHandle(请参阅 MSDN 文档中的 CreateControl)来解决,但如果这不是一个问题就更好了)。

然后我意识到...

在创建了刚才展示的解决方案之后,我意识到 DocComment 是一个 Control 的含义。我一直知道我无法直接访问它的完全派生形式(因为它对 .NET 库是内部的),但我意识到由于它继承自 System.Windows.Forms.ControlControl 实现的**任何**东西都将直接对我可用——包括 Height 属性……为什么不直接使用它而不是 DocCommentLines 属性呢?

更好的解决方案

我们可以改进第一个解决方案使其生效。我们所要做的就是在自定义 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 的高度以反映差值,以便 DocCommentPropertyGridView 之间的“分割线”保持不变。

这就是本解决方案的全部内容!您可以随时设置 DescriptionAreaHeight,而无需担心 PropertyGrid 的句柄是否已创建。最重要的是,唯一需要使用反射的地方就是将 userSized 设置为 true

结论

您现在可以以编程方式使描述区域足够大以满足您的需求,无论是原始像素还是行数。在我的例子中,我还使用 DescriptionAreaHeight 属性在应用程序关闭时保存描述区域的高度,并在应用程序再次启动时将其恢复。

在源代码中,两种解决方案是并发实现的。为了使演示工作,我还添加了一个名为 UserChangedDescriptionAreaSize 的事件,当用户手动更改描述区域高度时会触发该事件。要查看详细信息,您可以查看源代码和演示。

历史

  • 2008 年 12 月 5 日:初始帖子
© . All rights reserved.