c# - Is there A Faster Way to Highlighting Regex Matches (RichTextBox) - c#

I use RichTextBox for testing REGEX expression, with the code like:
rtbMain.SelectAll();
rtbMain.SelectionColor = Color.Black;
rtbMain.SelectionBackColor = Color.White;
Regex regex = new Regex(txtRegexPattern.Text, regexOptions);
Match matches = regex.Match(txtTest.Text);
while (matches.Success)
{
rtbMain.Select(matches.Index, match.Length);
rtbMain.SelectionColor = Color.Red;
rtbMain.SelectionBackColor = Color.Black;
}
But this method becomes too slow as soon as there are more than a few thousand (1000+) characters to be highlighted. I know I could delay processing, so that code gives user a chance to enter the whole Regular Expression, but still I think RichTextBox highlighting is working too slow.
I've searched the Google for different approaches and ways to speed up current solution, but I didn't have luck. I noticed that there are a few text editors which allow "syntax highlighting" (like ScintillNET, Avalon,...) but they use XML as input, so I think using them for my project (generating XML on every KeyUp event) wouldn't be the "best practice".
I have found and tested a "Fast colored Textbox" here: https://github.com/PavelTorgashov/FastColoredTextBox ...but the problem with this one is that it replaces the paste content while it uses its own new-line and tab character, and I cant use it in REGEX tester.
Is there any faster way to highlight all matches, maybe using a different user control?
EDIT:
APPROACH 1: Would generating the underlying RTF document be faster? I tried but had some problems with special characters, so I could test highlighting of the whole document, but it seemed to work quite fast with normal characters in a single line. I paused working on this since I read constructing RTF's can be quite hard, and I think I couldn't use none of the existing RTF libraries.
APPROACH 2: I am able to get only the displayed portion of RichTextBox, so I was thinking to only highlight that part. I guess this would significantly reduce processing (depends on RTB size), but I would need to trigger highlighting every time user scrolls; I'm not sure this would work well and create a decent user experience, so haven't tried it out yet.
Would anyone recommend any of the approaches above or maybe any others?

First:
The RichTextBox has an inherent problem: It is very slow in .NET. I found a solution how to make it 120 times faster. May be you try it out: C# RichEditBox has extremely slow performance (4 minutes loading) SOLVED
Second:
Building the RTF code from the scratch is far the fastest solution. Have a look a my article on codeproject. There is a RTF builder class that is reusable: http://www.codeproject.com/Articles/23513/SQL-Editor-for-Database-Developers

Please check Expresso at http://www.codeproject.com/Articles/3669/Expresso-A-Tool-for-Building-and-Testing-Regular-E
I have been using this program for editing and evaluating regex for years.

I have a doubt that you have setup your While loop in an incorrect manner.
Try something like this: (Untested, but will give you an idea how to troubleshoot this problem)
rtbMain.SelectAll();
rtbMain.SelectionColor = Color.Black;
rtbMain.SelectionBackColor = Color.White;
Regex regex = new Regex(txtRegexPattern.Text, regexOptions);
MatchCollection matches = regex.Matches(txtTest.Text);
if(matches.Count > 0)
{
foreach(Match m in matches)
{
rtbMain.Select(m.Index, m.Length);
rtbMain.SelectionColor = Color.Red;
rtbMain.SelectionBackColor = Color.Black;
}
}
else
{
Debug.Print("No matches found"); // See "Output" Window
}
EDIT
I did some workaround related to highlight RTF text and first thing I found is the mostly time taken by the process was these lines:
rtbMain.SelectionColor = Color.Red;
rtbMain.SelectionBackColor = Color.Black;
I tried selecting the text using SelectionStart and SelectionEnd properties instead .Select(), but NO change has been observed.
Regarding your first point which is related to constructing equivalent RTF, I tried that too but it is difficult to construct an equivalent RTF since there are lot of stuff there which needs to be handle. If it can be done the process time will be around < 1.5 seconds for more than 31k matches (a result of basic test on a specific sample).
So, I would suggest you to do it via THREADING and split task in two threads:
Here is an example source code:
(For worst case i found around 31341 matches and process took 4 seconds to highlight)
// declare variables either globally or in the same method
MatchCollection mcoll;
Stopwatch s;
int callbackCount = 0;
List<Match> m1 = null;
List<Match> m2 = null;
private void btnHighlight_Click(object sender, EventArgs e)
{
//reset any exisiting formatting
rtbMain.SelectAll();
rtbMain.SelectionBackColor = Color.White;
rtbMain.SelectionColor = Color.Black;
rtbMain.DeselectAll();
s = new Stopwatch();
s.Start();
Regex re = new Regex(#"(.)", RegexOptions.Compiled); // Notice COMPILED option
mcoll = re.Matches(rtbMain.Text);
// Break MatchCollection object into List<Matches> which is exactly half in size
m1 = new List<Match>(mcoll.Count / 2);
m2 = new List<Match>(mcoll.Count / 2);
for (int k = 0; k < mcoll.Count; k++)
{
if (k < mcoll.Count / 2)
m1.Add(mcoll[k]);
else
m2.Add(mcoll[k]);
}
Thread backgroundThread1 = new Thread(new ThreadStart(() => {
match1(null, null);
}));
backgroundThread1.Start();
Thread backgroundThread2 = new Thread(new ThreadStart(() =>
{
match2(null, null);
}));
backgroundThread2.Start();
}
public void match1(object obj, EventArgs e)
{
for (int i=0; i < m1.Count; i += 1)
{
if (rtbMain.InvokeRequired)
{
EventHandler d = new EventHandler(match1);
rtbMain.Invoke(d);
}
else
{
rtbMain.Select(m1[i].Index, m1[i].Length);
rtbMain.SelectionBackColor = Color.Black;
rtbMain.SelectionColor = Color.Red;
}
}
stopTimer();
}
public void match2(object obj, EventArgs e)
{
for (int j=0; j < m2.Count; j += 1)
{
if (rtbMain.InvokeRequired)
{
EventHandler d = new EventHandler(match2);
rtbMain.Invoke(d);
}
else
{
rtbMain.Select(m2[j].Index, m2[j].Length);
rtbMain.SelectionBackColor = Color.Black;
rtbMain.SelectionColor = Color.Red;
}
}
stopTimer();
}
void stopTimer()
{
callbackCount++;
if (callbackCount == 2) // 2 because I am using two threads.
{
s.Stop();
// Check Output Window
Debug.Print("Evaluated in : " + s.Elapsed.Seconds.ToString());
}
}
Since as you posted it takes around 30 sec to manipulate, hope 4 sec is bearable and user can be engaged by some loading screen as the other online converters do like Rubular and DerekSlager's .Net regex tester does.
Don't forget to have a look at Why Regex.Compiled preferred.

Related

How can I search for highlighted text in a RichTextBox Control

In a C# forms app, how can I search for highlighted text in a RichTextBox, like that available in Microsoft Word using the ‘Find and Replace’ format option.
There does not appear to be a field in the RichTextBoxFinds Enum that has this option. If I search for what I want to do on Google, I can find plenty of examples of how to search for text and highlight it, but nothing to find text that is highlighted. I also cannot find anything relating to this subject on the Stack Overflow site.
I am seeking suggestions on methods I could potentially use to do this, or ways to approach the problem.
Update
I have now created the below code in response to feedback to search for highlighted text (SelectionBackColour not white) in a RichTextBox which works but appears dreadfully slow compared to using Find to search for a string. I can speed things up if I set the HideSelection property to true before I start the loop, but still to slow, and when it enters the loop, the RichTextBox just turns dark grey until the loop is exited and I set HideSelection false again. What could be limiting the speed?
private bool FindNextH(int hightLightColour, int selectionStart, int selectionEnd)
{
int matchColour = hightLightColour;
bool matchHighlight = true;
if (matchColour == -1) // White
{
matchHighlight = false;
}
int state = 0;
int highlightStart = 0;
int highlightLength = 0;
int highlight = -1;
bool found = false;
richTextBox1.SelectionStart = selectionStart;
richTextBox1.SelectionLength = 1;
for (int i = selectionStart; i <= selectionEnd; i++)
{
richTextBox1.SelectionStart = i;
int colour = richTextBox1.SelectionBackColor.ToArgb();
switch (state)
{
case 0:
if (colour != -1)
{
if (matchHighlight)
{
if (colour == matchColour)
{
state = 1;
highlightStart = i;
highlight = colour;
}
}
else
{
state = 1;
highlightStart = i;
highlight = colour;
}
}
break;
case 1:
if ((colour != highlight) || (i == selectionEnd))
{
highlightLength = i - highlightStart;
richTextBox1.SelectionStart = highlightStart;
richTextBox1.SelectionLength = highlightLength;
textBox2.Text = richTextBox1.SelectedText;
found = true;
}
break;
default:
break;
}
if (found)
{
break;
}
}
return found;
}
Further testing has been done to quantify timings as follows:
Rich Text File containing 30,093 words, 137,984 characters (no spaces), 167,844 characters with spaces. ‘TestWord’ is contained in the file just over halfway through and is highlighted green.
Using Microsoft Word’s ‘Find and Replace’ (Ctrl H), with ‘Format’ set to ‘Highlight’ and the ‘Find what’ blank, it takes approximately one second for the document to scroll to the found highlighted word, starting with the cursor at the first character. If I use ‘Find and Replace’ to search for the text, no formatting, then it takes about the same time.
If I run a test using ‘richTextBox1.Find’ to search for ‘TestWord’, it takes between 5 and 15 ms to execute, and the text in the richTextBox1 jumps to it instantaneously, so very quick.
If I run a test using my function ‘FindNextH()’, it takes approximately 3.8 seconds to find the highlighted text, if I set ‘richTextBox1.HideSelection’ to ‘true’ before calling the function, and returning it to ‘false’ afterwards. During the 3.8 seconds, the richTextBox1 window goes dark gey and the text disappears, returning only when the highlighted word has been found.
If ‘richTextBox1.HideSelection’ is set to ‘false’ before calling ‘FindNextH(), it takes approximately 50 seconds to find the highlighted word, during which time you can the cursor scrolling through the text.
What can I do to speed this up? Clearly Microsoft have cracked it in MS Word. It is a pity that this feature is not a part of the ‘Find’ method.
Code that calls the two search functions below for reference:
richTextBox1.HideSelection = false;
stopWatch.Start();
int IndexOfSearchResultFound = richTextBox1.Find(_SearchKeyword, _KeywordSelectionStart, _KeywordSelectionEnd,
RichTextBoxFinds.None | RichTextBoxFinds.WholeWord | RichTextBoxFinds.MatchCase);
stopWatch.Stop();
richTextBox1.HideSelection = false;
TimeSpan timeDelay = stopWatch.Elapsed;
richTextBox1.HideSelection = true;
stopWatch.Start();
found = FindNextH(colour, selectionStart, selectionEnd);
stopWatch.Stop();
richTextBox1.HideSelection = false;
TimeSpan timeDelay = stopWatch.Elapsed;

How to display a specific line at the top of a RichTextBox

I'm pulling the content from a text file into a RichTextBox. I've got the RichTextBox set up to where it only shows 6 lines at a time. I've got a search method that finds the text I need within the RichTextBox, but what I am needing it to do is display 6 specific lines. Each "item" in my text file consists of 6 lines. No matter which of the six lines the search method finds the text on, I need the RichTextBox to only display the 6 lines of each "item" with the currently selected "found" text remaining highlighted.
I've gotten it working reasonably well thanks to a few code examples I've pull from this site. But every now and then, it doesn't work entirely well, and am looking for some advice from a fresh set of eyes looking at my code and perhaps even be told an easier/more efficient way to go about it. But here is my code so far. Thanks in advance!
try
{
string s = txtFindPlaylistEntry.Text;
rtxEditPlaylistEntry.Focus();
findPosEntry = rtxEditPlaylistEntry.Find(s, findPosEntry, RichTextBoxFinds.None);
// Jump to the line we need.
int count = rtxEditPlaylistEntry.GetLineFromCharIndex(findPosEntry);
count = (count - (count % 6)) + 1; // Must be divisible by 6 then plus 1
rtxEditPlaylistEntry.SelectionStart = rtxEditPlaylistEntry.Find(rtxEditPlaylistEntry.Lines[count]);
rtxEditPlaylistEntry.ScrollToCaret();
rtxEditPlaylistEntry.Select(findPosEntry, s.Length);
findPosEntry += txtFindPlaylistEntry.Text.Length;
}
catch
{
MessageBox.Show("No occurences found");
findPosEntry = 0;
}
As of right now, I'm attempting to use a line count with modulus plus 1 to get the line I need. Like I said, it works, just not 100% of the time and I can't figure out why.
EDIT to try to accommodate Minimal, Complete, Verifiable.
I've already posted my "find" function. Here is other related code that might be useful. First, here is my code for creating the various controls I am using.
rtxEditPlaylistEntry = new RichTextBox();
rtxEditPlaylistEntry.Location = new System.Drawing.Point(15, 90);
rtxEditPlaylistEntry.Size = new System.Drawing.Size(375, 85);
rtxEditPlaylistEntry.Multiline = true;
rtxEditPlaylistEntry.ScrollBars = RichTextBoxScrollBars.None;
Here is my button function to pull text from a file and place it into the RichTextBox.
private void btnBrowseForPlaylistToEditEntry_Click(object sender, EventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = "LPL Files|*.lpl";
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
savedFileNameEntry = openFileDialog.SafeFileName;
txtPathToPlaylistToEditEntry.Text = openFileDialog.FileName;
}
// After finding the file, load it into the richtextbox control
using (StreamReader sr = File.OpenText(openFileDialog.FileName))
{
// Initially show the first 6 lines (IE first entry). This should be accomplished by
// the richtextbox control settings
rtxEditPlaylistEntry.Text = sr.ReadToEnd();
}
previousNextCount = 0;
}
I hope this is sufficient. If not please let me know!

How to highlight HTML syntax in C# windows form RichTextBox?

I am writing a c# Html editor application, in which you type the code in a RichTextBox control. I want the RichTextBox to behave like notepad++ and other code editors in which the Html syntax gets highlighted in colors, like this for example:
How can I establish this in C# windows form RichTextBox? I have searched almost everywhere and didn't find anything that helped me. This is what I tried so far but I doesn't give the result I want:
private void SyntaxHighlight()
{
string[] tags = { "html","head","body","a","b","img","strong","p","h1","h2","h3","h4","h5","h6","embed","iframe","span","form",
"button","input","textarea","br","div","style","script","table","tr","td","th","i","u","link","meta","title"};
foreach (string s in tags)
{
richTextBox1.Find("<" + s);
richTextBox1.SelectionColor = Color.Blue;
richTextBox1.Find(">");
richTextBox1.SelectionColor = Color.Blue;
}
string[] attributes = { "href","src","height","width","rowspan","colspan","target","style","onclick","id","name","class"};
foreach (string s in attributes)
{
richTextBox1.Find(s + "=");
richTextBox1.SelectionColor = Color.Red;
}
}
Can someone help me? What should I write inside the SyntaxHighlight() method? can someone give me the appropriate code?
Thank you
Within your code you are only finding the 1st occurrence of the HTML tag and highlighting it. But instead, you should loop through the entire rich text content to finding proceeding occurrences of the same text. I just did a quick mock based on your exact code, please check it out.
private void highlightHTMLText()
{
string[] tags = { "html","head","body","a","b","img","strong","p","h1","h2","h3","h4","h5","h6","embed","iframe","span","form",
"button","input","textarea","br","div","style","script","table","tr","td","th","i","u","link","meta","title"};
foreach (string s in tags)
{
findAndHighlight("<" + s, Color.Blue);
findAndHighlight("</" + s, Color.Blue);
findAndHighlight(">", Color.Blue);
}
string[] attributes = { "href", "src", "height", "width", "rowspan", "colspan", "target", "style", "onclick", "id", "name", "class" };
foreach (string s in attributes)
{
findAndHighlight(s + "=", Color.Red);
}
}
private void findAndHighlight(string sSearchStr, Color oColor)
{
int index = richTextBox1.Text.IndexOf(sSearchStr);
while (index != -1)
{
richTextBox1.Select(index, sSearchStr.Length);
richTextBox1.SelectionColor = oColor;
index = richTextBox1.Text.IndexOf(sSearchStr, index + sSearchStr.Length);
}
}
Further as per this answer you should be able to make use of the same utility library Scintilla used by Notepad++ itself. As pointed out you do not need to re-invent the wheel, but as a developer I obviously prefer my own util (it is just me ;) ). Hope this helps.
Find doesn't move a cursor, it returns the location of the first match. Try this instead:
How to select text from the RichTextBox and then color it?
A little late to the party but, after wanting to create my own offline version of CodePen, I implemented my own version of html syntax highlighting following CodePen's theme.
This does syntax highlighting and markup formatting, though the formatting depends on whether or not your html is well-formed.
Just add this as a class for your RichTextBox, instantiate it accordingly and call it within whichever event works for you (I'm using it with the RTB's double_click event but that does eliminate double-click text selection). What I'm planning to do is add a timer, some boolean variables and work this within the key_up and key_down events to set the highlight update to be a bit more automatic and less intrusive on shortcuts. (which is hereby included below the class)
public void HighlightHTM(RichTextBox Htm_Input)
{
Htm_Input.Visible = false;
#region store the original caret position + forecolor
int originalIndex = Htm_Input.SelectionStart;
int originalLength = Htm_Input.SelectionLength;
Color originalColor = Color.FromArgb(200, 200, 200); // Grey
#endregion
#region try to format the markup
try { Htm_Input.Text = XElement.Parse(Htm_Input.Text).ToString(); } catch { }
#endregion
#region match everything but puncutation and equals
Regex e = new Regex(#"(.*?|=)[^\w\s]");
MatchCollection eMatches = e.Matches(Htm_Input.Text);
foreach (Match m in eMatches)
{
Htm_Input.SelectionStart = m.Groups[1].Index;
Htm_Input.SelectionLength = m.Groups[1].Length;
Htm_Input.SelectionColor = Color.FromArgb(221, 202, 126); // Yellow
}
#endregion
#region match tags
Regex t = new Regex(#"(<\w+|</\w+|/>|>)[^=]");
MatchCollection tMatches = t.Matches(Htm_Input.Text, 0);
foreach (Match m in tMatches)
{
Htm_Input.SelectionStart = m.Groups[1].Index;
Htm_Input.SelectionLength = m.Groups[1].Length;
Htm_Input.SelectionColor = Color.FromArgb(167, 146, 90); // Brown
}
#endregion
#region match quotes
Regex q = new Regex("\".*?\"");
MatchCollection qMatches = q.Matches(Htm_Input.Text);
foreach (Match m in qMatches)
{
Htm_Input.SelectionStart = m.Index;
Htm_Input.SelectionLength = m.Length;
Htm_Input.SelectionColor = Color.FromArgb(150, 179, 138); // Green
}
#endregion
#region match inner html
Regex h = new Regex(">(.+?)<");
MatchCollection hMatches = h.Matches(Htm_Input.Text);
foreach (Match m in hMatches)
{
Htm_Input.SelectionStart = m.Groups[1].Index;
Htm_Input.SelectionLength = m.Groups[1].Length;
Htm_Input.SelectionColor = Color.FromArgb(200, 200, 200); // Grey
}
#endregion
#region restoring the original colors, for further writing
Htm_Input.SelectionStart = originalIndex;
Htm_Input.SelectionLength = originalLength;
Htm_Input.SelectionColor = originalColor; // Light Grey
#endregion
Htm_Input.Focus();
Htm_Input.Visible = true;
}
Happy coding!
Edit: I should also mention that !doctype breaks formatting as it's not exactly xml-friendly in the context of "well-formed". For my purposes, all tags including body and relevant closings, css and js links are added programmatically at page save so only markup within the body tags are worked with inside the html RTB. This eliminates that problem.
You'll notice that this relies exclusively on Regex rather than on hard-coded tags and properties. I did this because tags and properties have a tendency to pop on and off the w3 scene quite often. That would force a dev to continually have to go back and edit those strings to remove deprecated tags / properties or to add new. Not optimal.
I also thought it prudent to go ahead and include the instantiation / usage examples to make this a bit more plug&play.
Above public Main(), instantiate like so:
#region Class Instantiation
SyntaxHighlight syntax = new SyntaxHighlight();
#endregion
... and, within your chosen event handler, call it like so:
private void htm_input_DoubleClick(object sender, EventArgs e)
{
syntax.HighlightHTM(Htm_Input);
}
Naturally, adding a SaveFileDialog and an OpenFileDialog pretty much provides this the functionality of your very own, albeit very basic, html editor. Toss in a WebBrowser control and apply the RTB's text as the WebBrowser's source and you've upgraded to live-view.
In the very least, this should serve as a viable reference for syntax highlighting in general. It really just boils down to identifying patterns and manipulating their colors so, for example, this will work effectively with css, javascript and even C# with some light adjusting of the pattern identification parameters.
The following is how I setup the automatic refresh with key_up / key_down and a timer set to 1000 ms:
#region Globals
int r = 0;
bool refresh = false;
#endregion
private void Htm_Input_KeyUp(object sender, KeyEventArgs e)
{
refresh = true; // enter refresh cycle
}
private void Htm_Input_KeyDown(object sender, KeyEventArgs e)
{
refresh = false; // abort refresh cycle
}
private void Timer_Refresh_Tick(object sender, EventArgs e)
{
// check if refresh cycle is entered, refresh at 3 seconds or reset the counter if aborted
if (refresh) { if (r == 3) { syntax.HighlightHTM(Htm_Input); refresh = false; r = 0; } r++; } else { r = 0; }
}

Links inside rich textbox?

I know that richtextboxes can detect links (like http://www.yahoo.com) but is there a way for me to add links to it that looks like text but its a link? Like where you can choose the label of the link? For example instead of it appearing as http://www.yahoo.com it appears as Click here to go to yahoo
edit: forgot, im using windows forms
edit: is there something thats better to use (as in easier to format)?
Of course it is possible by invoking some WIN32 functionality into your control, but if you are looking for some standard ways, check this post out:
Create hyperlink in TextBox control
There are some discussions about different ways of integration.
greetings
Update 1:
The best thing is to follow this method:
http://msdn.microsoft.com/en-us/library/f591a55w.aspx
because the RichText box controls provides some functionality to "DetectUrls". Then you can handle the clicked links very easy:
this.richTextBox1.LinkClicked += new System.Windows.Forms.LinkClickedEventHandler(this.richTextBox1_LinkClicked);
and you can simple create your own RichTextBox contorl by extending the base class - there you can override the methods you need, for example the DetectUrls.
Here you can find an example of adding a link in rich Textbox by linkLabel:
LinkLabel link = new LinkLabel();
link.Text = "something";
link.LinkClicked += new LinkLabelLinkClickedEventHandler(this.link_LinkClicked);
LinkLabel.Link data = new LinkLabel.Link();
data.LinkData = #"C:\";
link.Links.Add(data);
link.AutoSize = true;
link.Location =
this.richTextBox1.GetPositionFromCharIndex(this.richTextBox1.TextLength);
this.richTextBox1.Controls.Add(link);
this.richTextBox1.AppendText(link.Text + " ");
this.richTextBox1.SelectionStart = this.richTextBox1.TextLength;
And here is the handler:
private void link_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
System.Diagnostics.Process.Start(e.Link.LinkData.ToString());
}
I found a way which may not be the most elegant, but it's just a few lines of code and does the job. Namely, the idea is to simulate hyperlink appearance by means of font changes, and simulate hyperlink behavior by detecting what the mouse pointer is on.
The code:
public partial class Form1 : Form
{
private Cursor defaultRichTextBoxCursor = Cursors.Default;
private const string HOT_TEXT = "click here";
private bool mouseOnHotText = false;
// ... Lines skipped (constructor, etc.)
private void Form1_Load(object sender, EventArgs e)
{
// save the right cursor for later
this.defaultRichTextBoxCursor = richTextBox1.Cursor;
// Output some sample text, some of which contains
// the trigger string (HOT_TEXT)
richTextBox1.SelectionFont = new Font("Calibri", 11, FontStyle.Underline);
richTextBox1.SelectionColor = Color.Blue;
// output "click here" with blue underlined font
richTextBox1.SelectedText = HOT_TEXT + "\n";
richTextBox1.SelectionFont = new Font("Calibri", 11, FontStyle.Regular);
richTextBox1.SelectionColor = Color.Black;
richTextBox1.SelectedText = "Some regular text";
}
private void richTextBox1_MouseMove(object sender, MouseEventArgs e)
{
int mousePointerCharIndex = richTextBox1.GetCharIndexFromPosition(e.Location);
int mousePointerLine = richTextBox1.GetLineFromCharIndex(mousePointerCharIndex);
int firstCharIndexInMousePointerLine = richTextBox1.GetFirstCharIndexFromLine(mousePointerLine);
int firstCharIndexInNextLine = richTextBox1.GetFirstCharIndexFromLine(mousePointerLine + 1);
if (firstCharIndexInNextLine < 0)
{
firstCharIndexInNextLine = richTextBox1.Text.Length;
}
// See where the hyperlink starts, as long as it's on the same line
// over which the mouse is
int hotTextStartIndex = richTextBox1.Find(
HOT_TEXT, firstCharIndexInMousePointerLine, firstCharIndexInNextLine, RichTextBoxFinds.NoHighlight);
if (hotTextStartIndex >= 0 &&
mousePointerCharIndex >= hotTextStartIndex && mousePointerCharIndex < hotTextStartIndex + HOT_TEXT.Length)
{
// Simulate hyperlink behavior
richTextBox1.Cursor = Cursors.Hand;
mouseOnHotText = true;
}
else
{
richTextBox1.Cursor = defaultRichTextBoxCursor;
mouseOnHotText = false;
}
toolStripStatusLabel1.Text = mousePointerCharIndex.ToString();
}
private void richTextBox1_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left && mouseOnHotText)
{
// Insert your own URL here, to navigate to when "hot text" is clicked
Process.Start("http://www.google.com");
}
}
}
To improve on the code, one could create an elegant way to map multiple "hot text" strings to their own linked URLs (a Dictionary<K, V> maybe). An additional improvement would be to subclass RichTextBox to encapsulate the functionality that's in the code above.
Many moons later there is a solution for .NET (Core), as of at least .NET 6.0 (possibly earlier) - for .NET Framework (whose latest and last version is 4.8) you'll still need one of the other solutions here:
The .Rtf property now recognizes RTF-format hyperlinks; e.g., the following renders as:
This is a true RTF hyperlink: Example Link
this.richTextBox1.Rtf = #"{\rtf1 This is a true RTF hyperlink:\line {\field{\*\fldinst HYPERLINK ""https://example.org""}{\fldrslt Example Link}}} }";
The standard RichTextBox control (assuming you are using Windows Forms) exposes a rather limited set of features, so unfortunately you will need to do some Win32 interop to achieve that (along the lines of SendMessage(), CFM_LINK, EM_SETCHARFORMAT etc.).
You can find more information on how to do that in this answer here on SO.

