代码编辑器(第一部分)






1.61/5 (8投票s)
2007 年 6 月 28 日
2分钟阅读

31939

248
创建一个代码/文本编辑器。带或不带自动换行的行号。
引言
这篇文章应该可以帮助一些人了解如何构建代码/文本编辑器,或者至少获得一个起点。 在我的文章的第一部分中,我将从“行号”开始。 我到处寻找解决这个任务的方法。 我找到的最接近的是Michael Elly在这里发表的一篇文章,位于这里。 所以如果你阅读过这篇文章,那么一些代码会看起来很熟悉。
但是,那篇文章对我来说有一些问题。
首先,它使用了一个仅在 .Net 2.0 及以上版本中才有的特定函数。 其次,它在没有行的地方添加了行号。 第三,我不喜欢必须添加 vbCrLf 才能安全地测量字体高度/行距。 第四,它不处理自动换行。
我对 Michael 的代码的所有问题都很容易解决,除了自动换行,但经过几个小时后,我想我解决了这个问题。“我想”
使用代码
就我个人而言,我将此做成了一个单一控件,但对于这篇文章,所有代码都在一个单独的窗体中。
好的,问题 #1,该函数仅在 .Net >=2.0 中可用。 解决这个问题的方法是编写相同的函数,但必须为此使用一些互操作。
<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
第二个问题是它在没有行的地方添加了行号,可以通过这行代码解决“If i = numberoflines Then Exit Do
”
第三个问题是添加了不需要的vbCrLf
,可以通过这个函数测量高度来解决。 如果控件中没有文本,它只会返回控件的 Font.Height。 当文本框滚动时,静态变量 LastGoodHeight 会被使用,并且由于某些原因,它喜欢返回一个类似 65536 的高度。 所以,我使用 100 的值进行检查。
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
现在,第四个问题,也是最糟糕的问题…… 首先,我必须编写一个函数来确定在 Char Index 中找到的实际/逻辑行号。 该函数如下:请注意,这里有 2 个函数,你可以使用你认为最好的一个,但第一个函数似乎更快。
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
现在是一些辅助函数...
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
现在是把所有东西放在一起的主要函数...
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
'' Paint the background color of the linenumbers panel
g.Clear(Me.txtEditor.BackColor)
'' Paint the line numbers
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
'resize the Linenumbers panel according to the width of the highest number,
' and tack on some padding so the numbers look centered
Me.pnlLineNumbers.Width = CInt(g.MeasureString(numstring, Me.txtEditor.Font).Width) + 6
End Sub
差不多就是这样,这是我的第一篇文章,所以如果我解释得不够好,请见谅,但请通读所有代码,尝试一下,以便更好地理解它。
在您可以下载的 zip 文件中,您会找到一个可以运行的完整项目,我在控件中添加了一个菜单,因此您可以打开或关闭行号或自动换行以更好地查看所有内容的工作方式。
还有一件事,我不介意评论,但如果你只是想耍小聪明,那就滚开。
历史
7-28-07:自动换行已生效。