I have a control that inherits from RichTextBox and the idea is, that the user types in numbers per line and the control colors each line read or green depending on if the text in the line is a valid number.
On TextChanged
each line is checked and by applying SelectionColor
the color of each line is adjusted accordingly. Please find the full code below.
In order to keep that somewhat speedy I suspend redrawing of the control before updating the colors by sending the WM_SETREDRAW message, as is described in various questions/answers on this site, e.g. this one about RichTextBox in particular.
I have used this for quite a while without issue but recently reused the control in a project that targets .NET Framework 4.7.2 instead of older frameworks like 4.5 or 4.6.
Here I run into the problem of graphical glitches as soon as the control contains enough lines to cause scrolling. The colors are not applied correctly, ghost cursors appear and so on. I tried to show that in the picture:
The only thing I changed between the second and third picture was to change the target framework to 4.6.2 and recompile.
Does someone have an idea what causes this and how to work around the problem? Please feel free to use VB or C# to your taste, I don't judge :-)
Here is the code of the control I used to reproduce the behavior:
Public Class RichtextNumberlist
Inherits RichTextBox
Private Sub tbValues_TextChanged(sender As Object, e As EventArgs) Handles Me.TextChanged
SuspendDrawing(Me)
UpdateColor(Me)
ResumeDrawing(Me)
End Sub
<DllImport("user32.dll")>
Private Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal Msg As Integer, ByVal wParam As Boolean, ByVal lParam As IntPtr) As Integer
End Function
Private Const WM_SETREDRAW As Integer = 11
Public Sub SuspendDrawing(ByVal Target As Control)
SendMessage(Me.Handle, WM_SETREDRAW, False, 0)
End Sub
Public Sub ResumeDrawing(ByVal Target As Control)
SendMessage(Me.Handle, WM_SETREDRAW, True, 0)
Target.Invalidate()
End Sub
Private Function CheckLine(line As String) As Boolean
Dim d As Double
Return Double.TryParse(line, d)
End Function
Private Sub UpdateColor(tbValues As RichTextBox)
Dim t = Threading.Thread.CurrentThread
Console.WriteLine($"Updating color from thread STA={t.GetApartmentState().ToString()}, BG={t.IsBackground}")
'Save selection parameters to reset them afterwards
Dim oldsel As Integer = tbValues.SelectionStart
Dim oldsell As Integer = tbValues.SelectionLength
Dim pos As Integer = 0
Dim lCount = tbValues.Lines.Count
Dim lines = tbValues.Lines
For i = 0 To lCount - 1
'Set selection on the ith line
tbValues.SelectionStart = pos
tbValues.SelectionLength = lines(i).Length
Dim lineok = CheckLine(lines(i))
'Adjust the color accordingly
If lineok Then
tbValues.SelectionColor = Color.ForestGreen
Else
tbValues.SelectionColor = Color.Red
End If
'Move forward
pos += lines(i).Length + 1
Next
'Reset the selection
tbValues.SelectionStart = oldsel
tbValues.SelectionLength = oldsell
End Sub
End Class