VB.NET TextBox with Line Numbers

The Background

People all over the internet are looking for some simple solution to put line numbers to multiline textbox. Today I stumbled upon a similar question asked by someone on one of the forums here. At first glance I thought this should be simple with a RichTextBox control with numbered bullets turned on. But to my surprise, I found that the .NET RichTextBox control doesn’t expose any method directly to set the numbered bullets. I googled around if there is some good workaround to it, but couldn’t find a decent solution.

The Solution

OK. So here’s my solution for this problem. This uses a Panel, a ListBox and a TextBox to simulate the functionality. And there’s some trickery involved too! Tongue out

Add a Panel control to your form.

Add a listBox and a TextBox inside the Panel.

Set the following properties:

Panel

BorderStyle = Fixed3D
AutoScroll  = True

ListBox

BorderStyle = None
ForeColor   = AppWorkspace
Location    = 0, 0     [Location.X = 0, Location.Y = 0]
Width       = 100      [Size.Width = 100]
TabStop     = False
UseTabStop  = False

TextBox

BorderStyle = None
Location    = 50, 0  [Location.X=50, Location.Y = 0]
Multiline   = True
Text        = Your text goes here…
WordWrap    = False

Right-click the TextBox and click “Bring To Front” from the popup menu, so that it overlaps over the ListBox. This is to ensure that the ListBox scrollbars are not visible to the user.

And now some trick. Drag the bottom edge of the ListBox, just enough for the Panel vertical scrollbar to appear. Then drag the right edge of the TextBox so that it just touches the Panel vertical scrollbar, but doesn’t cause the horizontal scrollbar to appear. And then, drag the bottom edge of the TextBox so that it is the same height as the ListBox (or set it from properties window).
If you did everything right, this is how it should look like in Design window

Now let’s proceed to add some code. We start by adding code to TextBox TextChanged event.  We will synchronize line numbers here and also set the textbox height. (I’ll explain later why we need to dynamically change the height of ListBox and TextBox).

Private Sub TextBox1_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) _
                                 Handles TextBox1.TextChanged
    SyncLineNumbers()
    Dim newHeight As Integer = ListBox1.ItemHeight * ListBox1.Items.Count
    If newHeight > Panel1.Height Then
        ListBox1.Height = newHeight
        TextBox1.Height = newHeight
    End If
    ListBox1.SelectedIndex = TextBox1.GetLineFromCharIndex(TextBox1.SelectionStart)
End Sub
 
Private Sub SyncLineNumbers()
    If TextBox1.Lines.Count <> ListBox1.Items.Count Then
        Do While TextBox1.Lines.Count > ListBox1.Items.Count
            ListBox1.Items.Add((ListBox1.Items.Count + 1).ToString)
        Loop
        Do While TextBox1.Lines.Count < ListBox1.Items.Count
            ListBox1.Items.RemoveAt(ListBox1.Items.Count - 1)
        Loop
    End If
End Sub

 Run the code and see how it goes. It would automatically add and remove the Line numbers as and you type in the textbox.

But observe that the current row is not highlighted. And if you keep typing, as soon as text lines exceed the panel height, the cursor goes "inside" the panel. What you type is no longer visible, though it is added to textbox. Let’s fix this.

Private Sub TextBox1_KeyUp(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) _
                           Handles TextBox1.KeyUp
    ListBox1.SelectedIndex = TextBox1.GetLineFromCharIndex(TextBox1.SelectionStart)
    If ListBox1.Items.Count > Panel1.Height \ ListBox1.ItemHeight Then
        Panel1.VerticalScroll.Value = ListBox1.ItemHeight * ListBox1.SelectedIndex
    End If
End Sub

 This fixes both of our problems. The current row is highlighted and the cursor remains in view, instead of going inside the Panel, when text lines exceed the panel height. When you move your keyboard up/down button, the current row selection moves along with it.

Now the last bit of problem remaining. When you click anywhere inside the textbox, the cursor moves to that line, but the line number selection is not updated. We fix that by adding this code:

Private Sub TextBox1_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
                           Handles TextBox1.Click
    ListBox1.SelectedIndex = TextBox1.GetLineFromCharIndex(TextBox1.SelectionStart)
End Sub

Run the code and enjoy! Hot

How & Why this Works

Now that we have got it working, let’s see how it works.

The Panel is used to give the look of a textbox. The Panel control has a scrollbar built into it. We make it look like it is the scrollbar of the textbox. The ListBox is use to show line numbers, and the TextBox is used to show the actual text. The Font (font face, font size) of ListBox and TextBox is same. So when we add numbers to listbox, it looks like it is just beside the text lines. So effectively each item in the ListBox corresponds to one line in the textbox. We synchronize the scrollbars of ListBox and Panel so that the cursor in textbox is always visible. This also keeps the current row number always highlighted.

We keep the textbox/listbox height to just fit the text inside it, so that the panel scrollbar adjusts itself appropriately. That scrollbar scrolls the textbox/listbox controls up and down instead of the contents inside them.

Future Enhancements

This textbox can be enhanced in many ways. Here are a few ideas:

  • This is just a basic rig and there is no error handling code. Error handlers need to be added appropriately.
  • Wrap the entire thing nicely into a user control.
  • Add a horizontal scrollbar facility.
  • Add properties to toggle on/off the line numbers.
  • Replace the TextBox with a RichTextBox to add formatting capabilities to the control. This however adds more challenges to it. e.g. Each item’s height in the ListBox would need to be calculated and set appropriately as each line in RichTextBox would have a different height.

Advertisements

7 Responses to “VB.NET TextBox with Line Numbers”

  1. Abilene Fránquez Says:

    Perfect! Thank you so much, simple, functional… perfect.

  2. ABDUL KAREEM M A Says:

    Nice work Pradeep…!!

  3. Manos Says:

    Very good but you have to fix the problem occures when no text is in textbox.

    • pradeep1210 Says:

      Thanks for pointing this out. I’ll look into this and make the necessary fixes. 🙂

    • Kevin Says:

      add If txtwhatever.Text = “” = True Then
      Exit Sub
      Else
      If ListBox1.Items.Count > Panel1.Height \ ListBox1.ItemHeight Then

      Panel1.VerticalScroll.Value = ListBox1.ItemHeight * ListBox1.SelectedIndex

      End If

      End If

      This will fix that problem by testing if their is text in the textbox if not then it exits the sub else it executes the rest of the code

  4. Future Nyan Cat Says:

    You should add this in the SyncLineNumbers() Sub:

    Loop
    Do While ListBox1.Items.Count = 0
    ListBox1.Items.Add(“1”)

    Loop


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: