Click here to Skip to main content
15,892,005 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;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using TextRuler.TextRulerControl;

namespace TextRuler.TextRulerControl
{
    internal partial class TextRuler : UserControl
    {

        #region Variables
        //control dimensions
        RectangleF me = new RectangleF();
        //drawzone area
        RectangleF drawZone = new RectangleF();
        //area which is bounded by margins
        RectangleF workArea = new RectangleF();
        //items of the ruler
        List<RectangleF> items = new List<RectangleF>();
        //tab stops
        List<RectangleF> tabs = new List<RectangleF>();
        //pen to draw with
        Pen p = new Pen(Color.Transparent);        
        //margins and indents in pixels (that is why float)
        private int lMargin = 20, rMargin = 15, llIndent = 20, luIndent = 20, rIndent = 15;
        //border color
        Color _strokeColor = Color.Black;
        //background color
        Color _baseColor = Color.White;
        //position
        int pos = -1;
        //indicates if mouse button is being pressed and object is captured
        bool mCaptured = false;
        //indicates if margins are used
        bool noMargins = false;
        //index of the captured object
        int capObject = -1, capTab = -1;
        //are tabs enabled?
        bool _tabsEnabled = false;
        internal enum ControlItems
        {
            LeftIndent,
            LeftHangingIndent,
            RightIndent,
            LeftMargin,
            RightMargin
        }

        #region Events declarations
        public delegate void IndentChangedEventHandler(int NewValue);
        public delegate void MarginChangedEventHandler(int NewValue);
        public delegate void TabChangedEventHandler(TabEventArgs args);

        public event IndentChangedEventHandler LeftHangingIndentChanging;
        public event IndentChangedEventHandler LeftIndentChanging;
        public event IndentChangedEventHandler RightIndentChanging;

        public event MarginChangedEventHandler LeftMarginChanging;
        public event MarginChangedEventHandler RightMarginChanging;

        public event TabChangedEventHandler TabAdded;
        public event TabChangedEventHandler TabRemoved;
        public event TabChangedEventHandler TabChanged;

        #endregion

        #endregion

        #region Constructor
        private void InitializeComponent()
        {
            this.SuspendLayout();
            // 
            // TextRuler
            // 
            this.Name = "TextRuler";
            this.Size = new System.Drawing.Size(100, 20);
            this.ResumeLayout(false);

        }

        public TextRuler()
        {
            this.SetStyle(ControlStyles.UserPaint, true);
            this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
            this.SetStyle(ControlStyles.ResizeRedraw, true);
            this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
            this.BackColor = Color.Transparent;
            this.Font = new Font("Arial", 7.25f);

            tabs.Clear();

            //margins and indents
            items.Add(new RectangleF());
            items.Add(new RectangleF());
            items.Add(new RectangleF());
            items.Add(new RectangleF());
            items.Add(new RectangleF());
            items.Add(new RectangleF());
            items.Add(new RectangleF());

            /*
             * items[0] - left margin
             * items[1] - right margin
             * items[2] - left indent upper mark
             * items[3] - left indent lower mark (picture region)
             * items[4] - right indent mark
             * items[5] - left indent mark (self-moving region)
             * items[6] - left indent mark (all-moving region)
            */

        }
        #endregion

        #region Painting
        private void DrawBackGround(Graphics g)
        {            
            //clear background
            p.Color = Color.Transparent;
            g.FillRectangle(p.Brush, me);
            
            //fill background
            p.Color = _baseColor;
            g.FillRectangle(p.Brush, drawZone);            
        }

        private void DrawMargins(Graphics g)
        {            
            items[0] = new RectangleF(0f, 3f, lMargin * 3.775f, 14f);
            items[1] = new RectangleF(drawZone.Width - ((float)rMargin * 3.775f) + 1f, 3f, rMargin * 3.775f + 5f, 14f);
            p.Color = Color.DarkGray;
            g.FillRectangle(p.Brush, items[0]);
            g.FillRectangle(p.Brush, items[1]);

            g.PixelOffsetMode = PixelOffsetMode.None;
            //draw border
            p.Color = _strokeColor;
            g.DrawRectangle(p, 0, 3, me.Width - 1, 14);            
        }

        private void DrawTextAndMarks(Graphics g)
        {            
            int points = (int)(drawZone.Width / 3.775f) / 10;
            float range = 5 * 3.775f;
            int i = 0;
            p.Color = Color.Black;
            SizeF sz;
            for (i = 0; i <= points * 2 + 1; i++)
            {
                if (i % 2 == 0 && i != 0)
                {
                    sz = g.MeasureString((Convert.ToInt32(i / 2)).ToString(), this.Font);
                    g.DrawString((Convert.ToInt32(i / 2)).ToString(), this.Font, p.Brush, new PointF((float)(i * range - (float)(sz.Width / 2)), (float)(me.Height / 2) - (float)(sz.Height / 2)));
                }
                else
                {
                    g.DrawLine(p, (float)(i * range), 7f, (float)(i * range), 12f);
                }
            }
            g.PixelOffsetMode = PixelOffsetMode.Half;
        }

        private void DrawIndents(Graphics g)
        {
            items[2] = new RectangleF((float)luIndent * 3.775f - 4.5f, 0f, 9f, 8f);
            items[3] = new RectangleF((float)llIndent * 3.775f - 4.5f, 8.2f, 9f, 11.8f);
            items[4] = new RectangleF((float)(drawZone.Width - ((float)rIndent * 3.775f - 4.5f) - 7f), 11f, 9f, 8f);
            
            //regions for moving left indentation marks
            items[5] = new RectangleF((float)llIndent * 3.775f - 4.5f, 8.2f, 9f, 5.9f);
            items[6] = new RectangleF((float)llIndent * 3.775f - 4.5f, 14.1f, 9f, 5.9f);

            g.DrawImage(Properties.Resources.l_indet_pos_upper, items[2]);
            g.DrawImage(Properties.Resources.l_indent_pos_lower, items[3]);
            g.DrawImage(Properties.Resources.r_indent_pos, items[4]);
        }

        private void DrawTabs(Graphics g)
        {
            if (_tabsEnabled == false)
                return;

            int i = 0;

            if (tabs.Count == 0)
                return;

            for (i = 0; i <= tabs.Count - 1; i++)
            {
                g.DrawImage(Properties.Resources.tab_pos, tabs[i]);
            }            
        }
        #endregion

        #region Actions
        private void AddTab(float pos)
        {
            RectangleF rect = new RectangleF(pos, 10f, 8f, 8f);
            tabs.Add(rect);
            if (TabAdded != null)
                TabAdded.Invoke(CreateTabArgs(pos));
        }

        /// <summary>
        /// Returns List which contains positions of the tabs converted to millimeters.
        /// </summary>
        public List<int> TabPositions
        {
            get
            {
                List<int> lst = new List<int>();
                int i = 0;
                for (i = 0; i <= tabs.Count - 1; i++)
                {
                    lst.Add((int)(tabs[i].X / 3.775f));
                }
                lst.Sort();
                return lst;
            }
        }

        /// <summary>
        /// Returns List which contains positions of the tabs in pixels.
        /// </summary>
        public List<int> TabPositionsInPixels
        {
            get
            {
                List<int> lst = new List<int>();
                int i = 0;
                for (i = 0; i <= tabs.Count - 1; i++)
                {                    
                    lst.Add((int)(tabs[i].X));
                }
                lst.Sort();
                return lst;
            }
        }

        /// <summary>
        /// Sets positions for tabs. It uses positions represented in pixels.
        /// </summary>
        /// <param name="positions"></param>
        public void SetTabPositionsInPixels(int[] positions)
        {
            if (positions == null)
            {
                tabs.Clear();
            }
            else
            {
                tabs.Clear();
                int i = 0;                 
                for (i = 0; i <= positions.Length - 1; i++)
                {                    
                    RectangleF rect = new RectangleF(Convert.ToSingle(positions[i]), 10f, 8f, 8f);
                    tabs.Add(rect);                    
                }                
            }
            this.Refresh();
        }