Find and Find Next

I am using two forms, where one is a rich text editor with menus and a rich text box and the second form is for search and replace and contains four button and two text boxes. I have managed to do the find button but I am having problems with Find Next. I am using C# Windows Forms.
Here is the code I am using for Find:
private void button1_Click(object sender, EventArgs e)
{
RichTextBox frm1TB = ((Form1)this.Owner).txtDisplay;
int foundAt = frm1TB.Text.IndexOf(searchText.Text);
if (foundAt == -1)
{
MessageBox.Show("Not Found");
}
else
{
frm1TB.SelectionStart = foundAt;
frm1TB.SelectionLength = searchText.TextLength;
frm1TB.Focus();
}
}
Find next would be something like the following:
if (frm1TB.Text.Length >= frm1TB.Text.SelectionStart + frm1TB.Text.SelectionLength)
{
int foundAt = frm1TB.Text.IndexOf(
searchText.Text,
frm1TB.Text.SelectionStart + frm1TB.Text.SelectionLength);
}
You need to remember index at which you found your previous entry (or even better, at which you should start find next search) and then simply use IndexOf(string, int) overload, which allows you to start search at specified position. First, simply add next search start index field to your class:
private int nextSearchStartIndex;
Now, your Find method needs to keep update this index appropriately:
if (foundAt == -1)
{
this.nextSearchStartIndex = 0;
MessageBox.Show("Not Found");
}
else
{
this.nextSearchStartIndex = foundAt + searchText.TextLength;
// ...
}
And FindNext becomes trivial:
// ...
var foundAt = frm1TB.Text.IndexOf(searchText.Text,
this.nextSearchStartIndex);
// Here you can use exactly same update index logic as in Find
You can't use the IndexOf() method for it, you have to switch to Regular Expressions.
Here is an example how you can easily get all the search entries in RichtBox.Text:
using System.Text.RegularExpressions;
Regex re = new System.Text.RegularExpressions.Regex(searchText.Text.ToString(),RegexOptions.None);
MatchCollection mc = re.Matches(frm1TB.Text.ToString());
foreach (var ma in mc)
{
//do what you want
}

Categories