Click here to Skip to main content
Click here to Skip to main content

Numbering lines of RichTextBox in .NET 2.0

By , 3 Nov 2005
 

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 Labels, 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()
{
    //we get index of first visible char and 
    //number of first visible line
    Point pos = new Point(0, 0);
    int firstIndex = richTextBox1.GetCharIndexFromPosition(pos);
    int firstLine = richTextBox1.GetLineFromCharIndex(firstIndex);

    //now we get index of last visible char 
    //and number of last visible line
    pos.X = ClientRectangle.Width;
    pos.Y = ClientRectangle.Height;
    int lastIndex = richTextBox1.GetCharIndexFromPosition(pos);
    int lastLine = richTextBox1.GetLineFromCharIndex(lastIndex);

    //this is point position of last visible char, we'll 
    //use its Y value for calculating numberLabel size
    pos = richTextBox1.GetPositionFromCharIndex(lastIndex);

    //finally, renumber label
    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)
{
    //move location of numberLabel for amount 
    //of pixels caused by scrollbar
    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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Petr Minarik
Web Developer
Czech Republic Czech Republic
Member
Petr Minarik is currently studying at Czech Technical University Prague. He is interested in C# programming, Direct3D and graphics generally and he develops his own realtime 3D scene editor. He likes fun, beer and good people.
 
You can visit him at his homepage.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
BugBUGmembergao sir26 Nov '12 - 0:55 
Number of lines many later Display will dislocation
GeneralMy vote of 1memberxxbbcc19 Nov '12 - 17:48 
Very fragile implementation and extremely specific to the .NET version, font size, scroll and zoom settings.
GeneralMy vote of 5memberFarhan Ghumra23 Aug '12 - 2:10 
Excellent
QuestionSlight improvementmemberJoS@Work12 Jun '12 - 0:16 
Hi Petr,
 
great work! It's lean and works fine (for my needs). I've remarked and corrected some minor issues anyway:
 
1. When entering a blank line, line numbers are not updated immediately, but when the first character is typed in the new line.
 
2. For performance reasons, especially in big files, it's better to use StringBuilder when updating numbering.
 
Here's my corrected version of UpdateNumberLabel
 
   
/// <summary>
/// Update line numbering
/// </summary>
private void UpdateNumberLabel()
{
    // We get index of first visible char and number of first visible line
    var pos = new Point(0, 0);
    int firstIndex = richTextBox1.GetCharIndexFromPosition(pos);
    int firstLine = richTextBox1.GetLineFromCharIndex(firstIndex);
 
    // Now we get index of last visible char and number of last visible line
    pos.X = ClientRectangle.Width;
    pos.Y = ClientRectangle.Height;
    int lastIndex = richTextBox1.GetCharIndexFromPosition(pos);
    int lastLine = richTextBox1.GetLineFromCharIndex(lastIndex);
 
    // Small correction (JS)
    if(richTextBox1.Text.EndsWith("\n"))
        lastLine++;
 
    // Finally, renumber label (JS: slightly corrected) 
    var sb = new StringBuilder();
    for(int i = firstLine; i <= lastLine; i++)
    {
        sb.AppendLine((i + 1).ToString());
    }
    numberLabel.Text = sb.ToString();
}
 
Hope that helps
 
Jochen
GeneralRe: Slight improvementmemberbenyoman17 Aug '12 - 13:19 
Hi Jochen,
 
It works for me with your code, thank you.
 
And an off-topic style notice for you: don't use
var
tags for object variables, if it is possible.
 
Bryan
QuestionAnother one control to trymemberantgraf30 Oct '11 - 9:17 
https://github.com/antgraf/C--Numbered-Lines-Control-for-RichTextBox--LineNumbersControlForRichTextBox-
GeneralMy vote of 1memberMember 451749722 Jul '10 - 2:58 
too complicate and not fully functional (scrolling)
GeneralUncode supportmemberaldycool7 Apr '10 - 0:16 
Hi, I tried pasting some japanese text, it seems the line numbering cannot support other than latin characters. Any idea how to fix this?
GeneralWidth auto-adjustment and better management of first line offset and last existing line in RichTextBoxmemberMember 15499213 Jul '09 - 9:56 
<code>private void DrawLines(Graphics g, int firstLine)
{
      // Number of text lines
      int linesCount = richTextBox.Lines.Length;
 
      // Last visible line (used to determine numbers panel width)
      int lastChar = this.richTextBox.GetCharIndexFromPosition(new Point(this.richTextBox.ClientRectangle.Width, this.richTextBox.ClientRectangle.Height));
      int lastLine = this.richTextBox.GetLineFromCharIndex(lastChar);
 
      // Line numbers layout (position, width)
      int rightMargin = 2, leftMargin = 5, topMargin = 2, bottomMargin = 15, verticalMargin = 2;
      SizeF maxTextSize = g.MeasureString(new string((char)48, lastLine.ToString().Length), this.richTextBox.Font);
      this.panelNum.Width = (int)maxTextSize.Width + leftMargin + rightMargin;
 
      // Clear existing numbers
      g.Clear(this.panelNum.BackColor);
 
      // First line name
      int lineNumber = firstLine + 1;
 
      // Y position for first line number
      int firstLineY = this.richTextBox.GetPositionFromCharIndex(this.richTextBox.GetFirstCharIndexFromLine(firstLine)).Y;
      int lineY = topMargin + firstLineY;
 
      // Write all visible line numbers
      while (true)
      {
            // Draw line number string
            string lineNumberLabel = lineNumber.ToString().PadLeft(lastLine.ToString().Length);
            g.DrawString(lineNumberLabel, this.richTextBox.Font, Brushes.Black, leftMargin, lineY);
 
            // Next line
            lineNumber += 1;
            lineY += Font.Height + verticalMargin;
 
            // End of numeration if and of text content or end of RichTextBox height
            if (lineY > ClientRectangle.Height - bottomMargin || lineNumber > linesCount)
                  break;
      }
}</code>
 
Note: I'm using a Panel Control to display the line numbers (Dock = Left, Fill for RichTextBox)
QuestionRe: Width auto-adjustment and better management of first line offset and last existing line in RichTextBoxmemberzarawebfx8 Jan '10 - 3:11 
Would you provide some source code as a attachment?
 
kr, zara
QuestionHow about indentmemberdfpcnc17 Jun '09 - 10:55 
I can put a bullet in the indent column! why not a line number?
Great job to all! best ive seen yet.Confused | :confused:
GeneralDelete numbering lines of RichTextBox in c#memberSAADRAFID14 May '09 - 1:21 
Hi!
 
Can somebody tel me how to delete a linenumber when using two richtextbox?
 

thanks
GeneralProblems with ScrollmemberDiegoCol18 Jun '08 - 6:45 
I'm using the code in .Net 3.5, and I dont know if in others version its happens, but the first line when the scroll bar is visible, the numbers are not synchronized with the lines... the problem is that the first line number is the 1 but int firstIndex = editor.GetCharIndexFromPosition(pos) returned 0...
 
the solution is just change
Point pos = new Point(0, 0)
to
Point pos = new Point(0, 1).
QuestionSome problemsmemberChenQiang8 Dec '07 - 4:17 
Your solution really helped me a lot.Thank you!
However, I found some problems. First,Only 16 lines for line number can be displayed although code in richTextBox1 is more than 16 lines. What's more, the text and line number are not aligned well.
I'm sure you can solve the probelm.
ChenQiang
Thank you!
GeneralRe: Some problemsmemberRodUbi11 Dec '07 - 7:31 
Remove the following line in the NumberedTextBoxUC.designer.cs
 
this.numberLabel.Size = new System.Drawing.Size(37, 267);
 
and replace it by the following line:
 
this.numberLabel.AutoSize = true;
 
You can also speed up the controller by assigning the numberLabel.Text only once in the updateNumberLabel(). Construct a string containing the new label and then assign it once done.
 
Rod
GeneralNumbers only painted as far as original heightmemberjimmygyuma31 Oct '07 - 4:08 
Petr,
 
My text editor will be considerable larger than your demo. The RichTextBox and Label resize properly, however, the line numbers are only painted on the label as far as the original height. Can you suggest a fix for this?
 
Thanks,
Jim
GeneralRe: Numbers only painted as far as original heightmemberjimmygyuma31 Oct '07 - 5:00 
Petr,
 
Never mind, I fixed it. After:
 
//this is point position of last visible char, we'll use its Y value for calculating numberLabel size
pos = rtb.GetPositionFromCharIndex(lastIndex);
 
I added:
 
numberLabel.Size = new Size(numberLabel.Width, pos.Y + (int) rtb.Font.GetHeight());
 
I also inserted the following before the above, for when the insertion point is on a new line after the last character:
 
if (rtb.SelectionLength == 0)
{
int idx = rtb.SelectionStart;
if (idx > lastIndex) lastIndex = idx;
}
 
And all is well!: Big Grin | :-D
Jim
GeneralAnother font size fixmemberrareshh20 Jun '07 - 23:04 
First of all, thanks for the control, great job.
 
The easiest way to get rid of the label fontsize vs richtextbox font-size is to assign the same font for both controls and set the AutoScaleMode to None for the NumberedTextBoxUC (Properties->Layout->AutoScaleMode)
 
Hope this helps Smile | :)
GeneralGood JobmemberMike Hankey23 Apr '07 - 12:42 
Simple and easy to understand!
 
