views:

289

answers:

1

I am using Graphics.DrawString to draw my usercontrol's text like this:

protected override void OnPaint(PaintEventArgs e)
{
    RectangleF bounds = DisplayRectangle;
    bounds.Inflate(-4, -4); // Padding
    StringFormat format = new StringFormat();
    format.Alignment = StringAlignment.Near;
    format.LineAlignment = StringAlignment.Near;
    format.Trimming = StringTrimming.None;
    using (Brush bFore = new SolidBrush(ForeColor))
    {
        g.DrawString(Text, Font, bFore, bounds, format);
    }
}

If control's Text is wider than the DisplayRectangle, DrawString nicely breaks the Text into multiple lines at word boundaries.

Now I want to underline some words from Text, but I couldn't work it out. I tried splitting the Text, then MeasureString the string just before an underlined part starts, DrawString the normal part, then DrawString the underlined part. But this works only if Text is single-line.

I am sure using a child LinkLabel or RichTextBox to render my control's text will solve this, but I don't like the idea of using a child control just to underline a few words. Is there another way?

+2  A: 

This is a crude example that will work using the string split into parts and two different font styles, rather than drawing the underline separately ( though that would work too ). In actual practice, I would recommend splitting the text by word, not by phrase, and dealing with each word individually, in a loop. Otherwise, like in this example, the line-breaking doesn't work quite right.

Dim fntNormal As New Font(myFontFamily, myFontSize, FontStyle.Regular, GraphicsUnit.Pixel)
  Dim fntUnderline As New Font(myFontFamily, myFontSize, FontStyle.Underline, GraphicsUnit.Pixel)

  g.DrawString("This is ", fntNormal, Brushes.Black, rctTextArea)
  w1 = g.MeasureString("This is ", fntNormal).Width
  w2 = g.MeasureString("underlined", fntUnderline).Width
  If w1 + w2 > rctTextArea.Width Then
     yPos = rctTextArea.Y + g.MeasureString("This is ", fntNormal).Height + 5
     xPos = rctTextArea.X
  Else
     yPos = rctTextArea.Y
     xPos = 0
  End If

  g.DrawString("underlined", fntUnderline, Brushes.Black, xPos, yPos)

  w1 = g.MeasureString("underlined", fntUnderline).Width
  w2 = g.MeasureString(", and this is not.", fntNormal).Width

  If w1 + w2 > rctTextArea.Width Then
     yPos += g.MeasureString("underlined", fntUnderline).Height + 5
     xPos = rctTextArea.X
  Else
     xPos = 0
  End If


  g.DrawString(", and this is not.", fntNormal, Brushes.Black, xPos, yPos)

This code can really be cleaned up and made more efficient to let you loop through each word in your text string.

This example also does not include any code to check if you have exceeded the vertical limit of your bounds rectangle.

Sorry for the VB code, I just noticed your question was in C#.

Stewbob
The two lines with xPos = 0 above. Shouldn't they be xPos = xPos + w1?
Ozgur Ozcitak
Thanks, I guess this is the best I can do. I also tried using MeasureCharacterRanges on word boundaries, but it doesn't seem to work with multi-line text.
Ozgur Ozcitak
Yes, the xPos = 0 is wrong. I was in too much of a hurry with my cut and paste. :)
Stewbob