Introduction

This article presents what I call an improved RichTextBox control. Why improved? Well, browsing
the internet you can
find a lot of comments pointing out to the different features that are missing on the control provided by Microsoft but three of the most obvious are:

Line numbering

Highlighting a line

Show current line and column

The first one has been addressed in several articles like the following ones:

Of course you can find more articles and code snippets related to line numbering, but while this issue has been solved the highlight line
and showing current line/column were still a pending issues. For everyone using Notepad++ or PSPad it is obvious that one of the nice features
is to highlight the line where currently the caret is and of course having an indication of line/column can be very helpful.

So what can you expect from this article? I will go through these three points showing how I solved them for my own project. Please notice that I am new to the .NET world
so if anything can be improved or made in a different way please let me know. I hope the information here will be useful for anyone seeking for a small and simple
solution for his/her own projects.

The code is divided in the following "Regions":

Region "ImprovedRTB Properties"

Region "General Subroutines"

Region "IRTB Events"

Region "Extra Features"

Implementation

The control is formed by two components, a RichTextBox and a
PictureBox. The PictureBox will be used to draw the line numbers and the
RichTextBox
will provide all the features we need from a text editor.

IRTB features are:

Line Numbering. This can be enabled/disabled at run-time

HighLight Line. This can be enabled/disabled at run-time

Event LineInformation which provides the information about the Current Line and Column

Event DragDropFileInformation to allow direct Drag and Drop files into the control.

Event BMLInformation used to inform that a line has been added/removed to/from the bookmarks

HighLight Line Color can be changed at run-time

Prefix for Line Numbering can be enabled/disabled at runtime.

Font can be changed at runtime.

Zoom

Drag/Drop is enabled

Bookmark Lines. New 2013.04.02

XML verification. New 2013.04.02

Line/Column information without the need of LineInformation event. New 2013.04.02

Known issues:

Performance is not good with files bigger than 1500Kb and it is really bad with files bigger than 2500Kb when both options, line numbering and highlight, are enabled. This is mainly due the calculations needed to add the numbers and draw the highlight line in top of the
RichTextBox

Some flickering can be noticed depending of the action or the size of the
file

If wrap is set to true it won't give the correct line numbering

Line Numbering

So let's start with the first point, Line Numbering. You can find several solutions, as I have said before, some of them show really nice graphics but unfortunately this make the control unusable for large files, other ones are just not totally finished and other ones preferred the unmanaged code (sendmessage) approach which is totally valid (I have used it for some other projects), having this in mind I decide to get something in the middle, not too fancy that makes the control unusable and try not to use the "sendmessage" option; so since I have started already with a similar approach to the one proposed by Michael Elly I decided to continue on that line, the key of Michael's code is the way it is calculated the line height. Why? Because as everyone knows the
RichTextBox scrolls using smooth scroll so instead of lines it uses pixels, this is due the nature of the control because it can support different formats, fonts, colors, etc. Therefore the only way to scroll is based on pixels rather than lines. So here is the core of the line numbering.

OK, now we have the routine to draw the numbers, great! (Thanks to Michael Elly), but
how do we use it? When should we call it? Now is when we need to check the event
Paint for the PictureBox. How can we trigger this event? And, under what conditions should we do it? The following events must be used for this:

Me.SizeChanged

IRTBTextContainer.VScroll

IRTBTextContainer.MouseWheel

IRTBTextContainer.SelectionChanged

LoadFileAndNumbering

When any of these events is raised the following sentence should be used:

PBNumbering.Invalidate()

By doing this we are forcing the PictureBox to re-paint so the event
Paint will be fired and DrawIRTBLineNumbers will be called.

And now numbers will be painted based on the lines shown on the VisibleClipBounds of the
RichTextBox.

Highlight Line

