Click here to Skip to main content
14,978,516 members
Articles / Programming Languages / C#
Posted 22 May 2007


63 bookmarked

RichTextBox with Search Line Numbering, Bulleting, Printing, Searching Support

Rate me:
Please Sign up or sign in to vote.
4.16/5 (13 votes)
22 May 2007CPOL2 min read
A reusable control which overcomes the limitations of rich text box of VS.NET 2.0
Screenshot - RichTextBox_Form.png


Pretty good for us developers that Microsoft .NET 2.0 team has had a pity on us and developed a completely managed RichTextBox control which is ported with VS 2005. But it seems there is much work left in that control and that we would have to wait for a SP of VS 2005 or .NET Framework 2.0 for the general additional features which we look forward to in a rich text editor.

Some of the general features include:

  1. LineNumbering and Bulleting in richtextBox (can be achieved by ctrl+shift+L keys)
  2. Print facilities are not at all present and that too printing in a formatted pattern is a pain in the *** (you know where)
  3. Search function is given, but it is hard to implement and not quite clear for a newbie

May be, guys at Redmond shores have more mercy on us and think a 1000 times before releasing a control which is not complete and instead of decreasing a developers task increases it.


The main use of RichTextBox control is that it allows us to format text and allows OLE pictures, along with the advantage of handling extra length of text.

Now people, answer a simple question:


I know I am being a bit rude but, in the hurry of releasing a control, our friends at Redmond shores must have forgotten this feature (apart from many others).

The height of blunder is that they have forgotten to implement BulletType and BulletStyle properties and just have given a single BulletIndent property which is useless without having bullets inside the control.

I have tried my best to overcome these problems. I am sorry for being sarcastic but these high heads do need some shaking in order to explain to them that their audience is a developer and that their duty is to make his/her life simpler, not hell, by delopoying incomplete controls just because the release deadline is nearing.

Though there is a small drawback of using LineNumbering from Richtextbox, it stops numbering at 255, i.e. any lines beyond 255 are numbered as 255.

If any of you guys know how to overcome that, please let me know as well.

Using the Code

The source code is pretty simple unless you directly use the control.

Yeah, you might have to modify the search region if at all because I have used a case insensitive search option.

For printing, you need to place a PrintDocument conponent on the form and assign the PrintDocument property of the RichTextBoxEx with this component.

Without wasting more of your time, let's jump to the control source code.

// using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Drawing.Printing;

namespace RichTextBoxEx.Controls
    public class RichTextBoxEx : RichTextBox
        #region BULLETING

        private class PARAFORMAT2
            public int cbSize;
            public int dwMask;
            public short wNumbering;
            public short wReserved;
            public int dxStartIndent;
            public int dxRightIndent;
            public int dxOffset;
            public short wAlignment;
            public short cTabCount;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x20)]
            public int[] rgxTabs;

            public int dySpaceBefore; 	// Vertical spacing before para
            public int dySpaceAfter; 	// Vertical spacing after para
            public int dyLineSpacing; 	// Line spacing depending on Rule
            public short sStyle; 		// Style handle
            public byte bLineSpacingRule; 	// Rule for line spacing (see tom.doc)
            public byte bOutlineLevel; 	// Outline Level
            public short wShadingWeight; 	// Shading in hundredths of a per cent
            public short wShadingStyle; 	// Byte 0: style, nib 2: cfpat, 3: cbpat
            public short wNumberingStart; 	// Starting value for numbering
            public short wNumberingStyle; 	// Alignment, Roman/Arabic, (), ), ., etc.
            public short wNumberingTab; 	// Space bet 1st indent and 1st-line text
            public short wBorderSpace; 	// Border-text spaces (nbl/bdr in pts)
            public short wBorderWidth; 	// Pen widths (nbl/bdr in half twips)
            public short wBorders; 		// Border styles (nibble/border)

            public PARAFORMAT2()
                this.cbSize = Marshal.SizeOf(typeof(PARAFORMAT2));

        // PARAFORMAT mask values
        private const uint PFM_STARTINDENT = 0x00000001;
        private const uint PFM_RIGHTINDENT = 0x00000002;
        private const uint PFM_OFFSET = 0x00000004;
        private const uint PFM_ALIGNMENT = 0x00000008;
        private const uint PFM_TABSTOPS = 0x00000010;
        private const uint PFM_NUMBERING = 0x00000020;
        private const uint PFM_OFFSETINDENT = 0x80000000;

        // PARAFORMAT 2.0 masks and effects
        private const uint PFM_SPACEBEFORE = 0x00000040;
        private const uint PFM_SPACEAFTER = 0x00000080;
        private const uint PFM_LINESPACING = 0x00000100;
        private const uint PFM_STYLE = 0x00000400;
        private const uint PFM_BORDER = 0x00000800; // (*)
        private const uint PFM_SHADING = 0x00001000; // (*)
        private const uint PFM_NUMBERINGSTYLE = 0x00002000; // RE 3.0
        private const uint PFM_NUMBERINGTAB = 0x00004000; // RE 3.0
        private const uint PFM_NUMBERINGSTART = 0x00008000; // RE 3.0

        private const uint PFM_RTLPARA = 0x00010000;
        private const uint PFM_KEEP = 0x00020000; 		// (*)
        private const uint PFM_KEEPNEXT = 0x00040000; 		// (*)
        private const uint PFM_PAGEBREAKBEFORE = 0x00080000; 	// (*)
        private const uint PFM_NOLINENUMBER = 0x00100000; 	// (*)
        private const uint PFM_NOWIDOWCONTROL = 0x00200000; 	// (*)
        private const uint PFM_DONOTHYPHEN = 0x00400000; 	// (*)
        private const uint PFM_SIDEBYSIDE = 0x00800000; 	// (*)
        private const uint PFM_TABLE = 0x40000000; 		// RE 3.0
        private const uint PFM_TEXTWRAPPINGBREAK = 0x20000000; 	// RE 3.0
        private const uint PFM_TABLEROWDELIMITER = 0x10000000; 	// RE 4.0

        // The following three properties are read only
        private const uint PFM_COLLAPSED = 0x01000000; 	// RE 3.0
        private const uint PFM_OUTLINELEVEL = 0x02000000; 	// RE 3.0
        private const uint PFM_BOX = 0x04000000; 		// RE 3.0
        private const uint PFM_RESERVED2 = 0x08000000; 	// RE 4.0

        public enum AdvRichTextBulletType
            Normal = 1,
            Number = 2,
            LowerCaseLetter = 3,
            UpperCaseLetter = 4,
            LowerCaseRoman = 5,
            UpperCaseRoman = 6

        public enum AdvRichTextBulletStyle
            RightParenthesis = 0x000,
            DoubleParenthesis = 0x100,
            Period = 0x200,
            Plain = 0x300,
            NoNumber = 0x400

        [DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
        private static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam,
           [In, Out, MarshalAs(UnmanagedType.LPStruct)] PARAFORMAT2 lParam);

        private AdvRichTextBulletType _BulletType = AdvRichTextBulletType.Number;
        private AdvRichTextBulletStyle _BulletStyle = AdvRichTextBulletStyle.Period;
        private short _BulletNumberStart = 1;

        public AdvRichTextBulletType BulletType
            get { return _BulletType; }
                _BulletType = value;
        public AdvRichTextBulletStyle BulletStyle
            get { return _BulletStyle; }
                _BulletStyle = value;
        public void NumberedBullet(bool TurnOn)
            PARAFORMAT2 paraformat1 = new PARAFORMAT2();
            paraformat1.dwMask = (int)(PFM_NUMBERING | PFM_OFFSET | PFM_NUMBERINGSTART |
            if (!TurnOn)
                paraformat1.wNumbering = 0;
                paraformat1.dxOffset = 0;
                paraformat1.wNumbering = (short)_BulletType;
                paraformat1.dxOffset = this.BulletIndent;
                paraformat1.wNumberingStyle = (short)_BulletStyle;
                paraformat1.wNumberingStart = _BulletNumberStart;
                paraformat1.wNumberingTab = 500;
            SendMessage(new System.Runtime.InteropServices.HandleRef(this, this.Handle),
                0x447, 0, paraformat1);


        #region Printing
        //Convert the unit used by the .NET framework (1/100 inch) 
        //and the unit used by Win32 API calls (twips 1/1440 inch)
        private const double anInch = 14.4;

        private struct RECT
            public int Left;
            public int Top;
            public int Right;
            public int Bottom;

        private struct CHARRANGE
            public int cpMin;         //First character of range (0 for start of doc)
            public int cpMax;         //Last character of range (-1 for end of doc)

        private struct FORMATRANGE
            public IntPtr hdc;            //Actual DC to draw on
            public IntPtr hdcTarget;      //Target DC for determining text formatting
            public RECT rc;               //Region of the DC to draw to (in twips)
            public RECT rcPage;           //Region of the whole DC (page size) (in twips)
            public CHARRANGE chrg;        //Range of text to draw (see earlier 

        private const int WM_USER = 0x0400;
        private const int EM_FORMATRANGE = WM_USER + 57;

        private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp,
             IntPtr lp);

        // Render the contents of the RichTextBox for printing
        //    Return the last character printed + 1 (printing start from this point for
        //    next page)
        public int Print(int charFrom, int charTo, PrintPageEventArgs e)
            //Calculate the area to render and print
            RECT rectToPrint;
            rectToPrint.Top = (int)(e.MarginBounds.Top * anInch);
            rectToPrint.Bottom = (int)(e.MarginBounds.Bottom * anInch);
            rectToPrint.Left = (int)(e.MarginBounds.Left * anInch);
            rectToPrint.Right = (int)(e.MarginBounds.Right * anInch);

            //Calculate the size of the page
            RECT rectPage;
            rectPage.Top = (int)(e.PageBounds.Top * anInch);
            rectPage.Bottom = (int)(e.PageBounds.Bottom * anInch);
            rectPage.Left = (int)(e.PageBounds.Left * anInch);
            rectPage.Right = (int)(e.PageBounds.Right * anInch);

            IntPtr hdc = e.Graphics.GetHdc();

            FORMATRANGE fmtRange;
            fmtRange.chrg.cpMax = charTo;   //Indicate character from to character to 
            fmtRange.chrg.cpMin = charFrom;
            fmtRange.hdc = hdc;             //Use the same DC for measuring and rendering
            fmtRange.hdcTarget = hdc;       //Point at printer hDC
            fmtRange.rc = rectToPrint;      //Indicate the area on page to print
            fmtRange.rcPage = rectPage;     //Indicate size of page

            IntPtr res = IntPtr.Zero;

            IntPtr wparam = IntPtr.Zero;
            wparam = new IntPtr(1);

            //Get the pointer to the FORMATRANGE structure in memory
            IntPtr lparam = IntPtr.Zero;
            lparam = Marshal.AllocCoTaskMem(Marshal.SizeOf(fmtRange));
            Marshal.StructureToPtr(fmtRange, lparam, false);

            //Send the rendered data for printing 
            res = SendMessage(Handle, EM_FORMATRANGE, wparam, lparam);

            //Free the block of memory allocated

            //Release the device context handle obtained by a previous call

            //Return last + 1 character printer
            return res.ToInt32();

        private PrintDocument _PrintDocument;

        public PrintDocument PrintDocument
            get { return _PrintDocument; }
                _PrintDocument = value;
                if (_PrintDocument != null)
                    _PrintDocument.BeginPrint += new PrintEventHandler(
                    _PrintDocument.PrintPage += new PrintPageEventHandler(

        void _PrintDocument_PrintPage(object sender, PrintPageEventArgs e)
            // Print the content of RichTextBox. Store the last character printed.
            checkPrint = this.Print(checkPrint, this.TextLength, e);

            // Check for more pages
            if (checkPrint < this.TextLength)
                e.HasMorePages = true;
                e.HasMorePages = false;

        void _PrintDocument_BeginPrint(object sender, PrintEventArgs e)
            checkPrint = 0;

        int checkPrint = 0;


        #region Search
        int SearchIndex = 0;
        public void Search(string SearchText)
            //MDI form search term always holds precedence than the local search term 
            //as the user might be interested in searchning same term in more than
            //one windows...

            SearchIndex = (SearchIndex >= 0 ? SearchIndex : 0);

            if (SearchIndex >= Text.Length)
                "Reached Page End \nUnable to find Specified Text \"" 
						+ SearchText + "\"");

            if (this.Text.Length > 0 && !string.IsNullOrEmpty(SearchText))
                SearchIndex = this.Find(SearchText, SearchIndex, RichTextBoxFinds.None);
                if (SearchIndex == -1)
                "Reached Page End \nUnable to find Specified Text \"" 
						+ SearchText + "\"");
                    SearchIndex += SearchText.Length;

        public void Lock()
            this.ReadOnly = true;
            this.BackColor = SystemColors.Info;

        public void Unlock()
            this.ReadOnly = false;
            this.BackColor = SystemColors.Window;



  • 22nd May, 2007: Initial version


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


