
Introduction
Numbering of lines in text editors is a well known feature. But the standard RichTextBox
in .NET 2.0 does not support this feature. It is also hard to find a suitable solution on the Internet. Especially, a solution that does not directly use Win32 functions.
The RichTextBox
is not a standard control in Windows Forms. It does not use the OnPaint
method and other similar functions as it should and it also hides some of the important properties required for proper customization. One way to overcome this is to use win32 API functions and override the WndProc functions. I don't like this way of doing it but I am forced to use it again and again. I consider it as a fault of .NET developers.
Fortunately, for numbering lines of RichTextBox
, there is a satisfactory solution that does not use pure Win32 API functions.
Implementation
We implement the RichTextBox
with numbered lines as a UserControl
. We will not override anything in the RichTextBox
, we will only use its events. Our UserControl
named NumberedTextBoxUC
consists of SplitContainer
, Label
(numberLabel
) and RichTextBox
. Label
is used for displaying the line number and RichTextBox
for the text content, both are contained in SplitterContainer
.
The content of numberLabel
is updated in the RichTextBox
's event handlers. These events are:
OnTextChanged
OnVScroll
OnSizeChanged
OnFontChanged
Problems
There are several problems with this implementation. The first one is scrolling. Unlike the VS source code editor or TextBox
control, RichTextBox
uses smooth scrolling, thus scrolling with the scrollbar scrolls the text in pixels, not in lines. You will notice that the first line is displayed in half. This is not always a wanted behaviour and I would appreciate the possibility to turn it off. Another problem is the redrawing speed of large Label
s, you cannot afford to print too many lines to Label
in each OnTextChanged
event handler. Another strange problem is the RichTextBox
in .NET 2.0 uses strange line indentation, which is impossible to turn off or set to zero. The same font in Label
and RichTextBox
results in different line positions when the controls are top aligned.
Solution
I am displaying only the numbers of visible lines, thus the unnecessary hidden line numbers are not printed. The update function is called updateNumberLabel()
. It uses the RichTextBox
functions GetCharIndexFromPosition
and GetLineFromCharIndex
to determine the first and last visible line numbers.
private void updateNumberLabel()
{
Point pos = new Point(0, 0);
int firstIndex = richTextBox1.GetCharIndexFromPosition(pos);
int firstLine = richTextBox1.GetLineFromCharIndex(firstIndex);
pos.X = ClientRectangle.Width;
pos.Y = ClientRectangle.Height;
int lastIndex = richTextBox1.GetCharIndexFromPosition(pos);
int lastLine = richTextBox1.GetLineFromCharIndex(lastIndex);
pos = richTextBox1.GetPositionFromCharIndex(lastIndex);
numberLabel.Text = "";
for (int i = firstLine; i <= lastLine + 1; i++)
{
numberLabel.Text += i + 1 + "\n";
}
}
For different line indentations I have found a constant, which works best for font size 8. The size of the Label
font is bigger for this constant. I hope, in future versions of .NET this issue will be fixed and both the fonts will be exactly the same, as it was in .NET 1.1.
public NumberedTextBoxUC()
{
InitializeComponent();
numberLabel.Font = new Font(richTextBox1.Font.FontFamily,
richTextBox1.Font.Size + 1.019f);
}
Smooth scrolling is another issue which causes a lot of troubles. I use the small numberLabel
location update in each OnVScroll
event handler. The Label
is moved about as many pixels different from multiples of the RichTextBox
font height, thus the modulo text position of the font height. The reverse solution, to update the text position to be line aligned, is in my opinion impossible without using Win32 functions. Updating the text position with RichTextBox
functions in this way results in text shivering.
private void richTextBox1_VScroll(object sender, EventArgs e)
{
int d = richTextBox1.GetPositionFromCharIndex(0).Y %
(richTextBox1.Font.Height + 1);
numberLabel.Location = new Point(0, d);
updateNumberLabel();
}
Conclusion
I hope this user control helps developers handling RichTextBox
. I appreciate your advice and improvements to this control.