        /// <summary>
        /// Sets positions for tabs. It uses positions represented in millemeters.
        /// </summary>
        /// <param name="positions"></param>
        public void SetTabPositionsInMillimeters(int[] positions)
        {
            if (positions == null)
            {
                tabs.Clear();
            }
            else
            {
                tabs.Clear();
                int i = 0;
                RectangleF rect;
                for (i = 0; i <= positions.Length - 1; i++)
                {
                    if (positions[i] != 0)
                    {
                        rect = new RectangleF(positions[i] * 3.775f, 10f, 8f, 8f);
                        tabs.Add(rect);
                    }
                }
                this.Refresh();
            }
        }
        
        internal int GetValueInPixels(ControlItems item)
        {
            switch (item)
            {
                case ControlItems.LeftIndent:
                    return (int)(luIndent * 3.775f);
                    
                case ControlItems.LeftHangingIndent:
                    return (int)(llIndent * 3.775f);
                    
                case ControlItems.RightIndent:
                    return (int)(rIndent * 3.775f);
                    
                case ControlItems.LeftMargin:
                    return (int)(lMargin * 3.775f);
                    
                case ControlItems.RightMargin:
                    return (int)(rMargin * 3.775f);
                    
                default:
                    return 0;
                    
            }
        }
        #endregion

        #region Properties
        /// <summary>
        /// Gets or sets color for the border
        /// </summary>
        [DefaultValue(typeof(Color), "Black")]
        [Description("Color of the border drawn on the bounds of control.")]
        public Color BorderColor
        {
            get { return _strokeColor; }
            set { _strokeColor = value; this.Refresh(); }
        }

        /// <summary>
        /// Gets or sets color for the background
        /// </summary>
        [DefaultValue(typeof(Color), "White")]
        [Description("Base color for the control.")]        
        public Color BaseColor
        {
            get
            {
                return _baseColor;
            }
            set
            {
                _baseColor = value;
            }
        }

        /// <summary>
        /// Enables or disables usage of the margins. If disabled, margins values are set to 1.
        /// </summary>
        [Category("Margins")]
        [Description("If true Margins are disabled, otherwise, false.")]
        [DefaultValue(false)]
        public bool NoMargins
        {
            get { return noMargins; }
            set 
            { 
                noMargins = value;
                if (value == true)
                {
                    this.lMargin = 1;
                    this.rMargin = 1;
                }
                this.Refresh(); 
            }
        }

        /// <summary>
        /// Specifies left margin
        /// </summary>
        [Category("Margins")]
        [Description("Gets or sets left margin. This value is in millimeters.")]
        [DefaultValue(20)]
        public int LeftMargin
        {
            get { return lMargin; }
            set 
            {
                if (noMargins != true)
                {
                    lMargin = value;
                }
                this.Refresh(); 
            }
        }

        /// <summary>
        /// Specifies right margin
        /// </summary>
        [Category("Margins")]
        [Description("Gets or sets right margin. This value is in millimeters.")]
        [DefaultValue(15)]
        public int RightMargin
        {
            get { return rMargin; }
            set 
            {
                if (noMargins != true)
                {
                    rMargin = value;
                }
                this.Refresh();
            }
        }

        /// <summary>
        /// Gets or sets indentation of the first line of the paragraph
        /// </summary>
        [Category("Indents")]
        [Description("Gets or sets left hanging indent (first line of the paragraph). This value is in millimeters.")]
        [DefaultValue(20)]
        public int LeftHangingIndent
        {
            get { return llIndent; }
            set 
            {
                if (value < 1)
                    return;

                llIndent = value;
                //if (this.LeftHangingIndentChanging != null)
                    //this.LeftHangingIndentChanging.Invoke(value);
                this.Refresh(); 
            }
        }

        /// <summary>
        /// Gets or sets indentation from the left of the base text of the paragraph
        /// </summary>
        [Category("Indents")]
        [Description("Gets or sets left indent. This value is in millimeters.")]
        [DefaultValue(20)]
        public int LeftIndent
        {
            get { return luIndent; }
            set 
            {
                if (value < 1)
                    return;
                luIndent = value;
                //if (this.LeftIndentChanging != null)
                    //this.LeftIndentChanging.Invoke(value);           
                this.Refresh(); 
            }
        }

