I didn't expect you to get the whole way yourself but I did hope to see you put some effort into coming up with your own solution. I did some testing myself, using the link I provided in the comments as a starting point, and I came up with something that works. It's relatively complex so I figured that I'd just provide it as is, rather than waiting around to see how far you can get on your own.
Firstly, here's a useful type:
''' <summary>
''' Represents a section of a <see cref="String"/> that can be highlighted.
''' </summary>
Public Structure HighlightableSubstring
''' <summary>
''' The index at which the substring starts.
''' </summary>
''' <returns>
''' An <see cref="Int32"/> that represents the start index of the substring.
''' </returns>
Public ReadOnly Property StartIndex As Integer
''' <summary>
''' The number of characters in the substring.
''' </summary>
''' <returns>
''' An <see cref="Int32"/> that represents the length of the substring.
''' </returns>
Public ReadOnly Property Length As Integer
''' <summary>
''' Indicates whether or not the substring is highlighted.
''' </summary>
''' <returns>
''' <b>True</b> if the substring is highlighted; otherwise, <b>False</b>.
''' </returns>
Public ReadOnly Property IsHighlighted As Boolean
''' <summary>
''' Creates a new instance of the <see cref="HighlightableSubstring"/> class.
''' </summary>
''' <param name="startIndex">
''' The index at which the substring starts.
''' </param>
''' <param name="length">
''' The number of characters in the substring.
''' </param>
''' <param name="isHighlighted">
''' Indicates whether or not the substring is highlighted.
''' </param>
Public Sub New(startIndex As Integer, length As Integer, isHighlighted As Boolean)
Me.StartIndex = startIndex
Me.Length = length
Me.IsHighlighted = isHighlighted
End Sub
End Structure
Here's the method that makes use of that type and does the drawing:
''' <summary>
''' Draws text with instances of a specific substring highlighted.
''' </summary>
''' <param name="g">
''' The object with which to draw the text.
''' </param>
''' <param name="text">
''' The text to draw.
''' </param>
''' <param name="highlightText">
''' The substring to highlight in the drawn text.
''' </param>
''' <param name="defaultColour">
''' The default colour in which to draw the text.
''' </param>
''' <param name="highlightColour">
''' The colour in which to draw the highlighted sections of the text.
''' </param>
''' <param name="font">
''' The font in which to draw the text.
''' </param>
''' <param name="location">
''' The location of the top-left corner of the bounds within which to draw the text.
''' </param>
Private Sub DrawTextWithHighlights(g As Graphics,
text As String,
highlightText As String,
defaultColour As Color,
highlightColour As Color,
font As Font,
location As PointF)
If String.IsNullOrWhiteSpace(text) Then
Return
End If
Dim textLength = text.Length
Dim highlightTextLength = highlightText.Length
'The sections of the text that are highlighted and not highlighted.
Dim sections As New List(Of HighlightableSubstring)
'The index at which to start searching for the highlight text.
Dim startIndex = 0
If Not String.IsNullOrWhiteSpace(highlightText) Then
'Find the first instance of the highlight text.
Dim highlightIndex = text.IndexOf(highlightText)
'Keep searching until no more instances of the highlight text can be found.
Do Until highlightIndex = -1
'Check whether there is text before the found instance of the highlight text.
If highlightIndex > startIndex Then
'Add a section for the non-highlighted text.
sections.Add(New HighlightableSubstring(startIndex, highlightIndex - startIndex, False))
End If
'Add a section for the highlighted text.
sections.Add(New HighlightableSubstring(highlightIndex, highlightTextLength, True))
'Find the next instance of the highlight text.
startIndex = highlightIndex + highlightTextLength
highlightIndex = text.IndexOf(highlightText, startIndex)
Loop
End If
'Check whether there is text after the last instance of the highlight text.
If startIndex < textLength Then
'Add a section for the non-highlighted text.
sections.Add(New HighlightableSubstring(startIndex, textLength - startIndex, False))
End If
Dim format As New StringFormat
'Set a range for each section of the text.
format.SetMeasurableCharacterRanges(sections.Select(Function(hs) New CharacterRange(hs.StartIndex, hs.Length)).ToArray())
'Get the box that the text would normally occupy.
Dim layoutRect As New RectangleF(location, g.MeasureString(text, font))
'Get the regions occupied by the sections of the text.
Dim regions = g.MeasureCharacterRanges(text, font, layoutRect, format)
'Create brushes for the specified colours.
Using defaultBrush As New SolidBrush(defaultColour),
highlightBrush As New SolidBrush(highlightColour)
For i = 0 To regions.GetUpperBound(0)
'Get the current section.
Dim section = sections(i)
'Get the bounds for the current section.
Dim bounds = regions(i).GetBounds(g)
'Draw the current section of the text using the appropriate colour.
g.DrawString(text.Substring(section.StartIndex, section.Length),
font,
If(section.IsHighlighted,
highlightBrush,
defaultBrush),
bounds.X,
bounds.Y)
Next
End Using
End Sub
Here's how I initially tested that code, without a DataGridView
:
Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
e.Graphics.DrawString("highlight", Font, Brushes.Black, 100, 50)
DrawTextWithHighlights(e.Graphics, "highlight", "ig", Color.Black, Color.Red, Font, New PointF(100, 70))
e.Graphics.DrawString("abcdababefab", Font, Brushes.Black, 100, 150)
DrawTextWithHighlights(e.Graphics, "abcdababefab", "ab", Color.Black, Color.Red, Font, New PointF(100, 170))
End Sub
I used the second text to test how the code behaved when the highlight text was found at the beginning and end of the text and when two instances were found together and it worked as expected. If you run that code then you'll see that there's a slight offset between the horizontal position of the text drawn with a single call to DrawString
and that containing the highlighting. I don't think that it's enough to be a problem but you can address it if you want to.
To use that method with a DataGridView
, I did this:
Private Sub TextBox1_TextChanged(sender As Object, e As EventArgs) Handles TextBox1.TextChanged
DataGridView1.Refresh()
End Sub
Private Sub DataGridView1_CellPainting(sender As Object, e As DataGridViewCellPaintingEventArgs) Handles DataGridView1.CellPainting
If e.ColumnIndex >= 0 AndAlso e.RowIndex >= 0 Then
'Draw all the required parts of the cell except the text.
e.Paint(e.ClipBounds, e.PaintParts And Not DataGridViewPaintParts.ContentForeground)
'Draw the text.
DrawTextWithHighlights(e.Graphics,
e.FormattedValue?.ToString(),
TextBox1.Text,
DataGridView1.ForeColor,
Color.Red,
DataGridView1.Font,
e.CellBounds.Location)
e.Handled = True
End If
End Sub
In my testing, that worked to properly highlight the text when making changes in the grid or in the TextBox
. The one issue with that is where the text gets drawn in the cell. I'll leave it to you to figure out that detail.