[RESOLVED] RichTextBox - Determine lines have changed
Hello everyone.
I am working on a way to determine if the lines of a RichTextBox have changed.
I currently use this code:
Code:
Dim newhash As Integer = Me.Text.GetHashCode
If newhash <> Me._prevhash Then
Me._prevhash = newhash
Me._lines = MyBase.Lines
If Me._lines.Count = 0 Then
ReDim Me._lines(0)
Me._lines(0) = ""
End If
End If
But this method has a bad issue: The text property takes a while to "load up" plus getting its hash code adds to it. This code runs every time the Lines are needed (it is a property). So this code:
Code:
For i As Integer = 0 To Lines.Count - 1
Dim line As String = Lines(i)
Next
Takes ages to complete, because for every line it checks if the lines have changed.
I am trying to evade using MyBase.Lines too much, since that REALLY takes ages to load.
- I can not use the modified property since it is unreliable (set to true if only a text color changes)
- Can't use the RTF since it changes without the text changing (coloring) and the hash code is slow as well.
- Can't use the TextChanged event, since it is sent after the SelectionChanged event (for some reason)
Any help on this? :ehh:
Re: RichTextBox - Determine lines have changed
Quote:
Can't use the TextChanged event, since it is sent after the SelectionChanged event (for some reason)
Why is that a problem? The SelectionChanged event is raised when the SelectionStart or SelectionLength property changes. If you type into the control then the SelectionStart property will change, so it stands to reason that the SelectionChanged event is raised. If you change the SelectionStart or SelectionLength without changing the Text then SelectionChanged is raised but TextChanged isn't. Why can't you use TextChanged to determine when the Text changes? That's exactly what it's for.
Re: RichTextBox - Determine lines have changed
Because I need to read the lines in the SelectionStart event(s). If I place it in the TextChanged routine, these events will always read one character too late.
Could set a variable in the SelectionChanged routine that sets if the text has changed, but for that I need to know IF the text has changed.
This because the user could have just clicked somewhere else instead of typing.
Then it is easier to do this directly in the property.
The goal is the least amount of MyBase.Lines calls. ONLY when the lines are needed and the lines have changed.
Else I will return the backbuffer array.
I am currently going to use the Modified property, with some recover functions if I change things other than text. But it is not the best method.
Re: RichTextBox - Determine lines have changed
What is it that you are actually trying to determine? You say:
Quote:
determine if the lines of a RichTextBox have changed
but that doesn't actually make sense. Do you really mean the number of lines, or the text in those lines? If it's the text in the lines then you don't need to do anything other than handle the TextChanged event because that is raised when and only when the Text changes. If it's the number of lines then your code doesn't really make sense.
Re: RichTextBox - Determine lines have changed
I'll fully explain. :p
I have a custom control that inherits from the RichTextBox. This control has lots of properties. This includes many line calls. For example:
Code:
<Browsable(False), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)> _
Public ReadOnly Property LineIndex(ByVal charindex As Integer) As Integer
Get
Return Me.GetLineFromCharIndex(charindex)
End Get
End Property
<Browsable(False), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)> _
Public ReadOnly Property LineStart(ByVal lineindex As Integer) As Integer
Get
If lineindex = -1 Then Return SelectionLineStart
Return Me.GetFirstCharIndexFromLine(lineindex)
End Get
End Property
<Browsable(False), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)> _
Public ReadOnly Property LineEnd(ByVal lineindex As Integer) As Integer
Get
Return Me.LineStart(lineindex) + Me.Lines(lineindex).Length
End Get
End Property
For code information I may need to call lineend multiple times, for example, to process the current users' selection. If I use MyBase.Lines, this operation is very slow since it regenerates the lines for each call. So now I added overloaded properties to handle this:
Code:
Private _lines() As String
<Browsable(False), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)> _
Public Overloads Property Lines(ByVal index As Integer) As String
Get
If index > Me.Lines.Count - 1 Then Return ""
If index < 0 Then Return ""
Return Me.Lines()(index)
End Get
Set(ByVal value As String)
If index > Me.Lines.Count - 1 Then Return
Dim l() As String = Me.Lines
l(index) = value
Me.Lines = l
ColorizeLines(index, value.Split(vbLf).Count)
End Set
End Property
<Browsable(False), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)> _
Public Overloads Property Lines() As String()
Get
If IsNothing(Me._lines) OrElse MyBase.Modified Then
Me._lines = MyBase.Lines
MyBase.Modified = False
End If
If Me._lines.Count = 0 Then
ReDim Me._lines(0)
Me._lines(0) = ""
End If
Return Me._lines
End Get
Set(ByVal value As String())
MyBase.Lines = value
End Set
End Property
This property has two main functions:
- Make sure the line count is never 0 ; since lineindex can be 0 even if text is empty
- Make sure the backbuffer is updated with MyBase.Lines Only if it is needed.
As you saw in the Line* codes, I call "ColorizeLines" to update the text coloring in my control. This is done by "locking" the control, selecting the lines to update, obtain the selection text and generate the selection RTF from this text. Code is pretty complicated, but it comes down to using a "font header" containing the font and colors in the document and finally appending the text proceeded with "/cf1 " to "/cf7 " codes to generate coloring. I also use highlighting in my code which does the same, but then simply selects the text to highlight and sets the selection backcolor.
This highlighting is done commonly to highlight the current "fragment" info is given of.
Code:
Private _highlightstart As Integer = 0
Private _highlightlength As Integer = 0
Public Sub Highlight(ByVal start As Integer, ByVal length As Integer)
If Me._highlightstart <> start Or Me._highlightlength <> length Then
If start < 0 Then start = 0
If length < 0 Then length = 0
Me.Lock()
Me.SaveSelection()
Me.Select(Me._highlightstart, Me._highlightlength)
Me.SelectionBackColor = Color.White
Me.Select(start, length)
Me.SelectionBackColor = Color.Yellow
Me._highlightstart = start
Me._highlightlength = length
Me.LoadSelection()
Me.Unlock()
End If
End Sub
This highlighting causes modified events as well, making the lines update without any text changed. On my main form I may need to get line information when the selection changed, for example to get the current line the user is in.
Code:
<Browsable(False), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)> _
Public Property SelectionLine() As String
Get
Return Me.Lines(Me.SelectionLineIndex)
End Get
Set(ByVal value As String)
Me.Lines(Me.SelectionLineIndex) = value
End Set
End Property
But if I handle the textchanged for this, this is called after the selectionchanged event, thus the handler reads one character too late.
Example for you to test out:
Code:
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
RichTextBox1_TextChanged(Nothing, Nothing)
End Sub
Private _lines(-1) As String
Private Sub RichTextBox1_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles RichTextBox1.TextChanged
Me._lines = RichTextBox1.Lines
If Me._lines.Count = 0 Then
ReDim Me._lines(0)
Me._lines(0) = ""
End If
End Sub
Private Sub RichTextBox1_SelectionChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles RichTextBox1.SelectionChanged
MsgBox(Me._lines(0))
End Sub
At this point I save and reset the Modified property in the Lock and Unlock functions, and hold a separate Modified property which is set to true on text change.
Code:
Public Sub Lock(Optional ByVal lock As Boolean = True)
SendMessage(Me.Handle, WM_SETREDRAW, lock = False, Nothing)
If Not lock Then Me.Invalidate()
Me._ignoretextchange = lock
If lock Then
Me._prevmod = MyBase.Modified
Else
MyBase.Modified = Me._prevmod
End If
End Sub
But when the user select character 'a' for example, and types an 'a' modified is set to true as well, causing another useless line update.
I need to know, from the control, if the text in the control (or lines) have changed.
I hope you understand my issue. :)
EDIT
Just for those reading this; I fixed it by using the Modified property of the native RichTextBox. I added my a custom Modified property (with private member) in my custom control which is set to true when the text changes. Also made it activate the ModifiedChanged event.