Click here to Skip to main content
15,892,298 members
Articles / Programming Languages / C#

Advanced Text Editor with Ruler

Rate me:
Please Sign up or sign in to vote.
4.85/5 (47 votes)
20 Mar 2008CPOL3 min read 432.1K   32.4K   164  
Extending RichTextBox with ruler and much more
using System.Diagnostics;
using System.Collections;
using Microsoft.VisualBasic;
using System.Collections.Generic;
using System;
using System.Windows.Forms;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Drawing.Printing;
using System.ComponentModel; 
 
 /// <summary>
 /// The rich text box print control class was developed by Microsoft, information about
 /// this control can be found in your help files at:
 /// ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.KB.v10.en/enu_kbvbnetkb/vbnetkb/811401.htm
 /// In general, their intent was to create a rich text box control with print capability
 /// embedded into the control.
 /// </summary>
 /// <remarks>This control class replaces the use of the regular RichTextBox control; the
 /// purpose of this extension was specifically to facilitate printing the contents
 /// of a rich text box control.</remarks>

internal class ExtendedRichTextBox : RichTextBox
{

    #region FORMAT_RANGE_CONSTANTS
    private const int SCF_SELECTION = 0x0001;
    private const int SCF_WORD = 0x0002;
    private const int SCF_DEFAULT = 0x0000;	// set the default charformat or paraformat
    private const int SCF_ALL = 0x0004;		// not valid with SCF_SELECTION or SCF_WORD
    private const int SCF_USEUIRULES = 0x0008;
    #endregion

    #region Windows API

    private const int WM_SETREDRAW = 0x0B;
    private const int EM_SETEVENTMASK = 0x0431;
    private const int EM_SETCHARFORMAT = 0x0444;
    private const int EM_GETCHARFORMAT = 0x043A;
    private const int EM_GETPARAFORMAT = 0x043D;
    private const int EM_SETPARAFORMAT = 0x0447;
    private const int EM_SETTYPOGRAPHYOPTIONS = 0x04CA;
    private const int CFM_UNDERLINETYPE = 0x800000;
    private const int CFM_BACKCOLOR = 0x4000000;
    private const int CFE_AUTOBACKCOLOR = 0x4000000;    
    private const int PFM_ALIGNMENT = 0x08;
    private const int TO_ADVANCEDTYPOGRAPHY = 0x01;

    [DllImport("user32", CharSet = CharSet.Auto)]
    private static extern int SendMessage(HandleRef hWnd, int msg, int wParam, int lParam);

    [DllImport("user32", CharSet = CharSet.Auto)]
    private static extern int SendMessage(HandleRef hWnd, int msg, int wParam, ref CHARFORMAT2 lParam);

    [DllImport("user32", CharSet = CharSet.Auto)]
    private static extern int SendMessage(HandleRef hWnd, int msg, int wParam, ref PARAFORMAT2 lParam);

    [DllImport("uxtheme.dll", CharSet = CharSet.Unicode)]
    private static extern int SetWindowTheme(
    HandleRef hWnd,
    [MarshalAs(UnmanagedType.LPWStr)] 
string pszSubAppName,
    [MarshalAs(UnmanagedType.LPWStr)] 
string pszSubIdList);

    /// <summary> 
    /// Contains information about character formatting in a rich edit control. 
    /// </summary> 
    /// <remarks><see cref="CHARFORMAT"/> works with all Rich Edit versions.</remarks> 
    [StructLayout(LayoutKind.Sequential)]
    private struct CHARFORMAT
    {
        public int cbSize;
        public uint dwMask;
        public uint dwEffects;
        public int yHeight;
        public int yOffset;
        public int crTextColor;
        public byte bCharSet;
        public byte bPitchAndFamily;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
        public char[] szFaceName;
    }

