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

DataGridViewHTMLCell - 在 DataGridView 中显示 HTML 标记

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (7投票s)

2015年6月2日

CPOL

6分钟阅读

viewsIcon

40389

在 DataGridView 中显示 HTML 标记

引言

最近,我希望在 DataGridView 单元格中显示一些 HTML 内容。然而,在网上搜索之后,我没有找到任何符合我需求的内容(我什么都没找到)。于是我决定研究一下编写自定义 DataGridView 单元格需要做些什么。

基本上,要创建自己的自定义单元格,你需要从三个与 DataGridView 控件相关的基类创建/派生。它们是:

  • 从 DataGridViewColumn 派生一个类
  • 从 DataGridViewCell 派生一个类
  • 实现 IDataGridViewEditingControl 接口的类派生

下面是演示应用程序的截图。

此项目的源代码/示例应用程序可以在 GitHub 上找到:https://github.com/OceanAirdrop/DataGridViewHTMLCell

HTML 渲染器

为了在单元格中渲染 HTML 内容,我将使用 HTMLRenderer 库。这是一个非常棒的框架。它轻巧,拥有自己的渲染引擎(没有外部依赖),并且只包含 2 个 .DLL 文件。请在此处查看:https://htmlrenderer.codeplex.com/ 以获取更多信息。

要将 HTMLRenderer 添加到您的项目中,您可以选择使用 nuget 自动添加引用,或者从 codeplex/github 下载 .dll 文件并在 C# 项目中手动引用它们。

从 DataGridViewColumn 派生 (第 1 步,共 3 步)

让我们先看看需要派生的 3 个类中的第一个:DataGridViewColumn 类。MSDN 指出 DataGridViewColumn 类“表示 DataGridView 控件中的逻辑列”。这个类是必需的,并为 DataGridView 的工作提供了框架。它让 DataGridView 知道该列中包含什么类型的单元格。

这是完整的类

代码片段 1:设置 DataGridViewHTMLColumn

public class DataGridViewHTMLColumn : DataGridViewColumn
{
    public DataGridViewHTMLColumn() : base(new DataGridViewHTMLCell())
    { 
    }

    public override DataGridViewCell CellTemplate
    {
        get
        {
            return base.CellTemplate;
        }
        set
        {
            if (!(value is DataGridViewHTMLCell))
                throw new InvalidCastException("CellTemplate must be a DataGridViewHTMLCell");

            base.CellTemplate = value;  
        }
    }
}

简洁明了,嗯?这里没什么可看的。- 基本上,我们创建了一个名为 DataGridViewHTMLColumn 的类。请注意,在构造函数中,我们实例化了一个 DataGridViewHTMLCell(标有红色)。我们还重写了 CellTemplate 属性。CellTemplate 属性“设置用于创建新单元格的模板”。

从 DataGridViewCell 派生 (第 2 步,共 3 步)

这里将是我们大部分自定义代码所在的地方。DataGridViewCell 类表示 DataGridView 控件中的单个单元格。我们的大部分代码将驻留在此类中。

让我们看一下 Paint 方法

代码片段 2:DataGridViewCell Paint 方法

protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates cellState, object value, 
                              object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, 
                              DataGridViewPaintParts paintParts)
{
    base.Paint(graphics, clipBounds, cellBounds, rowIndex, cellState, null, null, errorText, cellStyle, advancedBorderStyle, paintParts);

    Image img = GenerateHTMLImage(rowIndex, value, base.Selected);

    if (img != null)
    {
        graphics.DrawImage(img, cellBounds.Left, cellBounds.Top);
    }
}

Paint 函数的目的是在屏幕上绘制 HTML。graphics.DrawImage(img, cellBounds.Left, cellBounds.Top); 这行代码实际上执行了绘制,但大部分工作被外包给了另一个函数:GenerateHTMLImage()。此函数生成将绘制到屏幕上单元格位置的 HTML 图像。

让我们仔细看看 GenerateHTMLImage() 函数

代码片段 3:GenerateHTMLImage 函数

private Image GenerateHTMLImage(int rowIndex, object value, bool selected)
{
    // Step 1: Get Size of DataGridView Cell.
    Size cellSize = GetSize(rowIndex);

    if (cellSize.Width < 1 || cellSize.Height < 1)
        return null;

    // Step 2: Set HTML Text to render.
    m_editingControl.Size = GetSize(rowIndex);
    SetHTMLPanelText(m_editingControl, Convert.ToString(value));

    if (m_editingControl != null)
    {
        // Step 3: Render HTML Image.
        Image htmlImage = null; Size fullImageSize;

        if ( RenderHTMLImage( Convert.ToString(value), cellSize, out htmlImage, out fullImageSize ) == false )
        {
            Console.WriteLine("Failed to Generate HTML image");
            return null;
        }

        // Step 4: If necessary, add an elipsis (...) to image.
        if ( fullImageSize.Height > cellSize.Height ) 
        {
            // there is more html than being displayed!! Lets add some elipsis (...) to the image
            // to let the user know there is more to display

            htmlImage = AddElipsisToImage(htmlImage);
        }

        return htmlImage;
    }

    return null;
}

跟随上面代码片段中的红色注释,我们首先要做的是获取单元格的大小。如果宽度或高度小于 1,我们直接返回 null,因为没有什么值得绘制的。

在第 2 步中,我们设置 HTML 的 ".Text" 属性(以便它知道要渲染哪个 HTML)。“value”变量是实际的 HTML 标记。

在第 3 步中,我们调用 RenderHTMLImage() 函数。该函数返回要显示的 HTML 的图像表示。

