Progress Bar OnPaint Marquee Mode, Thread error?

JaedenRuiner

Well-known member
Joined
Aug 13, 2007
Messages
340
Programming Experience
10+
Well, i'm completely editing this post because well, i've figured out how to do quite a bit of what I want by just overriding the entire Paint methods and doing it my way from scratch.

If you set the UserPaint style to the control, the OnPaint/OnPaintBackground events fire, which means the background is drawn but I have to draw the bar. Not much of a issue, i've done the 2 Label progress bar hack before, so drawing the continuous bar shouldn't be an issue.

But then I went to work on my Marquee animation. I got it working so even in design time I could actually see it functioning, (though I will probably take that out later).

So I figured I'd Run the application, (which makes a call to the form with the component and sets it initially in Marquee mode.

Now, in order to get the "cycling" write, I'd either have to call the marque step function from within my form, (similarly to how one would have to set the continuous "progress value" and I really didn't want to do that, so I decided to handle the motion internally...with a Timer.

When running the application though (for it works find in design mode) as soon as the marquee starts and the timer triggers the "step" function (my personal step not the built in one) I get a thread access violation error from the Forms Assembly when I try to set the ProgressBar.Value property. I'm not creating any threads so this is a built in .net thing. What's going on and what would be the recommended course of action to clear this up?

thanks