    /// <summary> 
    /// Contains information about character formatting in a rich edit control. 
    /// </summary> 
    /// <remarks><see cref="CHARFORMAT2"/> requires Rich Edit 2.0.</remarks> 
    [StructLayout(LayoutKind.Sequential)]
    private struct CHARFORMAT2
    {
        public int cbSize;
        public uint dwMask;
        public uint dwEffects;
        public int yHeight;
        public int yOffset;
        public int crTextColor;
        public byte bCharSet;
        public byte bPitchAndFamily;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
        public char[] szFaceName;
        public short wWeight;
        public short sSpacing;
        public int crBackColor;
        public int LCID;
        public uint dwReserved;
        public short sStyle;
        public short wKerning;
        public byte bUnderlineType;
        public byte bAnimation;
        public byte bRevAuthor;
    }

    /// <summary> 
    /// Contains information about paragraph formatting in a rich edit control. 
    /// </summary> 
    /// <remarks><see cref="PARAFORMAT"/> works with all Rich Edit versions.</remarks> 
    [StructLayout(LayoutKind.Sequential)]
    private struct PARAFORMAT
    {
        public int cbSize;
        public uint 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 = 32)]
        public int[] rgxTabs;
    }

    /// <summary> 
    /// Contains information about paragraph formatting in a rich edit control. 
    /// </summary> 
    /// <remarks><see cref="PARAFORMAT2"/> requires Rich Edit 2.0.</remarks> 
    [StructLayout(LayoutKind.Sequential)]
    private struct PARAFORMAT2
    {
        public int cbSize;
        public uint 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 = 32)]
        public int[] rgxTabs;
        public int dySpaceBefore;
        public int dySpaceAfter;
        public int dyLineSpacing;
        public short sStyle;
        public byte bLineSpacingRule;
        public byte bOutlineLevel;
        public short wShadingWeight;
        public short wShadingStyle;
        public short wNumberingStart;
        public short wNumberingStyle;
        public short wNumberingTab;
        public short wBorderSpace;
        public short wBorderWidth;
        public short wBorders;
    }

    #endregion
    #region Method: OnHandleCreated

    /// <summary> 
    /// Raises the <see cref="HandleCreated"/> event. 
    /// </summary> 
    /// <param name="e">An <see cref="EventArgs"/> that contains the event data.</param> 
    protected override void OnHandleCreated(EventArgs e)
    {
        base.OnHandleCreated(e);

        // Enable support for justification 
        SendMessage(new HandleRef(this, Handle), EM_SETTYPOGRAPHYOPTIONS, TO_ADVANCEDTYPOGRAPHY, TO_ADVANCEDTYPOGRAPHY);
    }

    #endregion

    /// <summary> 
    /// Specifies horizontal alignment for a segment of rich text. 
    /// </summary> 
    public enum RichTextAlign
    {
        /// <summary> 
        /// The text alignment is unknown. 
        /// </summary> 
        Unknown = 0,

        /// <summary> 
        /// The text is aligned to the left. 
        /// </summary> 
        Left = 1,

        /// <summary> 
        /// The text is aligned to the right. 
        /// </summary> 
        Right = 2,

        /// <summary> 
        /// The text is aligned in the center. 
        /// </summary> 
        Center = 3,

        /// <summary> 
        /// The text is justified. 
        /// </summary> 
        Justify = 4
    }

    /// <summary> 
    /// Specifies the underline styles for a segment of rich text. 
    /// </summary> 
    public enum UnderlineStyle
    {
        /// <summary> 
        /// No underlining. 
        /// </summary> 
        None = 0,

        /// <summary> 
        /// Single-line solid underline. 
        /// </summary> 
        Normal = 1,

        /// <summary> 
        /// Single-line underline broken between words. 
        /// </summary> 
        Word = 2,

        /// <summary> 
        /// Double-line underline. 
        /// </summary> 
        Double = 3,

        /// <summary> 
        /// 'Dotted' pattern underline. 
        /// </summary> 
        Dotted = 4,

        /// <summary> 
        /// 'Dash' pattern underline. 
        /// </summary> 
        Dash = 5,

        /// <summary> 
        /// 'Dash-dot' pattern underline. 
        /// </summary> 
        DashDot = 6,

        /// <summary> 
        /// 'Dash-dot-dot' pattern underline. 
        /// </summary> 
        DashDotDot = 7,

        /// <summary> 
        /// Single-line wave style underline. 
        /// </summary> 
        Wave = 8,

        /// <summary> 
        /// Single-line solid underline with extra thickness. 
        /// </summary> 
        Thick = 9,

        /// <summary> 
        /// Single-line solid underline with less thickness. 
        /// </summary> 
        HairLine = 10,

        /// <summary> 
        /// Double-line wave style underline. 
        /// </summary> 
        DoubleWave = 11,

        /// <summary> 
        /// Single-line wave style underline with extra thickness. 
        /// </summary> 
        HeavyWave = 12,

        /// <summary> 
        /// 'Long Dash' pattern underline. 
        /// </summary> 
        LongDash = 13,

        /// <summary> 
        /// 'Dash' pattern underline with extra thickness. 
        /// </summary> 
        ThickDash = 14,

        /// <summary> 
        /// 'Dash-dot' pattern underline with extra thickness. 
        /// </summary> 
        ThickDashDot = 15,

        /// <summary> 
        /// 'Dash-dot-dot' pattern underline with extra thickness. 
        /// </summary> 
        ThickDashDotDot = 16,

        /// <summary> 
        /// 'Dotted' pattern underline with extra thickness. 
        /// </summary> 
        ThickDotted = 17,

        /// <summary> 
        /// 'Long Dash' pattern underline with extra thickness. 
        /// </summary> 
        ThickLongDash = 18
    }

    /// <summary> 
    /// Specifies the color of underline for a segment of rich text. 
    /// </summary> 
    public enum UnderlineColor
    {
        /// <summary> 
        /// No specific underline color specified. 
        /// </summary> 
        None = -1,

        /// <summary> 
        /// Black. 
        /// </summary> 
        Black = 0x00,

        /// <summary> 
        /// Blue. 
        /// </summary> 
        Blue = 0x10,

        /// <summary> 
        /// Cyan. 
        /// </summary> 
        Cyan = 0x20,

        /// <summary> 
        /// LimeGreen. 
        /// </summary> 
        LimeGreen = 0x30,

        /// <summary> 
        /// Magenta. 
        /// </summary> 
        Magenta = 0x40,

        /// <summary> 
        /// Red. 
        /// </summary> 
        Red = 0x50,

        /// <summary> 
        /// Yellow. 
        /// </summary> 
        Yellow = 0x60,

        /// <summary> 
        /// White. 
        /// </summary> 
        White = 0x70,

        /// <summary> 
        /// DarkBlue. 
        /// </summary> 
        DarkBlue = 0x80,

        /// <summary> 
        /// DarkCyan. 
        /// </summary> 
        DarkCyan = 0x90,

        /// <summary> 
        /// Green. 
        /// </summary> 
        Green = 0xA0,

        /// <summary> 
        /// DarkMagenta. 
        /// </summary> 
        DarkMagenta = 0xB0,

        /// <summary> 
        /// Brown. 
        /// </summary> 
        Brown = 0xC0,

        /// <summary> 
        /// OliveGreen. 
        /// </summary> 
        OliveGreen = 0xD0,

        /// <summary> 
        /// DarkGray. 
        /// </summary> 
        DarkGray = 0xE0,

        /// <summary> 
        /// Gray. 
        /// </summary> 
        Gray = 0xF0
    }

    #region Property: SelectionUnderlineStyle

    /// <summary> 
    /// Gets or sets the underline style to apply to the current selection or insertion point. 
    /// </summary> 
    /// <value>A <see cref="UnderlineStyle"/> that represents the underline style to 
    /// apply to the current text selection or to text entered after the insertion point.</value> 
    [Browsable(false),
    DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public UnderlineStyle SelectionUnderlineStyle
    {
        get
        {
            CHARFORMAT2 fmt = new CHARFORMAT2();
            fmt.cbSize = Marshal.SizeOf(fmt);

            // Get the underline style 
            SendMessage(new HandleRef(this, Handle), EM_GETCHARFORMAT, SCF_SELECTION, ref fmt);
            if ((fmt.dwMask & CFM_UNDERLINETYPE) == 0)
            {
                return UnderlineStyle.None;
            }
            else
            {
                byte style = (byte)(fmt.bUnderlineType & 0x0F);
                return (UnderlineStyle)style;
            }
        }
        set
        {
            // Ensure we don't alter the color 
            UnderlineColor color = SelectionUnderlineColor;

            // Ensure we don't show it if it shouldn't be shown 
            if (value == UnderlineStyle.None)
                color = UnderlineColor.Black;

            // Set the underline type 
            CHARFORMAT2 fmt = new CHARFORMAT2();
            fmt.cbSize = Marshal.SizeOf(fmt);
            fmt.dwMask = CFM_UNDERLINETYPE;
            fmt.bUnderlineType = (byte)((byte)value | (byte)color);
            SendMessage(new HandleRef(this, Handle), EM_SETCHARFORMAT, SCF_SELECTION, ref fmt);
        }
    }

    #endregion
    #region Property: SelectionUnderlineColor

    /// <summary> 
    /// Gets or sets the underline color to apply to the current selection or insertion point. 
    /// </summary> 
    /// <value>A <see cref="UnderlineColor"/> that represents the underline color to 
    /// apply to the current text selection or to text entered after the insertion point.</value> 
    [Browsable(false),
    DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public UnderlineColor SelectionUnderlineColor
    {
        get
        {
            CHARFORMAT2 fmt = new CHARFORMAT2();
            fmt.cbSize = Marshal.SizeOf(fmt);

            // Get the underline color 
            SendMessage(new HandleRef(this, Handle), EM_GETCHARFORMAT, SCF_SELECTION, ref fmt);
            if ((fmt.dwMask & CFM_UNDERLINETYPE) == 0)
            {
                return UnderlineColor.None;
            }
            else
            {
                byte style = (byte)(fmt.bUnderlineType & 0xF0);
                return (UnderlineColor)style;
            }
        }
        set
        {
            // If the an underline color of "None" is specified, remove underline effect 
            if (value == UnderlineColor.None)
            {
                SelectionUnderlineStyle = UnderlineStyle.None;
            }
            else
            {
                // Ensure we don't alter the style 
                UnderlineStyle style = SelectionUnderlineStyle;

                // Ensure we don't show it if it shouldn't be shown 
                if (style == UnderlineStyle.None)
                    value = UnderlineColor.Black;

                // Set the underline color 
                CHARFORMAT2 fmt = new CHARFORMAT2();
                fmt.cbSize = Marshal.SizeOf(fmt);
                fmt.dwMask = CFM_UNDERLINETYPE;
                fmt.bUnderlineType = (byte)((byte)style | (byte)value);
                SendMessage(new HandleRef(this, Handle), EM_SETCHARFORMAT, SCF_SELECTION, ref fmt);
            }
        }
    }

    #endregion
    #region Property: SelectionBackColor

    /// <summary> 
    /// Gets or sets the background color to apply to the 
    /// current selection or insertion point. 
    /// </summary> 
    /// <value>A <see cref="Color"/> that represents the background color to 
    /// apply to the current text selection or to text entered after the insertion point.</value> 
    /// <remarks> 
    /// <para>A value of Color.Empty indicates that the default background color is used.</para> 
    /// <para>If the selection contains more than one background 
    /// color, then this property will indicate it by 
    /// returning Color.Empty.</para> 
    /// </remarks> 
    [Browsable(false),
    DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public Color SelectionBackColor2
    {
        get
        {
            CHARFORMAT2 fmt = new CHARFORMAT2();
            fmt.cbSize = Marshal.SizeOf(fmt);

            // Get the background color 
            SendMessage(new HandleRef(this, Handle), EM_GETCHARFORMAT, SCF_SELECTION, ref fmt);

            // Default to Color.Empty as there could be 
            // several colors present in this selection 
            if ((fmt.dwMask & CFM_BACKCOLOR) == 0)
                return Color.Empty;

            // Default to Color.Empty if the background color is automatic 
            if ((fmt.dwEffects & CFE_AUTOBACKCOLOR) == CFE_AUTOBACKCOLOR)
                return Color.Empty;

            // Deal with the weird Windows color format 
            return ColorTranslator.FromWin32(fmt.crBackColor);
        }
        set
        {
            CHARFORMAT2 fmt = new CHARFORMAT2();
            fmt.cbSize = Marshal.SizeOf(fmt);
            fmt.dwMask = CFM_BACKCOLOR;
            if (value.IsEmpty)
                fmt.dwEffects = CFE_AUTOBACKCOLOR;
            else
                fmt.crBackColor = ColorTranslator.ToWin32(value);

            // Set the background color 
            SendMessage(new HandleRef(this, Handle), EM_SETCHARFORMAT, SCF_SELECTION, ref fmt);
        }
    }

    #endregion
    #region Property: SelectionAlignment

    /// <summary> 
    /// Gets or sets the text alignment to apply to the current 
    /// selection or insertion point. 
    /// </summary> 
    /// <value>A member of the <see cref="RichTextAlign"/> enumeration that represents 
    /// the text alignment to apply to the current text selection or to text entered 
    /// after the insertion point.</value> 
    [Browsable(false),
    DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public new RichTextAlign SelectionAlignment
    {
        get
        {
            PARAFORMAT2 fmt = new PARAFORMAT2();
            fmt.cbSize = Marshal.SizeOf(fmt);
            SendMessage(new HandleRef(this, Handle), EM_GETPARAFORMAT, SCF_SELECTION, ref fmt);

            if ((fmt.dwMask & PFM_ALIGNMENT) == 0)
                return RichTextAlign.Unknown;
            else
                return (RichTextAlign)fmt.wAlignment;
        }
        set
        {
            PARAFORMAT2 fmt = new PARAFORMAT2();
            fmt.cbSize = Marshal.SizeOf(fmt);
            fmt.dwMask = PFM_ALIGNMENT;
            fmt.wAlignment = (short)value;

            // Set the alignment 
            SendMessage(new HandleRef(this, Handle), EM_SETPARAFORMAT, SCF_SELECTION, ref fmt);
        }
    }

    #endregion

    // Convert the unit that is used by the .NET framework (1/100 inch)
    // and the unit that is 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 above declaration)
    }

    private const int WM_USER = 0x400;
    private const int EM_FORMATRANGE = WM_USER + 57;  
    private const int PFM_NUMBERING = 20;
    private const int BULLET_NUMBER = 2;

    public enum ListType
    {
        Bullet, Number, None
    }

    public void SetListType()
    {
        PARAFORMAT2 param = new PARAFORMAT2();
        /// you must specify the size of the structure here, using Marshal.SizeOf , in VB6 it was Len( )
        param.cbSize = Marshal.SizeOf(param);
        /// build up the param structure with the current layout...
        SendMessage(new HandleRef(this, Handle), EM_GETPARAFORMAT, 0, ref param);
        /// wNumbering
        /// Options used for bulleted or numbered paragraphs. To use this member, set the PFM_NUMBERING flag in the dwMask member.
        /// PFN_BULLET ( 1 ) = Insert a bullet at the beginning of each selected paragraph.
        /// 2 = Uses Arabic numbers (1, 2, 3, ...).  , hence BULLET_NUMBER.
        param.dwMask = PFM_NUMBERING;
        param.wNumbering = BULLET_NUMBER;
        
        /// update the richtextbox...
        SendMessage(new HandleRef(this, Handle), EM_SETPARAFORMAT, 0, ref param);
    } 
        
    [DllImport("USER32", EntryPoint = "SendMessageA", ExactSpelling = true, CharSet = CharSet.Ansi, SetLastError = true)]
    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)
    {

        // Mark starting and ending character
        CHARRANGE cRange;
        cRange.cpMin = charFrom;
        cRange.cpMax = charTo;

        // 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 = cRange; // Indicate character from to character to
        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 whole size of page

        IntPtr res = IntPtr.Zero;

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

        // Move 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();
    }

}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

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


Written By
Kazakstan Kazakstan
Currently I am studying at East Kazakhstan State Technical University. My future occupation is engineer-programmer.

I use Visual Basic, C#, Delphi and a little C++. Also I am interested in using SQL and Perl.

Comments and Discussions