|
|
Autor
 |
|
Pulpapex
myCSharp.de-Poweruser/ Experte
Dabei seit: 22.07.2003
Beiträge: 939
Entwicklungsumgebung: Eclipse / VC# 2005 Herkunft: Rostock
|
|
Einfaches Keyword-Highlighting mit der RichTextBox
Die RichTextBox ist ja im allgemeinen als träge und lahm verschrien. Ich möchte im folgenden zeigen, wie man dennoch schnelles, interaktives Syntax Highlighting hinbekommt. Schlüsselwörter werden bei der Eingabe hervorgehoben.
Die Demo-Anwendung besteht aus vier kleinen Klassen, die ich im folgenden im Detail beschreiben werde: - KeywordHilightingTest
- KeywordHilightingForm
- RichTextBoxKeywordHilighter
- RichTextBoxUpdater
KeywordHilightingTest
Zu KeywordHilightingTest gibt es nicht viel zu sagen. Die Klasse enthält nur die Main-Methode:
C#-Code: |
class KeywordHilightingTest {
static void Main() {
Application.EnableVisualStyles();
Application.Run(new KeywordHilightingForm());
}
}
|
KeywordHilightingForm
KeywordHilightingForm ist auch sehr einfach. Als einziges Control befindet sich eine RichTextBox mit DockStyle.Fill auf der Form. Für das RichTextBox.TextChanged-Ereignis wurde ein EventHandler eingerichtet. Das Ereignis wird bei jedem Tastendruck ausgelöst und ruft die HilightAtCursor-Methode auf. Desweiteren enthält die Klasse noch ein String-Array mit den Schlüsselwörtern und verfügt über eine KeywordHilighter-Eigenschaft, die das eigentliche Hervorheben übernimmt.
C#-Code: |
public class KeywordHilightingForm : Form {
private RichTextBoxKeywordHilighter keywordHilighter;
private readonly string[] keywords = {
"abstract", "event", "new", "struct",
"as", "explicit", "null", "switch",
"base", "extern", "object", "this",
"bool", "false", "operator", "throw",
"break", "finally", "out", "true",
"byte", "fixed", "override", "try",
"case", "float", "params", "typeof",
"catch", "for", "private", "uint",
"char", "foreach", "protected", "ulong",
"checked", "goto", "public", "unchecked",
"class", "if", "readonly", "unsafe",
"const", "implicit", "ref", "ushort",
"continue", "in", "return", "using",
"decimal", "int", "sbyte", "virtual",
"default", "interface", "sealed", "volatile",
"delegate", "internal", "short", "void",
"do", "is", "sizeof", "while",
"double", "lock", "stackalloc",
"else", "long", "static",
"enum", "namespace", "string"
};
public KeywordHilightingForm() {
InitializeComponent();
}
private void InitializeComponent() {
}
private void HandleTextChanged(object sender, EventArgs e) {
HilightAtCursor();
}
public void HilightAtCursor() {
int index = this.richTextBox.SelectionStart;
KeywordHilighter.HilightAt(index);
}
public RichTextBoxKeywordHilighter KeywordHilighter {
get {
if(keywordHilighter == null) {
keywordHilighter = new RichTextBoxKeywordHilighter();
keywordHilighter.RichTextBox = this.richTextBox;
keywordHilighter.Keywords = this.keywords;
}
return keywordHilighter;
}
}
}
|
RichTextBoxKeywordHilighter
Die RichTextBoxKeywordHilighter-Klasse ist wie gesagt für das eigentliche Hervorheben zuständig. Der Trick besteht darin, so wenig Text wie nur möglich zu bearbeiten. Deshalb wird auch nur das Wort vor dem Cursor untersucht, das gerade eingegeben wird. Die gesamte Textzeile zu aktualisieren wäre zwar einfacher, dauert aber schon zu lange.
Der Code für die Texthervorhebungen befindet sich in der HilightAt-Methode. Als Parameter wird ein Index auf ein Zeichen übergeben. Der Index darf um 1 grösser sein als die Position des letzten Zeichens im hervorzuhebenden Wort. Er entspricht damit der Cursorposition, wenn das Wort gerade eingegeben wird. Den Code der Methode habe ich hier in mehrere Blöcke aufgeteilt, um ihre Funktion besser beschreiben zu können.
Zunächst einmal wird der Text aus der RichTextBox benötigt. RichTextBox ist eine Eigenschaft von RichTextBoxKeywordHilighter.
C#-Code: |
string text = RichTextBox.Text;
|
Jetzt kann das Wort vor der Cursorposition gesucht werden. Zuerst wird der Wortanfang ermittelt.
C#-Code: |
int wordStart = index;
Match m = wordStartRegex.Match(text, index);
if(m.Success) {
wordStart = m.Index;
}
|
Hier der im Code verwendete, reguläre Ausdruck wordStartRegex. Er sucht ausgehend vom Cursor rückwärts:
C#-Code: |
private static readonly Regex wordStartRegex = new Regex(
@"\\b\\w",
RegexOptions.Compiled | RegexOptions.RightToLeft);
|
Ab Wortanfang wird wieder vorwärts nach dem Wortende gesucht.
C#-Code: |
int wordEnd = index - 1;
m = wordEndRegex.Match(text, wordStart);
if(m.Success) {
wordEnd = m.Index;
}
|
Hier der zugehörige, reguläre Ausdruck wordEndRegex:
C#-Code: |
private static readonly Regex wordEndRegex = new Regex(
@"\\w\\b",
RegexOptions.Compiled);
|
Aus Wortanfang und Wortende wird die Wortlänge ermittelt. Ist sie 0, gibt es nichts zu highlighten und es kann sofort abgebrochen werden:
C#-Code: |
int wordLength = wordEnd - wordStart + 1;
if(wordLength == 0) return;
|
Wurde ein Wort gefunden, wird überprüft ob es sich um ein Schlüsselwort handelt. Dementsprechend wird eine Textfarbe ausgewählt. KeywordColor ist wieder eine Eigenschaft von RichTextBoxKeywordHilighter.
C#-Code: |
string word = text.Substring(wordStart, wordLength);
bool isKeyword = KeywordLookup.ContainsKey(word);
Color wordColor = isKeyword ? KeywordColor : Color.Black;
|
Nun folgt das eigentliche Hervorheben. Zuvor muss aber noch die aktuelle Selektion der RichTextBox gesichert werden. Sie legt auch die Cursorposition fest, die im Anschluss wieder hergestellt werden muss. Das geschied in den Methoden BeginUpdate und EndUpdate.
C#-Code: |
BeginUpdate();
RichTextBox.SelectionStart = wordStart;
RichTextBox.SelectionLength = wordLength;
RichTextBox.SelectionColor = wordColor;
bool wordSplit = index < text.Length &&
index - 2 == wordEnd &&
Char.IsLetterOrDigit(text[index]);
if(wordSplit) {
HilightAt(index + 1);
}
EndUpdate();
|
Wie man am Code sieht, gibt es einen Sonderfall, der extra behandelt werden muss. Normalerweise wird nur das Wort vor dem Cursor beachtet. Wird jedoch ein Wort durch einen Tastendruck in zwei Wörter aufgetrennt, kann sich auch die Farbe des Wortes hinter dem Cursor ändern. In dem Fall wird HilightAt einfach ein zweites Mal aufgerufen.
Der Code, wie er jetzt ist, funktioniert eigentlich schon zufriedenstellend. Es gibt keine Verzögerungen beim Tippen und gleichzeitigem Highlighting. Allerdings sind die in HilightAt programmatisch vorgenommenen Selektionen für den Benutzer sichtbar. Sie fallen durch kurzes Aufblitzen unangenehm auf. Dieses Problem löst die RichTextBoxUpdater-Klasse. Den Code hierfür habe ich in Pete's Weblog gefunden:
Pete's Weblog: Extending RichTextBox, Part I
RichTextBoxUpdater
RichTextBoxUpdater definiert zwei Methoden, BeginUpdate und EndUpdate. BeginUpdate deaktiviert die Aktualisierung der RichTextBox-Anzeige während vom Programm Textänderungen vorgenommen werden. EndUpdate reaktiviert die Aktualisierung wieder. Leider gibt es in der RichTextBox-Klasse keine Methoden, die das erledigen. Es muss also wiedermal auf P/Invoke und die Win-Api zurückgegriffen werden.
Die Einzelheiten können auf der angegebenen Seite nachgelesen werden, hier nur noch der Code für die Klasse:
C#-Code: |
class RichTextBoxUpdater {
private const int EM_SETEVENTMASK = 1073;
private const int WM_SETREDRAW = 11;
private int updating;
private int oldEventMask;
public void BeginUpdate(RichTextBox rtb) {
updating++;
if(updating > 1) return;
oldEventMask = SendMessage(
new HandleRef(rtb, rtb.Handle),
EM_SETEVENTMASK, 0, 0);
SendMessage(
new HandleRef(rtb, rtb.Handle),
WM_SETREDRAW, 0, 0);
}
public void EndUpdate(RichTextBox rtb) {
updating--;
if(updating > 0) return;
SendMessage(
new HandleRef(rtb, rtb.Handle),
WM_SETREDRAW, 1, 0);
SendMessage(
new HandleRef(rtb, rtb.Handle),
EM_SETEVENTMASK, 0, oldEventMask);
}
[DllImport("user32", CharSet = CharSet.Auto)]
private static extern int SendMessage(
HandleRef hWnd,
int msg,
int wParam,
int lParam);
}
|
Das war's auch schon. So sieht das Ergebnis aus:
Wenn ihr Lust habt, probiert die angehängte Demo-Anwendung aus. Die Zip-Datei enthält auch den kompletten Quellcode, den ihr nach Belieben zerpflücken und in eigene Anwendungen einbauen könnt. Bei Pete's Code bin ich mir nicht so sicher, aber ich denke, dass eine Quellenangabe ausreichen sollte.
In einem zweiten Teil werde ich die RichTextBoxKeywordHilighter-Klasse nochmal um das Highlighten für Textbereiche erweitern, damit sie auch beim Laden von Textdateien und bei Copy&Paste funktioniert.
Gruss
Pulpapex
Suchhilfe: Syntax, Keyword, Keywords, Highlight, Hilight, Highlighter, Hilighter, Highlighting, Hilighting, Syntaxhighlight, Syntaxhilight, Syntaxhighlighter, Syntaxhilighter, Syntaxhighlighting, Syntaxhilighting, Keywordhighlight, Keywordhilight, Keywordhighlighter, Keywordhilighter, Keywordhighlighting, Keywordhilighting
|
|
12.04.2005 21:45
|
E-Mail |
Beiträge des Benutzers |
zu Buddylist hinzufügen
|
|
| Zwischen diesen beiden Beiträgen liegen mehr als 4 Monate. |
Fabian
myCSharp.de-Mitglied
Dabei seit: 09.12.2004
Beiträge: 1.979
Entwicklungsumgebung: Visual Studio 2010 Herkunft: Dortmund
|
|
Hallo Pulpapex,
erstmal ein großes Danke für den Artikel. Ist sehr gut beschreiben und einfach erweiterbar.
Willst Du evtl. noch weiter dran arbeiten? Ich habe so ein paar Erweiterungen, die ich nicht ganz hinbekomme (liegt hauptsächlich daran, dass ich keine Ahnung von RegEx habe).
Ein Bereich, der auf Kommentare matched, wäre sehr gut. Ich komme leider nicht auf den entsprechenden RegEx-Ausdruck.
Auch das Laden von Dateien bzw. Copy & Paste wären nicht schlecht. Da komme ich auch nicht dahinter 
.
Würde mich freuen, wenn Du noch mal Lust hast, daran weiter zu arbeiten.
Gruß,
Fabian
|
|
31.08.2005 10:13
|
E-Mail |
Beiträge des Benutzers |
zu Buddylist hinzufügen
|
|
Fabian
myCSharp.de-Mitglied
Dabei seit: 09.12.2004
Beiträge: 1.979
Entwicklungsumgebung: Visual Studio 2010 Herkunft: Dortmund
|
|
Hallo Pulpapax,
das wäre auf jeden Fall sehr nett von Dir. Das Beispiel ist richtig gut.
Ein Problem habe ich noch beim Implementieren der Erkennung von Kommentaren. Kannst Du da vielleicht auch mal drüber gucken?
Gruß,
Fabian
|
|
01.09.2005 16:07
|
E-Mail |
Beiträge des Benutzers |
zu Buddylist hinzufügen
|
|
| Zwischen diesen beiden Beiträgen liegen mehr als 2 Jahre. |
TheProgrammer
myCSharp.de-Mitglied
Dabei seit: 28.10.2007
Beiträge: 3
Entwicklungsumgebung: Visual Studio C# 2005 Express
|
|
Vielen dank für das Tutorial. Habs gerade gefunden und es hat mir sehr geholfen.
Allerdings hab ich einen Kritikpunkt. Deine Klasse kann keine Schlüsselwörter wie "<html>" hervorheben sondern nur "html"
mfg
TheProgrammer
|
|
28.10.2007 17:53
|
E-Mail |
Beiträge des Benutzers |
zu Buddylist hinzufügen
|
|
herbivore
myCSharp.de-Team (Admin)
Dabei seit: 11.01.2005
Beiträge: 47.496
Entwicklungsumgebung: csc/nmake (nothing is faster) Herkunft: Berlin
|
|
Hallo TheProgrammer,
dazu wäre nur eine kleinere Änderung an den verwendeten Regex-Pattern nötig. Die momentanen Pattern arbeiten ja absichtlich mit Wortgrenzen (\b), damit bei "forget" nicht fälschlich "for" innerhalb des Wortes hervorgehoben würde.
Da Pulpapex schon lange nicht mehr im Forum war, wirst du die Änderung selber durchführen müssen. Mach für evtl. Fragen zu Regex oder der überhaupt der Umsetzung bitte einen neuen Thread auf.
herbivore
|
|
28.10.2007 18:03
|
E-Mail |
Beiträge des Benutzers |
zu Buddylist hinzufügen
|
|
| Zwischen diesen beiden Beiträgen liegen mehr als 5 Monate. |
ErfinderDesRades
myCSharp.de-Poweruser/ Experte
Dabei seit: 31.01.2008
Beiträge: 4.424
|
|
Hi!
Ich stelle grade fest, der RichtextboxUpdater funzt nur halb. In RichtextboxUpdater.BeginUpdate()
C#-Code: |
oldEventMask = SendMessage(
new HandleRef(rtb, rtb.Handle),
EM_SETEVENTMASK, 0, 0);
|
ließ mich ja erwarten, daß insbesondere die SelectionChanged-Events der Rtb unterdrückt würden. Werdenseabernich.
mein kleiner Test:
C#-Code: |
public RichTextBox RichTextBox {
get { return richTextBox; }
set {
richTextBox = value;
richTextBox.SelectionChanged += new EventHandler(richTextBox_SelectionChanged);
}
}
void richTextBox_SelectionChanged(object sender, EventArgs e) {
if (updating > 0) {
throw new Exception(
"Dieses Ereignis sollte doch in diesem Zustand nicht gefeuert werden!");
}
}
|
Ich erweitere den Richtextbox-Setter, und abonniere das SelectionChanged.
Anbei das Werk in lauffähiger Vollständigkeit:
|
|
24.10.2008 22:16
|
E-Mail |
Beiträge des Benutzers |
zu Buddylist hinzufügen
|
|
|