Results 1 to 14 of 14

Thread: Modal Wait Dialogue with BackgroundWorker

  1. #1

    Thread Starter
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,274

    Modal Wait Dialogue with BackgroundWorker

    C# version here.

    This is something that I've been meaning to do for some time but never got around to previously. Earlier today - and not for the first time - I saw someone trying to display a modal wait dialogue while they did some work and going about it in very much the wrong way.

    This demo provides a dialogue that you can display modally over another form while that form does some background work. You basically write a normal DoWork event handler for a BackgroundWorker, as well as optional ProgresssChanged and RunWorkerCompleted event handlers, and pass them to the dialogue for it to use as handlers for the events of its own BackgroundWorker. That means that your form creates and displays the dialogue, the dialogue then kicks off a BackgroundWorker and it invokes methods in your form to do the work. That way, this same dialogue can be used for any work you like without change.

    The dialogue displays a Marquee ProgressBar by default and no Cancel button. If you provide a handler for the ProgressChanged event, it will display a Continuous ProgressBar so the actual progress can be displayed. Note that the Maximum of that ProgressBar is 100 so you must provide a genuine percentage when you call ReportProgress. If you set SupportsCancellation to True, the dialogue will also display a Cancel button. As always, it's up to you to process the cancellation request in your DoWork event handler.

    I have replaced the original attachment with one that includes a few improvements, as well as a C# version of the project. This version was created in VS 2019 and targets .NET Framework 4.8. If you're using an earlier version, you should still just be able to add the forms as existing items to a project of your own, as long as you're not using too-old a version.

    See post #9 for simple instructions on how to add this dialogue form to your existing project.
    Attached Files Attached Files
    Last edited by jmcilhinney; Aug 14th, 2020 at 03:26 AM. Reason: Updated attachment and provided links to simple instructions for beginners.

  2. #2

    Thread Starter
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,274

    Re: Modal Wait Dialogue with BackgroundWorker

    For those who would like to see the code without having to download the solution, here you go:
    vb.net Code:
    1. Imports System.ComponentModel
    2.  
    3. Public Class BackgroundWorkerForm
    4.  
    5. #Region "Constructors"
    6.  
    7.     ''' <summary>
    8.     ''' Creates a new instance of the <see cref="BackgroundWorkerForm"/> class.
    9.     ''' </summary>
    10.     ''' <remarks>
    11.     ''' Parameterless constructor is private to ensure handlers are provided for <see cref="BackgroundWorker"/>.
    12.     ''' </remarks>
    13.     Private Sub New()
    14.         ' This call is required by the designer.
    15.         InitializeComponent()
    16.     End Sub
    17.  
    18.     ''' <summary>
    19.     ''' Creates a new instance of the <see cref="BackgroundWorkerForm" /> class.
    20.     ''' </summary>
    21.     ''' <param name="onDoWork">
    22.     ''' Handler for the <see cref="BackgroundWorker.DoWork">RunWorkerCompleted</see> event of a <see cref="BackgroundWorker"/>.
    23.     ''' </param>
    24.     Public Sub New(onDoWork As DoWorkEventHandler)
    25.         Me.New()
    26.  
    27.         'Remote event handlers
    28.         AddHandler BackgroundWorker1.DoWork, onDoWork
    29.  
    30.         'Local event handlers
    31.         AddHandler BackgroundWorker1.RunWorkerCompleted, AddressOf BackgroundWorker1_RunWorkerCompleted
    32.     End Sub
    33.  
    34.     ''' <summary>
    35.     ''' Creates a new instance of the <see cref="BackgroundWorkerForm" /> class.
    36.     ''' </summary>
    37.     ''' <param name="onDoWork">
    38.     ''' Handler for the <see cref="BackgroundWorker.DoWork">RunWorkerCompleted</see> event of a <see cref="BackgroundWorker"/>.
    39.     ''' </param>
    40.     ''' <param name="onProgressChanged">
    41.     ''' Handler for the <see cref="BackgroundWorker.ProgressChanged">RunWorkerCompleted</see> event of a <see cref="BackgroundWorker"/>.
    42.     ''' </param>
    43.     Public Sub New(onDoWork As DoWorkEventHandler,
    44.                    onProgressChanged As ProgressChangedEventHandler)
    45.         Me.New()
    46.  
    47.         'Remote event handlers
    48.         AddHandler BackgroundWorker1.DoWork, onDoWork
    49.         AddHandler BackgroundWorker1.ProgressChanged, onProgressChanged
    50.  
    51.         'Local event handlers
    52.         AddHandler BackgroundWorker1.ProgressChanged, AddressOf BackgroundWorker1_ProgressChanged
    53.         AddHandler BackgroundWorker1.RunWorkerCompleted, AddressOf BackgroundWorker1_RunWorkerCompleted
    54.  
    55.         'A ProgressChanged handler has been provided so the ProgressBar will be updated explicitly based on the BackgroundWorker.
    56.         BackgroundWorker1.WorkerReportsProgress = True
    57.         ProgressBar1.Style = ProgressBarStyle.Continuous
    58.     End Sub
    59.  
    60.     ''' <summary>
    61.     ''' Creates a new instance of the <see cref="BackgroundWorkerForm" /> class.
    62.     ''' </summary>
    63.     ''' <param name="onDoWork">
    64.     ''' Handler for the <see cref="BackgroundWorker.DoWork">RunWorkerCompleted</see> event of a <see cref="BackgroundWorker"/>.
    65.     ''' </param>
    66.     ''' <param name="onRunWorkerCompleted">
    67.     ''' Handler for the <see cref="BackgroundWorker.RunWorkerCompleted">RunWorkerCompleted</see> event of a <see cref="BackgroundWorker"/>.
    68.     ''' </param>
    69.     Public Sub New(onDoWork As DoWorkEventHandler,
    70.                    onRunWorkerCompleted As RunWorkerCompletedEventHandler)
    71.         Me.New()
    72.  
    73.         'Remote event handlers
    74.         AddHandler BackgroundWorker1.DoWork, onDoWork
    75.         AddHandler BackgroundWorker1.RunWorkerCompleted, onRunWorkerCompleted
    76.  
    77.         'Local event handlers
    78.         AddHandler BackgroundWorker1.RunWorkerCompleted, AddressOf BackgroundWorker1_RunWorkerCompleted
    79.     End Sub
    80.  
    81.     ''' <summary>
    82.     ''' Creates a new instance of the <see cref="BackgroundWorkerForm" /> class.
    83.     ''' </summary>
    84.     ''' <param name="onDoWork">
    85.     ''' Handler for the <see cref="BackgroundWorker.DoWork">RunWorkerCompleted</see> event of a <see cref="BackgroundWorker"/>.
    86.     ''' </param>
    87.     ''' <param name="onProgressChanged">
    88.     ''' Handler for the <see cref="BackgroundWorker.ProgressChanged">RunWorkerCompleted</see> event of a <see cref="BackgroundWorker"/>.
    89.     ''' </param>
    90.     ''' <param name="onRunWorkerCompleted">
    91.     ''' Handler for the <see cref="BackgroundWorker.RunWorkerCompleted">RunWorkerCompleted</see> event of a <see cref="BackgroundWorker"/>.
    92.     ''' </param>
    93.     Public Sub New(onDoWork As DoWorkEventHandler,
    94.                    onProgressChanged As ProgressChangedEventHandler,
    95.                    onRunWorkerCompleted As RunWorkerCompletedEventHandler)
    96.         Me.New()
    97.  
    98.         'Remote event handlers
    99.         AddHandler BackgroundWorker1.DoWork, onDoWork
    100.         AddHandler BackgroundWorker1.ProgressChanged, onProgressChanged
    101.         AddHandler BackgroundWorker1.RunWorkerCompleted, onRunWorkerCompleted
    102.  
    103.         'Local event handlers
    104.         AddHandler BackgroundWorker1.ProgressChanged, AddressOf BackgroundWorker1_ProgressChanged
    105.         AddHandler BackgroundWorker1.RunWorkerCompleted, AddressOf BackgroundWorker1_RunWorkerCompleted
    106.  
    107.         'A ProgressChanged handler has been provided so the ProgressBar will be updated explicitly based on the BackgroundWorker.
    108.         BackgroundWorker1.WorkerReportsProgress = True
    109.         ProgressBar1.Style = ProgressBarStyle.Continuous
    110.     End Sub
    111.  
    112. #End Region 'Constructors
    113.  
    114. #Region "Properties"
    115.  
    116.     Public WriteOnly Property SupportsCancellation As Boolean
    117.         Set
    118.             BackgroundWorker1.WorkerSupportsCancellation = Value
    119.  
    120.             'If the worker can be cancelled, show the Cancel button and make the form big enough to see it.
    121.             cancelWorkButton.Visible = Value
    122.             Height = If(Value, 115, 86)
    123.         End Set
    124.     End Property
    125.  
    126. #End Region 'Properties
    127.  
    128.     Private Sub BackgroundWorkerForm_Shown(sender As Object, e As EventArgs) Handles Me.Shown
    129.         'Start the background work when the form is displayed.
    130.         BackgroundWorker1.RunWorkerAsync()
    131.     End Sub
    132.  
    133.     Private Sub cancelWorkButton_Click(sender As Object, e As EventArgs) Handles cancelWorkButton.Click
    134.         'Disable the button to prevent another click.
    135.         cancelWorkButton.Enabled = False
    136.  
    137.         'Cancel the background work.
    138.         BackgroundWorker1.CancelAsync()
    139.     End Sub
    140.  
    141.     Private Sub BackgroundWorker1_ProgressChanged(sender As Object, e As ProgressChangedEventArgs)
    142.         'Update the ProgressBar.
    143.         ProgressBar1.Value = e.ProgressPercentage
    144.     End Sub
    145.  
    146.     Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs)
    147.         'Close the form when the work is done.
    148.         Close()
    149.     End Sub
    150.  
    151. End Class
    Here's the code for the demo main form:
    vb.net Code:
    1. Imports System.ComponentModel
    2. Imports System.Threading
    3.  
    4. Public Class Form1
    5.  
    6.     Private Sub BackgroundWorkerForm_DoWork(sender As Object, e As DoWorkEventArgs)
    7.         'Simulate some time-consuming work.
    8.         For i = 0 To 100
    9.             Dim worker = DirectCast(sender, BackgroundWorker)
    10.  
    11.             If worker.CancellationPending Then
    12.                 e.Cancel = True
    13.  
    14.                 Exit For
    15.             End If
    16.  
    17.             If reportProgressCheckBox.Checked Then
    18.                 worker.ReportProgress(i)
    19.             End If
    20.  
    21.             Thread.Sleep(100)
    22.         Next
    23.     End Sub
    24.  
    25.     'This method will be executed by the BackgroundWorker in the dialogue.
    26.     Private Sub BackgroundWorkerForm_ProgressChanged(sender As Object, e As ProgressChangedEventArgs)
    27.         statusLabel.Text = (e.ProgressPercentage / 100).ToString("p0")
    28.     End Sub
    29.  
    30.     'This method will be executed by the BackgroundWorker in the dialogue.
    31.     Private Sub BackgroundWorkerForm_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs)
    32.         statusLabel.Text = If(e.Cancelled, "Operation cancelled", "Operation complete")
    33.     End Sub
    34.  
    35.     Private Sub runButton_Click(sender As Object, e As EventArgs) Handles runButton.Click
    36.         'Display the dialogue to initiate the background work.
    37.         Using waitDialogue = GetWaitDialogue()
    38.             waitDialogue.ShowDialog()
    39.         End Using
    40.     End Sub
    41.  
    42.     Private Function GetWaitDialogue() As BackgroundWorkerForm
    43.         Dim dialogue As BackgroundWorkerForm
    44.  
    45.         'Only provide a ProgressChanged handler if the corresponding CheckBox is checked.
    46.         If reportProgressCheckBox.Checked Then
    47.             dialogue = New BackgroundWorkerForm(AddressOf BackgroundWorkerForm_DoWork,
    48.                                                 AddressOf BackgroundWorkerForm_ProgressChanged,
    49.                                                 AddressOf BackgroundWorkerForm_RunWorkerCompleted)
    50.         Else
    51.             dialogue = New BackgroundWorkerForm(AddressOf BackgroundWorkerForm_DoWork,
    52.                                                 AddressOf BackgroundWorkerForm_RunWorkerCompleted)
    53.         End If
    54.  
    55.         'Only display a Cancel button if the corresponding CheckBox is checked.
    56.         dialogue.SupportsCancellation = allowCancellationCheckBox.Checked
    57.  
    58.         Return dialogue
    59.     End Function
    60. End Class
    Last edited by jmcilhinney; Jan 24th, 2019 at 05:38 PM. Reason: Removed erroneous Handles clause.

  3. #3

    Thread Starter
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,274

    Re: Modal Wait Dialogue with BackgroundWorker

    Actually, there's one small mistake in the original code I posted. The BackgroundWorker1_ProgressChanged method should not have a Handles clause on it. It won't stop things working but it means that that method will be executed twice when progress is enable.

  4. #4
    PowerPoster
    Join Date
    Feb 2016
    Location
    Tennessee
    Posts
    2,437

    Re: Modal Wait Dialogue with BackgroundWorker

    Great code! Thanks a bunch...

  5. #5
    New Member
    Join Date
    Apr 2017
    Posts
    3

    Re: Modal Wait Dialogue with BackgroundWorker

    Thank you! Works in VS 2015 with .net 4.5.2 out of the box.

  6. #6
    New Member
    Join Date
    Apr 2017
    Posts
    3

    Re: Modal Wait Dialogue with BackgroundWorker

    Thanks for the example, I have one more request that I think would help me understand the full circle. If I wanted to add a label in the BackgroundWorkerForm and update the label from Form1 during the background work to add a label showing textual work along with the progress bar. I tried a few things using invoke and begininvoke but have not had any luck. Care to expand? Thank you!

  7. #7

    Thread Starter
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,274

    Re: Modal Wait Dialogue with BackgroundWorker

    Quote Originally Posted by grapegummy View Post
    Thanks for the example, I have one more request that I think would help me understand the full circle. If I wanted to add a label in the BackgroundWorkerForm and update the label from Form1 during the background work to add a label showing textual work along with the progress bar. I tried a few things using invoke and begininvoke but have not had any luck. Care to expand? Thank you!
    In the ProgressChanged event handler, the sender parameter is a reference to the wait dialogue. You can use that to access any members of that dialogue you like. If you want to be able to update a Label on that dialogue form then add a method that does that to the class and call it in that event handler.

  8. #8
    New Member
    Join Date
    Apr 2017
    Posts
    3

    Re: Modal Wait Dialogue with BackgroundWorker

    Thank you!

  9. #9

    Thread Starter
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,274

    Adding BackgroundWorkerForm to an Existing Project

    If you have an existing project that you would like to add this wait dialogue to but you're not sure how, you can follow the instructions below.

    1. Open the Solution Explorer, right-click your project and select Add -> Existing Item.
    2. Navigate to the project folder for the demo project and select the BackgroundWorkerForm.vb file. The BackgroundWorkerForm type should be added to your project.
    3. Open your own form and add the following field declaration to it, importing the System.ComponentModel namespace if required:
    vb.net Code:
    1. Private WithEvents BackgroundWorkerForm As BackgroundWorker
    4. Use the navigation bar at the top of the code window to create the required event handlers.
    4a. Select BackgroundWorkerForm in the middle drop-down and then select DoWork in the right-hand drop-down.
    4b. Repeat the step above for ProgressChanged and RunWorkerCompleted if required.
    5. Delete the field declaration that you added in step 3.
    6. Delete the Handles clauses from the event handlers you created in step 4.
    7. Add the appropriate code to each of the event handlers just as you would for any BackgroundWorker. If you intend for the dialogue's ProgressBar to display actual progress, your calls to ReportProgress must pass values in the range 0 to 100.
    8. Where you want to display the dialogue and perform the work in your own code, create an instance of the BackgroundWorkerForm with the appropriate event handlers and show it, e.g.
    vb.net Code:
    1. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    2.     Using waitDialogue As New BackgroundWorkerForm(AddressOf BackgroundWorkerForm_DoWork)
    3.         waitDialogue.ShowDialog()
    4.     End Using
    5. End Sub
    9. If you would like to be able to cancel the work before it completes, set the SupportsCancellation property to True, e.g.
    vb.net Code:
    1. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    2.     Using waitDialogue As New BackgroundWorkerForm(AddressOf BackgroundWorkerForm_DoWork,
    3.                                                    AddressOf BackgroundWorkerForm_ProgressChanged,
    4.                                                    AddressOf BackgroundWorkerForm_RunWorkerCompleted) With {.SupportsCancellation = True}
    5.         waitDialogue.ShowDialog()
    6.     End Using
    7. End Sub

  10. #10
    New Member
    Join Date
    Feb 2021
    Posts
    2

    Re: Adding BackgroundWorkerForm to an Existing Project

    Hello jmcilhinney, very useful code, thank you very much.
    I tried it in combination of OpenFileDialog() but seems not working, there is an "Exception thrown: 'System.Threading.ThreadStateException' in System.Windows.Forms.dll" do you know how to resolve it ?

  11. #11

    Thread Starter
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,274

    Re: Adding BackgroundWorkerForm to an Existing Project

    Quote Originally Posted by t0ret0s View Post
    Hello jmcilhinney, very useful code, thank you very much.
    I tried it in combination of OpenFileDialog() but seems not working, there is an "Exception thrown: 'System.Threading.ThreadStateException' in System.Windows.Forms.dll" do you know how to resolve it ?
    If it didn't work then you did it wrong. I don't know what you did so I have no idea what you did wrong. Did someone tell you I was psychic?

    That said, why would you try it with an OpenFileDialog in the first place? The whole point of this is to give the user something to look at and provide feedback while work is performed in the background. How exactly is an OpenFileDialog background work?

  12. #12
    New Member
    Join Date
    Feb 2021
    Posts
    2

    Re: Adding BackgroundWorkerForm to an Existing Project

    Quote Originally Posted by jmcilhinney View Post
    If it didn't work then you did it wrong. I don't know what you did so I have no idea what you did wrong. Did someone tell you I was psychic?

    That said, why would you try it with an OpenFileDialog in the first place? The whole point of this is to give the user something to look at and provide feedback while work is performed in the background. How exactly is an OpenFileDialog background work?
    The idea is to check some files into the computer, in case the exact file is missing, I want to let user select it.
    This is the first approach code :

    Code:
            Dim worker = DirectCast(sender, BackgroundWorker)
    
            'Simulate some time-consuming work.
            For i = 0 To 100
                If worker.CancellationPending Then
                    e.Cancel = True
    
                    Exit For
                End If
    
                If reportProgressCheckBox.Checked Then
                    worker.ReportProgress(i)
                End If
    
                If i = 50 Then
    
                    If MsgBox("Do you want to select it manually ?", CType(vbYesNo + vbQuestion, Global.Microsoft.VisualBasic.MsgBoxStyle), "test test test test") = vbYes Then
                        Dim myStream As Stream = Nothing
                        Dim openFileDialog1 As New OpenFileDialog()
    
                        openFileDialog1.Title = "File " & "2_333333"
                        openFileDialog1.InitialDirectory = "C:\"
                        openFileDialog1.Filter = "pdf files (*.pdf)|*.pdf|All files (*.*)|*.*|" & ".Test" & "|*" & ".Test"
                        openFileDialog1.FilterIndex = 2
                        openFileDialog1.RestoreDirectory = True
    
    
                        openFileDialog1.ShowDialog() 'here the error is generated
    
                        If openFileDialog1.ShowDialog() = DialogResult.OK Then
                            Try
                                myStream = openFileDialog1.OpenFile()
                                If (myStream IsNot Nothing) Then
                                    myStream.Close()
                                    MsgBox(openFileDialog1.FileName)
                                End If
                            Catch Ex As Exception
                                MessageBox.Show("Cannot read file from disk. Original error: " & Ex.Message)
                            Finally
                                If (myStream IsNot Nothing) Then
                                    myStream.Close()
                                End If
                            End Try
                        End If
                    End If
                End If
    
                Thread.Sleep(100)
            Next

    Another thing : if I use modal wait, is not possible to debug in case of error or exception, is it correct ?

  13. #13

    Thread Starter
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,274

    Re: Adding BackgroundWorkerForm to an Existing Project

    Quote Originally Posted by t0ret0s View Post
    The idea is to check some files into the computer, in case the exact file is missing, I want to let user select it.
    This is the first approach code :

    Code:
            Dim worker = DirectCast(sender, BackgroundWorker)
    
            'Simulate some time-consuming work.
            For i = 0 To 100
                If worker.CancellationPending Then
                    e.Cancel = True
    
                    Exit For
                End If
    
                If reportProgressCheckBox.Checked Then
                    worker.ReportProgress(i)
                End If
    
                If i = 50 Then
    
                    If MsgBox("Do you want to select it manually ?", CType(vbYesNo + vbQuestion, Global.Microsoft.VisualBasic.MsgBoxStyle), "test test test test") = vbYes Then
                        Dim myStream As Stream = Nothing
                        Dim openFileDialog1 As New OpenFileDialog()
    
                        openFileDialog1.Title = "File " & "2_333333"
                        openFileDialog1.InitialDirectory = "C:\"
                        openFileDialog1.Filter = "pdf files (*.pdf)|*.pdf|All files (*.*)|*.*|" & ".Test" & "|*" & ".Test"
                        openFileDialog1.FilterIndex = 2
                        openFileDialog1.RestoreDirectory = True
    
    
                        openFileDialog1.ShowDialog() 'here the error is generated
    
                        If openFileDialog1.ShowDialog() = DialogResult.OK Then
                            Try
                                myStream = openFileDialog1.OpenFile()
                                If (myStream IsNot Nothing) Then
                                    myStream.Close()
                                    MsgBox(openFileDialog1.FileName)
                                End If
                            Catch Ex As Exception
                                MessageBox.Show("Cannot read file from disk. Original error: " & Ex.Message)
                            Finally
                                If (myStream IsNot Nothing) Then
                                    myStream.Close()
                                End If
                            End Try
                        End If
                    End If
                End If
    
                Thread.Sleep(100)
            Next

    Another thing : if I use modal wait, is not possible to debug in case of error or exception, is it correct ?
    That makes no sense at all. Like I said, the whole point of this is to give the user something to look at while the application is doing work in the background and they can't use the UI. If you expect them select a file using a dialogue then that is the very definition of using the UI so why are you even trying to use this dialogue or do anything in the background? There's no reason for you to be using this dialogue so don't. That is the last I will say on the matter.

  14. #14
    Frenzied Member
    Join Date
    Apr 2016
    Posts
    1,415

    Re: Modal Wait Dialogue with BackgroundWorker

    Thank you, John

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