I'm currently struggling with a button I want to add dynamically inside a RichTextBox.
The main issue is that I can't seem to get the location down properly (Location X & Y) also that the Text adjusts with Form Changes but the location of the Buttons won't.
The concept behind this is that I want to be able to write RTF Documents and include Button Clicks for certain Functions (Copy for now) without having to adjust the program every time.
My idea was to have a very specific Pattern -> [Copy|WHAT-TO-COPY] and replace every occurrence of this with a [C] Button in the application.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
LoadRTF();
AddButtons();
}
private void LoadRTF()
{
richTextBox1.LoadFile("RTF Path");
richTextBox1.ReadOnly = true;
}
private void AddButtons()
{
String Text = richTextBox1.Rtf;
richTextBox1.ReadOnly = false;
foreach (Match CopyMatch in Regex.Matches(Text, #"\[Copy\|.*\]", RegexOptions.IgnoreCase))
{
string CopyString = CopyMatch.Value;
var CopyPosition = richTextBox1.GetPositionFromCharIndex(richTextBox1.Find(CopyString));
Button Copy = new Button
{
Text = "Copy",
Size = new Size(20, 23),
Location = new Point(CopyPosition.X, CopyPosition.Y)
};
Copy.Click += (_, args) =>
{
Clipboard.SetText(CopyString.Substring(6, CopyString.Length - 7));
};
richTextBox1.SelectionStart = richTextBox1.Find(CopyString);
richTextBox1.SelectionLength = CopyString.Length;
richTextBox1.SelectedText = "";
richTextBox1.Controls.Add(Copy);
}
richTextBox1.ReadOnly = true;
}
}
The first issue is that this method apparently doesn't work to get the proper X,Y values for the Button Creation. It seems the values don't align with the found PositionFromChar(Index).
Am I headed in the entirely wrong direction is this a proper start? What am I missing?
Also, I'm still completely new to programming so sorry if this is a total mess.
I am using TextRanges in a WPF RichTextBox and I want to change the font color of a TextRange, but that if then it is written text next to it, it has the original color.
For exmaple, I have a RichTextBox called richTextBox with font color black, and I use this code:
TextPointer start = richTextBox.Document.ContentStart;
TextPointer end = richTextBox.Document.ContentEnd;
new TextRange(start, end).ApplyPropertyValue(TextElement.ForegroundProperty, Brushes.Blue);
I want that if, after executing this code, someone adds text to the richTextBox, it is written in black, not in blue.
Is there any way to do this? Thank you!
I don't think you can set it directly so that the next caracter would be in a different color. (Someone please correct me if you know how).
But what you could do is subscribe to the TextChangedEvent of your RichTextBox and apply the new foreground color when the user add some text at the end.
The tricky part however is to detect how much was changed so you can apply the new foreground to this part only and to know if the changes made where done at the end of the text.
Where you execute the code for setting the foreground to blue:
TextPointer start = this.RTextBox.Document.ContentStart;
TextPointer end = this.RTextBox.Document.ContentEnd;
TextRange textRange = new TextRange(start, end);
textRange.ApplyPropertyValue(TextElement.ForegroundProperty, Brushes.Blue);
//Storing the text to allow for comparison later
this.textSaved = textRange.Text;
this.wasTextColorInitialized = true;
Variables and new method added:
private bool wasTextColorInitialized = false;
private string textSaved;
/// <summary>
/// Remove some of the invisible caracter in a string to allowe for comparison
/// </summary>
/// <param name="text"></param>
/// <returns></returns>
private string GetComparableString(string text)
{
return string.Join(string.Empty, Regex.Split(text, #"(?:\r\n|\n|\r)"));
}
Source for the very useful regex expression
https://stackoverflow.com/a/1982317/13448212
In the textChanged event, you have to check if the change is due to the user adding text, then determine if the changes were made at the end of the text:
private void RTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
TextPointer endOfDoc = this.RTextBox.Document.ContentEnd;
TextPointer startOfDoc = this.RTextBox.Document.ContentStart;
TextRange fullRange = new TextRange(startOfDoc, endOfDoc);
string newText = fullRange.Text;
//e.Changes.Count = 0 means that only a Property was changed (eg Foreground)
if (this.wasTextColorInitialized && e.Changes.Count != 0)
{
int lengthOfTextAdded = newText.Length - this.textSaved.Length;
//The text can end with "\r\n" and the text added will be before this hence the "GetComparableString" method
if (lengthOfTextAdded > 0 && newText.StartsWith(this.GetComparableString(this.textSaved)))
{
//in e.Changes, the changes are ordered, the latest being the furthest in the text
TextRange rangeOfTextAdded = new TextRange(endOfDoc.GetPositionAtOffset(-e.Changes.Last().AddedLength), endOfDoc);
rangeOfTextAdded.ApplyPropertyValue(TextElement.ForegroundProperty, Brushes.Black);
}
else
{
//Nothing to do
}
}
else
{
//Nothing to do
}
this.textSaved = newText;
}
Documentation for e.Changes: https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.textchangedeventargs.changes?view=netcore-3.1#System_Windows_Controls_TextChangedEventArgs_Changes
I think Ostas's approach will work. I just would like to suggest a simple workaround.
It is to add a space at the end of RichTextBox when changing the color. After that, if someone adds a new text immediately after the existing text, the color will be blue. If someone adds a new text at the end (after the space), the color will be default.
TextPointer start = textBox.Document.ContentStart;
TextPointer end = textBox.Document.ContentEnd;
var range = new TextRange(start, end);
range.ApplyPropertyValue(TextElement.ForegroundProperty, Brushes.Blue);
if (textBox.Document.Blocks.LastBlock is Paragraph paragraph)
paragraph.Inlines.Add(" ");
I have a rich text box that I'm allowing user to highlight text. Text being loaded is coming from a simple plain text file. But I need to store the absolute start and end character position (relative to beginning of document) of the highlighted text so that when they save it, it can reload with highlights.
So far I can do this to apply the highlighting
private void textBox_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
HighlightWordInTextBox(this.textBox, this.textBox.Selection.ToString(), new SolidColorBrush(Colors.Yellow));
}
public void HighlightWordInTextBox(RichTextBox textbox, string word, SolidColorBrush color)
{
TextRange tr = new TextRange(this.textBox.Selection.Start, this.textBox.Selection.End);
tr.ApplyPropertyValue(TextElement.BackgroundProperty, color);
}
But I do not see anywhere inside the Start or End objects of Selection anything that provides the character position? Almost all the methods return another TextPointer - but how do you get the character position from a TextPointer?
Assuming all the text is loaded into a single
this.textBox.Document.Blocks.Add(new Paragraph(new Run(fullText)));
EDIT:
In the immediate window when debugging I can access something called CharOffset and Offset but cannot do so in the source code, it gives a compile error. Also those properties although present when inspecting the object at runtime, they are not in the documentation.
And yet...
You can find the index of the Selection's start and end with this...
var docStart = textBox.Document.ContentStart;
var selectionStart = textBox.Selection.Start;
var selectionEnd = textBox.Selection.End;
//these will give you the positions needed to apply highlighting
var indexStart = docStart.GetOffsetToPosition(selectionStart);
var indexEnd = docStart.GetOffsetToPosition(selectionEnd);
//these values will give you the absolute character positions relative to the very beginning of the text.
TextRange start = new TextRange(docStart, selectionStart);
TextRange end = new TextRange(docStart, selectionEnd);
int indexStart_abs = start.Text.Length;
int indexEnd_abs = end.Text.Length;
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 want to implement something that programmatically changes the background of the text when provided with a documentline.(Something that looks very similar to a block selection of a text. I'm going to be using this for debug breakpoints of an IDE I'm designing). I don't want to have to use selection as it causes the textbox to scroll.
I think I need to make use of DocumentColorizingTransformer but I'm not 100% sure how to go about this.
public class ColorizeAvalonEdit : ICSharpCode.AvalonEdit.Rendering.DocumentColorizingTransformer
{
protected override void ColorizeLine(ICSharpCode.AvalonEdit.Document.DocumentLine line)
{
int lineStartOffset = line.Offset;
string text = CurrentContext.Document.GetText(line);
int start = 0;
int index;
if (line.LineNumber == LogicSimViewCodeWPFCtrl.currentLine)
{
while ((index = text.IndexOf(text, start)) >= 0)
{
base.ChangeLinePart(
lineStartOffset + index, // startOffset
lineStartOffset + index + text.Length, // endOffset
(VisualLineElement element) =>
{
element.TextRunProperties.SetBackgroundBrush(Brushes.Red);
});
start = index + 1; // search for next occurrence
}
}
}
}
currentLine is the portion that will be highlighted.
The above code does work properly.. only problem is if the currentLine ever changes while I am viewing that line, it doesn't highlight the updated line until I scroll to another portion of the document (hiding the updated line), and come back to the updated line.
Also, how do I make the line numbers start from zero?
Since it was their creation, I peeked at SharpDevelop's source and how they did it.
They defined a bookmark type (BreakpointBookmark) and added bookmark to the line.
bookmark itself sets the color of the line in CreateMarker method. It is strange that it is not possible to configure colors of the break-point in SharpDevelop.
Hope it helps.
protected override ITextMarker CreateMarker(ITextMarkerService markerService)
{
IDocumentLine line = this.Document.GetLine(this.LineNumber);
ITextMarker marker = markerService.Create(line.Offset, line.Length);
marker.BackgroundColor = Color.FromRgb(180, 38, 38);
marker.ForegroundColor = Colors.White;
return marker;
}
I found the answer
TxtEditCodeViewer.TextArea.TextView.Redraw();
Isn't this a duplicate of this question?
However it looks like you should call InvalidateArrange() on the editor or InvalidateVisual() on each changed visual.