VB.NET:
Public Class FlufProgBar
   Inherits System.Windows.Forms.ProgressBar

   Private Shared MarqueeChars() As Char = {"/", "-", "\", "|"}

   Private WithEvents _tmr As New Timers.Timer()
   Private _alpha As Byte = 192
   Private _mqwidth As Integer
   Private _showtext As Boolean = True
   Private _mcI As Integer = 0
   Private _mcDir As Integer = 1
   Private _mqDir As Integer = 1
   Private _br As New SolidBrush(Color.White)
   Private _tcol As System.Drawing.Color
   Private _msize As System.Drawing.SizeF
   Private _mqpos As System.Drawing.RectangleF


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

   <Browsable(True)> _
   Public Property ShowText() As Boolean
      Get
         Return _showtext
      End Get
      Set(ByVal value As Boolean)
         _showtext = value
         Me.Invalidate()
      End Set
   End Property

   <Browsable(True)> _
   Public Property TextColor() As Color
      Get
         Return _tcol
      End Get
      Set(ByVal value As Color)
         _tcol = Color.FromArgb(_alpha, value.R, value.G, value.B)
         Me.Invalidate()
      End Set
   End Property

   <Browsable(True)> _
   Public Property TextAlpha() As Byte
      Get
         Return _alpha
      End Get
      Set(ByVal value As Byte)
         _alpha = value
         TextColor = _tcol
      End Set
   End Property

   <Browsable(False)> _
   Public Property MarqueeSize() As System.Drawing.SizeF
      Get
         Return _msize
      End Get
      Set(ByVal value As System.Drawing.SizeF)
         _msize = value
         Me.Invalidate()
      End Set
   End Property

   <Browsable(True)> _
   Public Property MarqueeWidth() As Integer
      Get
         Return _mqwidth
      End Get
      Set(ByVal value As Integer)
         _mqwidth = value
         _msize.Width = CSng(_mqwidth * Me.ClientRectangle.Width) / Me.Maximum
         Me.Invalidate()
      End Set
   End Property

   <Browsable(True)> _
   Public Property MarqueeOn() As Boolean
      Get
         Return _tmr.Enabled
      End Get
      Set(ByVal value As Boolean)
         If (Me.Style = System.Windows.Forms.ProgressBarStyle.Marquee) AndAlso (_tmr.Enabled <> value) Then
            _tmr.Enabled = value
         End If
      End Set
   End Property

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

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

   Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
      Select Case Me.Style
         Case ProgressBarStyle.Blocks
         Case ProgressBarStyle.Continuous
            PaintContinuous(e)
         Case ProgressBarStyle.Marquee
            PaintMarquee(e)
      End Select
      MyBase.OnPaint(e)
   End Sub

   Public Shadows Property MarqueeAnimationSpeed() As Integer
      Get
         Return MyBase.MarqueeAnimationSpeed
      End Get
      Set(ByVal value As Integer)
         MyBase.MarqueeAnimationSpeed = value
         _tmr.Interval = value
      End Set
   End Property

   Private Sub PaintContinuous(ByVal e As PaintEventArgs)
      Dim rc As RectangleF = New RectangleF(e.ClipRectangle.Location, _
                                    New SizeF(CSng(e.ClipRectangle.Width * Me.Value / Me.Maximum), e.ClipRectangle.Height))
      Dim r2 As New Region(rc)
      _br.Color = ForeColor
      e.Graphics.FillRectangle(_br, rc)
      If _showtext Then
         Dim r1 As New Region(e.ClipRectangle)
         PaintText(e, (Me.Value * 100 \ Me.Maximum).ToString & "%", r1, r2)
         r1.Dispose()
      End If
      r2.Dispose()
   End Sub

   Private Sub PaintMarquee(ByVal e As PaintEventArgs)
      Dim rc As RectangleF = e.ClipRectangle
      Dim pos As Single = CSng(Value * rc.Width / Me.Maximum)
      Dim comp As Single = _msize.Width - CSng(Value * _msize.Width / Me.Maximum)
      _mqpos.Size = _msize
      _mqpos.X = pos - comp
      _br.Color = ForeColor
      e.Graphics.FillRectangle(_br, _mqpos)
      If _showtext Then
         Dim r1 As New Region(rc)
         Dim r2 As New Region(_mqpos)
         PaintText(e, MarqueeChars(_mcI), r1, r2)
         r1.Dispose()
         r2.Dispose()
      End If
   End Sub

   Private Sub DoMarqueeStep()
      Value += (Me.Step * _mqDir)
      If (Value >= Maximum OrElse Value <= 0) Then
         _mqDir *= -1
         _mcDir *= -1
      End If
      _mcI += _mcDir
      If (_mcI < 0 OrElse _mcI > 3) Then
         _mcI = IIf(_mcDir = 1, 0, 3)
      End If
   End Sub

   Private Sub PaintText(ByVal e As PaintEventArgs, ByVal text As String, ByVal r1 As Region, ByVal r2 As Region)
      If _showtext Then
         Dim rc As RectangleF = e.ClipRectangle
         Dim sz As System.Drawing.SizeF = e.Graphics.MeasureString(text, Me.Font)
         Dim pt As New System.Drawing.PointF((rc.Width - sz.Width) / 2, (rc.Height - sz.Height) / 2)
         _br.Color = _tcol
         e.Graphics.Clip = r1
         e.Graphics.DrawString(text, Me.Font, _br, pt)
         _br.Color = Color.FromArgb(_alpha, BackColor.R, BackColor.G, BackColor.B)
         r1.Exclude(r2)
         e.Graphics.Clip = r2
         e.Graphics.DrawString(text, Me.Font, _br, pt)
      End If
   End Sub

   Public Sub New()
      MyBase.New()
      SetStyle(ControlStyles.UserPaint, True)
      TextColor = Me.ForeColor
      MarqueeWidth = 15
      _mqpos = New RectangleF(Me.ClientRectangle.Location, _msize)
      _tmr.Enabled = False
      _tmr.Interval = Me.MarqueeAnimationSpeed
   End Sub

   Private Sub _tmr_Elapsed(ByVal sender As Object, ByVal e As System.Timers.ElapsedEventArgs) Handles _tmr.Elapsed
      DoMarqueeStep()
      'Invalidate()
   End Sub
End Class
 
Last edited:
You're using the Timers.Timer, which is threaded, setting SynchronizingObject property will solve it (for example with Parent/OnParentChanged). You can also use the regular Forms.Timer.
 
Back
Top