自定义DataGridView控件搜索结果高亮功能的问题求助
DataGridView自定义控件文本高亮优化方案
问题背景
我正在开发一个继承自DataGridView的自定义控件,需要实现搜索结果在单元格文本中的高亮显示。目前已重写OnCellPainting方法实现,但存在两个核心问题:
- 位置计算依赖大量硬编码数值,无法适配不同DPI环境,高亮位置偏移
- 文本对齐方式适配逻辑未生效,不同对齐下高亮框位置错误
当前高亮“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); }
优化方案
核心思路
- 移除所有硬编码数值,基于单元格内边距、文本测量尺寸和系统DPI计算高亮位置
- 修复文本对齐适配逻辑,统一使用
TextFormatFlags进行测量和位置计算 - 完善多行文本的高亮位置计算逻辑
完整优化代码
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
相关产品推荐
相关产品推荐