OK, now that we have the numbers painted we can try to get a rectangle highlighting the current line, to do this we need to use the standard Windows GDI+ library (System.Drawing) and we need to override
the OnPaint subroutine. It might be that you noticed that there is no Paint event exposed for the
RichTextbox control so the only way to get some control on what we want to paint is using the OnPaint method provided by
Control. We override the method and we add the needed code to paint the rectangle using the
RichTextBox width and the line height based on the Font used.

Why are we not using WndProc to catch the paint event of the
RichTextBox? Well I decided to go for the simpler solution but for those ones interested on this approach, here is a link showing how to catch the WM_PAINT message:

Great! Now we have a rectangle painted on top of the RichTextBox and semitransparent, so
we can see what it is behind! But now there are some new scenarios to be verified like these ones:

What happen if the user types a new letter in the RichTextBox

What happen if the user press the arrows

What happen if the user decides to zoom (Scroll+Ctrl)

What happen if the user press Page Up/Down

What happen if the user press Back/Delete/Enter

All these actions will trigger the hidden Paint event for the

RichTextBox

making the control to redraw and deleting the rectangle highlighting the line.
To handle this we need to use some of the events we have used to paint the numbers:

Me.SizeChanged

IRTBTextContainer.MouseWheel

IRTBTextContainer.SelectionChanged

And we need to use few more events:

IRTBTextContainer.MouseClick

IRTBTextContainer.HScroll

IRTBTextContainer.GotFocus

IRTBTextContainer.KeyDown

IRTBTextContainer.KeyUp

Now depending on the conditions we need to call the OnPaint method, by doing this the rectangle will be drawn after the Paint event for the
RichTextBox has done its work.

OnPaint(Nothing)

Since we are looking to highlight the line where the caret is currently positioned, the main event we have to check is SelectionChanged.
This event will be fired every time the selection changes inside the RichTextBox so here we can decide what to do based on the actions performed by the user (Key stroke, Mouse actions, etc.)

But there is a special case the "Delete" key, for this key We need to catch both events, KeyUp and KeyDown. Why?
Because if We don't do this the line numbers won't be updated until the key is released, or they will be updated late so there will be numbers for lines
that does not exist any more.

The subroutine that helps to do this is called RTBGetLineCol, this one is used to get the current Line and Column, but I will go through this later since
it is the third point I want to show on this article.

Note: Please noticed that there is one scenario where the numbers and the line/col information won't be updated correctly.
When the Arrows (Up/Down) are held down the action will be performed but due the calculations needed to paint the numbers and the highlight line depending
on the size of the file (more than 350KB) the information will be updated only after the key is released. Unfortunately I have not been able to solve this issue so far.

Correction: Now numbers and highlight line are being updated while the arrows (Up/Down) are being held down. To do this I have changed the following instruction:

PBNumbering.Invalidate()

For this one:

PBNumbering.Refresh()

By now we have managed to draw the rectangle based on key strokes, mouse click, but what would happen if We scroll and the caret goes beyond the visible bounds?
How can We detect when it is inside the visible bounds again? The following lines of code will do the trick:

If EnableHighLight = TrueThen'**Here it will redraw the highlight line when the caret position is coming inside the VisibleClipBoundsDim CaretPos As Point = IRTBTextContainer.GetPositionFromCharIndex(IRTBTextContainer.GetFirstCharIndexOfCurrentLine)
If (CaretPos.Y > -10 And CaretPos.Y < 20) Or (CaretPos.Y < IRTBTextContainer.Height + _
30And CaretPos.Y > IRTBTextContainer.Height - 100) Then
OnPaint(Nothing)
EndIfEndIf

This code is part of the subroutine that handles the RichTextBox event VScroll and it will be used only if highlight line is enabled

Line and Column Information

Now that We have Line Numbering and Highlight working the last point is to get the current line and column, these values are calculated on this subroutine:

New! Line and Column information can be read now using the property called IRTBLineCol. To get the information the method calledGetLineCol is used. For example this property can be used when a form has several tabs and change between them won't fire the event LineInformation so the only way to get the information without waiting for the event is through this new property.

Extras

As part of the project I decided to add few more features. Let's start with Drag and Drop.

Drag and Drop

I decided to implement Drag and Drop files so it won't be needed to implement the option in the project. So the code to do this is under the region called "Extras" and it is quite simple:

But remember, to be able to use Drag/Drop on a control you need to set this property IRTBTextContainer.AllowDrop to TRUE. This is already done when the control is loaded.

Zoom

One more thing that needs some explanation is how to handle the zoom option when Line Numbering and HighLight Line are enabled. As all we know a common shortcut for the zoom option
on the RichTextBox is CTRL+SCROLL. But how to handle this?, how the highlight line and the line numbering will be affected?

First we need to detect the scroll action and the Ctrl Key. Here is the code to detect the Mouse Wheel:

There are two keys detected here, CTRL and Escape, when CTRL is detected the IRTBKeysSelect is set to IRTBSelectionCase.KeysSpecial and when
the wheel action is fired the IRTBForceONPaint is set to true, by doing this We are sure that while zooming the numbers and the highlight line are refreshed. Please noticed that the bookmark icons won't be zoomed nor repainted.

When Escape is pressed the zoom will be set to 1 and the line numbers, highlight line and Bookmark icons will be painted again. Here is the code to detect the Escape key:

Bookmark Lines - New Feature 2013.04.02

This feature was not part of the original development of the IRTB, however the time goes by and while using the control in some other projects I felt this part was missing, so I decided to add it and update the article.

This new feature can be used in two ways:

By clicking on the line number.

By using Alt+B key combination. If the line is not part of the marked lines array will be added otherwise will be deleted.

One of the main issues here was to find the way to get the line number by clicking the PictureBox holding the line numbering. It took me a little bit of time but finally I figure out (If anyone has a better way please let me know), first we need to know the font height:

The purpose of the first line is to find the font height value corresponding to the font used by the line numbering at the moment. The second line will correct the value if for any reason the first calculation is less or equal to 0.

Once this is done we need to calculate the calculate where is the Y position of the first line number inside the visible clip bounds and the point where we are clicking. Once we have this we can calculate the line number but keep in mind this line number will be a value between 0 to the max line number that is shown in the visible clip Bounds.

MarkedLines is an ArrayList to save the lines that are bookmarked, if the line number is not in the collection then it will be added otherwise is removed, this Collection can be accessed using the property called IRTBMarkedLines.

The last part of this procedure is the new event BMLInformation that will be raised to inform that new lines are added or removed.

So now how can we bookmark the line using ALT+B? Adding the following lines to the method RTBTextContainer

Using the GetLineCol function we can get the line where the caret is at the moment, so just pressing ALT+B will add or remove the line from the MarkedLines collection and the new event BMLInformation will be raised to inform that new lines are added.

I have added one more option for this feature, a list of the bookmarked lines can be retrieved using the following property:

IRTBMarkedLines

Once you have the collection of lines you can use the following method to jump to the line in the IRTB control:

It works quite fine as far I have seen. So if you want further explanations about How an XML parser works google for "XML Parser" and you will get a lot of information, including some links to articles here in CodeProject like this one:

Conclusion

The three points mentioned at the beginning of the article have been presented and I hope the information is good enough to give you some ideas.
Unfortunately, and you will noticed this, the performance of the RichTextBox is being impacted when Line Numbering and/or High Light Line is enabled. Why is happening this?
The principal reason lies in the use of these lines of code:

Regarding the actions where the numbers and the highlight line need to be painted again handling the keys is not always easy but all combinations
can be detected so we can select what actions should be performed.

Now let's take a look on How to use the control

Using the Control

There are two zip files on this article:

IRTB_CodeProject.zip

IRTB_Demo_new.zip

To check what can be done with the IRTB control, download IRTB_Demo_new.zip, unzipped any place you want and then double click on IRTB_Test.exe.

