4

I'd like to put a number in the left-hand margin of a menu item in vb.net 2010, but it seems this can only be set to an image. So, I've been trying to create an image with the number I want there using Graphics.DrawString(). I've tried various ways, but I can't get the resulting image to look like the text in the menu item itself - is there a way to do this? Here's my current code (allocating an image to measure the text, then reallocating at the correct size is about version 3 of this - pretty ugly, but I'm not sure how else to measure).

mnuItem = New ToolStripMenuItem
numPeople = CInt(Math.Ceiling(Rnd() * 20))

' Calculate the size of the text
qImg = New Bitmap(1, 1)
sf.Alignment = StringAlignment.Center
sf.LineAlignment = StringAlignment.Center
gr = Graphics.FromImage(qImg)
gr.TextRenderingHint = Drawing.Text.TextRenderingHint.AntiAlias
sz = gr.MeasureString(numPeople, mnuItem.Font, New Point(0, 0), sf)
w = CInt(Math.Ceiling(sz.Width))
h = CInt(Math.Ceiling(sz.Height))
m = Math.Max(w, h)

' Now allocate an image of the correct size
qImg = New Bitmap(m, m)
gr = Graphics.FromImage(qImg)
gr.TextRenderingHint = Drawing.Text.TextRenderingHint.AntiAlias
gr.DrawString(numPeople, mnuItem.Font, Brushes.Black, New RectangleF((m - w) / 2, (m - h) / 2, w, h), sf)

mnuItem.Image = qImg

Here's a couple of examples of what this gives - note how fuzzy the margin text (image) is compared to the menu item text:

Example menu with fuzzy text Example menu with fuzzy text

I've tried all the TextRenderingHint options, and some are better than others, but none give the crisp look of the menu text. Is there a way to get closer to that look?

Matt Wilko
  • 26,994
  • 10
  • 93
  • 143
dsl101
  • 1,715
  • 16
  • 36
  • Have you tried other brushes or fonts for DrawString()? – TimG Apr 24 '14 at 12:16
  • The brush choice for `DrawString` seems to be limited to changing colours. As for font, I presumed if I want it to match the menu text, using the menu's font would be the best choice - is that not the case? – dsl101 Apr 24 '14 at 16:21
  • I haven't tried all of the features of System.Drawing.Brush class. It seems like Brushes.Black would be the same as Brush or SolidBrush but you never know. It sounds like you got a winner with TextRenderer. Kudos. – TimG Apr 24 '14 at 17:17
  • Actually, just found this in MSDN - I'll give it a try: If you drawing text for a Windows Form control, consider using the System.Windows.Forms.TextRenderer class instead of the System.Drawing.Graphics class. TextRenderer draws crisper text that more closely matches what Windows uses to draw controls outside of Windows Forms. – dsl101 Apr 25 '14 at 15:43
  • Well, sadly TextRenderer suffers from the same problem. I also found another link to someone having exactly the same problem - no resolution, other than a comment that the text routines sometimes struggle drawing on transparent and make the anttaliasing much more blurry than it should be. Since this text is going over a gradient, I have no choice but to use transparent (I couldn't actually see much difference when it was on white anyway!). So, looks like the vb controls don't use either DrawString or TextRenderer :(. I found that changing Graphics.TextContrast to 6 helped a little too. – dsl101 Apr 25 '14 at 15:45
  • 1
    TextRenderer is one, the more grave one is TextRenderingHint.AntiAlias. Which is not what the menu uses, it uses ClearTypeGridFit. A bitmap also cannot work, you can't get it to blend properly with the gradient background. You must override ProfessionalToolStripRenderer.DrawItemCheck(). – Hans Passant Apr 29 '14 at 15:19

2 Answers2

1

This had me stumped for a while and I conclude that this is a bug because as soon as you add a background colour the text displays as you would wish.

I have a solution for you. It uses a gradient brush derived from the Professional Colors class to draw a rectangle in the background of the bitmap before drawing the text.

Dim mnuItem = TestToolStripMenuItem
Dim numPeople = CInt(Math.Ceiling(Rnd() * 20))

Dim qImg = New Bitmap(mnuItem.Height, mnuItem.Height)
Using gr = Graphics.FromImage(qImg)
    Dim sz = gr.MeasureString(numPeople, mnuItem.Font)

    Dim w = CInt(Math.Ceiling(sz.Width))
    Dim h = CInt(Math.Ceiling(sz.Height))

    Dim linGrBrush As New LinearGradientBrush( _
       New Point(0, 0), _
       New Point(qImg.Width, 0), _
       ProfessionalColors.ToolStripGradientBegin, _
       ProfessionalColors.ToolStripGradientEnd)

    gr.FillRectangle(linGrBrush, New Rectangle(0, 0, qImg.Width, qImg.Height))
    gr.DrawString(numPeople, mnuItem.Font, Brushes.Black, New Point((qImg.Width / 2) - (w / 2), (qImg.Height / 2) - (h / 2)))
End Using

mnuItem.Image = qImg

enter image description here

You will need the Imports System.Drawing.Drawing2D line at the top of the class

Matt Wilko
  • 26,994
  • 10
  • 93
  • 143
  • Really interesting approach, and I tried it first, but I found a problem (can't put a screen shot in the comments here). Although it looked much much better than what I'd managed, the qImg generated from mnuItem.Height x mnuItem.Height was slightly smaller than menu itself - not sure if that scaling down happens when the image is assigned to the mnuItem at the end. But it left a border around the text where the gradients didn't quite line up. In the end, I went with @sallushan's method below, as it was much less code and completely clean text. – dsl101 Apr 30 '14 at 15:33
  • Yes I noticed that after posting (it is not that noticeable in my colour scheme because it is shades of grey) It's because the standard width of the gradient on the menu is 30 pixels and the size of the bitmap created is 24 pixels there a slight mismatch. This could be compensated for but @sallushan has a much better solution (I have upvoted it) – Matt Wilko Apr 30 '14 at 15:42
1

Have you tried with creating your own ToolStripMenuItem?

Class CustomToolStripMenuItem
    Inherits System.Windows.Forms.ToolStripMenuItem
    Protected Overrides Sub OnPaint(e As System.Windows.Forms.PaintEventArgs)
        MyBase.OnPaint(e)
        e.Graphics.DrawString("11", Me.Font, System.Drawing.Brushes.Black, New System.Drawing.PointF(0, 0))
    End Sub
End Class

I am NOT doing any calculation to correctly display it, but I think you can do it. It appears like this on my system (see the "Save As" item)

enter image description here

sallushan
  • 1,134
  • 8
  • 16
  • Fantastic - works a treat. I was able to pass the value in mnuItem.tag too, and combined with the MeasureString calls from my other code, everything lines up perfectly. Thanks! – dsl101 Apr 30 '14 at 15:34