0

I have a datagridview and a textbox. I want to do something like this.

If (textboxsearch.text.contains) any letter/character in (datagridview1. Columns (6).value.tostring) Then the colour of that particular latter/character in datagridview1. Columns (6) will turn into green.

I searched in internet but I failed. Help please. I am using vb.net 2019. Thanks.

user12647598
  • 21
  • 1
  • 3
  • You can't do that simply by setting properties. You will need to draw the text in the cells yourself. That is what you need to research. I would assume that you would need to handle the `CellPainting` event and draw the text as desired there. I'd suggest that you forget the grid for now and start by just drawing text on a form in its `Paint` event handler. Once you can draw the desired text, start trying to draw parts of the text in different colours, which will require multiple calls to `DrawString`. Do some research on WinForms GDI+ and some proper testing, then try the real thing. – jmcilhinney May 15 '21 at 06:10
  • Ideally, you would create a method that took a `Graphics` object, the text and the required information about how to colour it as arguments and then did the drawing. You could then call that method wherever you needed it, including from the `CellPainting` event handler of a `DataGridView`. You may even be able to find an existing method on the web and just plug it into your project as required. By creating a self-contained method, you can reuse it as is if you ever need to do the same thing again. That's how software development should work. – jmcilhinney May 15 '21 at 06:14
  • If you please give me the codes then it will be better for me. I am less experienced. Thanks. – user12647598 May 15 '21 at 06:21
  • For the record, [here](https://stackoverflow.com/questions/275836/multiple-colors-in-a-c-sharp-net-label) is an existing thread that demonstrates the principle. The code is C# but the equivalent VB code would be almost identical. – jmcilhinney May 15 '21 at 06:22
  • 1
    No, it won't be better for you. It would be easier for you because then you don't need to put in any effort. That's not better because it doesn't improve you as a developer. the best way to learn is to do. If you fail you fail, but you can then ask a question about your failure and we can actually help you, rather than just doing your work for you. If you're not prepared to make that effort then you probably ought to give up programming now, because you won't always be able to get other people to give you the code. – jmcilhinney May 15 '21 at 06:24

1 Answers1

2

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.

jmcilhinney
  • 50,448
  • 5
  • 26
  • 46