About the Author

Software Developer (Senior)
United Kingdom United Kingdom
No Biography provided

Comments and Discussions

QuestionUpdated richedit? Pin
Member 79482861-Mar-19 9:56
MemberMember 79482861-Mar-19 9:56 
GeneralMy vote of 5 Pin
Franc Morales17-Sep-13 14:07
MemberFranc Morales17-Sep-13 14:07 
QuestionNice Article Pin
deveoper1234522-Jun-11 19:34
Memberdeveoper1234522-Jun-11 19:34 
GeneralBullet Indent Bug Pin
rocketman22118-Dec-10 12:44
Memberrocketman22118-Dec-10 12:44 
Questionhow to set bullet indent Pin
chiya2vas19-Aug-10 20:16
Memberchiya2vas19-Aug-10 20:16 
QuestionLine Number Bug? Pin
Jonathan3216-Nov-09 2:06
MemberJonathan3216-Nov-09 2:06 
GeneralRight To Left Pin
khkkerenn778-Jan-09 9:44
Memberkhkkerenn778-Jan-09 9:44 
GeneralUse of RichTextBox extension in project Pin
Peter Motzfeldt19-Sep-07 22:40
MemberPeter Motzfeldt19-Sep-07 22:40 
GeneralRe: Use of RichTextBox extension in project Pin
samir4118019-Sep-07 23:31
Membersamir4118019-Sep-07 23:31 
GeneralRe: Use of RichTextBox extension in project Pin
Peter Motzfeldt19-Sep-07 23:34
MemberPeter Motzfeldt19-Sep-07 23:34 
GeneralSweet!!!!!!!!!!!!! Pin
zi3228-Jul-07 9:01
Memberzi3228-Jul-07 9:01 
GeneralRe: Sweet!!!!!!!!!!!!! Pin
samir4118029-Jul-07 22:53
Membersamir4118029-Jul-07 22:53 
Generalvery helpful Pin
BillW3330-May-07 6:54
professionalBillW3330-May-07 6:54 
GeneralNice Pin
merlin98122-May-07 3:31
professionalmerlin98122-May-07 3:31 

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

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.