Mike
 
Theres light at the end of the tunnel. Lord I hope it ain't no train!

GeneralDisable smooth scrollingmemberfirefoxxi22 Apr '07 - 4:28 
I used the following to disable smooth scrolling:
 
class myRichTextBox : RichTextBox
{
  private const short WM_SCROLL = 0x115;
  private const UInt32 SB_THUMBPOSITION = 4;
  private const UInt32 SB_THUMBTRACK = 5;
 
  protected override void WndProc(ref Message m)
  {
    if(m.Msg == WM_SCROLL)
    {
      UInt32 wparam = (UInt32)m.WParam;
      UInt32 type = wparam & 0x0000FFFF;
      UInt32 pos = (wparam & 0xFFFF0000) >> 16;
      if (type == SB_THUMBPOSITION || type == SB_THUMBTRACK)
      {
        pos = pos - (pos % ((UInt32)this.Font.Height+1));
      }
      wparam = (pos << 16) + type;
      m.WParam = (IntPtr)wparam;
      base.WndProc(ref m);
    }
  }
}

GeneralDisable smooth wheelscrollingmembermlr7929 Dec '07 - 5:08 
Great Code!
 
Here's an enhancement for no smooth wheelscroll:
 

[DllImport("user32.dll")]
private static extern bool GetScrollInfo(IntPtr hwnd, int fnBar, ref SCROLLINFO ScrollInfo);
 
