You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义DataGridView控件搜索结果高亮功能的问题求助

DataGridView自定义控件文本高亮优化方案

问题背景

我正在开发一个继承自DataGridView的自定义控件,需要实现搜索结果在单元格文本中的高亮显示。目前已重写OnCellPainting方法实现,但存在两个核心问题:

  • 位置计算依赖大量硬编码数值,无法适配不同DPI环境,高亮位置偏移
  • 文本对齐方式适配逻辑未生效,不同对齐下高亮框位置错误

当前高亮“19”的效果:
Highlighting 19

原实现代码

protected override void OnCellPainting(DataGridViewCellPaintingEventArgs e)
{
    if ((e.RowIndex <= -1) || (e.ColumnIndex <= -1) || string.IsNullOrWhiteSpace(HighlightedText) ||
        (e.Value is null) || (e.Value == DBNull.Value) || e.Value.GetType().IsImage())
    {
        base.OnCellPainting(e);
        return;
    }

    var cellText = e.FormattedValue.ToString();
    var ind = cellText.IndexOf(HighlightedText, StringComparison.OrdinalIgnoreCase);
    if (ind < 0)
    {
        base.OnCellPainting(e);
        return;
    }

    var newLineInd = cellText.IndexOf(Environment.NewLine);

    var rect = new Rectangle { Y = e.CellBounds.Y + 2 };

    var before = cellText[..ind];

    var flags = TextFormatFlags.TextBoxControl;

    //I tried this but doesn't seem to work
    var pos = e.CellStyle.Alignment switch
    {
        DataGridViewContentAlignment.TopLeft => TextFormatFlags.Top | TextFormatFlags.Left,
        DataGridViewContentAlignment.TopCenter => TextFormatFlags.Top | TextFormatFlags.HorizontalCenter,
        DataGridViewContentAlignment.TopRight => TextFormatFlags.Top | TextFormatFlags.Right,
        DataGridViewContentAlignment.MiddleLeft => TextFormatFlags.VerticalCenter | TextFormatFlags.Left,
        DataGridViewContentAlignment.MiddleCenter => TextFormatFlags.VerticalCenter | TextFormatFlags.HorizontalCenter,
        DataGridViewContentAlignment.MiddleRight => TextFormatFlags.VerticalCenter | TextFormatFlags.Right,
        DataGridViewContentAlignment.BottomLeft => TextFormatFlags.Bottom | TextFormatFlags.Left,
        DataGridViewContentAlignment.BottomCenter => TextFormatFlags.Bottom | TextFormatFlags.HorizontalCenter,
        DataGridViewContentAlignment.BottomRight => TextFormatFlags.Bottom | TextFormatFlags.Right,
        _ => TextFormatFlags.Left,
    };

    flags |= pos;
    var match = cellText.Substring(ind, HighlightedText.Length);
    var sizeBefore = TextRenderer.MeasureText(e.Graphics, before, e.CellStyle.Font, e.CellBounds.Size, flags);
    var sizeMatch = TextRenderer.MeasureText(e.Graphics, match, e.CellStyle.Font, e.CellBounds.Size, flags);

    if (sizeBefore.Width > 5)
    {
        rect.X = e.CellBounds.X + sizeBefore.Width - 5;
        rect.Width = sizeMatch.Width - 6;

        //if match is on the new line
        if ((newLineInd != -1) && (ind > newLineInd))
        {
            var cellRow2Index = ind - newLineInd;
            var breakWord = cellText.Substring(newLineInd, cellRow2Index);
            var s3 = TextRenderer.MeasureText(e.Graphics, breakWord, e.CellStyle.Font, e.CellBounds.Size,
                                              TextFormatFlags.TextBoxControl);
            rect.X = e.CellBounds.X + 2 + s3.Width - 7;
            rect.Y = e.CellBounds.Y + sizeMatch.Height + 2;
        }
    }
    else
    {
        rect.X = e.CellBounds.X + 2;
        rect.Width = sizeMatch.Width - 6;
    }

    if (newLineInd == -1)
    {
        var sizeCell = TextRenderer.MeasureText(e.Graphics, cellText, e.CellStyle.Font, e.CellBounds.Size,
                                                TextFormatFlags.TextBoxControl);
        if (sizeCell.Width < e.CellBounds.Width)
        {
            rect.Y = e.CellBounds.Y + ((e.CellBounds.Height - sizeCell.Height) / 2);
        }
    }

    rect.Height = sizeMatch.Height;
    e.Handled = true;
    e.PaintBackground(e.CellBounds, true);
    e.Graphics.FillRectangle(_highlightBrush, rect);
    e.PaintContent(e.CellBounds);
}

优化方案

核心思路

  1. 移除所有硬编码数值,基于单元格内边距、文本测量尺寸和系统DPI计算高亮位置
  2. 修复文本对齐适配逻辑,统一使用TextFormatFlags进行测量和位置计算
  3. 完善多行文本的高亮位置计算逻辑

完整优化代码

protected override void OnCellPainting(DataGridViewCellPaintingEventArgs e)
{
    if (e.RowIndex <= -1 || e.ColumnIndex <= -1 || string.IsNullOrWhiteSpace(HighlightedText)
        || e.Value is null || e.Value == DBNull.Value || e.Value.GetType().IsImage())
    {
        base.OnCellPainting(e);
        return;
    }

    string cellText = e.FormattedValue.ToString();
    int matchStartIndex = cellText.IndexOf(HighlightedText, StringComparison.OrdinalIgnoreCase);
    if (matchStartIndex < 0)
    {
        base.OnCellPainting(e);
        return;
    }

    // 获取单元格样式与内边距
    DataGridViewCellStyle style = e.CellStyle;
    Padding cellPadding = style.Padding;
    Rectangle contentBounds = new Rectangle(
        e.CellBounds.X + cellPadding.Left,
        e.CellBounds.Y + cellPadding.Top,
        e.CellBounds.Width - cellPadding.Horizontal,
        e.CellBounds.Height - cellPadding.Vertical
    );

    // 转换对齐方式到TextFormatFlags
    TextFormatFlags textFlags = TextFormatFlags.TextBoxControl | TextFormatFlags.WordBreak;
    textFlags |= style.Alignment switch
    {
        DataGridViewContentAlignment.TopLeft => TextFormatFlags.Top | TextFormatFlags.Left,
        DataGridViewContentAlignment.TopCenter => TextFormatFlags.Top | TextFormatFlags.HorizontalCenter,
        DataGridViewContentAlignment.TopRight => TextFormatFlags.Top | TextFormatFlags.Right,
        DataGridViewContentAlignment.MiddleLeft => TextFormatFlags.VerticalCenter | TextFormatFlags.Left,
        DataGridViewContentAlignment.MiddleCenter => TextFormatFlags.VerticalCenter | TextFormatFlags.HorizontalCenter,
        DataGridViewContentAlignment.MiddleRight => TextFormatFlags.VerticalCenter | TextFormatFlags.Right,
        DataGridViewContentAlignment.BottomLeft => TextFormatFlags.Bottom | TextFormatFlags.Left,
        DataGridViewContentAlignment.BottomCenter => TextFormatFlags.Bottom | TextFormatFlags.HorizontalCenter,
        DataGridViewContentAlignment.BottomRight => TextFormatFlags.Bottom | TextFormatFlags.Right,
        _ => TextFormatFlags.Left
    };

    // 拆分文本为匹配前、匹配、匹配后三部分
    string beforeMatch = cellText[..matchStartIndex];
    string matchText = cellText.Substring(matchStartIndex, HighlightedText.Length);

    // 计算各部分文本的尺寸
    Size beforeSize = TextRenderer.MeasureText(e.Graphics, beforeMatch, style.Font, contentBounds.Size, textFlags);
    Size matchSize = TextRenderer.MeasureText(e.Graphics, matchText, style.Font, contentBounds.Size, textFlags);
    Size fullTextSize = TextRenderer.MeasureText(e.Graphics, cellText, style.Font, contentBounds.Size, textFlags);

    // 计算文本起始位置
    Point textOrigin = GetTextOrigin(contentBounds, fullTextSize, textFlags);
    Point matchOrigin = textOrigin;

    // 处理多行文本的高亮位置
    if (beforeSize.Height > style.Font.Height)
    {
        string[] lines = cellText.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
        int currentLine = 0;
        int charCount = 0;
        
        // 定位匹配文本所在的行
        while (currentLine < lines.Length && charCount + lines[currentLine].Length < matchStartIndex)
        {
            charCount += lines[currentLine].Length + Environment.NewLine.Length;
            currentLine++;
        }
        
        // 计算当前行内匹配文本的X偏移
        string lineBeforeMatch = lines[currentLine].Substring(0, matchStartIndex - charCount);
        Size lineBeforeSize = TextRenderer.MeasureText(e.Graphics, lineBeforeMatch, style.Font, contentBounds.Size, textFlags);
        
        matchOrigin.X = contentBounds.Left + (textFlags.HasFlag(TextFormatFlags.HorizontalCenter) 
            ? (contentBounds.Width - lineBeforeSize.Width - matchSize.Width) / 2 + lineBeforeSize.Width
            : textFlags.HasFlag(TextFormatFlags.Right) 
                ? contentBounds.Right - lineBeforeSize.Width - matchSize.Width 
                : lineBeforeSize.Width);
        matchOrigin.Y = textOrigin.Y + (style.Font.Height + style.Padding.Vertical) * currentLine;
    }
    else
    {
        // 单行文本的X偏移计算
        matchOrigin.X += textFlags.HasFlag(TextFormatFlags.HorizontalCenter)
            ? (contentBounds.Width - fullTextSize.Width) / 2 + beforeSize.Width
            : textFlags.HasFlag(TextFormatFlags.Right)
                ? contentBounds.Width - fullTextSize.Width + beforeSize.Width
                : beforeSize.Width;
    }

    // 构建高亮矩形并确保不超出单元格范围
    Rectangle highlightRect = new Rectangle(matchOrigin, matchSize);
    highlightRect.Intersect(contentBounds);

    // 执行绘制流程
    e.Handled = true;
    e.PaintBackground(e.CellBounds, true);
    e.Graphics.FillRectangle(_highlightBrush, highlightRect);
    e.PaintContent(e.CellBounds);
}

// 辅助方法:计算文本在内容区域内的起始坐标
private Point GetTextOrigin(Rectangle contentBounds, Size textSize, TextFormatFlags flags)
{
    int x = contentBounds.Left;
    int y = contentBounds.Top;

    if (flags.HasFlag(TextFormatFlags.HorizontalCenter))
        x += (contentBounds.Width - textSize.Width) / 2;
    else if (flags.HasFlag(TextFormatFlags.Right))
        x += contentBounds.Width - textSize.Width;

    if (flags.HasFlag(TextFormatFlags.VerticalCenter))
        y += (contentBounds.Height - textSize.Height) / 2;
    else if (flags.HasFlag(TextFormatFlags.Bottom))
        y += contentBounds.Height - textSize.Height;

    return new Point(x, y);
}

关键优化点说明

  • DPI适配:所有位置计算依赖系统测量的文本尺寸和单元格内边距,无硬编码数值,自动适配不同DPI环境
  • 对齐逻辑修复:通过GetTextOrigin方法精确计算文本起始位置,确保高亮区域与文本位置完全匹配
  • 多行文本处理:拆分文本行后单独计算匹配行的偏移,解决多行场景下的高亮错位问题
  • 边界安全:使用Intersect方法限制高亮矩形在单元格内容区域内,避免超出显示范围

内容的提问来源于stack exchange,提问作者Alireza Noori

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 18:05:29