Color different parts of a RichTextBox string

I'm trying to color parts of a string to be appended to a RichTextBox. I have a string built from different strings.

string temp = "[" + DateTime.Now.ToShortTimeString() + "] " +
userid + " " + message + Environment.NewLine;

This is what the message would look like once it is constructed.

[9:23pm] User: my message here.

I want everything within and including the brackets [9:23] to be one color, 'user' to be another color and the message to be another color. Then I'd like the string appended to my RichTextBox.

How can I accomplish this?

197517 次浏览

Here is an extension method that overloads the AppendText method with a color parameter:

public static class RichTextBoxExtensions
{
public static void AppendText(this RichTextBox box, string text, Color color)
{
box.SelectionStart = box.TextLength;
box.SelectionLength = 0;


box.SelectionColor = color;
box.AppendText(text);
box.SelectionColor = box.ForeColor;
}
}

And this is how you would use it:

var userid = "USER0001";
var message = "Access denied";
var box = new RichTextBox
{
Dock = DockStyle.Fill,
Font = new Font("Courier New", 10)
};


box.AppendText("[" + DateTime.Now.ToShortTimeString() + "]", Color.Red);
box.AppendText(" ");
box.AppendText(userid, Color.Green);
box.AppendText(": ");
box.AppendText(message, Color.Blue);
box.AppendText(Environment.NewLine);


new Form {Controls = {box}}.ShowDialog();

Note that you may notice some flickering if you're outputting a lot of messages. See this C# Corner article for ideas on how to reduce RichTextBox flicker.

I have expanded the method with font as a parameter:

public static void AppendText(this RichTextBox box, string text, Color color, Font font)
{
box.SelectionStart = box.TextLength;
box.SelectionLength = 0;


box.SelectionColor = color;
box.SelectionFont = font;
box.AppendText(text);
box.SelectionColor = box.ForeColor;
}

Selecting text as said from somebody, may the selection appear momentarily. In Windows Forms applications there is no other solutions for the problem, but today I found a bad, working, way to solve: you can put a PictureBox in overlapping to the RichtextBox with the screenshot of if, during the selection and the changing color or font, making it after reappear all, when the operation is complete.

Code is here...

//The PictureBox has to be invisible before this, at creation
//tb variable is your RichTextBox
//inputPreview variable is your PictureBox
using (Graphics g = inputPreview.CreateGraphics())
{
Point loc = tb.PointToScreen(new Point(0, 0));
g.CopyFromScreen(loc, loc, tb.Size);
Point pt = tb.GetPositionFromCharIndex(tb.TextLength);
g.FillRectangle(new SolidBrush(Color.Red), new Rectangle(pt.X, 0, 100, tb.Height));
}
inputPreview.Invalidate();
inputPreview.Show();
//Your code here (example: tb.Select(...); tb.SelectionColor = ...;)
inputPreview.Hide();

Better is to use WPF; this solution isn't perfect, but for Winform it works.