The IRTB_CodeProject.zip contains the solution, unzip the file and open the solution. I have added two projects so when the file is open you can see the following:

Figure 3. Solution opened on Visual Studio

The one called IRTB is the control and the one called IRTB_Test is a
Windows Form to check the basic functionality of the control.

The properties exposed by the IRTB control are:

IRTBFileToLoad

IRTBFont

IRTBLNFontColor

IRTBHighLightColor

ShowTotalChar New Name IRTBShowTotalChar

IRTBEnableNumbering

IRTBEnableHighLight

RTBContainer

RTBnumbering

IRTBPrefix

IRTBAlignNumbers

There are 2 new properties (2013.04.02):

IRTBMarkedLines >> It contains the Bookmarked lines, so the information can be read and used by the application to jump to the marked lines. See GoToBookmark method

IRTBLineCol>> Line and Column information are provided by an event called LineInformation, but if the application needs to get this information without wait for the event to be raised it can be done through this property.

Figure 4. IRTB Properties

The purpose adding the Windows Form was to show what can be done with the control, please check the code and I hope is clear enough and shows what can be done with the IRTB control.

If you are going to use the IRTB control on your personal project don't forget to add the DLL on the reference tab, otherwise you will no be able to use it. Noticed that the new version is 2.5.0.0

Figure 5. Add IRTB to the Reference tab

Here is the code used on the IRTB_Test Windows Form, I have added the new options (2013.04.02) to the form.

To show the new features I have added two components to the original demo, the first one is a button called "Check XML" to do the XML parsing. Please notice that if the file is not XML formatted the process will throw an error, this can be caught to show a Messagebox with the error information. To do this here is the coded used:

The second one is a ListBox called BMLinesTool. This listbox will show the bookmarked lines, by clicking on any of the items in the list the method GoToBookmark will be used and the line will be highlighted.

Private Sub BMLinesTool_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BMLinesTool.SelectedIndexChanged
If Not BMLinesTool.SelectedItem Is Nothing Then
Dim LineNumber As Integer = CInt(Replace(BMLinesTool.SelectedItem.ToString, "Line ", ""))
Irtb1.GoToBookmark(LineNumber)
End If
End Sub

Please review the project, check the code and if you have any questions let me know. I will try to answer them in the best possible way.

The code within the method 'InitializeComponent' is generated by the designer and should not be manually modified. Please remove any changes and try opening the designer again. IRTB_Master\IRTB_Test\Form1.Designer.vb 26
----------------------------

I am sure your project is fine, and I am just making some fundamental mistake, but I would be very grateful if you could tell me what mistake I am making!!

Sorry for the late reply and thanks for checking the project!. I will take a look on it but I haven't open the project in quite sometime so I need to check and I will reply as soon I can.

Update.

Now I have checked and found the problem.
I have downloaded the project and opened with VS 2015 Community as you did.
When you open the project(IRTB.sln) you can see 2 solutions in the solution explorer, one called IRTB and one called IRTB_Test. If you try to run the IRTB_Test without building first the IRTB solution you will end up in the problem you mentioned. So to fix it double click on "My Project" link under "IRTB" and then select Build--> Rebuild Solution. Once it has finished try to open "My Porject" under "IRTB_Test" and press "Start".

I know you don't like the sendmessage, but I will use it on IRTB2 2.4 'cuz it's less CPU consupting. Don Ho(creator of Notepad++) was just boasting that his program use API calls more than any other, so you computer don't waste CPU/Battery(or energy) and environment is greener.
IRTB is good, but Intel Pentium is not.
'ps: sendmessage is very difficulty. Aborting...

I've created a code on my IRTB2(CodeProject rejected it) that does it.
I used it on not exactly a usercontrol with a picBox, but a a class wich inherited picBox with some codes to make the line numbering. To highlight the correct line, it compares i with the IRTB2 Line property that return current line.