Click here to Skip to main content
15,885,956 members
Articles / Programming Languages / C#

CodeTextBox - another RichTextBox control with syntax highlightning and intellisense

Rate me:
Please Sign up or sign in to vote.
4.87/5 (34 votes)
8 Sep 2008CPOL2 min read 145.3K   6.4K   147  
A RichTextBox control with syntax highlightning and intellisense.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Moonlight.Intellisense;
using Moonlight.SyntaxHighlight;

namespace Moonlight
{
    public partial class CodeTextBox : RichTextBox
    {
        #region Members

        private SyntaxHighlighter m_SyntaxHighLighter = new SyntaxHighlighter();
        private IntellisenseManager m_IntellisenseManager;
        private ImageListBox mp_IntellisenseBox;
        private TreeView mp_IntellisenseTree;
        private Keys mp_IntellisenseKey = Keys.Space;
        
        #region Drawing
        /// <summary>
        /// Enables or disables control's painting - internal
        /// </summary>
        private bool mp_EnablePainting = true;
        #endregion

        #region Word lists
        private List<string> mp_CodeWords_Keywords;
        private List<string> mp_CodeWords_Types;
        private List<string> mp_CodeWords_Functions;
        private List<string> mp_CodeWords_Comments;
        private List<string> mp_CodeWords_ScopeOperators;
        #endregion

        #region Syntax highlightning colors
        private Color mp_CodeColor_Keyword           = Color.Blue;
        private Color mp_CodeColor_Type              = Color.CornflowerBlue;
        private Color mp_CodeColor_Function          = Color.CornflowerBlue;
        private Color mp_CodeColor_Comment           = Color.Green;
        private Color mp_CodeColor_PlainText         = Color.Black;
        #endregion

        #region Intellisense images
        private Image mp_CodeImage_Class = Resource._class;
        private Image mp_CodeImage_Event = Resource._event;
        private Image mp_CodeImage_Interface = Resource._interface;
        private Image mp_CodeImage_Method = Resource._method;
        private Image mp_CodeImage_Namespace = Resource._namespace;
        private Image mp_CodeImage_Property = Resource._property;
        #endregion

        #endregion

        #region Properties
        #region Public properties

        #region Word lists
        /// <summary>
        /// Gets or Sets the keywords used for syntax highlightning and intellisense.
        /// </summary>
        [Browsable(true), Category("CodeTexbox"), Description("Gets or Sets the keywords used for syntax highlightning and intellisense.")]
        [Editor("System.Windows.Forms.Design.StringCollectionEditor, System.Design","System.Drawing.Design.UITypeEditor, System.Drawing")]
        public List<string> CodeWords_Keywords
        {
            get { return mp_CodeWords_Keywords; }
            set { mp_CodeWords_Keywords = value; }
        }
        
        /// <summary>
        /// Gets or Sets the types used for syntax highlightning and intellisense.
        /// </summary>
        [Browsable(true), Category("CodeTexbox"), Description("Gets or Sets the types used for syntax highlightning and intellisense.")]
        [Editor("System.Windows.Forms.Design.StringCollectionEditor, System.Design", "System.Drawing.Design.UITypeEditor, System.Drawing")]
        public List<string> CodeWords_Types
        {
            get { return mp_CodeWords_Types; }
            set { mp_CodeWords_Types = value; }
        }
        
        /// <summary>
        /// Gets or Sets the functions used for syntax highlightning and intellisense.
        /// </summary>
        [Browsable(true), Category("CodeTexbox"), Description("Gets or Sets the functions used for syntax highlightning and intellisense.")]
        [Editor("System.Windows.Forms.Design.StringCollectionEditor, System.Design", "System.Drawing.Design.UITypeEditor, System.Drawing")]
        public List<string> CodeWords_Functions
        {
            get { return mp_CodeWords_Functions; }
            set { mp_CodeWords_Functions = value; }
        }
        
        /// <summary>
        /// Gets or Sets the comment strings used for syntax highlightning.
        /// </summary>
        [Browsable(true), Category("CodeTexbox"), Description("Gets or Sets the comment strings used for syntax highlightning.")]
        [Editor("System.Windows.Forms.Design.StringCollectionEditor, System.Design", "System.Drawing.Design.UITypeEditor, System.Drawing")]
        public List<string> CodeWords_Comments
        {
            get { return mp_CodeWords_Comments; }
            set { mp_CodeWords_Comments = value; }
        }

        /// <summary>
        /// Gets or Sets the comment strings used for syntax highlightning.
        /// </summary>
        [Browsable(true), Category("CodeTexbox"), Description("Gets or Sets the object separator strings used for intellisense.")]
        [Editor("System.Windows.Forms.Design.StringCollectionEditor, System.Design", "System.Drawing.Design.UITypeEditor, System.Drawing")]
        public List<string> CodeWords_ScopeOperators
        {
            get { return mp_CodeWords_ScopeOperators; }
            set { mp_CodeWords_ScopeOperators = value; }
        }
        #endregion

        #region Syntax highlightning colors
        /// <summary>
        /// Gets or Sets the color of plain texts for syntax highlightning.
        /// </summary>
        [Browsable(true), Category("CodeTexbox"), Description("Gets or Sets the color of plain texts for syntax highlightning.")]
        public Color CodeColor_PlainText
        {
            get { return mp_CodeColor_PlainText; }
            set { mp_CodeColor_PlainText = value; }
        }

        /// <summary>
        /// Gets or Sets the color of keywords for syntax highlightning.
        /// </summary>
        [Browsable(true), Category("CodeTexbox"), Description("Gets or Sets the color of keywords for syntax highlightning.")]
        public Color CodeColor_Keyword
        {
            get { return mp_CodeColor_Keyword; }
            set { mp_CodeColor_Keyword = value; }
        }

        /// <summary>
        /// Gets or Sets the color of types for syntax highlightning.
        /// </summary>
        [Browsable(true), Category("CodeTexbox"), Description("Gets or Sets the color of types for syntax highlightning.")]
        public Color CodeColor_Type
        {
            get { return mp_CodeColor_Type; }
            set { mp_CodeColor_Type = value; }
        }

        /// <summary>
        /// Gets or Sets the color of functions for syntax highlightning.
        /// </summary>
        [Browsable(true), Category("CodeTexbox"), Description("Gets or Sets the color of functions for syntax highlightning.")]
        public Color CodeColor_Function
        {
            get { return mp_CodeColor_Function; }
            set { mp_CodeColor_Function = value; }
        }

        /// <summary>
        /// Gets or Sets the color of comments for syntax highlightning.
        /// </summary>
        [Browsable(true), Category("CodeTexbox"), Description("Gets or Sets the color of comments for syntax highlightning.")]
        public Color CodeColor_Comment
        {
            get { return mp_CodeColor_Comment; }
            set { mp_CodeColor_Comment = value; }
        }
        #endregion

        #region Intellisense images
        [Browsable(true), Category("CodeTexbox"), Description("Gets or Sets the intellisense image of keywords.")]
        public Image CodeImage_Class
        {
            get { return mp_CodeImage_Class; }
            set { mp_CodeImage_Class = value; }
        }

        [Browsable(true), Category("CodeTexbox"), Description("Gets or Sets the intellisense image of events.")]
        public Image CodeImage_Event
        {
            get { return mp_CodeImage_Event; }
            set { mp_CodeImage_Event = value; }
        }

        [Browsable(true), Category("CodeTexbox"), Description("Gets or Sets the intellisense image of interfaces.")]
        public Image CodeImage_Interface
        {
            get { return mp_CodeImage_Interface; }
            set { mp_CodeImage_Interface = value; }
        }

        [Browsable(true), Category("CodeTexbox"), Description("Gets or Sets the intellisense image of methods.")]
        public Image CodeImage_Method
        {
            get { return mp_CodeImage_Method; }
            set { mp_CodeImage_Method = value; }
        }

        [Browsable(true), Category("CodeTexbox"), Description("Gets or Sets the intellisense image of namespaces.")]
        public Image CodeImage_Namespace
        {
            get { return mp_CodeImage_Namespace; }
            set { mp_CodeImage_Namespace = value; }
        }

        [Browsable(true), Category("CodeTexbox"), Description("Gets or Sets the intellisense image of properties.")]
        public Image CodeImage_Property
        {
            get { return mp_CodeImage_Property; }
            set { mp_CodeImage_Property = value; }
        }
        #endregion

        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        [Browsable(true), Category("CodeTexbox"), Description("Gets or Sets the intellisense item tree.")]
        public TreeView IntellisenseTree
        {
            get { return mp_IntellisenseTree; }
            set { mp_IntellisenseTree = value; }
        }

        [Browsable(true), Category("CodeTexbox"), Description("Gets or Sets the key to open intellisense.")]
        public Keys IntellisenseKey
        {
            get { return mp_IntellisenseKey; }
            set { mp_IntellisenseKey = value; }
        }
        #endregion

        #region Internal Properties
        /// <summary>
        /// Enables or disables the control's paint event.
        /// </summary>
        internal bool EnablePainting
        {
            get { return mp_EnablePainting; }
            set { mp_EnablePainting = value; }
        }
        /// <summary>
        /// Gets the intellisense's ImageListBox
        /// </summary>
        internal ImageListBox IntellisenseBox
        {
            get { return mp_IntellisenseBox; }
        }

        #endregion
        #endregion

        #region Constructors
        public CodeTextBox()
        {
            InitializeComponent();

            //Set some defaults...
            this.AcceptsTab = true;
            this.Font = new Font(FontFamily.GenericMonospace, 8f);

            // TODO
            //
            //Do not enable drag and dropping text
            //The same problem, as paste - the onDragDrop event fires, BEFORE the text is written into the textbox
            //Need to be handled in WndPrc
            this.EnableAutoDragDrop = false;
            
            this.DetectUrls = false;
            this.WordWrap = false;
            this.AutoWordSelection = true;

            #region Instantiate Syntax highlightning and Intellisense members
            //Instantiate word lists
            mp_CodeWords_Keywords        = new List<string>();
            mp_CodeWords_Types           = new List<string>();
            mp_CodeWords_Functions       = new List<string>();
            mp_CodeWords_Comments        = new List<string>();
            mp_CodeWords_ScopeOperators  = new List<string>();

            //Instantiate intellisense manager
            m_IntellisenseManager        = new IntellisenseManager(this);

            //Instantiate the intellisense box
            mp_IntellisenseBox           = new ImageListBox();

            //Instantiate intellisense tree
            mp_IntellisenseTree          = new TreeView();
            #endregion

            #region Setup intellisense box
            //Setup intellisense box
            this.Controls.Add(mp_IntellisenseBox);
            mp_IntellisenseBox.Size = new Size(250, 150);
            mp_IntellisenseBox.Visible = false;
            mp_IntellisenseBox.KeyDown += new KeyEventHandler(mp_IntellisenseBox_KeyDown);
            mp_IntellisenseBox.DoubleClick += new EventHandler(mp_IntellisenseBox_DoubleClick);
            #endregion
        }
        #endregion

        #region Public methods
        /// <summary>
        /// Force a full update of syntax highlightning
        /// </summary>
        public void UpdateSyntaxHightlight()
        {
            m_SyntaxHighLighter.Update(this);
            m_SyntaxHighLighter.DoSyntaxHightlight_AllLines(this);
        }
        #endregion

        #region Overridden methods

        #region Some overridden native function...
        private const int WM_COPY = 0x301;
        private const int WM_CUT = 0x300;
        private const int WM_PASTE = 0x302;
        private const int WM_CLEAR = 0x303;
        private const int WM_UNDO = 0x304;
        private const int EM_UNDO = 0xC7;
        private const int EM_CANUNDO = 0xC6;
        private const int WM_PAINT = 0x000F;
        private const int WM_CHAR = 0x102;

        [DllImport("user32.dll", SetLastError = true)]
        public static extern bool PostMessage(
        IntPtr hWnd,// handle to destination window
        UInt32 Msg, // message
        IntPtr wParam, // first message parameter
        IntPtr lParam // second message parameter
        );

