Richtextbox avec coloration syntaxique et auto-indentation simple

Contenu du snippet

Ce code est une classe dérivée de System.Windows.Form.RichTextBox, avec une coloration syntaxique simple et auto-indentation. Une fois ajouté à un projet, il est possible de l'ajouter à une interface graphique et de l'utiliser exactement comme un RichTextBox classique.

Source / Exemple :


using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using System;
using System.Drawing;
using System.ComponentModel;

namespace MyComponents
{
	/// <summary>
	/// Tag a colorer: mot clé, nombre, chaine de caractères...
	/// </summary>
	struct Tag
	{
		/// <summary>
		/// Constructeur du Tag. Permet de définir ces paramètres
		/// </summary>
		/// <param name="start">Index de départ du tag</param>
		/// <param name="len">Nombres de caractères du tag</param>
		/// <param name="color">Couleur à appliquer</param>
		/// <param name="style">Style de police à appliquer</param>
		public Tag(int start, int len, Color color, FontStyle style)
		{
			iStart = start;
			iLength = len;
			cColor = color;
			fStyle = style;
		}

		/// <summary>
		/// Index de départ du tag
		/// </summary>
		public int iStart;
		/// <summary>
		/// Nombres de caractères du tag
		/// </summary>
		public int iLength;
		/// <summary>
		/// Couleur à appliquer
		/// </summary>
		public Color cColor;
		/// <summary>
		/// Style de police à appliquer
		/// </summary>
		public FontStyle fStyle;
	}

	/// <summary>
	/// Structure de définition groupes d'éléments à rechercher
	/// </summary>
	struct Coloring
	{
		/// <summary>
		/// Expression régulière permettant de trouver les éléments
		/// </summary>
		public Regex regex;
		/// <summary>
		/// Couleur à appliquer pour cette recherche
		/// </summary>
		public Color cColor;
		/// <summary>
		/// Style à appliquer pour cette recherche
		/// </summary>
		public FontStyle fStyle;
	}

	/// <summary>
	/// Classe principale de l'objet. Doit remplacer les RichTextBox!
	/// 
	/// Cette classe a une gestion automatique d'indentation et de coloration syntaxique.
	/// </summary>
	public class ColorTextBox : RichTextBox
	{
		/// <summary>
		/// Controle invisible utilisé pour éviter les clignottements de sélections
		/// </summary>
		private Control hideSelection = new Control();

		/// <summary>
		/// Liste des mots-clés colorés avec le style 1
		/// !!! A PARAMETRER SELON VOS BESOIN !!!
		/// </summary>
		private string[] keywords_s1 = new string[] { "char",
			"short",
			"int",
			"long",
			"__int64",
			"float",
			"double",
			"bool",
			"unsigned",
			"time_t",
			"dateiso",
			"Date" };

		/// <summary>
		/// Liste des mots-clés colorés avec le style 2
		/// !!! A PARAMETRER SELON VOS BESOIN !!!
		/// </summary>
		private string[] keywords_s2 = new string[] { "struct",
			"enum",
			"class",
			"public",
			"private",
			"protected" };

		/// <summary>
		/// Liste des recherches de coloration
		/// </summary>
		private Coloring[] colors = new Coloring[] {
			new Coloring()										// futur keyword1
			{
				regex = null,
				cColor = Color.Blue,
				fStyle = FontStyle.Bold
			},
			new Coloring()										// futur keyword2
			{
				regex = null,
				cColor = Color.DarkOrchid,
				fStyle = FontStyle.Regular
			},
			new Coloring()										// Chaines de caractères: "a2c"
			{
				regex = new Regex("\"[^\\n]+\""),
				cColor = Color.Gray,
				fStyle = FontStyle.Italic
			},
			new Coloring()										// Caractère isolé: 'a'
			{
				regex = new Regex("'[^\\n]'"),
				cColor = Color.Gray,
				fStyle = FontStyle.Italic
			},
			new Coloring()										// dates: 18/11/2012 16:50:00 (correspond à DateTime.ToShortDateString() + " " + DateTime.ToLongTimeString())
			{
				regex = new Regex(@"(\d{2}/){2}\d{2,4} (\d{2}:){2}\d{2}"),
				cColor = Color.Green,
				fStyle = FontStyle.Regular
			},
			new Coloring()										// Entiers, partie 1
			{
				regex = new Regex(@"\s\d+(;|,|}|\s)"),
				cColor = Color.Orange,
				fStyle = FontStyle.Regular
			},
			new Coloring()										// Entiers, partie 2 pour: [123]
			{
				regex = new Regex(@"[[]\d+"),
				cColor = Color.Orange,
				fStyle = FontStyle.Regular
			}
		};

		/// <summary>
		/// Menu "clic-droit"
		/// </summary>
		private ContextMenuStrip mMenu;
		/// <summary>
		/// Eléments du menu
		/// </summary>
		private ToolStripMenuItem miCopy, miCut, miPaste;

		#region ctor
		/// <summary>
		/// Constructeur de l'object. Appel le constructeur parent et paramétrise certains éléments
		/// </summary>
		public ColorTextBox()
			: base()
		{
			// Paramétrisation du richTextBox. A conserver de préférence
			this.AcceptsTab = true;
			this.DetectUrls = false;
			this.Font = new Font("Courier New", 10, FontStyle.Regular);
			this.Text = "";
			this.WordWrap = false;
			// /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\
			// A GARDER ET NE PAS MODIFIER DANS L'EDITEUR GRAPHIQUE
			this.HideSelection = true;
			// /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\
			this.SelectionTabs = new int[] {40,60,90,120};

			this.SelectionIndent = 10;

			// Définition des regex pour les mots-clés définits en tableau
			string regKw = @"(\s|^)(";
			foreach (string s in keywords_s1)
				regKw += s + "|";
			regKw = regKw.Substring(0, regKw.Length - 1) + @")\s";
			colors[0].regex = new Regex(regKw);

			regKw = @"(\s|^)(";
			foreach (string s in keywords_s2)
				regKw += s + "|";
			regKw = regKw.Substring(0, regKw.Length - 1) + @")\s";
			colors[1].regex = new Regex(regKw);

			// Création du menu
			miCopy = new ToolStripMenuItem("Copier", null, OnMenuItemClick, Keys.Control | Keys.C);
			miCut = new ToolStripMenuItem("Couper", null, OnMenuItemClick, Keys.Control | Keys.X);
			miPaste = new ToolStripMenuItem("Coller", null, OnMenuItemClick, Keys.Control | Keys.V);
			mMenu = new ContextMenuStrip();
			mMenu.Items.AddRange(new ToolStripMenuItem[] {miCopy, miCut, miPaste});
			mMenu.Opening += OnMenuOpening;
			this.ContextMenuStrip = mMenu;

			// Evènements
			this.TextChanged += OnTextChanged;
			this.KeyDown += richTextBox1_KeyDown;

			// Le controle d'utilité graphique
			this.Controls.Add(hideSelection);
		}

		#endregion

		#region colorisation
		/// <summary>
		/// Modifie la couleur d'une partie du texte dans le richTextBox
		/// </summary>
		/// <param name="start">index de départ dans le texte</param>
		/// <param name="len">longueur à colorer</param>
		/// <param name="color">couleur à utiliser</param>
		/// <param name="style">style de police à appliquer</param>
		private void ChangeColor(int start, int len, Color color, FontStyle style)
		{
			this.SelectionStart = start;
			this.SelectionLength = len;
			this.SelectionColor = color;
			this.SelectionFont = new Font("Courier New", 10, style);
		}

		/// <summary>
		/// Organise la liste des tags trouvés dans l'ordre croissant des index de départ.
		/// Utilisé par List.Sort();
		/// </summary>
		/// <param name="t1">Tag 1</param>
		/// <param name="t2">Tag 2</param>
		/// <returns></returns>
		private static int SortTags(Tag t1, Tag t2)
		{
			if (t1.iStart < t2.iStart)
				return -1;
			else if (t1.iStart > t2.iStart)
				return 1;
			else
			{
				if (t1.iLength > t2.iLength)
					return -1;
				else
					return 1;
			}
		}

		/// <summary>
		/// Sur changement du texte dans le richTextBox (par entrée au clavier ou 'richTextBox.Text = ""')
		/// </summary>
		/// <param name="sender">Objet ayant généré l'évènement</param>
		/// <param name="e">Paramètres de l'évènement</param>
		private void OnTextChanged(object sender, EventArgs e)
		{
			MatchCollection ms;
			bool hadFocus = false;
			List<Tag> tags = new List<Tag>();

			// On libère le focus
			if (this.Focused)
			{
				// Grace au Control créé plus tot et la propriété 'HideSelection', les sélections faites pour les colorations
				// passent innaperçues graphiquement
				hideSelection.Focus();
				hadFocus = true;
			}

			int selStart = this.SelectionStart;
			int selLen = this.SelectionLength;

			// On repasse tout en noir
			this.ForeColor = Color.Black;
			this.Font = new Font("Courier New", 10, FontStyle.Regular);

			// Pour chaque recherche définie plus haut
			foreach (Coloring c in colors)
			{
				// On regarde si il y a un (des) chaine(s) dans le texte qui correspondent
				ms = c.regex.Matches(this.Text);
				foreach (Match m in ms)
					// Et on l'ajoute à la liste des tags trouvés
					tags.Add(new Tag(m.Index, m.Length, c.cColor, c.fStyle));
			}

			// On trie les tags par ordre croissant d'index de départ
			tags.Sort(SortTags);
			foreach (Tag t in tags)
			{
				// Et on change les couleurs
				ChangeColor(t.iStart, t.iLength, t.cColor, t.fStyle);
				ChangeColor(t.iLength + t.iStart, this.Text.Length, Color.Black, FontStyle.Regular);
			}

			// On replace le curseur à son emplacement de départ
			this.SelectionStart = selStart;
			this.SelectionLength = selLen;

			// et on récupère le focus si on l'avait.
			if (hadFocus)
				this.Focus();
		}
		#endregion

		#region autoindent
		/// <summary>
		/// Sur appuie d'une touche, on vérifie et on crée (si besoin) l'indentation
		/// </summary>
		/// <param name="sender">l'objet ayant généré l'évènement</param>
		/// <param name="e">paramètres de l'évènement</param>
		private void richTextBox1_KeyDown(object sender, KeyEventArgs e)
		{
			switch (e.KeyValue)
			{
				// Enter
				case '\r':
					// On compte le nombre de 'tab' de la ligne précédente
					string temp = Convert.ToString(this.Lines.GetValue(this.GetLineFromCharIndex(this.SelectionStart - 1)));
					Regex tab = new Regex("\t");

					int indent = tab.Matches(temp).Count;
					if (temp.EndsWith("{"))
						// si la ligne finit par "{" (début de struct / class / etc...) on ajoute une indentation
						indent++;

					temp = Convert.ToString(this.Lines.GetValue(this.GetLineFromCharIndex(this.SelectionStart)));
					if(temp.Contains("}") && !temp.Contains("{"))
						// si la ligne contient "}" (fin de struct / class / etc...) on enlève une indentation
						indent = Math.Max(0, indent-1);

					// et on la place dans le texte
					string temp2 = "{TAB " + indent + "}";

					SendKeys.Send(temp2);
					break;

				// '}'
				case 187:
					// si on a appuyé sur "}", c'est qu'on finit un groupe et on enlève une indentation
					this.Focus();
					SendKeys.Send("^(%()){LEFT}{BKSP}{RIGHT}");
					break;

				default:
					break;
			}
		}
		#endregion

		#region menu
		/// <summary>
		/// Gestion des éléments de menu
		/// </summary>
		/// <param name="sender">objet ayant généré l'évènement</param>
		/// <param name="e">paramètres de l'évènement</param>
		private void OnMenuItemClick(object sender, EventArgs e)
		{
			ToolStripMenuItem miSender = (ToolStripMenuItem)sender;

			// Copier / Couper
			if (miSender == miCopy || miSender == miCut)
			{
				// On passe au presse-papier le texte sélectionné
				Clipboard.SetText(this.SelectedText);

				if (miSender == miCut)
				{
					//en cas de "Couper", on enlève le texte sélectionné
					int i = this.SelectionStart;
					this.Text = this.Text.Substring(0, this.SelectionStart) + this.Text.Substring(this.SelectionStart + this.SelectionLength);
					this.SelectionStart = i;
				}
			}

			// Coller
			if (miSender == miPaste)
			{
				// Ajout du texte contenu dans le presse-papier
				string txt = Clipboard.GetText();
				if (this.SelectionLength != 0)
				{
					int i = this.SelectionStart;
					this.Text = this.Text.Substring(0, this.SelectionStart) + this.Text.Substring(this.SelectionStart + this.SelectionLength);
					this.SelectionStart = i;
				}

				// On vérifie (et refait si nécessaire) l'indentation
				int k = this.SelectionStart;
				this.Text = this.Text.Substring(0, k) + txt + this.Text.Substring(k);

				int indent = 0;
				string[] text = this.Text.Split('\n');
				for (int i = 0; i < text.Length; i++)
				{
					int tmp = new Regex("\t").Matches(text[i]).Count;
					if (indent != tmp)
					{
						string line = text[i].Trim();
						for (int j = 0; j < indent; j++)
							line = "\t" + line;
						text[i] = line;
					}

					if (text[i].Contains("{") && !text[i].Contains("}"))
						tmp++;
					if (text[i].Contains("}") && !text[i].Contains("{"))
						tmp = Math.Max(0, tmp - 1);
					indent = tmp;
				}
				this.Text = "";
				for(int i=0; i<text.Length; i++)
					this.Text += text[i] + (i==text.Length-1 ? "" : "\n");

				this.SelectionStart = k + txt.Length;
			}
		}

		/// <summary>
		/// Gestion de l'ouverture du menu
		/// </summary>
		/// <param name="sender">objet ayant généré l'évènement</param>
		/// <param name="e">paramètres de l'évènement</param>
		private void OnMenuOpening(object sender, CancelEventArgs e)
		{
			miCopy.Enabled = true;
			miCut.Enabled = true;
			miPaste.Enabled = true;

			if (this.SelectionLength == 0)
			{
				// Si rien n'est sélectionner, on ne peut pas copier!!
				miCopy.Enabled = false;
				miCut.Enabled = false;
			}
			if (!Clipboard.ContainsText())
			{
				// Le presse-papier est vide, impossible de coller!!
				miPaste.Enabled = false;
			}
		}
		#endregion

	}
}

Conclusion :


Une base de départ pour des éditeurs de texte avec coloration syntaxique multi-langages et auto-indentation.
Permet aussi de comprendre certains fonctionnement, comme les évènements, le SendKey ou les RegEx.

A voir également

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.