        /// <summary>
        /// Gets or sets right indentation of the paragraph
        /// </summary>
        [Category("Indents")]
        [Description("Gets or sets right indent. This value is in millimeters.")]
        [DefaultValue(15)]
        public int RightIndent
        {
            get { return rIndent; }
            set 
            {
                if (value < 1)
                    return;
               
                    //if (this.RightIndentChanging != null)
                        //this.RightIndentChanging.Invoke(value);
                
                rIndent = value; 
                this.Refresh();
            }
        }

        [Category("Tabulation")]
        [Description("True to display tab stops, otherwise, False")]
        [DefaultValue(false)]
        public bool TabsEnabled
        {
            get { return _tabsEnabled; }
            set { _tabsEnabled = value; this.Refresh(); }
        }
        #endregion

        #region Overriders
        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            //set compositing to high quality because of using images for indents
            e.Graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;

            e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
            //make smooth control
            e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
            //this will braw smoother control, without blur and fast ;).
            e.Graphics.PixelOffsetMode = PixelOffsetMode.Half;
            //this will braw text with highest quality
            e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
            
            me = new RectangleF(0f, 0f, (float)this.Width, (float)this.Height);
            drawZone = new RectangleF(1f, 3f, me.Width - 2f, 14f);
            workArea = new RectangleF((float)lMargin * 3.775f, 3f, drawZone.Width - ((float)rMargin * 3.775f) - drawZone.X * 2, 14f);            

            //firstly, draw background.
            DrawBackGround(e.Graphics);

            //then, draw margins
            DrawMargins(e.Graphics);

            //then, draw text (numbers) and marks (vertical lines)
            DrawTextAndMarks(e.Graphics);

            //then, draw indent marks
            DrawIndents(e.Graphics);

            //finally, draw tab stops
            DrawTabs(e.Graphics);
        }

        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);
            this.Height = 20;
        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);
            int i = 0;

            //process left mouse button only
            if (e.Button != MouseButtons.Left)
                return;

            for (i = 0; i <= 6; i++)
            {
                if (items[i].Contains(e.Location) == true && i != 3) //i must not be equal to 3, as this is region for whole image
                {
                    if (noMargins == true && (i == 0 || i == 1))
                        break;

                    capObject = i;
                    mCaptured = true;
                    break;
                }
            }

            if (mCaptured == true)
                return;

            if (tabs.Count == 0)
                return;

            if (_tabsEnabled == false)
                return;

            i = 0;

            for (i = 0; i <= tabs.Count - 1; i++)
            {
                if (tabs[i].Contains(e.Location) == true)
                {
                    capTab = i;
                    pos = (int)(tabs[i].X / 3.775f);
                    mCaptured = true;
                    break;
                }
            }

        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            base.OnMouseUp(e);

            //process left mouse button only
            if (e.Button != MouseButtons.Left)
                return;

            if (workArea.Contains(e.Location) == false)
            {
                if (mCaptured == true && capTab != -1 && _tabsEnabled == true)
                {
                    try
                    {
                        float pos = tabs[capTab].X * 3.775f;
                        tabs.RemoveAt(capTab);
                        if (TabRemoved != null)
                            TabRemoved.Invoke(CreateTabArgs(pos));
                    }
                    catch (Exception)
                    {
                    }
                }
            }
            else if (workArea.Contains(e.Location) == true)
            {
                if (mCaptured != true && _tabsEnabled == true)
                {
                    AddTab((float)e.Location.X);
                }
                else if (mCaptured == true && capTab != -1)
                {
                    if (TabChanged != null && _tabsEnabled == true)
                        TabChanged.Invoke(CreateTabArgs(e.Location.X));
                }
            }

            capTab = -1;
            mCaptured = false;
            capObject = -1;
            this.Refresh();
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);

            if (mCaptured == true && capObject != -1)
            {
                switch (capObject)
                {
                    case 0:
                        if (noMargins == true)
                            return;
                        if (e.Location.X <= (int)(me.Width - rMargin * 3.775f - 10))
                        {
                            this.lMargin = (int)(e.Location.X / 3.775f);
                            if (this.lMargin < 1)
                                this.lMargin = 1;
                            if (LeftMarginChanging != null)
                                LeftMarginChanging.Invoke(lMargin);
                            this.Refresh();
                        }
                        break;

                    case 1:
                        if (noMargins == true)
                            return;
                        if (e.Location.X >= (int)(lMargin * 3.775f + 10))
                        {
                            this.rMargin = (int)((drawZone.Width / 3.775f) - (int)(e.Location.X / 3.775f));
                            if (this.rMargin < 1)
                                this.rMargin = 1;
                            if (RightMarginChanging != null)
                                RightMarginChanging.Invoke(rMargin);
                            this.Refresh();
                        }
                        break;

                    case 2:
                        if (e.Location.X <= (int)(me.Width - rIndent * 3.775f - 10))
                        {
                            this.luIndent = (int)(e.Location.X / 3.775f);
                            if (this.luIndent < 1)
                                this.luIndent = 1;
                            if (LeftIndentChanging != null)
                                LeftIndentChanging.Invoke(luIndent);
                            this.Refresh();
                        }
                        break;
                    

                    case 4:
                        if (e.Location.X >= (int)(Math.Max(llIndent, luIndent) * 3.775f + 10))
                        {
                            this.rIndent = (int)((me.Width / 3.775f) - (int)(e.Location.X / 3.775f));
                            if (this.rIndent < 1)
                                this.rIndent = 1;
                            if (RightIndentChanging != null)
                                RightIndentChanging.Invoke(rIndent);
                            this.Refresh();
                        }
                        break;

                    case 5:
                        if (e.Location.X <= (int)(drawZone.Width - rIndent * 3.775f - 10))
                        {
                            this.llIndent = (int)(e.Location.X / 3.775f);
                            if (this.llIndent < 1)
                                this.llIndent = 1;
                            if (LeftHangingIndentChanging != null)
                                LeftHangingIndentChanging.Invoke(llIndent);
                            this.Refresh();
                        }
                        break;

                    case 6:
                        if (e.Location.X <= (int)(drawZone.Width - rIndent * 3.775f - 10))
                        {
                            this.luIndent = luIndent + (int)(e.Location.X / 3.775f) - llIndent;
                            if (this.luIndent < 1)
                                this.luIndent = 1;
                            if (LeftIndentChanging != null)
                                LeftIndentChanging.Invoke(llIndent);
                            this.llIndent = (int)(e.Location.X / 3.775f);
                            if (this.llIndent < 1)
                                this.llIndent = 1;
                            if (LeftHangingIndentChanging != null)
                                LeftHangingIndentChanging.Invoke(llIndent);
                            this.Refresh();
                        }
                        break;

                }
            }
            else if (mCaptured == true && capTab != -1)
            {
                if (workArea.Contains(e.Location) == true)
                {
                    tabs[capTab] = new RectangleF((float)e.Location.X, tabs[capTab].Y, tabs[capTab].Width, tabs[capTab].Height);
                    this.Refresh();
                }
            }
            else
            {
                int i = 0;

                for (i = 0; i <= 4; i++)
                {
                    if (items[i].Contains(e.Location) == true)
                    {
                        switch (i)
                        {
                            case 0:
                                if (noMargins == true) return;
                                Cursor = Cursors.SizeWE;
                                break;

                            case 1:
                                if (noMargins == true) return;
                                Cursor = Cursors.SizeWE;
                                break;
                        }
                        break;
                    }
                    this.Cursor = Cursors.Default;
                }
            }

        }
        #endregion

        #region Events classes
        internal class TabEventArgs : EventArgs
        {
            private int _posN = -1;
            private int _posO = -1;

            internal int NewPosition
            {
                get { return _posN; }
                set { _posN = value; }
            }

            internal int OldPosition
            {
                get { return _posO; }
                set { _posO = value; }
            }
        }

        private TabEventArgs CreateTabArgs(float newPos)
        {
            TabEventArgs tae = new TabEventArgs();
            tae.NewPosition = (int)(newPos / 3.775f);            
            tae.OldPosition = pos;
            return tae;
        }

        #endregion
    }
}

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