        /// <summary>
        /// Let control enable and disable it's drawing...
        /// </summary>
        /// <param name="m"></param>
        protected override void WndProc(ref System.Windows.Forms.Message m)
        {
            switch (m.Msg)
            {
                case WM_PAINT:
                    {
                        #region PAINT
                        if (mp_EnablePainting)
                        {
                            base.WndProc(ref m);
                        }
                        else
                        {
                            m.Result = IntPtr.Zero;
                        }
                        #endregion
                    }
                    break;

                case WM_PASTE:
                    {
                        #region PASTE
                        int selectionStart = this.SelectionStart;
                        int selectionLength = 0;
                        
                        string text = Clipboard.GetText();
                        

                        #region Hack to find out correct selection length
                        //Hack...
                        //The text's length readed from the clipboard doesn't match the length pasted to the richTextBox
                        //So I used a dummy richtextbox, to find out the correct length of the text...
                        RichTextBox rtxt = new RichTextBox();
                        rtxt.SelectionStart = 0;
                        rtxt.SelectedText = text;
                        selectionLength = rtxt.Text.Length;
                        #endregion


                        //Paste text from the clipboard...
                        this.SelectedText = text;

                        m_SyntaxHighLighter.DoSyntaxHightlight_Selection(this, selectionStart, selectionLength);
                        #endregion
                    }
                    break;

                case WM_CHAR:
                    {
                        #region CHAR
                        ProcessKeyDownWndPrc((uint)m.WParam, (uint)m.LParam);
                        base.WndProc(ref m);
                        #endregion
                    }
                    break;

                default:
                    {
                        base.WndProc(ref m);
                    }
                    break;
            }
        }
        /// <summary>
        /// I needed this for the WM_PASTE event to fire...
        /// </summary>
        /// <param name="m"></param>
        /// <param name="keyData"></param>
        /// <returns></returns>
        protected override bool ProcessCmdKey(ref Message m, Keys keyData)
        {
            switch (keyData)
            {
                #region Paste
                case Keys.Control | Keys.V:
                    {
                        PostMessage(this.Handle, WM_PASTE, IntPtr.Zero, IntPtr.Zero);
                        return true;
                    }
                case Keys.Shift | Keys.Insert:
                    {
                        PostMessage(this.Handle, WM_PASTE, IntPtr.Zero, IntPtr.Zero);
                        return true;
                    }
                #endregion

                #region Copy
                case Keys.Control | Keys.C:
                    {
                        PostMessage(this.Handle, WM_COPY, IntPtr.Zero, IntPtr.Zero);
                        return true;
                    }
                case Keys.Control | Keys.Insert:
                    {
                        PostMessage(this.Handle, WM_COPY, IntPtr.Zero, IntPtr.Zero);
                        return true;
                    }
                #endregion

                #region Cut
                case Keys.Control | Keys.X:
                    {
                        PostMessage(this.Handle, WM_CUT, IntPtr.Zero, IntPtr.Zero);
                        return true;
                    }
                case Keys.Shift | Keys.Delete:
                    {
                        PostMessage(this.Handle, WM_CUT, IntPtr.Zero, IntPtr.Zero);
                        return true;
                    }
                #endregion

                #region Delete
                case Keys.Control | Keys.Delete:
                    {
                        PostMessage(this.Handle, WM_CLEAR, IntPtr.Zero, IntPtr.Zero);
                        return true;
                    }
                #endregion

                #region Undo
                case Keys.Control | Keys.Z:
                    {
                        PostMessage(this.Handle, WM_UNDO, IntPtr.Zero, IntPtr.Zero);
                        return true;
                    }
                #endregion

                default:
                    return base.ProcessCmdKey(ref m, keyData);
            }
        }
        #endregion

        protected override void OnPaint(PaintEventArgs pe)
        {
            if (EnablePainting)
            {
                base.OnPaint(pe);
            }
        }
        protected override void OnTextChanged(EventArgs e)
        {
            //Syntax Highlight the current line... :)
            m_SyntaxHighLighter.DoSyntaxHightlight_CurrentLine(this);

            base.OnTextChanged(e);
        }
        void mp_IntellisenseBox_KeyDown(object sender, KeyEventArgs e)
        {
            //Let the textbox handle keypresses inside the intellisense box
            OnKeyDown(e);
        }
        void mp_IntellisenseBox_DoubleClick(object sender, EventArgs e)
        {
            m_IntellisenseManager.ConfirmIntellisense();
        }
        protected override void OnKeyDown(KeyEventArgs e)
        {
            #region Show Intellisense
            if (e.KeyData == mp_IntellisenseKey)
            {
                m_IntellisenseManager.ShowIntellisenseBox();
                e.Handled = true;
                this.Focus();
                return;
            }
            #endregion

            if (mp_IntellisenseBox.Visible)
            {
                #region ESCAPE - Hide Intellisense
                if (e.KeyCode == Keys.Escape)
                {
                    m_IntellisenseManager.HideIntellisenseBox();
                    e.Handled = true;
                }
                #endregion

                #region Navigation - Up, Down, PageUp, PageDown, Home, End
                else if (e.KeyCode == Keys.Up)
                {
                    m_IntellisenseManager.NavigateUp(1);
                    e.Handled = true;
                }
                else if (e.KeyCode == Keys.Down)
                {
                    m_IntellisenseManager.NavigateDown(1);
                    e.Handled = true;
                }
                else if (e.KeyCode == Keys.PageUp)
                {
                    m_IntellisenseManager.NavigateUp(10);
                    e.Handled = true;
                }
                else if (e.KeyCode == Keys.PageDown)
                {
                    m_IntellisenseManager.NavigateDown(10);
                    e.Handled = true;
                }
                else if (e.KeyCode == Keys.Home)
                {
                    m_IntellisenseManager.NavigateHome();
                    e.Handled = true;
                }
                else if (e.KeyCode == Keys.End)
                {
                    m_IntellisenseManager.NavigateEnd();
                    e.Handled = true;
                }
                #endregion

                #region Typing - Back
                else if (e.KeyCode == Keys.Back)
                {
                    m_IntellisenseManager.TypeBackspace();
                }
                #endregion

                #region Typing - Brackets
                else if (e.KeyCode == Keys.D9)
                {
                    // Trap the open bracket key, displaying a cheap and
                    // cheerful tooltip if the word just typed is in our tree
                    // (the parameters are stored in the tag property of the node)
                }
                else if (e.KeyCode == Keys.D8)
                {
                    // Close bracket key, hide the tooltip textbox
                }
                #endregion

                #region Typing - TAB and Enter
                else if (e.KeyCode == Keys.Tab)
                {
                    m_IntellisenseManager.ConfirmIntellisense();
                    e.Handled = true;
                }
                else if (e.KeyCode == Keys.Enter)
                {
                    m_IntellisenseManager.ConfirmIntellisense();
                    e.Handled = true;
                }
                #endregion
            }

            this.Focus();
            base.OnKeyDown(e);
        }
        private void ProcessKeyDownWndPrc(uint wParam, uint lParam)
        {
            //We process the keys here instead of the OnKeyDown event, because the OnKeyDown has made
            //some mistakes in converting e.KeyValue to char...

            //Convert wParam to char...
            char c = (char)wParam;


            if (!char.IsLetterOrDigit(c))
            {
                //Char is not alphanumerical
                m_IntellisenseManager.TypeNonAlphaNumerical(c);
            }
            else
            {
                //Char is alphanumerical
                m_IntellisenseManager.TypeAlphaNumerical(c);
            }
        }
        #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
Software Developer (Senior) Moonlight Studios
Hungary Hungary
I'm working as a software developer since 2005 mostly using C# and MS SQL Server for building windows and web applications. I have 3 years experience in developing CRM softwares, but I'm also interested in Game development using C++, DirectX, and C#-XNA.
I'm always open for new technologies, preferring code reusability, stability, and "nice solutions".

Comments and Discussions