struct SCROLLINFO
{
            public int cbSize;
            public UInt32 fMask;
            public Int32 nMin;
            public Int32 nMax;
            public UInt32 nPage;
            public Int32 nPos;
            public Int32 nTrackPos;
}
 
public enum ScrollInfoMask
{
             SIF_RANGE = 0x1,
             SIF_PAGE = 0x2,
             SIF_POS = 0x4,
             SIF_DISABLENOSCROLL = 0x8,
             SIF_TRACKPOS = 0x10,
             SIF_ALL = SIF_RANGE | SIF_PAGE | SIF_POS | SIF_TRACKPOS
}
 

private const short WM_MOUSEWHEEL = 522;
private const int SB_PAGEDOWN  = 3;
private const int SB_PAGEUP = 2;
 
////////////////////////////////

if (m.Msg == WM_MOUSEWHEEL)
{
   int rotation = (int)m.WParam / 0x0000FFFF;
   int pos;
   int lines = SystemInformation.MouseWheelScrollLines;
 
   if (lines > 0)
   {
      SCROLLINFO info = new SCROLLINFO();
      info.cbSize = Marshal.SizeOf(info);
      info.fMask = (int)ScrollInfoMask.SIF_ALL;
      GetScrollInfo(this.Handle, SB_VERT, ref info);
      
      if (rotation > 0)
      {
         pos = info.nPos - (this.Font.Height + 1) * lines;
      }
      else
      {
         pos = info.nPos + (this.Font.Height + 1) * line
      }
      pos = Math.Max(0, pos << 16);
      m.WParam = (IntPtr)(SB_THUMBPOSITION | pos);
   }
   else
   {
      if (rotation > 0)
      {
         m.WParam = (IntPtr)SB_PAGEUP;
      }
      else
      {
         m.WParam = (IntPtr)SB_PAGEDOWN;
      }
   }
 
   m.Msg = WM_SCROLL;
   base.WndProc(ref m);
}

GeneralLine numbermemberpline12 Feb '07 - 0:29 
Merci beaucoup....
 
Thank'a lot....
 
Grazie mille....
 
Muchos gracias....
 
Smile | :)
 
pline

QuestionCustom horizontal scrollbarmembercyberjunky3 Dec '06 - 15:42 
to complete the final piece i added a scroll bar to the bottom of my version of the control but i cant seem to get the right properties for setting the propper width of the content...ie hscroll.maximum = ??????
 
your line numbering trick rocks so far, thanks..
GeneralwndProcmembercurlypaul3 Nov '06 - 4:21 
Great article, best I found on the subject!
 
Just one small thing, you mention that the a lot of problems can be solved by setting the scroll increments to a whole line width. Any pointers as to how I could do this?
 
Cheers
 
Paul
GeneralDon't Use a Labelmemberwaynebe21 Oct '06 - 9:55 
Don't Use a Label, instead use another TextBox (Multiline) Or Another RichEditBox, either way you solve your font problem. Otherwise good job!!!
 
-Wayne

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130523.1 | Last Updated 3 Nov 2005
Article Copyright 2005 by Petr Minarik
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid