Introduction
This article should help some figure out how to, or atleast get a starting point for building a Code/Text Editor. In this first part of my article I will start with "Line Numbering". I looked everywhere trying to figure this task out. The closest I got was an article posted here by Michael Elly, which is located here. So some of this code will look familiar if you read that article.
There was a few problems for me though with that article.
First it uses a particular function that is only in the .Net 2.0 version and above. Second, it added line numbers where there were no lines. Third, I dont like having to add the vbCrLf's in order to safely measure the font height/line spacing. Fourthly, It does not handle wordwrapping.
All problems I had with Michael's code were easily fixed, except the wordwrapping, but after several hours, I figured it out. "I think"
Using the code
For myself I made a single control out of this, but for this article, all the code is within a single form.
Ok, problem #1, the function only in .Net >=2.0. The fix for this was to write the same function, but had to use a bit of interop for this.
<System.Runtime.InteropServices.DllImport("user32.dll", CharSet:=System.Runtime.InteropServices.CharSet.Auto)> _
Private Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal msg As Integer, ByVal wParam As Integer, ByVal lParam As Integer) As IntPtr
End Function
Public Function GetFirstCharIndexFromLine(ByVal lineNumber As Integer) As Integer
If (lineNumber < 0) Then
Throw New ArgumentOutOfRangeException("lineNumber")
End If
Return SendMessage(Me.txtEditor.Handle, &HBB, lineNumber, 0).ToInt32
End Function
The second problem being that it added line numbers where there were no lines was fixed by this line "If i = numberoflines Then Exit Do
"
The third problem that added the unwanted vbCrLf
's was resolved by measuring the height with this function. If there is no text in the control, it simply returns the controls Font.Height. The static variable LastGoodHeight is used when the textbox is scrolling and for some reason it likes to return a number something like 65536 for a height. So, I used a value of 100 to check against.
Private Function GetFontHeight(ByVal ctl As RichTextBox) As Single
Static LastGoodHeight As Single = 0
Dim height As Single = ctl.GetPositionFromCharIndex(GetFirstCharIndexFromLine(2)).Y - ctl.GetPositionFromCharIndex(GetFirstCharIndexFromLine(1)).Y
height = Math.Abs(height)
If height = 0 Then
height = ctl.Font.GetHeight() + 1
LastGoodHeight = height
Else
If height < 100 Then
LastGoodHeight = height
Else
height = LastGoodHeight
End If
End If
Return height
End Function
Now, the 4th problem, and the worst of all.. First off I had to write a function to determine the Real/Logical line number a Char Index was found in. The function is as follows: Note that there is 2 so you can use the one that you think is best but the first one seems to be faster.
Private Function GetLogicalLineNumberFromIndex(ByVal index As Integer) As Integer
If Me.txtEditor.TextLength > 0 Then
Dim sindex As Integer = 0
If sindex = index Then Return 0
Dim lines As String() = Me.txtEditor.Text.Split(New Char() {ChrW(10)})
For ln As Integer = 0 To lines.Length - 1
sindex += (lines(ln).Length + 1)
If index < sindex Then Return ln
Next
Else
Return -1
End If
End Function
Private Function GetLogicalLineNumberFromCharIndex(ByVal index As Integer) As Integer
Dim ret As Integer = -1
Const NEWLINE As Char = Chr(10)
If Me.txtEditor.TextLength > 0 Then
Dim curline As Integer = 0
Dim txtlen As Integer = Me.txtEditor.TextLength
For i As Integer = 0 To txtlen - 1
Dim ch As Char = Me.txtEditor.Text.Chars(i)
If i = index Then Return curline
If ch = NEWLINE Then
curline += 1
End If
Next
End If
Return ret
End Function
Now a couple helper functions...
Private Function GetNumStringToPrint(ByVal numbertoprint As Integer, ByVal printedlines As ArrayList) As String
Dim numstring As String
If Not printedlines.Contains(numbertoprint) Then
printedlines.Add(numbertoprint)
numstring = numbertoprint & ":"
Else
numstring = " "
End If
Return numstring
End Function
Private Function GetNumberToPrint(ByVal logiclinenum As Integer, ByVal currentloopnum As Integer, ByVal numberofvisiblelines As Integer, ByVal currentchar As Char) As Integer
Dim ret As Integer = 1
If currentloopnum = numberofvisiblelines AndAlso currentchar = ChrW(10) Then
ret = logiclinenum + 2
Else
ret = logiclinenum + 1
End If
Return ret
End Function
and now the main function that puts it all together...
Private Sub DrawLineNumbers(ByVal g As Graphics)
Dim fontheight As Single = Me.GetFontHeight(Me.txtEditor)
If fontheight = 0 Then Exit Sub
Dim firstindex As Integer = Me.txtEditor.GetCharIndexFromPosition(New Point(0, CInt(g.VisibleClipBounds.Y + fontheight / 3)))
Dim firstline As Integer = Me.txtEditor.GetLineFromCharIndex(firstindex)
Dim firstliney As Integer = Me.txtEditor.GetPositionFromCharIndex(firstindex).Y
g.Clear(Me.txtEditor.BackColor)
Dim i As Integer = firstline
Dim y As Single
Dim numberoflines As Integer = Me.txtEditor.GetLineFromCharIndex(Int32.MaxValue) + 1
Dim numstring As String = ""
Dim PrintedLines As New ArrayList
Do While y < g.VisibleClipBounds.Y + g.VisibleClipBounds.Height
y = firstliney + fontheight * (i - firstline - 1)
If i > 0 Then
If Me.txtEditor.TextLength > 0 Then
Dim cindex As Integer = Me.txtEditor.GetCharIndexFromPosition(New Point(0, CInt(y)))
Dim ch As Char = Me.txtEditor.GetCharFromPosition(New Point(0, CInt(y)))
Dim ln As Integer = Me.GetLogicalLineNumberFromIndex(cindex)
Dim numtoprint As Integer = Me.GetNumberToPrint(ln, i, numberoflines, ch)
numstring = Me.GetNumStringToPrint(numtoprint, PrintedLines)
Else
numstring = "1:"
End If
g.DrawString(numstring, Me.txtEditor.Font, Brushes.DarkBlue, (Me.pnlLineNumbers.Width - g.MeasureString(numstring, Me.txtEditor.Font).Width) - 4, y)
End If
If i = numberoflines Then Exit Do
i += 1
Loop
Me.pnlLineNumbers.Width = CInt(g.MeasureString(numstring, Me.txtEditor.Font).Width) + 6
End Sub
Thats pretty much it, this is my first article so sorry if I didnt explain well enough, but go through all the code, try it out to get a better understanding of it all.
In the zip file that you can download youll find a full working project ready to go, I added a menu to the control so you turn on,off the line numbering, or wordwrapping to see it all working better.
One more thing, I dont mind comments, but if your just trying to be a smartass, then piss off.
History
7-28-07: Got wordwrapping working.