最后,在第 4 步中,我们检查是否需要在图像的右下角添加省略号。稍后将对此进行详细讨论。

现在让我们看一下 RenderHTMLImage() 函数

代码片段 4:实际渲染 HTML 图像

private bool RenderHTMLImage(string htmlText, Size cellSize, out Image htmlImage, out Size fullImageSize)
{
    bool bResult = true;

    // Step 1: Set out parameters
    htmlImage = null;
    fullImageSize = new System.Drawing.Size();
            
    try
    {
        // Step 2: Check for null html text
        if (string.IsNullOrEmpty(htmlText) == true)
            htmlText = "This cell has a null value!";

        // Need to render image twice! Once to get the full size of image and once to get image clipped to the size of the cell

        // Step 3: Render the html image using the full height but keep the cell width.
        htmlImage = HtmlRender.RenderToImage(htmlText, maxWidth: cellSize.Width);

        // Step 4: Keep a record of the full image size to send back to caller.
        fullImageSize.Height = htmlImage.Height;
        fullImageSize.Width = htmlImage.Width;

        // Step 5: Render the HTML imaage a second time with the cell size width / height
        htmlImage = HtmlRender.RenderToImage(htmlText, new Size(cellSize.Width, cellSize.Height)); 

        m_editingControl.Text = htmlText;
    }
    catch(Exception ex)
    {
        bResult = false;
    }
            
    return bResult;
}

这里要关注的主要代码部分在步骤 3 和 5 中。本质上,我们是通过第三方 HTMLRenderer 库来渲染 HTML 为图像。

htmlImage = HtmlRender.RenderToImage(htmlText, new Size(cellSize.Width, cellSize.Height)); 

上面的这行代码是我们利用 HTMLRenderer 库的功能的地方。

实现 IDataGridViewEditingControl 接口 (第 3 步,共 3 步)

起初我没有实现这个接口,但这意味着用户无法与此控件交互。单元格感觉不自然。用户无法将单元格中的文本复制到剪贴板。然后我(最初)尝试使用简单的 TextBox 控件,但这显示了完整的标记文本(我怀疑您不希望用户看到这一点)。让我们看一下代码

代码片段 5:设置 DataGridView 编辑控件

public class DataGridViewHTMLEditingControl : HtmlLabel, IDataGridViewEditingControl
{
    private DataGridView m_dataGridView;
    private int m_rowIndex;
    private bool m_valueChanged;

    public DataGridViewHTMLEditingControl()
    {
        this.BorderStyle = BorderStyle.None;
        AutoSize = false;
    }
}

简而言之,这个类的代码看起来就像上面的代码片段(为了简洁起见,我省略了重写的接口方法)。需要注意的重要一点是,这个类是 HtmlLabel。我们从 HtmlLabel 类派生。因此,DataGridViewHTMLEditingControl 是一个 HtmlLabel。为了满足成为可编辑 DataGridView 控件的要求,我们还需要实现 IDataGridViewEditingControl 接口中的方法。这些接口方法没有什么令人兴奋的地方,因此我没有在此处显示它们。您可以在上面引用的 GitHub 页面上查看它们。

完成了!

至此,我们已完成。就是这样。我们已经实现了创建 DataGridView 控件中自定义单元格所需的全部 3 个类。它可以很好地显示 HTML 内容。但在总结之前,我想先讨论一下上面提到的省略号代码。

附加:在单元格右下角添加省略号 (...)

我在玩弄 HTML 单元格时注意到,如果有更多文本要显示,单元格不会向用户发出任何指示,让他们知道“还有更多”。然后我创建了一个简单的文本单元格,并用随机文本填充它,注意到它显示了一个省略号 (...) 来告诉用户:“嘿!如果你想调整我的大小,还有更多文本供我显示”。这就是省略号的由来。

在上面的代码片段 4 中,您可能注意到我渲染了两次 HTML 图像,并且您可能想:“你为什么要这样做?”。嗯,我第一次渲染图像时,让 HTMLRenderer 渲染完整的文本。这会产生包含完整 HTML 的图像。然后我第二次渲染 HTML,但这次将图像大小限制为单元格的宽度和高度。现在,我们可以检查完整图像的高度是否大于单元格图像的高度。如果是,我们就知道还有更多 HTML 需要显示,可以在单元格中添加省略号。

添加省略号的代码如下:

代码片段 6:向单元格添加省略号

Image AddElipsisToImage(Image img)
{
    // This function will grab a graphics object from the image and then draw another image on-top
    Graphics g = Graphics.FromImage(img);

    // elipsis
    g.DrawImage(Resources.elipsis, new Point(img.Width - Resources.elipsis.Width, img.Height - Resources.elipsis.Height));

    return img;
}

我发现的唯一限制是,当单元格处于编辑模式时,控件由自身绘制,这意味着(显然)省略号不会显示。但这在意料之中。

结论

这是使用出色的第三方库“HTMLRenderer”创建自定义 HTML DataGridView 单元格所涉及步骤的简要概述。此项目的代码可在 Github 上找到,这意味着您可以自由地根据需要添加、编辑和修改此代码。

资源

HTML 渲染器

https://htmlrenderer.codeplex.com/

CodeProject.com 上的文章“RichTextBox Cell in a DataGridView”,作者:MrWisdom

https://codeproject.org.cn/Articles/31823/RichTextBox-Cell-in-a-DataGridView

如何:通过扩展行为和外观自定义 Windows 窗体 DataGridView 控件中的单元格和列

https://msdn.microsoft.com/en-us/library/vstudio/7fb61s43(v=vs.100).aspx

本文代码

https://github.com/OceanAirdrop/DataGridViewHTMLCell/

© . All rights reserved.