Draw Text with Blend to background

JaedenRuiner

Well-known member
Joined
Aug 13, 2007
Messages
340
Programming Experience
10+
basically, I'm using a Graphics class to draw some text on the screen (ProgressBar with text) okay, and I want the given TextColor to blend with the progress bar, so that when the progress bar starts sliding beneath the text, the text color blends with it. (i would assume a form of XOR copying of the color codes, or something to that effect)

Either way, I don't know enough about the GDI in order to get the DrawString() to blend my Text over the dual color background (much they way they do in all setup progress bars etc.)

Thanks
 
Have you considered just drawing the text in a partly transparent colour?

An easy way to do that would be to set the alpha value less than 255 in the brush colour. For example:
VB.NET:
Dim sb As New SolidBrush(Color.FromArgb(127, 255, 255, 0))
Then you can use sb in the DrawString statement.

I hope this helps, Vic
 
Well,

that sort of helped. I can play with it a bit, but what I'm looking for is this:
prog.jpg


Now in my component here is my text code:

VB.NET:
   Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
      MyBase.WndProc(m)
      If m.Msg = 15 AndAlso _showtext Then
         PaintText(Me.CreateGraphics())
      End If
   End Sub

   Private Sub PaintText(ByVal dc As Graphics)
      Dim pct As String = (Me.Value * 100 \ Me.Maximum).ToString & "%"
      Dim sz As System.Drawing.Size = TextRenderer.MeasureText(pct, Me.Font)
      Dim pt As New System.Drawing.PointF((dc.VisibleClipBounds.Width \ 2) - (sz.Width \ 2), _
                                          (dc.VisibleClipBounds.Height \ 2) - (sz.Height \ 2))

      Dim sb As New SolidBrush(Color.FromArgb(_alpha, _tcol.R, _tcol.G, _tcol.B))

      'TextRenderer.DrawText(dc, pct, Me.Font, pt, Color.White)
      dc.DrawString(pct, Me.Font, sb, pt)
   End Sub

This was quite a hack, because the "OnPaint" method that you can override is only called when ControlStyles.UserPaint is set, which then eliminates the background progress bar and you have to draw it yourself. I didn't want to, so I trapped the WndProc() msg to basically write my "Additional" code for the writing of the text. I've added user selected color and alpha for the text, but in only helps some, it doesn't "invert" the colors as in the above picture.

progme.jpg


now If i have to do all the painting to get the same effect as the above image, cest la vie, but I would think that would be unnecessary. I though about trying to do a Region.Xor() but the issue i'm having there is:
Defining a "Memory" region, painting to a "Memory" canvas, etc. In Delphi there was the "Canvas" class which was a memory DC and you could paint to your hearts content, and only when you wanted it visible could you copy it to a screen DC. the Graphics class has no constructors so I can't seem to find a parallel in VB.net.
Anyway, any suggestions would be helpful,

Thanks
 
Hi Jaeden,

Thanks for your explanation of what you are trying to do. I suspect you have more experience in programming than me, but I have spent a lot of time playing around with GDI+ graphics so perhaps I can offer a few ideas for what they are worth.

1. First of all, in GDI+ you can draw anything to a bitmap in memory. For example:
VB.NET:
Dim bmp As Bitmap
Using g As Graphics  = Graphics.FromImage(bmp)
   g.DrawImage... and so on
End Using
Then of course you can use the Paint event or the OnPaint method to copy the bitmap to the screen.

2. Some Windows Forms controls, among them ProgressBar, have a DrawToBitmap method which copies the control surface straight into a bitmap. You might find this useful.

3. I can think of several ways to draw contrasting text. For example, you could define a GraphicsPath and use the AddString method to draw the string. Then loop through the path's PathPoints, connecting up the dots with ControlPaint.DrawReversibleLine. My experience with this is that it is complicated, extremely slow and the result is illegible [appropriate smiley].

4. I think the best way to get the effect you want is to use clipping. For example, calculate a clipping rectangle from the ProgressBar's value, width and height. Set the Graphics.Clip to the blue area and draw the text on it in white. Then set the clip the white area, and draw the same text over it in black. Perfect, isn't it? (I hope so, I haven't tried it!)

5. A final note. Results in GDI+ are affected by several graphics properties. The ones you might want to use here are:
VB.NET:
PixelOffsetMode = Drawing2D.PixelOffsetMode.HighQuality
SmoothingMode = Drawing2D.SmoothingMode.HighQuality
For any image which changes rapidly, use Form.DoubleBuffered=True (much easier than SetStyles). Probably it won't matter for the small area of the progress bar.

All the best, Vic
 
Have a play with this custom control that inherits ProgressBar:
VB.NET:
Imports System.ComponentModel

Public Class TextProgressBar
    Inherits ProgressBar
    Private g As Graphics

    Protected Overrides Sub OnHandleCreated(ByVal e As System.EventArgs)
        MyBase.OnHandleCreated(e)
        g = Graphics.FromHwnd(Me.Handle)
    End Sub

    Protected Overrides Sub OnHandleDestroyed(ByVal e As System.EventArgs)
        g.Dispose()
        MyBase.OnHandleDestroyed(e)
    End Sub

    <EditorBrowsable(EditorBrowsableState.Always), Browsable(True)> _
    Public Overrides Property Font() As Font
        Get
            Return MyBase.Font
        End Get
        Set(ByVal value As Font)
            MyBase.Font = value
        End Set
    End Property

    Private Const WM_PAINT As Int32 = &HF
    Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
        MyBase.WndProc(m)
        If m.Msg = WM_PAINT Then
            TextOverlay()
        End If
    End Sub

    Private Sub TextOverlay()        
        Dim r1 As RectangleF = Me.ClientRectangle
        r1.Width = CSng(r1.Width * Me.Value / 100)
        Dim reg1 As New Region(r1)
        Dim reg2 As New Region(Me.ClientRectangle)
        reg2.Exclude(reg1)

        Dim text As String = Me.Value.ToString & "%"
        Dim textsize As SizeF = g.MeasureString(text, Me.Font)
        Dim x As Single = (Me.ClientRectangle.Width - textsize.Width) / 2
        Dim y As Single = (Me.ClientRectangle.Height - textsize.Height) / 2
        g.Clip = reg1
        g.DrawString(text, Me.Font, Brushes.White, x, y)
        g.Clip = reg2
        g.DrawString(text, Me.Font, Brushes.Black, x, y)
        reg1.Dispose()
        reg2.Dispose()
    End Sub
End Class
Short explanations; looking out for WM_PAINT messages in WndProc enables to react on the controls otherwise hidden Paint event. An additional graphics instance is created when control has a window handle, and this is used after control has painted the regular bar. As VicJ mentioned the graphics can be clipped for regions, enabling to draw same text with different brushes appearing only in the specified regions. Font property was also unhidden again.
Sample image: progtext.png
 
Awesome!

That worked beautifully, and I like the idea of maintaining the graphics handle internally to my stuff instead of always trying to "me.creategraphics()" or listen to the OnPaint method.

Now I'm on to the Marquee mode, I just don't like the way they implement it. But that will be for later.
Thanks!
 
Back
Top