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

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

By , 22 May 2007
 
Screenshot - RichTextBox_Form.png

Introduction

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.

Background

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:

IS A TEXT EDITOR COMPLETE WITHOUT PRINTING FACILITIES ?

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

        [StructLayout(LayoutKind.Sequential)]
        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));
            }
        }

        #region PARAFORMAT MASK VALUES
        // 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
        }
        #endregion

        [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; }
            set
            {
                _BulletType = value;
                NumberedBullet(true);
            }
        }
        public AdvRichTextBulletStyle BulletStyle
        {
            get { return _BulletStyle; }
            set
            {
                _BulletStyle = value;
                NumberedBullet(true);
            }
        }
        public void NumberedBullet(bool TurnOn)
        {
            PARAFORMAT2 paraformat1 = new PARAFORMAT2();
            paraformat1.dwMask = (int)(PFM_NUMBERING | PFM_OFFSET | PFM_NUMBERINGSTART |
                PFM_NUMBERINGSTYLE | PFM_NUMBERINGTAB);
            if (!TurnOn)
            {
                paraformat1.wNumbering = 0;
                paraformat1.dxOffset = 0;
            }
            else
            {
                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);
        }

        #endregion

        #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;

        [StructLayout(LayoutKind.Sequential)]
        private struct RECT
        {
            public int Left;
            public int Top;
            public int Right;
            public int Bottom;
        }

        [StructLayout(LayoutKind.Sequential)]
        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)
        }

        [StructLayout(LayoutKind.Sequential)]
        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 
                                          //declaration)
        }

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

        [DllImport("USER32.dll")]
        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
            Marshal.FreeCoTaskMem(lparam);

            //Release the device context handle obtained by a previous call
            e.Graphics.ReleaseHdc(hdc);

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

        private PrintDocument _PrintDocument;

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

        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;
            else
                e.HasMorePages = false;
        }

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


        int checkPrint = 0;

        #endregion

        #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)
                MessageBox.Show(
                "Reached Page End \nUnable to find Specified Text \"" 
						+ SearchText + "\"");

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

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

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

        #endregion
    }   
}
//

History

  • 22nd May, 2007: Initial version

License

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

About the Author

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

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   
QuestionNice Articlememberdeveoper1234522 Jun '11 - 19:34 
Thanks for providing such a nice article. I need some help in it can we add functionality like bullets and number behaviour with indentation in ms word.
 
like
1. dfsf
2.fsfsfs
a.fdfsdfs
b.dfsdfsdfs
i.fsdfsdf
ii.dfsdfsdfsd
GeneralBullet Indent Bugmemberrocketman22118 Dec '10 - 12:44 
The BulletIndent property doesn't appear to be working.
Questionhow to set bullet indentmemberchiya2vas19 Aug '10 - 20:16 
hi , how to set bulletIndent for number style. we have bullet indent for bulleting but didnt fount for Numbering or roman can u help me in this
QuestionLine Number Bug?memberJonathan3216 Nov '09 - 2:06 
Hi,
 
this is a useful article.
 
Running the test program I noticed that line number above 255 will stay on 255.
 
Any tip?
 
Jon
GeneralRight To Leftmemberkhkkerenn778 Jan '09 - 9:44 
Hi,
 
First, I would like to thank you for this excellent article!
This code could be very useful for me, but I have one problem: the 'right to left' does not work. Sigh | :sigh:
Is there any way to use it in right to left?
 
Thank you,
Keren.
GeneralUse of RichTextBox extension in projectmemberPeter Motzfeldt19 Sep '07 - 22:40 
Hi Samir
 
I am a developer from Norway and is employed on a project where I need to be able to use numbered bullets in a RichTextBox.
 
I found your solution and was impressed.
 
I wonder if I have you written concent for using the Bulleting region in your RichTextBox extension in my project, or is it copyrighted so that I am not able to use the code?
 
Best regard Peter
GeneralRe: Use of RichTextBox extension in projectmembersamir4118019 Sep '07 - 23:31 
Hi Peter, You have my conscent to use any part of this code in your project, just be kind Wink | ;) enough to keep my name as a developer in comments on the file where you use this code.
:-DThough it should be necessary as this would always make any developer feel proud of his work regardless of who uses it,of course Confused | :confused: , without a fear that some one else might take a credit of his/her development.Laugh | :laugh:
GeneralRe: Use of RichTextBox extension in projectmemberPeter Motzfeldt19 Sep '07 - 23:34 
Thank you very much!
I will keep your name in the file, absolutely.
GeneralSweet!!!!!!!!!!!!!memberCodeDotNetInWin3228 Jul '07 - 9:01 
Just what i needed. Wink | ;) Ive been looking to implement more bullet styles but couldn't find constant valuesSmile | :) in the API's.
 
Anyway thanks soooo much Smile | :)
GeneralRe: Sweet!!!!!!!!!!!!!membersamir4118029 Jul '07 - 22:53 
Hi,Frown | :( I am sorry for that...But I am afraid thats all they have given Confused | :confused: in MSDN...Big Grin | :-D
Generalvery helpfulmemberCIDev30 May '07 - 6:54 
A very helpful article, thanks.

GeneralNicemembermerlin98122 May '07 - 3:31 
Great article. Thank you
 


~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
I get my developer tools from Merlin A.I. Soft

I get my news and jokes from Daily Roundup

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

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

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130516.1 | Last Updated 22 May 2007
Article Copyright 2007 by samir41180
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid