[ Webhosting profitux.cz ]
hertpl: Undefined index: page_title(template line: 74) in templates/articles_print.html on line 39

Syntax highlighting klíčových slov pomocí RichTextBoxu (C#)

Úvod

V tomto článku si předvedeme, jak je možné upravit .NET komponentu RichTextBox za účelem provádění syntax highlightingu, neboli zvýrazňování syntaxe (například na zvýrazňování kódu C/C++, C#, PHP, ...).

Pozn.: Implementace je spíše než ukázkovým řešením jen zjednodušení, určitě nebude vždy fungovat správně... je to tedy jen jakýsi "proof of concept".

Cílem je udělat to celé tak, že:

  • řešení je co nejvíce obecné, aby některé algoritmy (třeba už tokenizace, viz níže) mohly být v případě potřeby pozměněny
  • řešení je co nejvíce čitelné (protože o to jde ve webových tutoriálech především, optimalizaci za daným účelem si pak schopnější programátor zajisté provede sám)
  • zvýraznění syntaxe musí probíhat v "rozumném čase" (třeba při otevření souboru je potřeba obarvit celkem velký kus kódu)
  • text při zvýrazňování nemá blikat (to je častý nedostatek na webu nalezitelných řešení)
  • bude to umět operace Undo/Redo (taktéž často není diskutováno) - není totiž možné použít vestavěné operace Undo/Redo, neboť například operace Undo by pouze znovu odbarvila text, apod. (za tímto účelem si napíšeme nejprimitivnější implementaci těchto funkcí)

Soubory ke stažení

  • ukázkový projekt demonstrující syntax higlighting pomocí RichTextBoxu (jednoduché zvýraznění jazyka C#, psáno ve Visual C# Express Edition 2008)
  • přeložený spustitelný soubor - jednoduchý editor podporující syntax higlighting jazyka C# (asi nejnázornější ukázka, která vám pomůže se rozhodnout, jestli má cenu číst celý článek, program vyžaduje .NET Framework 2)
  • samostatný soubor třídy pro syntax highlighting
Náhled editoru používajícího syntax highlighting

Algoritmus

Obarvování textu bude založeno na tokenizaci kódu pomocí regulárního výrazu (.NET třída Regexp, metoda split()). K tomuto kompromisu - kompromis spočívá v "líném" použití třídy Regexp - jsem se uchýlil proto, aby kód, který zde budu prezentovat, zůstal rozumně dlouhý a co nejvíce srozumitelný. Na druhou stranu tak celý kód běží o něco pomaleji, než by mohl, kdyby byla tokenizace šitá na míru. Navíc toto řešení neumí obarvit například řetězce nebo komentáře (tokeny které mohou obsahovat mezeru, čárku, ... nebo jiný znak indikující hranici dvou tokenů). Jedná se tedy spíše o zvýrazňování klíčových slov (nicméně to se dá upravit právě napsáním vlastní tokenizace a určením "typu tokenu" spíše než konkrétního klíčového slova, jak to dělám zde)... Pro samotné obarvení textu se bude používat metod RichTextBoxu spojených s úpravami vybraného textu.

Implementace

Jendotlivé části implementace jsem kvůli přehlednosti rozdělil do následujících částí:

  • Kostra programu
  • Reprezentace tokenu
  • Datové položky, konstruktor třídy
  • Historie úprav - operace Undo a Redo
  • Metody pro vypnutí překreslování RichTextBoxu - zabránění problikávání
  • Tokenizace, obarvování textu
  • Kopírování a vkládání textu
  • Jak nakonec třídu používat
  • Implementace - celý kód

Kostra programu

Na začátku (poté, co přidáme do projektu novou třídu) upravíme kód do této podoby:

» Rozbalit/sbalit kód «
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Collections;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using System.ComponentModel;
using System.Runtime.InteropServices;
 
namespace SyntaxHighlighter
{
 
  /*Slot A*/
 
  public class extendedRichText : RichTextBox
  {
  /* Slot B */
  }
 
}

Máme zde tedy uvedené všechny potřebné jmenné prostory a deklaraci třídy extendedRichText, odvozené od RichTextBoxu. Povšimněte si také komenářů - ty nic neříkají o kódu samotném, používám je zde jako výukovou pomůcku pro to, abych vám mohl říct kam co psát.

Reprezentace tokenu

Začneme tedy psát třídu token na místo komentáře "Slot A":

» Rozbalit/sbalit kód «
public class token {
  private String tokenValue;
  private Color tokenColor;
  private Font tokenFont;
 
  public token(String tValue, Color tColor, Font tFont) {
    this.tokenValue = tValue;
    this.tokenColor = tColor;
    this.tokenFont = tFont;
  }
 
  public String getValue() {
    return this.tokenValue;
  }
 
  public Color getColor()
  {
    return this.tokenColor;
  }
 
  public Font getFont()
  {
    return this.tokenFont;
  }
}

Token je tedy v našem (jednoduchém, výukovém) pojetí slovo, které má k sobě asociovanou barvu a typ písma...

Datové položky, konstruktor třídy

Budeme pokračovat definicí těla třídy extendedRichText - všechno odteď dolů tedy píšeme do Slotu B (!!!).

Nejprve samozřejmně specifikujeme datové položky, o kterých už víme, že je budeme potřebovat. Po nich následuje definice třídy pro zpracování operací Undo/Redo, dále pak konstruktor třídy - extendedRichTex­t():

» Rozbalit/sbalit kód «
// Klicova slova
private token[] keywords;
 
// Tridu UndoClass definujeme nize
private List<UndoClass> History;
// UndoPointer ukazuje na konkretni pozici v seznamu historie
private int UndoPointer = 0;
 
// Promenne spojene s updatovanim platna (prekreslenim)
private int updating = 0;
private int oldEventMask = 0;
 
// Jednoducha trida pro ulozeni historie uprav
private class UndoClass
{
  public string rtfText;
  public int posText;
  public UndoClass(int pos, string str)
  {
    this.rtfText = str;
    this.posText = pos;
  }
}
 
// Konstruktor tridy
public extendedRichText(token[] Tokens, int UndoSize)
{
  this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer, true);
  this.Multiline = true;
  this.WordWrap = false;
  this.AcceptsTab = true;
  this.DetectUrls = false;
  this.ScrollBars = RichTextBoxScrollBars.ForcedBoth;
  this.Dock = DockStyle.Fill;
 
  // Velikost historie uprav se definuje v konstruktoru
  History = new List<UndoClass>(UndoSize);
  // Uloz jedno undo, aby ses mohl vratit az na zacatek...
  // metoda StoreNewUndo je uvedena nize...
  this.StoreNewUndo();
 
  // Seznam klicovych slov se definuje v konstruktoru
  this.keywords = Tokens;
 
  // Handlery pro zmenu textu a pro zachytavani klaves
  this.TextChanged += new EventHandler(TextChangedEvent);
  this.KeyDown += new KeyEventHandler(KeyPressEvent);
}

Historie úprav (Undo/Redo)

Již z definice třídy UndoClass je vidět, že operace Undo a Redo budou prováděny velice jednoduše - prostě si jen uložíme do seznamu aktuální text a pozici kurzoru v textu. Aby toto bylo únosné, neukládáme si historii po stisku každé klávesy, nýbrž po stisku "význačných kláves" - handler KeyPressEvent(­...) vypadá tedy takto:

» Rozbalit/sbalit kód «
private void KeyPressEvent(object sender, System.Windows.Forms.KeyEventArgs e)
{
  if (e.Control && (e.Shift || e.Alt) && (e.KeyValue == 'Z' || e.KeyValue == 'Y'))
    return;
  if (e.KeyCode == Keys.Enter ||
      e.KeyCode == Keys.Delete ||
      e.KeyCode == Keys.Back ||
      e.KeyValue == ' ' ||
      e.KeyValue == ',' ||
      e.KeyValue == '.' ||
      e.KeyValue == ';' ||
      e.KeyValue == '}' ||
      e.KeyValue == '{' ||
      e.KeyValue == '(' ||
      e.KeyValue == ')')
  {
      StoreNewUndo();
  }
}

Následují metody pro samotné provádění operací Undo a Redo. Metoda StoreNewUndo() slouží k uložení aktuálního bodu historie úprav (je privátní, není asi potřeba ji explicitně volat z venku), RedoEvent() je metoda pro operaci Redo, UndoEvent() je metoda pro operaci Undo (obě metody jsou veřejné).

» Rozbalit/sbalit kód «
private int StoreNewUndo()
{
  if (History.Count > UndoPointer)
  {
    History.RemoveRange(UndoPointer, History.Count - UndoPointer);
  }
 
  History.Add(new UndoClass(this.SelectionStart, this.Rtf));
 
  if (History.Count > 80)
  {
    History.RemoveAt(0);
    UndoPointer = History.Count;
  }
  else
  {
    UndoPointer++;
  }
 
  return UndoPointer;
}
 
public void RedoEvent()
{
  if (UndoPointer < 50 && UndoPointer < History.Count - 1)
  {
    this.BeginUpdate();
    UndoPointer++;
    this.Rtf = History[UndoPointer].rtfText;
    this.SelectionStart = History[UndoPointer].posText;
    this.EndUpdate();
    this.Refresh();
  }
}
 
public void UndoEvent()
{
  if (UndoPointer > 0)
  {
    if (UndoPointer == History.Count)
    {
      StoreNewUndo();
      if (UndoPointer > 0) UndoPointer--;
    }
    this.BeginUpdate();
    UndoPointer--;
    this.Rtf = History[UndoPointer].rtfText;
    this.SelectionStart = History[UndoPointer].posText;
    this.EndUpdate();
    this.Refresh();
  }
}

Vyvolání metod UndoEvent() a RedoEvent() ponecháme na položce menu a k ní asociované klávesové zkratce, řešeni subclassingem vynecháme.

Metody pro vypnutí překreslování RichTextBoxu

Dále si rozšíříme třídu o metody BeginUpdate() a EndUpdate(), které zabrání (resp. povolí) překreslování RichTextBoxu (tím zabráníme problikávání).

Za tímto účelem nejprve jednorázově importujeme WinAPI funkci SendMessage(...) a definujeme podstatné konstanty... Je pěkné to uzavřít do "regionu", ať nám to nestraší...

» Rozbalit/sbalit kód «
#region Windows API Balast
 
private const int WM_SETREDRAW = 0x0B;
private const int EM_SETEVENTMASK = 0x0431;
 
[DllImport("user32", CharSet = CharSet.Auto)]
private static extern int SendMessage(HandleRef hWnd, int msg, int wParam, int lParam);
 
#endregion

Nyní už můžeme napsat přímo metody BeginUpdate() a EndUpdate():

» Rozbalit/sbalit kód «
public void BeginUpdate()
{
    this.TextChanged -= this.TextChangedEvent;
    ++updating;
 
    if (updating > 1)
        return;
 
    oldEventMask = SendMessage(new HandleRef(this, Handle), EM_SETEVENTMASK, 0, 0);
    SendMessage(new HandleRef(this, Handle), WM_SETREDRAW, 0, 0);
}
 
 
public void EndUpdate()
{
    this.TextChanged += this.TextChangedEvent;
    --updating;
 
    if (updating > 0)
        return;
 
    SendMessage(new HandleRef(this, Handle), WM_SETREDRAW, 1, 0);
    SendMessage(new HandleRef(this, Handle), EM_SETEVENTMASK, 0, oldEventMask);
    // this.Refresh();
}

Povšimněte si zakomentovaného řádku v metodě EndUpdate(). Obnovení (překreslení) RichTextBoxu ve chvíli, kdy opětovně povolíme překreslování totiž z principu do metody EndUpdate() nepatří, nicméně volání metody Refresh() po zavolání EndUpdate() je tak časté, že já jsem ve svém řešení tento řádek odkomentoval, abych to nepsal pořád dokola (ale zde si to předvedeme "pěkně", proto ho nechám zakomentovaný).

Dále je na začátku BeginUpdate() potřeba odebrat (a následně na konci EndUpdate() přidat) handler pro událost TextChanged - na to nezapomenout, jinak se nám budou lavinovitě valit události!

Tokenizace, obarvování textu

Teď se dostáváme k samotnému jádru naší třídy. Napíšeme si funkci pro tokenizaci textu (využijeme třídu Regexp), funkci na obarvování textu a handleru události TextChanged.

» Rozbalit/sbalit kód «
// Napred si napiseme metodu pro tokenizaci textu dle znaku
private string[] tokenize(string Line)
{
  // Do regularniho vyrazu zahrneme vsechny znaky, ktere mohou
  // potencialne oddelovat jednotlive tokeny (znaky " ", ",", ...) ...
  Regex r = new Regex("([ \\t{}():;,\"'])");
  return r.Split(Line);
}
 
public void ParseLine(string Line)
{
  // ...a roztrhneme text podle techto znaku na jednotlive tokeny
  String[] tokens = this.tokenize(Line);
  string token;
 
  // Kazdy z techto tokenu potom porovname se seznamem tokenu, ktere
  // specifikujeme v konstruktoru tridy...
  foreach (string token2 in tokens)
  {
    // Osetreni v pripade, ze text "Line" obsahuje newline
    // v takovem pripade je nutne ho odstranit, nebot:
    // "\r\nif" != "if" apod.
    if (token2.StartsWith("\r"))
    {
      if (token2 == "\r\n" || token2 == "\n" || token2 == " " || token2 == "\t")
      {
        this.SelectedText = token2;
        continue;
      }
      token = token2.Replace("\r\n", "");
    }
    else
    {
      token = token2;
    }
 
    // Nastaveni vychozi barvy
    this.SelectionColor = Color.Black;
    this.SelectionFont = new Font("Courier New", 10, FontStyle.Regular);
 
    // Porovnani aktualniho tokenu s tokeny jazyka, nastaveni barvy
    // a typu pisma v pripade shody...
    for (int i = 0; i < keywords.Length; i++)
    {
      if (keywords[i].getValue() == token)
      {
        this.SelectionColor = keywords[i].getColor();
        this.SelectionFont = keywords[i].getFont();
        break;
      }
    }
    // A prepiseme vybrany text...
    this.SelectedText = token2;
  }
}
 
public void TextChangedEvent(object sender, EventArgs e)
{
  if (this.SelectionStart < 0) return;
 
  int start, end;
 
  // zabraneni tomu aby se prekreslil vice nez jeden radek
  for (start = this.SelectionStart - 1; start > 0; start--)
  {
    if (this.Text[start] == '\n')
    {
      start++;
      break;
    }
  }
 
  // zabraneni tomu aby se prekreslil vice nez jeden radek
  for (end = this.SelectionStart; end < this.Text.Length; end++)
  {
    if (this.Text[end] == '\n')
    {
      break;
    }
  }
 
  // osetreni podteceni pro prvni radek
  if (start < 0) start = 0;
 
  // ulozime si text ktery se ma updatovat...
  String line = this.Text.Substring(start, end - start);
 
  // vypneme prekreslovani RichTextBoxu
  this.BeginUpdate();
 
  // Ulozime si stav vyberu
  int selectionStart = this.SelectionStart;
  int selectionLength = this.SelectionLength;
 
  // ... a vybereme text, ktery se ma prekreslit
  this.SelectionStart = start;
  this.SelectionLength = end - start;
 
  // Zavolame "magickou cernou skrinku" ktera
  // nam ten text prekresli
  this.ParseLine(line);
 
  // Vratime stav vyberu tak, jak byl, vybereme okno do "fokusu" a obnovime prekreslovani (prekreslime)...
  this.SelectionStart = selectionStart;
  this.SelectionLength = selectionLength;
  this.Focus();
  this.EndUpdate();
  this.Refresh();
}

Kopírování a vkládání textu

Abychom mohli rychleji provádět vložení textu, budeme rozlišovat dva případy. Jeden z nich je, že schránka obsahuje prostý text. V tom případě si nepomůžeme a musíme celý text ve schránce obarvit (při vkládání velkého kusu kódu je to pomalejší). V případě, že schránka obsahuje RichText, je pravděpodobné, že se jedná o zkopírováný již jednou zvýrazněný text - v takovém případě text prostě vložíme. Samozřejmě to je hodně zjednodušený přístup - když si někdo zkopíruje RichText z jiného editoru a prostě ho vloží, obarvení nebude správné. Pro jednoduchost nám to ale bude stačit.

» Rozbalit/sbalit kód «
public new void Paste()
{
  this.BeginUpdate();
  if (Clipboard.ContainsText(TextDataFormat.Rtf))
  {
    base.Paste();
  }
  else
  {
    this.ParseLine(Clipboard.GetText());
  }
  this.EndUpdate();
  this.Refresh();
}

Metody pro kopírování (Copy) a vyjmutí (Cut) můžeme ponechat tak jak jsou...

Jak nakonec třídu používat

Příklad jednoduchého použití naší třídy:

public mainForm()
{
    InitializeComponent();
 
    token T1 = new token("if", Color.Blue, new Font("Courier New", 10, FontStyle.Bold));
    token T2 = new token("using", Color.Purple, new Font("Courier New", 10, FontStyle.Bold));
    token T3 = new token("namespace", Color.Purple, new Font("Courier New", 10, FontStyle.Bold));
    token T4 = new token("class", Color.Blue, new Font("Courier New", 10, FontStyle.Bold));
    token T5 = new token("public", Color.Blue, new Font("Courier New", 10, FontStyle.Bold));
    token T6 = new token("private", Color.Blue, new Font("Courier New", 10, FontStyle.Bold));
    token T7 = new token("new", Color.Blue, new Font("Courier New", 10, FontStyle.Bold));
    token T8 = new token("void", Color.Blue, new Font("Courier New", 10, FontStyle.Bold));
    token T9 = new token("(", Color.Orange, new Font("Courier New", 10, FontStyle.Bold));
    token T10 = new token(")", Color.Orange, new Font("Courier New", 10, FontStyle.Bold));
    token T11 = new token("{", Color.Orange, new Font("Courier New", 10, FontStyle.Bold));
    token T12 = new token("}", Color.Orange, new Font("Courier New", 10, FontStyle.Bold));
    token T13 = new token("==", Color.Red, new Font("Courier New", 10, FontStyle.Bold));
    token[] tokenArray = { T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13 };
 
    ExtRichText = new extendedRichText(tokenArray , 50);
    this.Controls.Add(ExtRichText);
}

Implementace - celý kód

Celý "necenzurovaný" a nerozstříhaný kód třídy vypadá následovně:

» Rozbalit/sbalit kód «
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Collections;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using System.ComponentModel;
using System.Runtime.InteropServices;
 
namespace SyntaxHighlighter
{
 
    public class token
    {
        private String tokenValue;
        private Color tokenColor;
        private Font tokenFont;
        public token(String tValue, Color tColor, Font tFont)
        {
            this.tokenValue = tValue;
            this.tokenColor = tColor;
            this.tokenFont = tFont;
        }
        public String getValue()
        {
            return this.tokenValue;
        }
        public Color getColor()
        {
            return this.tokenColor;
        }
        public Font getFont()
        {
            return this.tokenFont;
        }
    }
 
    public class extendedRichText : RichTextBox
    {
        // Klicova slova
        private token[] keywords;
 
        // Tridu UndoClass definujeme nize
        private List<UndoClass> History;
        // UndoPointer ukazuje na konkretni pozici v seznamu historie
        private int UndoPointer = 0;
 
        // Promenne spojene s updatovanim platna (prekreslenim)
        private int updating = 0;
        private int oldEventMask = 0;
 
        // Jednoducha trida pro ulozeni historie uprav
        private class UndoClass
        {
            public string rtfText;
            public int posText;
            public UndoClass(int pos, string str)
            {
                this.rtfText = str;
                this.posText = pos;
            }
        }
 
        // Konstruktor tridy
        public extendedRichText(token[] Tokens, int UndoSize)
        {
            this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer, true);
            this.Multiline = true;
            this.WordWrap = false;
            this.AcceptsTab = true;
            this.DetectUrls = false;
            this.ScrollBars = RichTextBoxScrollBars.ForcedBoth;
            this.Dock = DockStyle.Fill;
            // Velikost historie uprav se definuje v konstruktoru
            History = new List<UndoClass>(UndoSize);
            // Seznam klicovych slov se definuje v konstruktoru
            this.keywords = Tokens;
            // Handlery pro zmenu textu a pro zachytavani klaves
            this.TextChanged += new EventHandler(TextChangedEvent);
            this.KeyDown += new KeyEventHandler(KeyPressEvent);
            this.StoreNewUndo();
        }
 
        private void KeyPressEvent(object sender, System.Windows.Forms.KeyEventArgs e)
        {
            if (e.Control && (e.Shift || e.Alt) && (e.KeyValue == 'Z' || e.KeyValue == 'Y'))
                return;
            if (e.KeyCode == Keys.Enter ||
                e.KeyCode == Keys.Delete ||
                e.KeyCode == Keys.Back ||
                e.KeyValue == ' ' ||
                e.KeyValue == ',' ||
                e.KeyValue == '.' ||
                e.KeyValue == ';' ||
                e.KeyValue == '}' ||
                e.KeyValue == '{' ||
                e.KeyValue == '(' ||
                e.KeyValue == ')')
            {
                StoreNewUndo();
            }
        }
 
        private int StoreNewUndo()
        {
            if (History.Count > UndoPointer)
            {
                History.RemoveRange(UndoPointer, History.Count - UndoPointer);
            }
            History.Add(new UndoClass(this.SelectionStart, this.Rtf));
            if (History.Count > 80)
            {
                History.RemoveAt(0);
                UndoPointer = History.Count;
            }
            else
            {
                UndoPointer++;
            }
            return UndoPointer;
        }
 
        public void RedoEvent()
        {
            if (UndoPointer < 50 && UndoPointer < History.Count - 1)
            {
                this.BeginUpdate();
                UndoPointer++;
                this.Rtf = History[UndoPointer].rtfText;
                this.SelectionStart = History[UndoPointer].posText;
                this.EndUpdate();
                this.Refresh();
            }
        }
 
        public void UndoEvent()
        {
            if (UndoPointer > 0)
            {
                if (UndoPointer == History.Count)
                {
                    StoreNewUndo();
                    if (UndoPointer > 0) UndoPointer--;
                }
                this.BeginUpdate();
                UndoPointer--;
                this.Rtf = History[UndoPointer].rtfText;
                this.SelectionStart = History[UndoPointer].posText;
                this.EndUpdate();
                this.Refresh();
            }
        }
 
        #region Windows API Balast
 
        private const int WM_SETREDRAW = 0x0B;
        private const int EM_SETEVENTMASK = 0x0431;
 
        [DllImport("user32", CharSet = CharSet.Auto)]
        private static extern int SendMessage(HandleRef hWnd, int msg, int wParam, int lParam);
 
        #endregion
 
        public void BeginUpdate()
        {
            this.TextChanged -= this.TextChangedEvent;
            ++updating;
 
            if (updating > 1)
                return;
 
            oldEventMask = SendMessage(new HandleRef(this, Handle), EM_SETEVENTMASK, 0, 0);
            SendMessage(new HandleRef(this, Handle), WM_SETREDRAW, 0, 0);
        }
 
 
        public void EndUpdate()
        {
            this.TextChanged += this.TextChangedEvent;
            --updating;
 
            if (updating > 0)
                return;
 
            SendMessage(new HandleRef(this, Handle), WM_SETREDRAW, 1, 0);
            SendMessage(new HandleRef(this, Handle), EM_SETEVENTMASK, 0, oldEventMask);
            // this.Refresh();
        }
 
        // Napred si napiseme metodu pro tokenizaci textu dle znaku
        private string[] tokenize(string Line)
        {
            // Do regularniho vyrazu zahrneme vsechny znaky, ktere mohou
            // potencialne oddelovat jednotlive tokeny (znaky " ", ",", ...) ...
            Regex r = new Regex("([ \\t{}():;,\"'])");
            return r.Split(Line);
        }
 
        public void ParseLine(string Line)
        {
            // ...a roztrhneme text podle techto znaku na jednotlive tokeny
            String[] tokens = this.tokenize(Line);
            string token;
 
            // Kazdy z techto tokenu potom porovname se seznamem tokenu, ktere
            // specifikujeme v konstruktoru tridy...
            foreach (string token2 in tokens)
            {
                // Osetreni v pripade, ze text "Line" obsahuje newline
                // v takovem pripade je nutne ho odstranit, nebot:
                // "\r\nif" != "if" apod.
                if (token2.StartsWith("\r"))
                {
                    if (token2 == "\r\n" || token2 == "\n" || token2 == " " || token2 == "\t")
                    {
                        this.SelectedText = token2;
                        continue;
                    }
                    token = token2.Replace("\r\n", "");
                }
                else
                {
                    token = token2;
                }
 
                // Nastaveni vychozi barvy
                this.SelectionColor = Color.Black;
                this.SelectionFont = new Font("Courier New", 10, FontStyle.Regular);
 
                // Porovnani aktualniho tokenu s tokeny jazyka, nastaveni barvy
                // a typu pisma v pripade shody...
                for (int i = 0; i < keywords.Length; i++)
                {
                    if (keywords[i].getValue() == token)
                    {
                        this.SelectionColor = keywords[i].getColor();
                        this.SelectionFont = keywords[i].getFont();
                        break;
                    }
                }
                // A prepiseme vybrany text...
                this.SelectedText = token2;
            }
        }
 
        public void TextChangedEvent(object sender, EventArgs e)
        {
            if (this.SelectionStart < 0) return;
 
            int start, end;
 
            // zabraneni tomu aby se prekreslil vice nez jeden radek
            for (start = this.SelectionStart - 1; start > 0; start--)
            {
                if (this.Text[start] == '\n')
                {
                    start++;
                    break;
                }
            }
 
            // zabraneni tomu aby se prekreslil vice nez jeden radek
            for (end = this.SelectionStart; end < this.Text.Length; end++)
            {
                if (this.Text[end] == '\n')
                {
                    break;
                }
            }
 
            // osetreni podteceni pro prvni radek
            if (start < 0) start = 0;
 
            // ulozime si text ktery se ma updatovat...
            String line = this.Text.Substring(start, end - start);
 
            // vypneme prekreslovani RichTextBoxu
            this.BeginUpdate();
 
            // Ulozime si stav vyberu
            int selectionStart = this.SelectionStart;
            int selectionLength = this.SelectionLength;
 
            // ... a vybereme text, ktery se ma prekreslit
            this.SelectionStart = start;
            this.SelectionLength = end - start;
 
            // Zavolame "magickou cernou skrinku" ktera
            // nam ten text prekresli
            this.ParseLine(line);
 
            // Vratime stav vyberu tak, jak byl, vybereme okno do "fokusu" a obnovime prekreslovani (prekreslime)...
            this.SelectionStart = selectionStart;
            this.SelectionLength = selectionLength;
            this.Focus();
            this.EndUpdate();
            this.Refresh();
        }
 
        public new void Paste()
        {
            this.BeginUpdate();
            if (Clipboard.ContainsText(TextDataFormat.Rtf))
            {
                base.Paste();
            }
            else
            {
                this.ParseLine(Clipboard.GetText());
            }
            this.EndUpdate();
            this.Refresh();
        }
 
 
    }
 
}

Komentáře čtenářů

hertpl: Undefined index: comments(template line: 119) in templates/articles_print.html on line 55