Results 1 to 4 of 4

Thread: Control background worker exception when program closes

  1. #1

    Thread Starter
    Hyperactive Member
    Join Date
    Apr 2007
    Location
    cobwebbed to PC
    Posts
    311

    Control background worker exception when program closes

    Hi Folks,

    I have created a windows form control derived from the ComboBox to allow visual feedback from the control to confirm if the required change is complete or not after the SelectedIndex is changed. This is because the change actually sends data to an external device by writing to the serial port and then reading back the same item to confirm the change. If the data on the device has not been changed as intended the user needs to know about that.

    The first part of this visual feedback is undoing the SelectedIndex change. So the derived control always saves the current index. It then overrides the SelectedIndexChanging event, raising a SelectedIndexChanging event in its place. This allows the system to cancel the event if the change was not confirmed which then replaces the selected index with the previously saved, old, index, undoing the change.

    This works fine, but with lots of controls on a form it's hard to notice that one in particular hasn't changed as a user thought it should.

    Thus I added in a color change and fade-out to the BackColor. Now when a change is made and is not cancelled the background of the control changes to green, then fades back to white. If it is cancelled then the background changes to red before also fading back to white.

    The fading is achieved by adding a BackgroundWorker to the control which should complete when the color has faded to white. So far, so good. The problem comes in however when a user closes the application and the BackgroundWorker is still running, then the ReportProgress() method call causes an InvalidOperationException with the message "Invoke or BeginInvoke cannot be called on a control until the window handle has been created.". I assume this is because the BackgroundWorker is trying to call an Invoke type method on the control that, at this point, has been disposed.

    I tried several avenues of attack to overcome the issue: an Applications.DoEvents() in the form's FormClosing handler (this seemed to help somewhat but not completely), adding multiple checks for CancellationPending in the DoWork event of the BackgroundWorker, but nothing solve the issue completely.

    I imagine I can add a Dispose() override to the derived control to wait for the BackgroundWorker to complete before calling MyBase.Dispose() might prevent the exception but what is the correct and reliable way to do that? And how can I avoid the BackgroundWorker taking ages to actually complete and thus noticeably affecting my applications close time?
    Or is there a better way to have the BackgroundWorker work?

    (For this minor BackgroundWorker work I wasn't really interested in switching to using threads directly unless achieving this using a BackgroundWorker would prove prohibitive.)

    The derived control class code:

    vb Code:
    1. Public Class BetterComboBox
    2.     Inherits ComboBox
    3.  
    4.     Public Event SelectedIndexChanging As System.ComponentModel.CancelEventHandler 'Create a new event
    5.  
    6.     Private WithEvents FadingWorker As New System.ComponentModel.BackgroundWorker
    7.     Private _lastIndex As Integer = -1
    8.     Private Const _fade As Double = 17
    9.     Private Const _fadeInterval As Double = 150
    10.  
    11.     <System.ComponentModel.Browsable(False)>
    12.     Public ReadOnly Property LastIndex As Integer
    13.         Get
    14.             Return _lastIndex
    15.         End Get
    16.     End Property
    17.  
    18.     Public Sub New()
    19.         MyBase.New()
    20.         _lastIndex = -1 'Initialise the last index as the default first index.
    21.         FadingWorker.WorkerReportsProgress = True
    22.         FadingWorker.WorkerSupportsCancellation = True
    23.     End Sub
    24.  
    25.     Protected Overridable Sub OnSelectedIndexChanging(e As System.ComponentModel.CancelEventArgs)
    26.         RaiseEvent SelectedIndexChanging(Me, e) 'Raise the new event
    27.     End Sub
    28.  
    29.     Protected Overrides Sub OnSelectedIndexChanged(e As System.EventArgs)
    30.         If _lastIndex <> SelectedIndex Then
    31.             Dim cancelEventArgs As New System.ComponentModel.CancelEventArgs()
    32.             OnSelectedIndexChanging(cancelEventArgs) 'Raise the new event
    33.  
    34.             If cancelEventArgs.Cancel = False Then 'Check the response from the event
    35.                 _lastIndex = SelectedIndex 'Save the index change
    36.                 BackColor = Color.FromArgb(0, &HFF, 0) 'Change BG color to green
    37.                 If Not FadingWorker.IsBusy Then 'Check the worker isnt already started
    38.                     FadingWorker.RunWorkerAsync(Me) 'Start the worker
    39.                 End If
    40.                 MyBase.OnSelectedIndexChanged(e) 'Raise the normal event
    41.             Else
    42.                 BackColor = Color.FromArgb(&HFF, 0, 0) 'Change BG color to red
    43.                 If Not FadingWorker.IsBusy Then 'Check the worker isnt already started
    44.                     FadingWorker.RunWorkerAsync(Me) 'Start the worker
    45.                 End If
    46.                 SelectedIndex = _lastIndex 'undo the index change
    47.             End If
    48.         End If
    49.     End Sub
    50.  
    51.     Private Sub FadingWorker_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles FadingWorker.DoWork
    52.         Dim worker As System.ComponentModel.BackgroundWorker = CType(sender, System.ComponentModel.BackgroundWorker)
    53.         While Not worker.CancellationPending 'Check the worker wasn't cancelled during progress reporting
    54.             System.Threading.Thread.Sleep(_fadeInterval) ' Wait N milliseconds
    55.             If Not worker.CancellationPending Then 'Check the worker wasn't cancelled during the delay
    56.                 FadingWorker.ReportProgress(0, e.Argument) ' Signal the main thread to adjust the color
    57.             End If
    58.         End While
    59.     End Sub
    60.  
    61.     Private Sub FadingWorker_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles FadingWorker.ProgressChanged
    62.         Dim cb As BetterComboBox = CType(e.UserState, BetterComboBox)
    63.        
    64.         ' Code that fades the BackgroundColor RGB value here
    65.  
    66.         If r = g = b = &HFF Then 'Check if the BackColor has reached white
    67.             FadingWorker.CancelAsync() 'Cancel the BackgroundWorker
    68.         End If
    69.     End Sub
    70.  
    71.     Protected Overrides Sub Dispose(ByVal disposing As Boolean)
    72.         'Use this to wait for the background worker to be die before disposing
    73.         If Not Me.IsDisposed Then
    74.             If disposing Then
    75.                 'Free managed resources here
    76.             End If
    77.             'Free unmanaged resources here
    78.         End If
    79.         MyBase.Dispose(disposing)
    80.     End Sub
    81. End Class

    PS: Also, is a BackgroundWorker considered managed or unmanaged?

    Many thanks!! T
    Thanks

  2. #2

    Thread Starter
    Hyperactive Member
    Join Date
    Apr 2007
    Location
    cobwebbed to PC
    Posts
    311

    Re: Control background worker exception when program closes

    As my BackgroundWroker takes the form of:

    wait n msec
    do some work
    repeat

    Maybe I should switch to using a System.Timer instead?
    Would I see problems (such as UI sometimes becoming unresponsive) with using a timer when there are multiple instances of this control on a form?
    Thanks

  3. #3
    Smooth Moperator techgnome's Avatar
    Join Date
    May 2002
    Posts
    34,532

    Re: Control background worker exception when program closes

    You need to cancel the BGW when the form closes... the only call to cancel is in the progress changed event... but the problem is that when the form closes, you're not cancelling the BGW, so it's still running.

    -tg
    * I don't respond to private (PM) requests for help. It's not conducive to the general learning of others.*
    * I also don't respond to friend requests. Save a few bits and don't bother. I'll just end up rejecting anyways.*
    * How to get EFFECTIVE help: The Hitchhiker's Guide to Getting Help at VBF - Removing eels from your hovercraft *
    * How to Use Parameters * Create Disconnected ADO Recordset Clones * Set your VB6 ActiveX Compatibility * Get rid of those pesky VB Line Numbers * I swear I saved my data, where'd it run off to??? *

  4. #4

    Thread Starter
    Hyperactive Member
    Join Date
    Apr 2007
    Location
    cobwebbed to PC
    Posts
    311

    Re: Control background worker exception when program closes

    D'oh! So I don't!!
    Just added that to my Dispose()
    Thanks

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width