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;
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!
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; }
}
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.
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
}