5

I am using Graphics.DrawString in an ObjectListView. I need to highlight some words in the string (make their background yellow). Can this be done?

Before using Graphics.DrawString, I could use DefaultRenderer for highlighting. But now it doesn't work.

I am using Graphics.DrawString as in the Task List sample here

Jerry
  • 4,258
  • 3
  • 31
  • 58

2 Answers2

5

If ObjectListView supports it (the normal ListView certainly does)), simply use the TextRenderer to draw text :

enter image description here

private void listView1_DrawItem(object sender, DrawListViewItemEventArgs e)
{
    TextFormatFlags flags =  TextFormatFlags.Left | TextFormatFlags.VerticalCenter;
    TextRenderer.DrawText(e.Graphics,  e.Item.Text, Font, e.Bounds,
                          Color.Blue, Color.LightSteelBlue, flags);
}

As usual only one style is supported per call. So to highlight certain words you will have use more than one call and also use the TextRenderer.MeasureText method to find the next positions. It will be rather hard to get this pixel-perfect, unless you can settle for a fixed-font..

Here is a quick and dirty example:

enter image description here

private void listView1_DrawItem(object sender, DrawListViewItemEventArgs e)
{
    TextFormatFlags flags =  TextFormatFlags.Left;

    int leftPadding = 3;
    int x = leftPadding;
    var words = e.Item.Text.Split('#');
    for (int i = 0; i < words.Count(); i++)
    {
        Point pt = new Point(x, e.Bounds.Top );
        Size sz = TextRenderer.MeasureText(words[i], Font);
        if (i % 2 == 0 )
            TextRenderer.DrawText(e.Graphics, words[i], Font, pt, 
                                  Color.Black, flags);
        else
            TextRenderer.DrawText(e.Graphics, words[i], Font, pt,
                                  Color.Blue, Color.LightSteelBlue, flags);
        x += sz.Width;
    }
}

As you can see, there is some extra space after the chinks. Maybe using the E.Graphics.MeasureString call wih the StringFormat.GenericTypographic would be better, as it is tuned to creating sizes without slack..:

Update:

This looks better, making us of both renderers:

enter image description here

Point pt = new Point(x, e.Bounds.Top );
Size sz1 = TextRenderer.MeasureText(words[i], Font);
SizeF sz2 = e.Graphics.MeasureString(words[i],Font, 9999, StringFormat.GenericTypographic);

if (i % 2 == 0 )
    TextRenderer.DrawText(e.Graphics, words[i], Font, pt, Color.Black);
else
    TextRenderer.DrawText(e.Graphics, words[i], Font, pt, 
                          Color.Blue, Color.LightSteelBlue);
x += (int )(sz1.Width + sz2.Width) / 2;

To draw wrapped text you need to take the available space into account and include the y coordinate in the calculation.. For this you can either draw word by word or use the RenderText overload that takes a Rectangle. This will get rather tedious!

Update 2

enter image description here

Here is another quick one showing how to handle wrapping text:

private void listView1_DrawItem(object sender, DrawListViewItemEventArgs e)
{
    int maxW = e.Bounds.Width;
    int leftPadding = 3;
    int leading = 1;
    int x = leftPadding;
    int y = e.Bounds.Y;
    var chunks = e.Item.Text.Split('#');

    SizeF s0 = e.Graphics.MeasureString("_|", Font);
    Size s1 = e.Bounds.Size;

    for (int i = 0; i < chunks.Count(); i++)
    {
        Point pt = new Point(x, e.Bounds.Top );
        var words = chunks[i].Split(' ');

        for (int j = 0; j < words.Count(); j++)
        {
            Size sz1 = TextRenderer.MeasureText(words[j], Font);
            SizeF sz2 = e.Graphics.MeasureString(words[j], Font, 9999,
                                                StringFormat.GenericTypographic);
            int w = (int)(sz1.Width + sz2.Width) / 2;
            if (x + w > maxW)
            {
                y += sz1.Height + leading;
                x = leftPadding;
            }
            DrawWords(e.Graphics, words[j], Font, new Point( x, y),
                      Color.Blue, Color.LightSteelBlue, i % 2 != 1);
            x += w;
        }
    }
}

It uses a small function:

void DrawWords(Graphics g, string text, Font font, Point pt, 
               Color fCol, Color Bcol, bool highlite )
{
        if (highlite)
            TextRenderer.DrawText(g, text, font, pt, Color.Black);
        else
            TextRenderer.DrawText(g, text, font, pt, Color.Blue, Color.LightSteelBlue); 
}
TaW
  • 53,122
  • 8
  • 69
  • 111
  • Yes, it is supported. But I want to highlight specific words. Say all the occurrences of word1 and word2. – Jerry Feb 02 '16 at 14:49
  • 1
    See my update for an example. Support for this is rather week in Winforms but still doable.. – TaW Feb 02 '16 at 15:15
  • 1
    TextRenderer is *always* a better choice for rendering text. :-) – Cody Gray - on strike Feb 02 '16 at 15:23
  • 1
    Yeah, well so I have heard. But not only have I not really really really seen it; here is a case for using at least MeasureString as well.. – TaW Feb 02 '16 at 15:52
  • Thanks. I'll give it a try and see if I can write a function that takes a list of words and highlights them. – Jerry Feb 02 '16 at 19:31
  • Good luck; I have expanded the answer to include wrapping text.. – TaW Feb 03 '16 at 23:21
3

You can only use Graphics.FillRectangle() method for this. Hope your ListView doesn't wrap the text to next line.

using (Graphics g = Graphics.FromImage(yourImage.Image))
{
    Font drawFont = new Font("Arial", 12);
    SolidBrush drawBrush = new SolidBrush(Color.Black);

    //your text starting position
    double textX = 350.0,textY=340.0;

    //Get length of one character for your font style
    float CharWidth = g.MeasureString("A", drawFont).Width;

    //Take the words before the word that has to be highlighted and get it's length
    float widthTotal = "First text strip that doesn't need highlighting".Length() * CharWidth


    //Create and draw a rectangle here offsetting this widthTotal from the starting position for the desired Length
    var size = g.MeasureString("Text to be highlighted", drawFont);
    var rectangle = new RectangleF(textX+widthTotal , textY, size.Width, size.Height);

    //Filling a rectangle before drawing the string.
    g.FillRectangle(Brushes.Yellow, rectangle);
    //Repeat above process for all required words

    //Finally draw your string over it
    g.DrawString("your complete text",drawFont,drawBrush,new Point(textX,textY))



}
Vishnu Prasad V
  • 1,228
  • 1
  • 8
  • 18