I need to highlight the particular character in a control using the fill rect.
I can get the location of the text when it's not wrapped by using the Graphics.MeasureString() method like below,
var size = g.MeasureString(tempSearchText, style.Font, 0, StringFormat.GenericTypographic);
If the text is wrapped then I'm not able to find the exact bounds of the character to highlight the text.
I need to get the exact bounds of the given character in the text which is wrapped. Provide your suggestion to achieve this scenario.
There is no clear specification of which controls to target, so I'm testing 3 different:
TextBox, RichTextbox and ListBox.
TextBox and RichTextbox have the same behavior and share the same tools, so there's no need to define two different methods to achieve the same result.
Of course RichTextbox offers many more options, including RTF.
Also, I'm testing both Graphics.DrawString() and TextRenderer.DrawText().
This is the result of this test, so it's more clear what the code does.
Warning:
For this example I'm using Control.CreateGraphics(), because TextBox and RichTextBox controls don't provide a Paint() event. For a real world application, you should create a Custom Control derived from TextBox or RichTextBox, override WndPrc and handle WM_PAINT.
1) Highlight all t in a multiline TextBox control.
TextRenderer->DrawText():
//Define some useful flags for TextRenderer
TextFormatFlags flags = TextFormatFlags.Left | TextFormatFlags.Top |
TextFormatFlags.NoPadding | TextFormatFlags.WordBreak |
TextFormatFlags.TextBoxControl;
//The char to look for
char TheChar = 't';
//Find all 't' chars indexes in the text
List<int> TheIndexList = textBox1.Text.Select((chr, idx) => chr == TheChar ? idx : -1)
.Where(idx => idx != -1).ToList();
//Or with Regex - same thing, pick the one you prefer
List<int> TheIndexList = Regex.Matches(textBox1.Text, TheChar.ToString())
.Cast<Match>()
.Select(chr => chr.Index).ToList();
//Using .GetPositionFromCharIndex(), define the Point [p] where the highlighted text is drawn
if (TheIndexList.Count > 0)
{
foreach (int Position in TheIndexList)
{
Point p = textBox1.GetPositionFromCharIndex(Position);
using (Graphics g = textBox1.CreateGraphics())
TextRenderer.DrawText(g, TheChar.ToString(), textBox1.Font, p,
textBox1.ForeColor, Color.LightGreen, flags);
}
}
The same operation using Graphics.FillRectangle() and Graphics.DrawString():
if (TheIndexList.Count > 0)
{
using (Graphics g = textBox1.CreateGraphics())
{
foreach (int Position in TheIndexList)
{
PointF pF = textBox1.GetPositionFromCharIndex(Position);
SizeF sF = g.MeasureString(TheChar.ToString(), textBox1.Font, 0,
StringFormat.GenericTypographic);
g.FillRectangle(Brushes.LightGreen, new RectangleF(pF, sF));
using (SolidBrush brush = new SolidBrush(textBox1.ForeColor))
{
g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
g.DrawString(TheChar.ToString(), textBox1.Font, brush, pF, StringFormat.GenericTypographic);
}
}
}
}
There is no notable difference in behavior: TextRenderer.DrawText()
and Graphics.DrawString() do the exact same thing here.
Setting Application.SetCompatibleTextRenderingDefault() to true or
false does not seem to have any affect (in the current context, at least).
2) Highlight some string patterns ("Words") in a TextBox control and a multiline RichTextbox control.
Using TextRenderer only, since there's no difference in behavior.
I'm simply letting IndexOf() find the the first occurrence of the
strings, but the same search pattern used before can take it's place. Regex works better.
string[] TheStrings = {"for", "s"};
foreach (string pattern in TheStrings)
{
Point p = TextBox2.GetPositionFromCharIndex(TextBox2.Text.IndexOf(pattern));
using (var g = TextBox2.CreateGraphics()) {
TextRenderer.DrawText(g, pattern, TextBox2.Font, p,
TextBox2.ForeColor, Color.LightSkyBlue, flags);
}
}
TheStrings = new string []{"m", "more"};
foreach (string pattern in TheStrings)
{
Point p = richTextBox1.GetPositionFromCharIndex(richTextBox1.Text.IndexOf(pattern));
using (Graphics g = richTextBox1.CreateGraphics())
TextRenderer.DrawText(g, pattern, richTextBox1.Font, p,
richTextBox1.ForeColor, Color.LightSteelBlue, flags);
}
3) Highlight all s in all the ListItems of a ListBox control (of course it can be any other string :)
The ListBox.DrawMode is set to Normal and changed "on the fly" to OwnerDrawVariable to evaluate whether TextRenderer and Graphics behave differently here.
There is a small difference: a different offset, relative to the left
margin of the ListBox, compared to the standard implementation.
TextRenderer, with TextFormatFlags.NoPadding renders 2 pixels to the
left (the opposite without the flag). Graphics renders 1 pixel to the
right. Of course if OwnerDrawVariable is set in design mode,
this will not be noticed.
string HighLightString = "s";
int GraphicsPaddingOffset = 1;
int TextRendererPaddingOffset = 2;
private void button1_Click(object sender, EventArgs e)
{
listBox1.DrawMode = DrawMode.OwnerDrawVariable;
}
How the following code works:
Get all the positions in the ListItem text where the pattern (string HighLightString) appears.
Define an array of CharacterRange structures with the position and length of the pattern.
Fill a StringFormat with all the CharacterRange structs using .SetMeasurableCharacterRanges()
Define an array of Regions using Graphics.MeasureCharacterRanges() passing the initialized StringFormat.
Define an array of Rectangles sized using Region.GetBounds()
Fill all the Rectangles with the highlight color using Graphics.FillRectangles()
Draw the ListItem text.
TextRenderer.DrawText() implementation:
private void listBox1_DrawItem(object sender, DrawItemEventArgs e)
{
e.DrawBackground();
TextFormatFlags flags = TextFormatFlags.Left | TextFormatFlags.Top | TextFormatFlags.NoPadding |
TextFormatFlags.WordBreak | TextFormatFlags.TextBoxControl;
Rectangle bounds = new Rectangle(e.Bounds.X + TextRendererPaddingOffset,
e.Bounds.Y, e.Bounds.Width, e.Bounds.Height);
string ItemString = listBox1.GetItemText(listBox1.Items[e.Index]);
List<int> TheIndexList = Regex.Matches(ItemString, HighLightString)
.Cast<Match>()
.Select(s => s.Index).ToList();
if (TheIndexList.Count > 0)
{
CharacterRange[] CharRanges = new CharacterRange[TheIndexList.Count];
for (int CharX = 0; CharX < TheIndexList.Count; CharX++)
CharRanges[CharX] = new CharacterRange(TheIndexList[CharX], HighLightString.Length);
StringFormat format = new StringFormat(StringFormat.GenericDefault);
format.SetMeasurableCharacterRanges(CharRanges);
Region[] regions = e.Graphics.MeasureCharacterRanges(ItemString, e.Font, e.Bounds, format);
RectangleF[] rectsF = new RectangleF[regions.Length];
for (int RFx = 0; RFx < regions.Length; RFx++)
rectsF[RFx] = regions[RFx].GetBounds(e.Graphics);
e.Graphics.FillRectangles(Brushes.LightGreen, rectsF);
}
TextRenderer.DrawText(e.Graphics, ItemString, e.Font, bounds, e.ForeColor, flags);
}
`Graphics.DrawString()` implementation
private void listBox1_DrawItem(object sender, DrawItemEventArgs e)
{
e.DrawBackground();
Rectangle bounds = new Rectangle(e.Bounds.X - GraphicsPaddingOffset,
e.Bounds.Y, e.Bounds.Width, e.Bounds.Height);
string ItemString = listBox1.GetItemText(listBox1.Items[e.Index]);
List<int> TheIndexList = Regex.Matches(ItemString, HighLightString)
.Cast<Match>()
.Select(s => s.Index).ToList();
StringFormat format = new StringFormat(StringFormat.GenericDefault);
if (TheIndexList.Count > 0)
{
CharacterRange[] CharRanges = new CharacterRange[TheIndexList.Count];
for (int CharX = 0; CharX < TheIndexList.Count; CharX++)
CharRanges[CharX] = new CharacterRange(TheIndexList[CharX], HighLightString.Length);
format.SetMeasurableCharacterRanges(CharRanges);
Region[] regions = e.Graphics.MeasureCharacterRanges(ItemString, e.Font, e.Bounds, format);
RectangleF[] rectsF = new RectangleF[regions.Length];
for (int RFx = 0; RFx < regions.Length; RFx++)
rectsF[RFx] = regions[RFx].GetBounds(e.Graphics);
e.Graphics.FillRectangles(Brushes.LightGreen, rectsF);
}
using (SolidBrush brush = new SolidBrush(e.ForeColor))
e.Graphics.DrawString(ItemString, e.Font, brush, bounds, format);
}
Note: Depending on the ListBox.DrawMode, it may become necessary to
subscribe the ListBox.MeasureItem() event or set the .ItemHeight
property to the corrent value.
private void listBox1_MeasureItem(object sender, MeasureItemEventArgs e)
{
e.ItemHeight = listBox1.Font.Height;
}
Related
I am having an odd issue whereby when I then try to iterate over the pixels of the PictureBox or save the bmp it is just all black.
The intention of the tool is that you select a font, size, and style, and then it loops over the ASCII chars of that font and shows each character in the picture box, and converts the pixel data into a HEX array so I can use them on LCD displays.
The main part of the tool works whereby it is correctly looping through the ASCII chars and displaying them in the picture box but after each char is drawn to the picture box and I then try to iterate over the pixels of the PictureBox every pixel is returned as 0,0,0 RGB "black" and if I save the bmp which was drawn to the PictureBox that too is all black, but again I can see the bmp of that char correct drawn in the PictureBox yet the PictureBox data and bmp data does not match what I see in the PictureBox itself, I am truly lost as to why I am unable to correctly iterate or save the bmp or PictureBox.
I have tried not using async functions which is not ideal as I want the UI to be free, and I have tried various means to read the pixels and save the bmp but the result is the same. I hope to ask if anyone knows why I am getting this odd behavior and the solution to the issue.
Regards from Ed.
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace generateFonts
{
public partial class Form1 : Form
{
string font = "";
float fontSize = 0;
FontStyle fontStyle = FontStyle.Regular;
public Form1()
{
InitializeComponent();
comboBox1.SelectedIndex = 0;
comboBox2.SelectedIndex = 0;
comboBox3.SelectedIndex = 0;
font = comboBox1.SelectedItem.ToString();
fontSize = Convert.ToInt32(comboBox2.SelectedItem);
fontStyle = FontStyle.Regular;
}
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
font = comboBox1.SelectedItem.ToString();
}
private void comboBox2_SelectedIndexChanged(object sender, EventArgs e)
{
fontSize = Convert.ToInt32(comboBox2.SelectedItem);
}
private void comboBox3_SelectedIndexChanged(object sender, EventArgs e)
{
if (comboBox3.SelectedIndex == 0)
fontStyle = FontStyle.Regular;
else if (comboBox3.SelectedIndex == 1)
fontStyle = FontStyle.Italic;
else if(comboBox3.SelectedIndex == 2)
fontStyle = FontStyle.Bold;
}
private async void button1_Click(object sender, EventArgs e)
{
await Task.Run(() => StartProcess(1));
}
private void StartProcess(int runs)
{
// Font
Font myFont = new Font(font, fontSize, fontStyle);
List<string> bytes = new List<string>();
string[] bits = { "0", "0", "0", "0", "0", "0", "0", "0" };
byte bitPos = 0;
for (byte i = 32; i < 126; i++)
{
//Create a Image-Object on which we can paint
Image bmp = new Bitmap(200, 200);
//Create the Graphics-Object to paint on the Bitmap
Graphics g = Graphics.FromImage(bmp);
string c = Convert.ToChar(i).ToString();
//Get the perfect Image-Size so that Image-Size = String-Size
SizeF size = g.MeasureString(c, myFont);
//Use this to become better Text-Quality on Bitmap.
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
//Here we draw the string on the Bitmap
g.DrawString(c, myFont, new SolidBrush(Color.Black), 0, 0);
if (!Directory.Exists("FontBmps"))
Directory.CreateDirectory("FontBmps");
this.Invoke((MethodInvoker)delegate ()
{
pictureBox2.Width = Convert.ToInt32(size.Width);
pictureBox2.Height = Convert.ToInt32(size.Height);
pictureBox2.Image = bmp; // <--- this is working and the picturebox shows the bmp correctly
bmp.Save("FontBmps/" + i + "_" + font + "_" + fontSize + "px_" + fontStyle + ".bmp", ImageFormat.Bmp); // <--- error here: this saves a black square instead of the bmp i see displayed in the picturebox GUI ??
// Even if i save the picturebox itself that too is just a black square instead of the correct image shown in the GUI ??
// now convert the bmp to a HEX array of pixels
for (int h = 0; h < pictureBox2.Height; h++)
{
for (int w = 0; w < pictureBox2.Width; w++)
{
Color colour = (pictureBox2.Image as Bitmap).GetPixel(w, h);
if (colour.R == 0 && colour.G == 0 && colour.B == 0)
{
bits[bitPos] = "1";
}
else
{
bits[bitPos] = "0"; // <-- never hits me, again for some reason the bmp or picturebox is all black pixels data but i see it correctly show in the picturebox GUI ??
}
if (bitPos < 7)
bitPos++;
else
{
//string bitStr = bits.ToString();
//string hex = Convert.ToByte(bitStr, 2).ToString("x2");
//bytes.Add(hex);
bitPos = 0;
for(byte n = 0; n < 8; n++)
{
bits[n] = "0";
}
}
}
}
if (bitPos != 0)
{
// TO DO...
}
});
Thread.Sleep(500); // <--- i have this just to see it displaying the chars but i have removed it with no effect to the issue
}
// now add List to a file in the correct format
foreach (string str in bytes)
{
Console.WriteLine(str);
// TO DO...
}
}
}
}
I believe the image is black, but some parts have transparency. That is, you would need to check Alpha (Color.A). What you see in the picture box, would be the background color of the picture box where it is transparent.
You won't see the transparency in the saved file, given that the ImageFormat.Bmp does not support transparency. Try Png instead, which supports transparency (and has lossless compression).
Alternatively, you can use Graphics.Clear to have the image be the color you want for background (white, I guess) before drawing to it.
Aside from that, I'll suggest to use bmp instead of (pictureBox2.Image as Bitmap), and use Bitmap.LockBits. That would improve performance.
This might be useful for reference: Converting a bitmap to monochrome. See also C# - Faster Alternatives to SetPixel and GetPixel for Bitmaps for Windows Forms App.
I am trying to make the points in a C# Chart show up as letters (e.g. 'A' and 'B') to separate sets of points, instead of coloring them with green/red.
The Label property of the point collection does not satisfy this request, since it only places labels beside the point, and I want the label to replace the point.
Here is what I have:
while (reader.Read())
{
if (reader[2].ToString() == "Kupi 002")
chart3.Series["Good_Group"].Points[pointsCounter].Label = "A";
else
chart3.Series["Good_Group"].Points[pointsCounter].Label = "B";
pointsCounter = pointsCounter + 1;
}
What property, instead of Label, should I use to achieve my goal?
Thanks in advance.
One option would be to use the MarkerImage property of either the series or point to apply custom bitmap labels of each letter:
chart.Series[0].MarkerImage = "a.png";
chart.Series[0].Points[2].MarkerImage = "b.png";
chart.Series[0].Points[4].MarkerImage = "b.png";
The bitmap images could be created in a paint program and distributed with your program. They could also be generated dynamically, as in the following (highly simplified) example:
private void CreateLetterBitmap(char letter, string path)
{
using (var bmp = new Bitmap(13, 13))
using (var gfx = Graphics.FromImage(bmp))
using (var font = new Font(FontFamily.GenericSansSerif, 12.0f, FontStyle.Bold, GraphicsUnit.Pixel))
{
gfx.TextRenderingHint = TextRenderingHint.AntiAlias;
gfx.DrawString(letter.ToString(), font, Brushes.Black, new Point(0, 0));
bmp.Save(path, ImageFormat.Png);
}
}
private void PrepareChart()
{
CreateLetterBitmap('A', "a.png");
CreateLetterBitmap('B', "b.png");
chart.Series[0].MarkerImage = "a.png";
chart.Series[0].Points[2].MarkerImage = "b.png";
chart.Series[0].Points[4].MarkerImage = "b.png";
}
Can label1 text&property and label2 text&property become one and display it in label3 and added text = ? because what I am using now is using label1 and label2 putting side by side.
Tell me if there's another approach
Ps: I define the color in a database like red or blue.
Your can combine the text content like this:
label3.Text = label1.Text + " = " + label2.Text;
But you will loose the different colours. This is unfortunately not possible. For more details check this answer
Use string.format to combine the 2 labels texts together.
label3.Text = string.Format("{0}={1}", label1.Text, label2.Text);
you can write your own text image your label3. like here
and.
firstly set label3 AutoSize = false and set size .
// Add this lines to InitializeComponent() in yourform.Designer.cs
this.label1.TextChanged += new System.EventHandler(this.label_TextChanged);
this.label2.TextChanged += new System.EventHandler(this.label_TextChanged);
// this is label1 and label2 TextCahanged Event
private void label_TextChanged(object sender, EventArgs e)
{
SetMultiColorText(string.Format("{0} = {1}", label1.Text, label2.Text),label3);
}
// this method set multi color image text for label(paramter lb)
public void SetMultiColorText(string Text, Label lb)
{
lb.Text = "";
// PictureBox needs an image to draw on
lb.Image = new Bitmap(lb.Width, lb.Height);
using (Graphics g = Graphics.FromImage(lb.Image))
{
SolidBrush brush = new SolidBrush(Form.DefaultBackColor);
g.FillRectangle(brush, 0, 0,
lb.Image.Width, lb.Image.Height);
string[] chunks = Text.Split('=');
brush = new SolidBrush(Color.Black);
// you can get this colors from label1 and label2 colors... or from db.. or an other where you want
SolidBrush[] brushes = new SolidBrush[] {
new SolidBrush(Color.Black),
new SolidBrush(Color.Red) };
float x = 0;
for (int i = 0; i < chunks.Length; i++)
{
// draw text in whatever color
g.DrawString(chunks[i], lb.Font, brushes[i], x, 0);
// measure text and advance x
x += (g.MeasureString(chunks[i], lb.Font)).Width;
// draw the comma back in, in black
if (i < (chunks.Length - 1))
{
g.DrawString("=", lb.Font, brush, x, 0);
x += (g.MeasureString(",", lb.Font)).Width;
}
}
}
}
I am trying to highlight List of strings in my dataGrid view. For this purpose, I used the existing code that highlights a keyword in dataGridView. but resultant code only higlights last occurrence of the list (i.e. last record fetched).
Here is What I've tried
private void dvg_ClauseSent_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
foreach (string sw in HighlightStrings) // HighlightStringsis the list of string containing all strings I need to highlight in DataGrid view
{
int strt = 0;
int cnt = -1;
int idx = -1;
TextFormatFlags flags = TextFormatFlags.Default | TextFormatFlags.NoPrefix;
if ((((e.RowIndex >= 0) && (e.ColumnIndex >= 0))))
{
e.Handled = true;
e.PaintBackground(e.CellBounds, true);
if (!string.IsNullOrEmpty(sw))
{
string val = e.FormattedValue.ToString();
int sindx = val.IndexOf(sw);
if ((sindx >= 0))
{
while (strt != -1)
{
strt = val.IndexOf(sw, idx + 1);
cnt += 1;
idx = strt;
if (strt != -1)
{
if (strt != 0 && ((strt + sw.Length) != val.Length))
{
Rectangle hl_rect = new Rectangle();
hl_rect.Y = (e.CellBounds.Y + 2);
hl_rect.Height = (e.CellBounds.Height - 5);
// find the size of the text before the search word
// and the size of the search word
// paint the background behind the search word
e.Graphics.FillRectangle(hl_brush, hl_rect);
hl_brush.Dispose();
}
}
}
}
}
}
// paint the content as usual
e.PaintContent(e.CellBounds);
}
}
Attached ScreenShots
Following screenshot shows the strings that should appear as highligted in dataGridView
http://i42.tinypic.com/2dtrea1.png
Part of strings enclosed in ANGLE BRACKET followed/preceeded by |* / *| should be appeared as highlighted but only last entry is being highlighted.
http://i39.tinypic.com/30cbw9l.png
Any help will be appreciated...
Your code has this strange thing:
Rectangle hl_rect = new Rectangle();
hl_rect.Y = (e.CellBounds.Y + 2);
hl_rect.Height = (e.CellBounds.Height - 5);
You don't even initialize the X and the Width (hence they will be empty by default). So how could it be rendered???
I would like to talk this to everyone who will be asking questions, please post your actual code. Don't try simplifying it if you don't understand how wrong it is after being simplified. Except the code above, I don't find any thing which may cause the issue (it's of course not tested, just a quick scan). I've tried writing another code for you, also tested. The problem is we have to draw the string and measure the string together so that the Text Bound can be determined exactly. The TextRenderingHint.AntiAlias should also be used, although sometimes it works without that. The drawn text may look a little blurry however it can be partially eliminated by using a large font, the larger the better. Here is the code for you.
private void dataGridView1_CellPainting(object sender,
DataGridViewCellPaintingEventArgs e) {
if (e.RowIndex > -1 && e.ColumnIndex > -1 && e.Value != null) {
string value = e.Value.ToString();
foreach (var s in HighlightStrings) {
int i = 0;
while (i < value.Length && (i = value.IndexOf(s,i))!=-1) {
if (!e.Handled){
e.Handled = true;
e.PaintBackground(e.ClipBounds, true);
}
StringFormat sf = StringFormat.GenericTypographic;
sf.LineAlignment = StringAlignment.Center;
RectangleF textBounds = GetTextBounds(e.Graphics,
value, i, s.Length,
e.CellBounds,
e.CellStyle.Font, sf);
//highlight it
e.Graphics.FillRectangle(Brushes.Yellow, textBounds);
i += s.Length;
using (Brush brush = new SolidBrush(e.CellStyle.ForeColor)) {
//draw string , don't use PaintContent
e.Graphics.DrawString(value, e.CellStyle.Font, brush,
e.CellBounds, sf);
}
}
}
}
}
public RectangleF GetTextBounds(Graphics g, string text,
int subIndex, int subLength,
RectangleF layout,
Font font, StringFormat sf) {
var charRange = new CharacterRange(0, text.Length);
var subCharRange = new CharacterRange(subIndex, subLength);
sf.SetMeasurableCharacterRanges(new[]{ charRange, subCharRange });
var regions = g.MeasureCharacterRanges(text, font, layout, sf);
return regions.Length < 2 ? RectangleF.Empty : regions[1].GetBounds(g);
}
NOTE: The reason we have to use DrawString is as I said to measure the TextBound exactly. If you have some way to measure it exactly without having to draw the string yourself, you can use PaintContent.
I want to color on part of tree node but not through the user (without using "selected node")
so DrawMode is not helping me.
I am using c#
For example I want that all tree nodes with space on the text will color in one side at green and the other side to red.
Thanks!!
DrawMode is the way to go. You have to set it to OwnerDrawText, and subscribe to the DrawNode event. I.e.:
this.treeView1.DrawMode = System.Windows.Forms.TreeViewDrawMode.OwnerDrawText;
this.treeView1.DrawNode += new System.Windows.Forms.DrawTreeNodeEventHandler(this.treeView1_DrawNode);
This is just a sample of how the drawing method could look like. It's up to you to modify it in order to have a good graphical result, but it can give you an idea of the way to go.
private void treeView1_DrawNode(object sender, DrawTreeNodeEventArgs e)
{
Font nodeFont = e.Node.NodeFont;
if (nodeFont == null) nodeFont = ((TreeView)sender).Font;
string txt = e.Node.Text;
int idx = txt.IndexOf(' ');
string greenTxt;
string redTxt;
if (idx >= 0)
{
greenTxt = txt.Substring(0, idx);
redTxt = txt.Substring(idx);
}
else
{
greenTxt = txt;
redTxt = string.Empty;
}
Rectangle greenRect = new Rectangle(e.Bounds.Location, new Size((int)Math.Ceiling(e.Graphics.MeasureString(greenTxt, nodeFont).Width), e.Bounds.Height));
Rectangle redRect = new Rectangle(e.Bounds.Location + new Size(greenRect.Width, 0), new Size((int)Math.Ceiling(e.Graphics.MeasureString(redTxt, nodeFont).Width), e.Bounds.Height));
e.Graphics.DrawString(greenTxt, nodeFont, Brushes.Green, greenRect);
if (!string.IsNullOrEmpty(redTxt))
e.Graphics.DrawString(redTxt, nodeFont,
Brushes.Red, redRect);
}
You can find a more complex example here.