This question already has answers here:
RichTextBox syntax highlighting in real time--Disabling the repaint
(3 answers)
Closed 9 years ago.
im working on a code editor and i came up with this set of codes:
public class Test2 : Form {
RichTextBox m_rtb = null;
public static void Main() {
Application.Run(new Test2());
}
public Test2() {
Text = "Test2";
ClientSize = new Size(400, 400);
m_rtb = new RichTextBox();
m_rtb.Multiline = true;
m_rtb.WordWrap = false;
m_rtb.AcceptsTab = true;
m_rtb.ScrollBars = RichTextBoxScrollBars.ForcedBoth;
m_rtb.Dock = DockStyle.Fill;
m_rtb.SelectionFont = new Font("Courier New", 10, FontStyle.Regular);
m_rtb.SelectionColor = Color.Black;
Controls.Add(m_rtb);
Parse();
m_rtb.TextChanged += new EventHandler(this.TextChangedEvent);
}
void Parse() {
String inputLanguage =
"// Comment.\n" +
"using System;\n" + "\n" +
"public class Stuff : Form { \n" +
" public static void Main(String args) {\n" +
" }\n" +
"}\n" ;
// Foreach line in input,
// identify key words and format them when adding to the rich text box.
Regex r = new Regex("\\n");
String [] lines = r.Split(inputLanguage);
foreach (string l in lines) {
ParseLine(l);
}
}
void ParseLine(string line) {
Regex r = new Regex("([ \\t{}();])");
String [] tokens = r.Split(line);
foreach (string token in tokens) {
// Set the token's default color and font.
m_rtb.SelectionColor = Color.Black;
m_rtb.SelectionFont = new Font("Courier New", 10, FontStyle.Regular);
// Check for a comment.
if (token == "//" || token.StartsWith("//")) {
// Find the start of the comment and then extract the whole comment.
int index = line.IndexOf("//");
string comment = line.Substring(index, line.Length - index);
m_rtb.SelectionColor = Color.LightGreen;
m_rtb.SelectionFont = new Font("Courier New", 10, FontStyle.Regular);
m_rtb.SelectedText = comment;
break;
}
// Check whether the token is a keyword.
String [] keywords = { "public", "void", "using", "static", "class" };
for (int i = 0; i < keywords.Length; i++) {
if (keywords[i] == token) {
// Apply alternative color and font to highlight keyword.
m_rtb.SelectionColor = Color.Blue;
m_rtb.SelectionFont = new Font("Courier New", 10, FontStyle.Bold);
break;
}
}
m_rtb.SelectedText = token;
}
m_rtb.SelectedText = "\n";
}
private void TextChangedEvent(object sender, EventArgs e) {
// Calculate the starting position of the current line.
int start = 0, end = 0;
for (start = m_rtb.SelectionStart - 1; start > 0; start--) {
if (m_rtb.Text[start] == '\n') { start++; break; }
}
// Calculate the end position of the current line.
for (end = m_rtb.SelectionStart; end < m_rtb.Text.Length; end++) {
if (m_rtb.Text[end] == '\n') break;
}
// Extract the current line that is being edited.
String line = m_rtb.Text.Substring(start, end - start);
// Backup the users current selection point.
int selectionStart = m_rtb.SelectionStart;
int selectionLength = m_rtb.SelectionLength;
// Split the line into tokens.
Regex r = new Regex("([ \\t{}();])");
string [] tokens = r.Split(line);
int index = start;
foreach (string token in tokens) {
// Set the token's default color and font.
m_rtb.SelectionStart = index;
m_rtb.SelectionLength = token.Length;
m_rtb.SelectionColor = Color.Black;
m_rtb.SelectionFont = new Font("Courier New", 10, FontStyle.Regular);
// Check for a comment.
if (token == "//" || token.StartsWith("//")) {
// Find the start of the comment and then extract the whole comment.
int length = line.Length - (index - start);
string commentText = m_rtb.Text.Substring(index, length);
m_rtb.SelectionStart = index;
m_rtb.SelectionLength = length;
m_rtb.SelectionColor = Color.LightGreen;
m_rtb.SelectionFont = new Font("Courier New", 10, FontStyle.Regular);
break;
}
// Check whether the token is a keyword.
String [] keywords = { "public", "void", "using", "static", "class" };
for (int i = 0; i < keywords.Length; i++) {
if (keywords[i] == token) {
// Apply alternative color and font to highlight keyword.
m_rtb.SelectionColor = Color.Blue;
m_rtb.SelectionFont = new Font("Courier New", 10, FontStyle.Bold);
break;
}
}
index += token.Length;
}
// Restore the users current selection point.
m_rtb.SelectionStart = selectionStart;
m_rtb.SelectionLength = selectionLength;
}
}
problem was everytime i press space keys or type the entire code editor keeps on scanning like searching on what to highlight and i find it a bit annoying ...
so i just want to ask for possible solution about this ... to avoid highlighting of whole richtextbox like scanning what to highlight next .
thanks a lot in advance for the help! more power!
I answered this in your most recent question, but in case someone else is reading this and doesn't find it, I'll post it here (Since this is specifically about performance):
You can use a couple of things to improve performance:
1) You can get the line that the user is editing by getting the text range from the current selection. I would recommend using WPF's richtextbox as it contains much more features and has the useful TextPointer class which you can utilise to get the current line. It seems like you are using WinForms however, so this can be done with these few lines of code (with the addition of some trivial code):
int start_index = RTB.GetFirstCharIndexOfCurrentLine();
int line = RTB.GetLineFromCharIndex(index);
int last_index = RTB.GetFirstCharIndexFromLine(line+1);
RTB.Select(start_index, last_index);
You can then work with the current selection.
2) If you don't want it to update so frequently, you can create a timer which measures the delay since the last edit, and reset the timer if another edit is made before the timer elapses.
Related
I am using C# WinForm, and I have a RichTextBox that I am trying to make look like a C# script.
Means when using specific words, I want them to be colored. When they edit the word by changing it, I want it to go back to be black.
My approach works, but it really messy and cause bugs when the a scroll option is created and needed to be used to see the code below. (When typing, pretty much the richtextbox jumps up and down without stop)
private void ScriptRichTextBox_TextChanged(object sender, EventArgs e)
{
ScriptTextChange = ScriptRichTextBox.Text;
ScriptColorChange();
}
private void ScriptColorChange()
{
int index = ScriptRichTextBox.SelectionStart;
ScriptRichTextBox.Text = ScriptTextChange; //Only way I found to make the all current text black again, SelectAll() didn't work well.
ScriptRichTextBox.SelectionStart = index;
String[] coloredNames = {"Main", "ClickMouseDown", "ClickMouseUp", "PressKey", "StopMoving", "Delay", "GoRight", "GoLeft", "GoUp", "GoDown", "MousePosition", "LastHorizontalDirection", "LastVerticalDirections", "CurrentDirection", "Directions" };
String[] coloredNames2 = { "cm.", "if", "else", "while", "switch", "case", "break", "return", "new" };
String[] coloredNames3 = { "MyPosition", "MyHp", "MyMp", "OtherPeopleInMap", ".RIGHT", ".LEFT", ".UP", ".DOWN", ".STOP_MOVING" };
foreach (String s in coloredNames)
this.CheckKeyword(s, Color.LightSkyBlue, 0);
foreach (String s in coloredNames2)
this.CheckKeyword(s, Color.Blue, 0);
foreach (String s in coloredNames3)
this.CheckKeyword(s, Color.DarkGreen, 0);
}
private void CheckKeyword(string word, Color color, int startIndex)
{
if (this.ScriptRichTextBox.Text.Contains(word))
{
int index = 0;
int selectStart = this.ScriptRichTextBox.SelectionStart;
while ((index = this.ScriptRichTextBox.Text.IndexOf(word, (index + 1))) != -1)
{
this.ScriptRichTextBox.Select((index + startIndex), word.Length);
this.ScriptRichTextBox.SelectionColor = color;
this.ScriptRichTextBox.Select(selectStart, 0);
this.ScriptRichTextBox.SelectionColor = Color.Black;
}
}
}
I refactored your code a little to hopefully demonstrate a better approach to colouring the text. It is also not optimal to instantiate your string arrays every time you fire the TextChanged event.
Updated:The idea is to build up a word buffer that will be matched with your set of words when typing.
The buffer records each key and if it .IsLetterOrDigit it adds it to the StringBuilder buffer. The buffer has some additional bugs, with recording key press values and not removing recorded chars if you hit backspace etc..
Instead of the word buffer, use RegEx to match any of the words in your reserve word list. Build up the reserve word RegEx so you end up with something like \b(word|word2|word3....)\b This is done in the code in the BuildRegExPattern(..) method.
Once you hit any key other than a letter or number the buffer is checked for content and if the content matches a word then only the text right before the cursor in the ScriptRichTextBox.Text is checked and changed.
Remove the .(dots) from the reserve words as this just complicates the matching criteria. The RegEx in the built up patters will match the words exactly, so if you type something like FARRIGHT or cms the words will not partially change colour.
As an extra I also covered the paste process pressing Ctrl+V because it is a bit of a pain in WinForms and will probably happen quite often.
There are older questions eg. this one that cover the scrolling behaviour, where it shows how to interop by adding the [System.Runtime.InteropServices.DllImport("user32.dll")] attribute, but it can be done without it.
To prevent all the scroll jumping you can make use of the .DefWndProc(msg) method on the form. this question pointed me towards the WM_SETREDRAW property.
There is also this list of other properties that can be set.
The full implementation is this:
public partial class Form1 : Form
{
private readonly string[] _skyBlueStrings;
private readonly string[] _blueStrings;
private readonly string[] _greenStrings;
//for pasting
bool _IsCtrl;
bool _IsV;
//value to fix the colour not setting first character after return key pressed
int _returnIdxFix = 0;
//regex patterns to use
string _LightBlueRegX = "";
string _BlueRegX = "";
string _GreenRegX = "";
//match only words
Regex _rgxAnyWords = new Regex(#"(\w+)");
//colour setup
Color _LightBlueColour = Color.LightSkyBlue;
Color _BlueColour = Color.Blue;
Color _GreenColour = Color.DarkGreen;
Color _DefaultColour = Color.Black;
public Form1()
{
InitializeComponent();
_skyBlueStrings = new string[] { "Main", "ClickMouseDown", "ClickMouseUp", "PressKey", "StopMoving", "Delay", "GoRight", "GoLeft", "GoUp", "GoDown", "MousePosition", "LastHorizontalDirection", "LastVerticalDirections", "CurrentDirection", "Directions" };
_blueStrings = new string[] { "cm", "if", "else", "while", "switch", "case", "break", "return", "new" };
_greenStrings = new string[] { "MyPosition", "MyHp", "MyMp", "OtherPeopleInMap", "RIGHT", "LEFT", "UP", "DOWN", "STOP_MOVING" };
_LightBlueRegX = BuildRegExPattern(_skyBlueStrings);
_BlueRegX = BuildRegExPattern(_blueStrings);
_GreenRegX = BuildRegExPattern(_greenStrings);
}
string BuildRegExPattern(string[] keyworkArray)
{
StringBuilder _regExPatern = new StringBuilder();
_regExPatern.Append(#"\b(");//beginning of word
_regExPatern.Append(string.Join("|", keyworkArray));//all reserve words
_regExPatern.Append(#")\b");//end of word
return _regExPatern.ToString();
}
private void ProcessAllText()
{
BeginRtbUpdate();
FormatKeywords(_LightBlueRegX, _LightBlueColour);
FormatKeywords(_BlueRegX, _BlueColour);
FormatKeywords(_GreenRegX, _GreenColour);
//internal function to process words and set their colours
void FormatKeywords(string regExPattern, Color wordColour)
{
var matchStrings = Regex.Matches(ScriptRichTextBox.Text, regExPattern);
foreach (Match match in matchStrings)
{
FormatKeyword(keyword: match.Value, wordIndex: match.Index, wordColour: wordColour);
}
}
EndRtbUpdate();
ScriptRichTextBox.Select(ScriptRichTextBox.Text.Length, 0);
ScriptRichTextBox.Invalidate();
}
void ProcessWordAtIndex(string fullText, int cursorIdx)
{
MatchCollection anyWordMatches = _rgxAnyWords.Matches(fullText);
if (anyWordMatches.Count == 0)
{ return; } // no words found
var allWords = anyWordMatches.OfType<Match>().ToList();
//get the word just before cursor
var wordAtCursor = allWords.FirstOrDefault(w => (cursorIdx - _returnIdxFix) == (w.Index + w.Length));
if (wordAtCursor is null || string.IsNullOrWhiteSpace(wordAtCursor.Value))
{ return; }//no word at cursor or the match was blank
Color wordColour = CalculateWordColour(wordAtCursor.Value);
FormatKeyword(wordAtCursor.Value, wordAtCursor.Index, wordColour);
}
private Color CalculateWordColour(string word)
{
if (_skyBlueStrings.Contains(word))
{ return _LightBlueColour; }
if (_blueStrings.Contains(word))
{ return _BlueColour; }
if (_greenStrings.Contains(word))
{ return _GreenColour; }
return _DefaultColour;
}
private void FormatKeyword(string keyword, int wordIndex, Color wordColour)
{
ScriptRichTextBox.Select((wordIndex - _returnIdxFix), keyword.Length);
ScriptRichTextBox.SelectionColor = wordColour;
ScriptRichTextBox.Select(wordIndex + keyword.Length, 0);
ScriptRichTextBox.SelectionColor = _DefaultColour;
}
#region RichTextBox BeginUpdate and EndUpdate Methods
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
//wait until the rtb is visible, otherwise you get some weird behaviour.
if (ScriptRichTextBox.Visible && ScriptRichTextBox.IsHandleCreated)
{
if (m.LParam == ScriptRichTextBox.Handle)
{
rtBox_lParam = m.LParam;
rtBox_wParam = m.WParam;
}
}
}
IntPtr rtBox_wParam = IntPtr.Zero;
IntPtr rtBox_lParam = IntPtr.Zero;
const int WM_SETREDRAW = 0x0b;
const int EM_HIDESELECTION = 0x43f;
void BeginRtbUpdate()
{
Message msg_WM_SETREDRAW = Message.Create(ScriptRichTextBox.Handle, WM_SETREDRAW, (IntPtr)0, rtBox_lParam);
this.DefWndProc(ref msg_WM_SETREDRAW);
}
public void EndRtbUpdate()
{
Message msg_WM_SETREDRAW = Message.Create(ScriptRichTextBox.Handle, WM_SETREDRAW, rtBox_wParam, rtBox_lParam);
this.DefWndProc(ref msg_WM_SETREDRAW);
//redraw the RichTextBox
ScriptRichTextBox.Invalidate();
}
#endregion
private void ScriptRichTextBox_TextChanged(object sender, EventArgs e)
{
//only run all text if it was pasted NOT ON EVERY TEXT CHANGE!
if (_IsCtrl && _IsV)
{
_IsCtrl = false;
ProcessAllText();
}
}
protected void ScriptRichTextBox_KeyPress(object sender, KeyPressEventArgs e)
{
if (!char.IsLetterOrDigit(e.KeyChar))
{
//if the key was enter the cursor position is 1 position off
_returnIdxFix = (e.KeyChar == '\r') ? 1 : 0;
ProcessWordAtIndex(ScriptRichTextBox.Text, ScriptRichTextBox.SelectionStart);
}
}
private void ScriptRichTextBox_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
{
if (e.KeyCode == Keys.ControlKey)
{
_IsCtrl = true;
}
if (e.KeyCode == Keys.V)
{
_IsV = true;
}
}
private void ScriptRichTextBox_KeyUp(object sender, System.Windows.Forms.KeyEventArgs e)
{
if (e.KeyCode == Keys.ControlKey)
{
_IsCtrl = false;
}
if (e.KeyCode == Keys.V)
{
_IsV = false;
}
}
}
It looks like this when you paste some "code" with keywords:
and typing looks like this:
Ok after 2 days of not finding something that actually works good or has annoying bugs. I managed to find a solution myself after a big struggle of trying to make it work. The big idea is people try to edit all the RichTextBox words at once, which cause bugs. Why to edit all of the rich text box when you can do your checks on the current word only to get the same result. Which is what I did, I checked if any of my array strings is in the current word, and colored all of them.
private void ScriptRichTextBox_TextChanged(object sender, EventArgs e)
{
FindStringsInCurrentWord();
}
private void FindStringsInCurrentWord()
{
RichTextBox script = ScriptRichTextBox;
String finalWord, forwards, backwards;
int saveLastSelectionStart = script.SelectionStart;
int index = script.SelectionStart;
String[] coloredNames = { "Main", "ClickMouseDown", "ClickMouseUp", "PressKey", "StopMoving", "Delay", "GoRight", "GoLeft", "GoUp", "GoDown", "MousePosition", "LastHorizontalDirection", "LastVerticalDirections", "CurrentDirection", "Directions" };
String[] coloredNames2 = { "cm.", "if", "else", "while", "switch", "case", "break", "return", "new" };
String[] coloredNames3 = { "MyPosition", "MyHp", "MyMp", "OtherPeopleInMap", ".RIGHT", ".LEFT", ".UP", ".DOWN", ".STOP_MOVING" };
String[] arr2 = coloredNames.Union(coloredNames2).ToArray();
Array arrAll = arr2.Union(coloredNames3).ToArray(); //Gets all arrays together
Array[] wordsArray = { coloredNames, coloredNames2, coloredNames3 }; //All found strings in the word
List<String> wordsFoundList = new List<String>();
int foundChangedColor = 0;
int wordsFound = 0;
char current = (char)script.GetCharFromPosition(script.GetPositionFromCharIndex(index)); //Where the editor thingy is
//Check forward text where he uses space and save text
while (!System.Char.IsWhiteSpace(current) && index < script.Text.Length)
{
index++;
current = (char)script.GetCharFromPosition(script.GetPositionFromCharIndex(index));
}
int lengthForward = index - saveLastSelectionStart;
script.Select(script.SelectionStart, lengthForward);
forwards = script.SelectedText;
//Debug.WriteLine("Forwards: " + forwards);
script.SelectionStart = saveLastSelectionStart;
this.ScriptRichTextBox.Select(script.SelectionStart, 0);
index = script.SelectionStart;
current = (char)script.GetCharFromPosition(script.GetPositionFromCharIndex(index));
int length = 0;
//Check backwords where he uses space and save text
while ((!System.Char.IsWhiteSpace(current) || length == 0) && index > 0 && index <= script.Text.Length)
{
index--;
length++;
current = (char)script.GetCharFromPosition(script.GetPositionFromCharIndex(index));
}
script.SelectionStart -= length;
script.Select(script.SelectionStart + 1, length - 1);
backwards = script.SelectedText;
//Debug.WriteLine("Backwards: " + backwards);
script.SelectionStart = saveLastSelectionStart;
this.ScriptRichTextBox.Select(saveLastSelectionStart, 0);
this.ScriptRichTextBox.SelectionColor = Color.Black;
finalWord = backwards + forwards; //Our all word!
//Debug.WriteLine("WORD:" + finalWord);
//Setting all of the word black, after it coloring the right places
script.Select(index + 1, length + lengthForward);
script.SelectionColor = Color.Black;
foreach (string word in arrAll)
{
if (finalWord.IndexOf(word) != -1)
{
wordsFound++;
wordsFoundList.Add(word);
script.Select(index + 1 + finalWord.IndexOf(word), word.Length);
if (coloredNames.Any(word.Contains))
{
script.SelectionColor = Color.LightSkyBlue;
foundChangedColor++;
}
else if (coloredNames2.Any(word.Contains))
{
script.SelectionColor = Color.Blue;
foundChangedColor++;
}
else if (coloredNames3.Any(word.Contains))
{
script.SelectionColor = Color.DarkGreen;
foundChangedColor++;
}
//Debug.WriteLine("Word to edit: " + script.SelectedText);
this.ScriptRichTextBox.Select(saveLastSelectionStart, 0);
this.ScriptRichTextBox.SelectionColor = Color.Black;
}
}
//No strings found, color it black
if (wordsFound == 0)
{
script.Select(index + 1, length + lengthForward);
script.SelectionColor = Color.Black;
//Debug.WriteLine("WORD??: " + script.SelectedText);
this.ScriptRichTextBox.Select(saveLastSelectionStart, 0);
this.ScriptRichTextBox.SelectionColor = Color.Black;
}
}
I'm registering syntax highlighting with AvalonEdit with:
PythonPrompt.SyntaxHighlighting = pythonHighlighting;
Text can then be input by the user throughout the course of the program. Is there a way to take the formatted text and move it to a TextBlock without loosing the formatting?
As this formatted text will not be edited again I presume it is more efficient to create a TextBlock rather than creating a TextEditor on the fly.
I managed to get something that works. Its based off the latest code for AvalonEdit (HighlightedLine and RichTextModel)
TextBlock Item = new TextBlock();
Code = Code.Replace("\t", new String(' ', Editor.Options.IndentationSize));
TextDocument Document = new TextDocument(Code);
IHighlightingDefinition HighlightDefinition = Editor.SyntaxHighlighting;
IHighlighter Highlighter = new DocumentHighlighter(Document, HighlightDefinition.MainRuleSet);
int LineCount = Document.LineCount;
for (int LineNumber = 1; LineNumber <= Document.LineCount; LineNumber++)
{
HighlightedLine Line = Highlighter.HighlightLine(LineNumber);
string LineText = Document.GetText(Line.DocumentLine);
int Offset = Line.DocumentLine.Offset;
int SectionCount = Line.Sections.Count;
for (int SectionNumber = 0; SectionNumber < SectionCount; SectionNumber++)
{
HighlightedSection Section = Line.Sections[SectionNumber];
//Deal with previous text
if (Section.Offset > Offset)
{
Item.Inlines.Add(
new Run(Document.GetText(Offset, Section.Offset - Offset))
);
}
Run RunItem = new Run(Document.GetText(Section));
if (RunItem.Foreground != null)
{
RunItem.Foreground = Section.Color.Foreground.GetBrush(null);
}
if (Section.Color.FontWeight != null)
{
RunItem.FontWeight = Section.Color.FontWeight.Value;
}
Item.Inlines.Add(RunItem);
Offset = Section.Offset + Section.Length;
}
//Deal with stuff at end of line
int LineEnd = Line.DocumentLine.Offset + LineText.Length;
if (LineEnd > Offset)
{
Item.Inlines.Add(
new Run(Document.GetText(Offset, LineEnd-Offset))
);
}
//If not last line add a new line
if (LineNumber < LineCount)
{
Item.Inlines.Add(new Run("\n"));
}
}
I'm working on a code-editor and I want to call the string line into a keyargs event which is inside another void-returning method.
Output should occur when I type enter key, and then the selected-list from ComboBox should append to text held in RichTextBox.
Now to fulfill that, I'd like to ask you, how to call this method:
void Parse()
{
String inputLanguage =
"using System;\n" + "\n" +
"public class Stuff : Form { \n" +
" public static void Main(String args) {\n" +
"\n" + "\n" +
" }\n" +
"}\n";
// Foreach line in input,
// identify key words and format them when adding to the rich text box.
Regex r = new Regex("\\n");
String[] lines = r.Split(inputLanguage);
foreach (string l in lines)
{
ParseLine(l);
}
}
void ParseLine(string line)
{
Regex r = new Regex("([ \\t{}();])");
String[] tokens = r.Split(line);
foreach (string token in tokens)
{
// Set the token's default color and font.
rtb.SelectionColor = Color.Black;
rtb.SelectionFont = new Font("Courier New", 10, FontStyle.Regular);
// Check for a comment.
if (token == "//" || token.StartsWith("//"))
{
// Find the start of the comment and then extract the whole comment.
int index = line.IndexOf("//");
rtb.SelectedText = comment;
break;
}
// Check whether the token is a keyword.
var keywordsDef = new KeyWord();
String[] keywords = keywordsDef.keywords;
for (int i = 0; i < keywords.Length; i++)
{
if (keywords[i] == token)
{
// Apply alternative color and font to highlight keyword.
HighlighType.keywordsType(rtb);
break;
}
}
rtb.SelectedText = token;
}
rtb.SelectedText = "\n";
}
from within this one:
void lb_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Escape)
{
lb.Visible = false;
lb.Items.Clear();
}
if (e.KeyCode == Keys.Enter)
{
//ParseLine(string line);
Parse();
string comment = line.Substring(index, line.Length - index);
rtb.SelectedText = comment + " " + lb.SelectedIndex.ToString();
}
}
I really need help. Big thanks in advance!
You are passing the parameter wrong. You can not pass a type when calling a method. The commented line should read
ParseLine(line);
The variable line must be declared somewhere above ParseLine. What it contains is up to you, but probably you want to set
string line = lb.Text;
So your code could read like this:
void lb_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Escape)
{
lb.Visible = false;
lb.Items.Clear();
}
if (e.KeyCode == Keys.Enter)
{
string line = lb.Text;
ParseLine(line);
//Parse();
string comment = line.Substring(index, line.Length - index);
rtb.SelectionColor = Color.Green;
rtb.SelectionFont = new Font("Courier New", 10, FontStyle.Italic);
rtb.SelectedText = comment + " " + lb.SelectedIndex.ToString();
}
}
Calling the function is not the problem, but you need some way of retrieving the current line in whichever editor you're using. Once you have retrieved it, you can call ParseLine on it, but until you have it you have nothing to work on.
I am working with this code. It is for syntax highlighting in a RichTextBox. I am specifically looking at the function ProcessLine() and OnTextChanged(), which I have modified as such:
protected override void OnTextChanged(EventArgs e)
{
// Calculate stuff here.
m_nContentLength = this.TextLength;
int nCurrentSelectionStart = SelectionStart;
int nCurrentSelectionLength = SelectionLength;
m_bPaint = false;
// Find the start of the current line.
m_nLineStart = nCurrentSelectionStart;
while ((m_nLineStart > 0) && (Text[m_nLineStart - 1] != '\n'))
m_nLineStart--;
// Find the end of the current line.
m_nLineEnd = nCurrentSelectionStart;
while ((m_nLineEnd < Text.Length) && (Text[m_nLineEnd] != '\n'))
m_nLineEnd++;
// Calculate the length of the line.
m_nLineLength = m_nLineEnd - m_nLineStart;
// Get the current line.
m_strLine = Text.Substring(m_nLineStart, m_nLineLength);
// Process this line.
ProcessLine();
m_bPaint = true;
}
// Process a line.
private void ProcessLine()
{
// Save the position and make the whole line black
int nPosition = SelectionStart;
SelectionStart = m_nLineStart;
SelectionLength = m_nLineLength;
SelectionColor = Color.Black;
/*// Process the keywords
ProcessRegex(m_strKeywords, Settings.KeywordColor);
// Process numbers
if(Settings.EnableIntegers)
ProcessRegex("\\b(?:[0-9]*\\.)?[0-9]+\\b", Settings.IntegerColor);
// Process strings
if(Settings.EnableStrings)
ProcessRegex("\"[^\"\\\\\\r\\n]*(?:\\\\.[^\"\\\\\\r\\n]*)*\"", Settings.StringColor);
// Process comments
if(Settings.EnableComments && !string.IsNullOrEmpty(Settings.Comment))
ProcessRegex(Settings.Comment + ".*$", Settings.CommentColor);*/
SelectionStart = nPosition;
SelectionLength = 0;
SelectionColor = Color.Red;
m_nCurSelection = nPosition;
}
My first question is, when I enter into the ProcessLine() in OnTextChanged(), will I always have a newline character at the end of m_strLine? Will the smallest value or m_strLine be "\n" and the largest "any#ofchars+\n"?
And just so I have this right, SelectionStart is my caret position if SelectionLength is zero, and if SelectionLength is greater than zero my caret is at SelectStart+SelectionLength?
I am trying to modify this code to color a whole lot of different syntax expressions, and I plan to go through it one character at a time, for each line. How might this fair when pasting or loading a file of 20k+ lines?
All I can suggest you right now is to use something stable, more powerful and less error prone such as Scintilla for .NET and Color Code. These controls are free and open source. Try them out:
ScintillaNETColorCode - Syntax Highlighting/Colorization for .NET
RichTextBox is extremely inefficient for working with large text. Even if you get some decent highlighting, the performance issues will start to pop up pretty soon.
This is going to scale really badly. You should do what DelegateX suggests if your goal is simply a functioning application; if you're in this to learn how, start by figuring out ways to lower the amount of work that gets done. To that end, here are some general pointers:
Only highlighting text that's inside the window will be a massive improvement that doesn't have any visual side-effects - its probably also workable to break the text into blocks (by function, method, class, etc) and only highlight visible blocks, even the occluded portions, to avoid issues where an offset starting position affects the highlight. If you don't do this, you will run into situations where the first rendered line is partway through an if or parenthesis block, and you have an unbalanced syntax tree as a result.
You still won't be able to handle 20k lines with a RichTextBox control, but a few thousand should be speedy.
Facing the same issue and failed to find "5 minutes ready to go" solution, I have developed my own RichTextBox extension to highlight XML.
I developed it really quick because of time pressure and didn't have the time to revised it - so feel free to refine it.
Just copy & paste the extension code to use with your RichTextBox or copy the whole
application code including synchronous & asynchronous usage
Extension Method
// Use for asynchronous highlight
public delegate void VoidActionOnRichTextBox(RichTextBox richTextBox);
// Extension Class
public static class RichTextBoxExtensions
{
public static void HighlightXml(this RichTextBox richTextBox)
{
new StandardHighlight().HighlightXml(richTextBox);
}
public async static void HighlightXmlAsync(this RichTextBox richTextBox)
{
var helper = new StandardHighlight();
var win = new MainWindow();
await Task.Factory.StartNew(() =>
{
richTextBox.Dispatcher.BeginInvoke(new VoidActionOnRichTextBox(helper.HighlightXml), richTextBox);
});
}
}
// You can extent it with more highlight methods
public class StandardHighlight
{
public void HighlightXml(RichTextBox richTextBox)
{
// Collect Text-Box Information
var textRange = new TextRange(richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd).Text;
XDocument xDocument;
try
{
xDocument = XDocument.Parse(textRange);
}
catch
{
return;
}
var documentLines = xDocument.ToString().Split(new[] { Environment.NewLine }, StringSplitOptions.None);
// Get the Longest Line Length
int? maxVal = null;
for (int i = 0; i < documentLines.Length; i++)
{
int thisNum = documentLines[i].Length;
if (!maxVal.HasValue || thisNum > maxVal.Value) { maxVal = thisNum; }
}
// Set Text-Box Width & Clear the Current Content
if (maxVal != null) richTextBox.Document.PageWidth = (double)maxVal * 5.5;
richTextBox.Document.Blocks.Clear();
#region *** Process Lines ***
foreach (var documentLine in documentLines)
{
// Parse XML Node Components
var indentSpace = Regex.Match(documentLine, #"\s+").Value;
var xmlTags = Regex.Matches(documentLine, #"(<[^/].+?)(?=[\s])|(<[^/].+?>)|(</.+?>)");
if (documentLine.Contains("<!--")) xmlTags = Regex.Matches(documentLine, #"(<[^/].+?>)"); // Parse comments
var nodeAttributes = Regex.Matches(documentLine, #"(?<=\s)(.+?)(?=\s)");
// Process XML Node
var nodeAttributesCollection = new List<Run>();
if (nodeAttributes.Count > 0)
{
for (int i = 0; i < nodeAttributes.Count; i++)
{
if (!(nodeAttributes[i].Value.Length < 2) && !(documentLine.Contains("<!--")))
{
var attributeName = $"{Regex.Match(nodeAttributes[i].Value, #"(.+?=)").Value}";
if (i == 0) attributeName = $" {Regex.Match(nodeAttributes[i].Value, #"(.+?=)").Value}";
var attributeValue = $"{Regex.Match(nodeAttributes[i].Value, #"(?<=(.+?=))"".+?""").Value} ";
if (i == nodeAttributes.Count - 1) attributeValue = attributeValue.Trim();
nodeAttributesCollection.Add(new Run { Foreground = new SolidColorBrush(Colors.Green), Text = $"{attributeName}" });
nodeAttributesCollection.Add(new Run { Foreground = new SolidColorBrush(Colors.Brown), Text = $"{attributeValue}" });
}
}
}
// Initialize IndentSpace
Run run = null;
if (indentSpace.Length > 1) run = new Run { Text = indentSpace };
// Initialize Open Tag
var tagText = xmlTags[0].Value.Substring(1, xmlTags[0].Value.Length - 2);
var tagTextBrush = new SolidColorBrush(Colors.Blue);
var tagBorderBruh = new SolidColorBrush(Colors.Red);
if (tagText.StartsWith("!--"))
{
tagTextBrush = new SolidColorBrush(Colors.DarkSlateGray);
tagBorderBruh = new SolidColorBrush(Colors.DarkSlateGray);
}
var openTag = new Run
{
Foreground = tagTextBrush,
Text = tagText
};
// Initialize Content Tag
var content = new Run
{
Foreground = new SolidColorBrush(Colors.Black),
};
// Initialize Paragraph
var paragraph = new Paragraph();
paragraph.Margin = new Thickness(0);
if (run != null) paragraph.Inlines.Add(run); // Add indent space if exist
// Process Open Tag
paragraph.Inlines.Add(new Run { Foreground = tagBorderBruh, Text = "<" });
paragraph.Inlines.Add(openTag);
// Process Open Tag Attributes
if (nodeAttributesCollection.Count > 0)
{
nodeAttributesCollection.ForEach(attribute => { paragraph.Inlines.Add(attribute); });
nodeAttributesCollection.Clear();
}
paragraph.Inlines.Add(new Run { Foreground = tagBorderBruh, Text = ">" });
// Process Closing Tag
if (xmlTags.Count > 1)
{
Run closingTag = new Run();
content.Text = documentLine.Replace(xmlTags[0].Value, "").Replace(xmlTags[1].Value, "").Trim();
closingTag = new Run
{
Foreground = new SolidColorBrush(Colors.Blue),
Text = xmlTags[1].Value.Substring(1, xmlTags[1].Value.Length - 2)
};
paragraph.Inlines.Add(content);
paragraph.Inlines.Add(new Run { Foreground = new SolidColorBrush(Colors.Red), Text = "<" });
paragraph.Inlines.Add(closingTag);
paragraph.Inlines.Add(new Run { Foreground = new SolidColorBrush(Colors.Red), Text = ">" });
}
richTextBox.Document.Blocks.Add(paragraph);
}
#endregion
}
}
Fixed version - with handling JSON as inner text and better elements extraction
public static void HighlightXml(this RichTextBox richTextBox)
{
// Collect Text-Box Information
var textRange = new TextRange(richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd).Text;
XmlDocument xmlDocument = new XmlDocument();
try
{
xmlDocument.LoadXml(textRange.Trim());
}
catch
{
return;
}
var documentLines = xmlDocument.OuterXml.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
// Get the Longest Line Length
int? maxVal = null;
for (int i = 0; i < documentLines.Length; i++)
{
int thisNum = documentLines[i].Length;
if (!maxVal.HasValue || thisNum > maxVal.Value) { maxVal = thisNum; }
}
// Set Text-Box Width & Clear the Current Content
if (maxVal != null) richTextBox.Document.PageWidth = (double)maxVal * 10;
richTextBox.Document.Blocks.Clear();
#region *** Process Lines ***
foreach (var documentLine in documentLines)
{
// Parse XML Node Components
var indentSpace = Regex.Match(documentLine, #"\s+").Value;
var xmlTags = Regex.Matches(documentLine, #"(?<=<)[^>\s+]*");
if (documentLine.Contains("<!--")) xmlTags = Regex.Matches(documentLine, #"(<[^/].+?>)");
var nodeAttributes = Regex.Matches(documentLine, #"(?<=\s)[^><:\s]*=*(?=[>,\s])");
// Process XML Node
var nodeAttributesCollection = new List<Run>();
if (nodeAttributes.Count > 0)
{
for (int i = 0; i < nodeAttributes.Count; i++)
{
if (!(nodeAttributes[i].Value.Length < 2) && !(documentLine.Contains("<!--")))
{
var attributeName = $"{Regex.Match(nodeAttributes[i].Value, #"(.+?=)").Value}";
if (i == 0) attributeName = $" {Regex.Match(nodeAttributes[i].Value, #"(.+?=)").Value}";
var attributeValue = $"{Regex.Match(nodeAttributes[i].Value, #"(?<=(.+?=))"".+?""").Value} ";
if (i == nodeAttributes.Count - 1) attributeValue = attributeValue.Trim();
nodeAttributesCollection.Add(new Run { Foreground = new SolidColorBrush(Colors.Green), Text = $"{attributeName}" });
nodeAttributesCollection.Add(new Run { Foreground = new SolidColorBrush(Colors.Brown), Text = $"{attributeValue}" });
}
}
}
// Initialize IndentSpace
Run run = null;
if (indentSpace.Length > 1) run = new Run { Text = indentSpace };
// Initialize Open Tag
var tagText = xmlTags[0].Value;//.Substring(1, xmlTags[0].Value.Length - 2);
var tagTextBrush = new SolidColorBrush(Colors.Blue);
var tagBorderBruh = new SolidColorBrush(Colors.Red);
if (tagText.StartsWith("!--"))
{
tagTextBrush = new SolidColorBrush(Colors.DarkSlateGray);
tagBorderBruh = new SolidColorBrush(Colors.DarkSlateGray);
}
var openTag = new Run
{
Foreground = tagTextBrush,
Text = tagText
};
// Initialize Content Tag
var content = new Run
{
Foreground = new SolidColorBrush(Colors.Black),
};
// Initialize Paragraph
var paragraph = new Paragraph();
paragraph.Margin = new Thickness(0);
if (run != null) paragraph.Inlines.Add(run); // Add indent space if exist
// Process Open Tag
paragraph.Inlines.Add(new Run { Foreground = tagBorderBruh, Text = "<" });
paragraph.Inlines.Add(openTag);
// Process Open Tag Attributes
if (nodeAttributesCollection.Count > 0)
{
nodeAttributesCollection.ForEach(attribute => { paragraph.Inlines.Add(attribute); });
nodeAttributesCollection.Clear();
}
paragraph.Inlines.Add(new Run { Foreground = tagBorderBruh, Text = ">" });
// Process Closing Tag
if (xmlTags.Count > 1)
{
Run closingTag = new Run();
content.Text = documentLine.Replace($"<{xmlTags[0].Value}>", "").Replace($"<{xmlTags[1].Value}>", "").Trim();
closingTag = new Run
{
Foreground = new SolidColorBrush(Colors.Blue),
Text = xmlTags[1].Value.Substring(1, xmlTags[1].Value.Length - 1)
};
paragraph.Inlines.Add(content);
paragraph.Inlines.Add(new Run { Foreground = new SolidColorBrush(Colors.Red), Text = "<" });
paragraph.Inlines.Add(closingTag);
paragraph.Inlines.Add(new Run { Foreground = new SolidColorBrush(Colors.Red), Text = ">" });
}
richTextBox.Document.Blocks.Add(paragraph);
}
#endregion
}
I am searching XML files to see if there are contents which match the words inserted in these textboxes txtComKeyword1, txtComKeyword2, txtComKeyword3 and/or txtComKeyword4. The function below is working, but may I know how can I highlight the keywords that user entered in the four textboxes that match that appear in my richComResults richtextbox?
For example, my user will fill in those four textboxes ie. txtComKeyword1, txtComKeyword2, txtComKeyword3 and txtComKeyword4. Then, my code will parse the XML file to see if the nodes contain these four keywords, if yes, the nodes' data will be output on my richComResults, I wanna highlight those four keywords (eg txtComKeyword1=hello, txtComKeyword2=bye, txtComKeyword3=morning, txtComKeyword4=night). These 4 words, if found and appear in richComResults, will be highlighted with color.
I have no clue after searching for a while, my case is much different from other questions. I am a newbie in programming, your help would be much appreciated. Thank you!
My Code:
private void searchComByKeywords()
{
// Process the list of files found in the directory.
string[] fileEntries = Directory.GetFiles(sourceDir);
foreach (string fileName in fileEntries)
{
XmlDocument xmlDoc = new XmlDocument(); //* create an xml document object.
string docPath = fileName;
xmlDoc.Load(docPath); //* load the XML document from the specified file.
XmlNodeList nodeList = xmlDoc.GetElementsByTagName("item");
foreach (XmlNode node in nodeList)
{
XmlElement itemElement = (XmlElement) node;
string itemDescription = itemElement.GetElementsByTagName("description")[0].InnerText;
if (txtComKeyword1.Text != (String.Empty) && itemDescription.ToLower().Contains(txtComKeyword1.Text.ToLower()) ||
txtComKeyword2.Text != (String.Empty) && itemDescription.ToLower().Contains(txtComKeyword2.Text.ToString()) ||
txtComKeyword3.Text != (String.Empty) && itemDescription.ToLower().Contains(txtComKeyword3.Text.ToString()) ||
txtComKeyword4.Text != (String.Empty) && itemDescription.ToLower().Contains(txtComKeyword4.Text.ToString()))
{
string itemTitle = itemElement.GetElementsByTagName("title")[0].InnerText;
string itemDate = itemElement.GetElementsByTagName("pubDate")[0].InnerText;
string itemAuthor = itemElement.GetElementsByTagName("author")[0].InnerText;
richComResults.AppendText("Author: " + itemAuthor + "\nDate: " + itemDate + "\nTitle: " + itemTitle + "\nDescription: " + itemDescription + "\n\n--------\n\n");
}
}
}
}
Try this:
int pointer = 0;
int index = 0;
string keyword = "txtComKeyword1";
while (true)
{
index = richComResults.Text.IndexOf(keyword, pointer);
//if keyword not found
if (index == -1)
{
break;
}
richComResults.Select(index, keyword.Length);
richComResults.SelectionFont = new System.Drawing.Font(richComResults.Font, FontStyle.Bold);
pointer = index + keyword.Length;
}
This searches for the keyword and highlights it. Then it continues the search after the found keyword. The pointer is used to keep track of the search position in your text. The index marks the position of the found keyword.
Jan's answer contains great content, but I shuddered mildly at the while(true) and break aspect! Here's my tweaked (case-insensitive) version...
int nextHigh = RTF.Text.IndexOf(txSearch, 0, StringComparison.OrdinalIgnoreCase);
while (nextHigh >= 0)
{
RTF.Select(nextHigh, txSearch.Length);
RTF.SelectionColor = Color.Red; // Or whatever
RTF.SelectionFont = new Font("Arial", 12, FontStyle.Bold); // you like
nextHigh = RTF.Text.IndexOf(txSearch, nextHigh + txSearch.Length, StringComparison.OrdinalIgnoreCase);
}
try this code :
void ParseLine(string line)
{
Regex r = new Regex("([ \\t{}():;])");
String[] tokens = r.Split(line);
foreach (string token in tokens)
{
// Set the tokens default color and font.
richTextBox1.SelectionColor = Color.Black;
richTextBox1.SelectionFont = new Font("Courier New", 10, FontStyle.Regular);
// Check whether the token is a keyword.
String[] keywords = { "Author", "Date", "Title", "Description", };
for (int i = 0; i < keywords.Length; i++)
{
if (keywords[i] == token)
{
// Apply alternative color and font to highlight keyword.
richTextBox1.SelectionColor = Color.Blue;
richTextBox1.SelectionFont = new Font("Courier New", 10, FontStyle.Bold);
break;
}
}
richTextBox1.SelectedText = token;
}
richTextBox1.SelectedText = "\n";
}
and after fill your string str with your method call my method :
string strRich =
"Author : Habib\nDate : 2012-08-10 \nTitle : mytitle \nDescription : desc\n";
Regex r = new Regex("\\n");
String[] lines = r.Split(strRich);
foreach (string l in lines)
{
ParseLine(l);
}
enjoy.