This is the modified version that I put in my code (I'm using .Net 4.5) but I think it should work on 4.0 too.

public void AppendText(string text, Color color, bool addNewLine = false)
{
box.SuspendLayout();
box.SelectionColor = color;
box.AppendText(addNewLine
? $"{text}{Environment.NewLine}"
: text);
box.ScrollToCaret();
box.ResumeLayout();
}

Differences with original one:

  • Possibility to add text to a new line or simply append it
  • No need to change selection, it works the same
  • Inserted ScrollToCaret to force autoscroll
  • Added ABC0/ResumeLayout calls for better performance
private void Log(string s , Color? c = null)
{
richTextBox.SelectionStart = richTextBox.TextLength;
richTextBox.SelectionLength = 0;
richTextBox.SelectionColor = c ?? Color.Black;
richTextBox.AppendText((richTextBox.Lines.Count() == 0 ? "" : Environment.NewLine) + DateTime.Now + "\t" + s);
richTextBox.SelectionColor = Color.Black;


}

EDIT : sorry this is a WPF answer

I think modifying a "selected text" in a RichTextBox isn't the right way to add colored text. So here a method to add a "color block" :

        Run run = new Run("This is my text");
run.Foreground = new SolidColorBrush(Colors.Red); // My Color
Paragraph paragraph = new Paragraph(run);
MyRichTextBlock.Document.Blocks.Add(paragraph);

From MSDN :

The Blocks property is the content property of RichTextBox. It is a collection of Paragraph elements. Content in each Paragraph element can contain the following elements:

  • Inline

  • InlineUIContainer

  • Span

  • Bold

  • Hyperlink

  • Italic

  • Underline

  • LineBreak

So I think you have to split your string depending on parts color, and create as many Run objects as needed.

Using Selection in WPF, aggregating from several other answers, no other code is required (except Severity enum and GetSeverityColor function)

 public void Log(string msg, Severity severity = Severity.Info)
{
string ts = "[" + DateTime.Now.ToString("HH:mm:ss") + "] ";
string msg2 = ts + msg + "\n";
richTextBox.AppendText(msg2);


if (severity > Severity.Info)
{
int nlcount = msg2.ToCharArray().Count(a => a == '\n');
int len = msg2.Length + 3 * (nlcount)+2; //newlines are longer, this formula works fine
TextPointer myTextPointer1 = richTextBox.Document.ContentEnd.GetPositionAtOffset(-len);
TextPointer myTextPointer2 = richTextBox.Document.ContentEnd.GetPositionAtOffset(-1);


richTextBox.Selection.Select(myTextPointer1,myTextPointer2);
SolidColorBrush scb = new SolidColorBrush(GetSeverityColor(severity));
richTextBox.Selection.ApplyPropertyValue(TextElement.BackgroundProperty, scb);


}


richTextBox.ScrollToEnd();
}

It`s work for me! I hope it will be useful to you!

public static RichTextBox RichTextBoxChangeWordColor(ref RichTextBox rtb, string startWord, string endWord, Color color)
{
rtb.SuspendLayout();
Point scroll = rtb.AutoScrollOffset;
int slct = rtb.SelectionIndent;
int ss = rtb.SelectionStart;
List<Point> ls = GetAllWordsIndecesBetween(rtb.Text, startWord, endWord, true);
foreach (var item in ls)
{
rtb.SelectionStart = item.X;
rtb.SelectionLength = item.Y - item.X;
rtb.SelectionColor = color;
}
rtb.SelectionStart = ss;
rtb.SelectionIndent = slct;
rtb.AutoScrollOffset = scroll;
rtb.ResumeLayout(true);
return rtb;
}


public static List<Point> GetAllWordsIndecesBetween(string intoText, string fromThis, string toThis,bool withSigns = true)
{
List<Point> result = new List<Point>();
Stack<int> stack = new Stack<int>();
bool start = false;
for (int i = 0; i < intoText.Length; i++)
{
string ssubstr = intoText.Substring(i);
if (ssubstr.StartsWith(fromThis) && ((fromThis == toThis && !start) || !ssubstr.StartsWith(toThis)))
{
if (!withSigns) i += fromThis.Length;
start = true;
stack.Push(i);
}
else if (ssubstr.StartsWith(toThis) )
{
if (withSigns) i += toThis.Length;
start = false;
if (stack.Count > 0)
{
int startindex = stack.Pop();
result.Add(new Point(startindex,i));
}
}
}
return result;
}

I created this Function after researching on the internet since I wanted to print an XML string when you select a row from a data grid view.

static void HighlightPhrase(RichTextBox box, string StartTag, string EndTag, string ControlTag, Color color1, Color color2)
{
int pos = box.SelectionStart;
string s = box.Text;
for (int ix = 0; ; )
{
int jx = s.IndexOf(StartTag, ix, StringComparison.CurrentCultureIgnoreCase);
if (jx < 0) break;
int ex = s.IndexOf(EndTag, ix, StringComparison.CurrentCultureIgnoreCase);
box.SelectionStart = jx;
box.SelectionLength = ex - jx + 1;
box.SelectionColor = color1;
        

int bx = s.IndexOf(ControlTag, ix, StringComparison.CurrentCultureIgnoreCase);
int bxtest = s.IndexOf(StartTag, (ex + 1), StringComparison.CurrentCultureIgnoreCase);
if (bx == bxtest)
{
box.SelectionStart = ex + 1;
box.SelectionLength = bx - ex + 1;
box.SelectionColor = color2;
}
        

ix = ex + 1;
}
box.SelectionStart = pos;
box.SelectionLength = 0;
}

and this is how you call it

   HighlightPhrase(richTextBox1, "<", ">","</", Color.Red, Color.Black);

I prepared a little helper for the RichTextBox control which makes it very easy to generate colored text on the screen:

using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;


namespace Common.Helpers
{
public class ColouredText
{
public string Text;
public Color Foreground;
public Color Background;


public ColouredText(string text, Color foreground, Color background)
{
Text = text;
Foreground = foreground;
Background = background;
}


public ColouredText(string text, Color foreground) : this(text, foreground, Color.Transparent) { }


public ColouredText(string text) : this(text, Color.Transparent, Color.Transparent) { }
}


public static class RichTextBoxHelper
{
private static RichTextBox _AppendText(RichTextBox box, string text, Color foreColor, Color backColor)
{
if (string.IsNullOrEmpty(text)) return box;


box.SelectionStart = box.TextLength;
box.SelectionLength = 0;


box.SelectionColor = foreColor;
box.SelectionBackColor = backColor;
box.AppendText(text);
box.SelectionColor = box.ForeColor;


return box;
}


private static void _UpdateText(RichTextBox box, IEnumerable<ColouredText> newTextWithColors)
{
box.Text = "";


foreach (var text in newTextWithColors)
{
var foreColor = text.Foreground; if (foreColor == Color.Transparent) foreColor = box.ForeColor;
var backColor = text.Background; if (backColor == Color.Transparent) backColor = box.BackColor;


_AppendText(box, text.Text, foreColor, backColor);
}
}


public static void UpdateText(this RichTextBox richTextbox, IEnumerable<ColouredText> text)
{
if (richTextbox.InvokeRequired) richTextbox.Invoke((MethodInvoker)(() => { _UpdateText(richTextbox, text); }));
else _UpdateText(richTextbox, text);
}


public static void UpdateText(this RichTextBox richTextbox, ColouredText text)
{
var list = new List<ColouredText>() { text };


if (richTextbox.InvokeRequired) richTextbox.Invoke((MethodInvoker)(() => { _UpdateText(richTextbox, list); }));
else _UpdateText(richTextbox, list);
}
}
}

and now you can use:

var text = new List<ColouredText>()
{
new ColouredText($"text#1    ", Color.Black),
new ColouredText($"text#2    ", Color.Red, Color.Yellow),
new ColouredText($" "),
new ColouredText($"text#2    ", Color.White, Color.Black)
};


richTextBox1.UpdateText(text);

or simpler usage for single-line text:

richTextBox1.UpdateText(new ColouredText($"warning message", Color.Yellow, Color.Red));