using System;
using System.Collections;
using System.Drawing;
using System.Windows.Forms;
using System.Text.RegularExpressions;
using System.Runtime.InteropServices;
namespace Apex.Forms {
public struct HighlightInfo
{
public int wordStart;
public int wordEnd;
public int wordLength;
}
///
/// Schlüsselwort-Hervorhebung für die RichTextBox.
///
public class RichTextBoxKeywordHilighter {
// Findet den Wortanfang.
private static readonly Regex wordStartRegex = new Regex(
@"\b\w",
RegexOptions.Compiled | RegexOptions.RightToLeft);
// Findet das Wortende.
private static readonly Regex wordEndRegex = new Regex(
@"\w\b",
RegexOptions.Compiled);
private string[] keywords;
private Hashtable keywordLookup;
private Color keywordColor = Color.Blue;
private RichTextBox richTextBox;
private RichTextBoxUpdater updater = new RichTextBoxUpdater();
// Speichert die Selektion während Änderungen vorgenommen werden.
private int updating;
private int selectionStart;
private int selectionLength;
private Color selectionColor;
///
/// String-Array mit Schlüsselwörtern.
///
///
public string[] Keywords {
get { return keywords; }
set {
if(keywords != value) {
keywords = value;
keywordLookup = null;
}
}
}
// Hashtable zum Nachschlagen von Schlüsselwörtern.
protected Hashtable KeywordLookup {
get {
if(keywordLookup == null) {
keywordLookup = BuildKeywordLookup(keywords);
}
return keywordLookup;
}
}
private Hashtable BuildKeywordLookup(string[] keywords) {
Hashtable lookup = new Hashtable();
foreach(string k in keywords) {
lookup[k] = null;
}
return lookup;
}
///
/// Hervorhebungsfarbe der Schlüsselwörter.
///
///
public Color KeywordColor {
get { return keywordColor; }
set { keywordColor = value; }
}
///
/// Die RichTextBox.
///
///
public RichTextBox RichTextBox {
get { return richTextBox; }
set { richTextBox = value; }
}
///
/// Hebt ein Schlüsselwort am Index farblich hervor.
///
///
/// Das Wort wird vor dem Index gesucht. Der Index darf
/// ein Zeichen hinter dem Wort liegen. Das erlaubt es
/// als Index die Cursorposition anzugeben, die immer
/// um 1 höher als die Position des zuletzt eingegebenen
/// Zeichens ist.
///
/// Zeichenposition in RichTextBox.Text
public HighlightInfo HilightAt(int index)
{
HighlightInfo hi;
// Der gesamte Text.
string text = RichTextBox.Text;
// Wortanfang ermitteln.
int wordStart = index;
Match m = wordStartRegex.Match(text, index);
if(m.Success) {
wordStart = m.Index;
}
// Wortende ermitteln.
int wordEnd = index - 1;
m = wordEndRegex.Match(text, wordStart);
if(m.Success) {
wordEnd = m.Index;
}
// Wortlänge ermitteln. Ist die Länge 0, gleich zurück.
int wordLength = wordEnd - wordStart + 1;
hi.wordStart = wordStart;
hi.wordEnd = wordEnd;
hi.wordLength = wordLength;
if(wordLength == 0) return hi;
// Das Wort am Index.
string word = text.Substring(wordStart, wordLength);
// Textfarbe ermitteln.
bool isKeyword = KeywordLookup.ContainsKey(word);
Color wordColor = isKeyword ? KeywordColor : Color.Black;
BeginUpdate();
// Textfarbe ändern.
RichTextBox.SelectionStart = wordStart;
RichTextBox.SelectionLength = wordLength;
RichTextBox.SelectionColor = wordColor;
// Spezialfall, wenn ein Wort mit Space in zwei Wörter
// aufgetrennt wird, muss zusätzlich das Wort
// hinter dem Index verarbeitet werden.
bool wordSplit = index < text.Length &&
index - 2 == wordEnd &&
Char.IsLetterOrDigit(text[index]);
if(wordSplit) {
HilightAt(index + 1);
}
EndUpdate();
return hi;
}
private int GetWordType(String word, Char c)
{
if (c=='\n')
return 2;
else if (c == ' ')
return 3;
else if (Char.IsLetterOrDigit(c) ||
(c == '<' && word.IndexOf('>')<0) ||
c == '>' ||
(c == '/' && word.IndexOf('<')>=0)
)
return 1;
else
return 0;
}
private void HighlightWord(String word, int nWordStart, bool bRemark)
{
Color wordColor = Color.Black;
if (bRemark)
wordColor = Color.Green;
else
{
bool isKeyword = KeywordLookup.ContainsKey(word);
wordColor = isKeyword ? KeywordColor : Color.Black;
}
// Textfarbe ändern.
RichTextBox.SelectionStart = nWordStart;
RichTextBox.SelectionLength = word.Length + 1;
RichTextBox.SelectionColor = wordColor;
}
///
/// Hebt alle Schlüsselwörter der RichTextBox farblich hervor.
///
public void Hilight() {
String text = RichTextBox.Text;
if (text.Length == 0)
return;
int nWordStart = 0;
String word = "";
int nWordType = GetWordType(word,text[0]);
bool bIsRemark = false;
bool bIsLineRemark = false;
BeginUpdate();
for (int i = 0; i < text.Length; i++)
{
if (GetWordType(word, text[i]) != nWordType || i==text.Length-1)
{
String sTrimmed = word.Trim();
if (sTrimmed == "//")
bIsLineRemark = true;
if (sTrimmed == "/*")
bIsRemark = true;
HighlightWord(word, nWordStart, bIsRemark || bIsLineRemark);
if (sTrimmed == "*/")
bIsRemark = false;
if (word == "\n")
bIsLineRemark = false;
word = Convert.ToString(text[i]);
nWordType = GetWordType(word, text[i]);
nWordStart = i;
}
else
word += text[i];
}
EndUpdate();
}
// Sichert die Selektion und deaktiviert
// die Aktualisierung der RichTextBox.
private void BeginUpdate() {
updating++;
if(updating > 1) return;
// Selektion speichern.
selectionStart = RichTextBox.SelectionStart;
selectionLength = RichTextBox.SelectionLength;
selectionColor = RichTextBox.SelectionColor;
// Redraw und Events deaktivieren.
this.updater.BeginUpdate(RichTextBox);
}
// Stellt die ursprüngliche Selektion wieder her
// und reaktiviert die Aktualisierung der RichTextBox.
private void EndUpdate() {
updating--;
if(updating > 0) return;
// Selektion wiederherstellen.
RichTextBox.SelectionLength = selectionLength;
RichTextBox.SelectionStart = selectionStart;
RichTextBox.SelectionColor = selectionColor;
// Redraw und Events aktivieren.
this.updater.EndUpdate(RichTextBox);
RichTextBox.Invalidate();
}
}
///
/// Gestattet es Redraw und Events zu deaktivieren,
/// während Änderungen im Text einer RichTextBox
/// vorgenommen werden.
///
///
/// Quelle: Pete's Weblog - Extending RichTextBox, Part I
/// http://geekswithblogs.net/pvidler/archive/2003/10/14/182.aspx
///
class RichTextBoxUpdater {
private const int EM_SETEVENTMASK = 1073;
private const int WM_SETREDRAW = 11;
private int updating;
private int oldEventMask;
///
/// Deaktiviert Redraw und Events der RichTextBox.
///
///
public void BeginUpdate(RichTextBox rtb) {
// Deal with nested calls.
updating++;
if(updating > 1) return;
// Prevent the control from raising any events.
oldEventMask = SendMessage(
new HandleRef(rtb, rtb.Handle),
EM_SETEVENTMASK, 0, 0);
// Prevent the control from redrawing itself.
SendMessage(
new HandleRef(rtb, rtb.Handle),
WM_SETREDRAW, 0, 0);
}
///
/// Reaktiviert Redraw und Events der RichTextBox.
///
///
public void EndUpdate(RichTextBox rtb) {
// Deal with nested calls.
updating--;
if(updating > 0) return;
// Allow the control to redraw itself.
SendMessage(
new HandleRef(rtb, rtb.Handle),
WM_SETREDRAW, 1, 0);
// Allow the control to raise event messages.
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);
}
}