Rueben.Ramirez
Rueben.Ramirez

Reputation: 53

Cross-thread operation not valid - Need to use and update controls on UI thread from async thread

I have a program that builds report documents and am wanting to place the routine to build the report under a "DoWork" handler for a background worker. The initial part of the report is started, however, once I reference selected items in a combo box it stops executing?

Here is my code:

 Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
    ProgressBar1.Visible = True
    Application.EnableVisualStyles()
    ProgressBar1.Style = ProgressBarStyle.Marquee
    ProgressBar1.MarqueeAnimationSpeed = 10

    Dim x As New Thread(AddressOf buildReport)
    x.Start()
    MessageBox.Show("Build Complete")
    ProgressBar1.Visible = False
    GC.Collect()
    GC.WaitForPendingFinalizers()
    GC.Collect()
    GC.WaitForPendingFinalizers()
End Sub

'builds the report
Public Sub buildReport()
    Dim app As word.Application = New word.Application
    Dim document As word.Document
    Dim today As String()
    app.Visible = True
    document = app.Documents.Add("K:\ETL Test Files\" & mycallerPreview.previewInst.txtYear.Text & "\" & mycallerPreview.previewInst.txtVendor.Text & "\" & mycallerPreview.previewInst.txtReport.Text & "\Test Report\Report Data\ReportTemplate.doc") 'open up template

    'document.Styles.Add("Contents1")
    'document.Styles.Add("Contents2")
    'document.Styles.Add("Contents3")
    'add info to pre-made bookmarks
    today = Date.Today.ToString.Split(" ")
    document.Bookmarks("Date").Range.Text = today(0).ToString
    document.Bookmarks("Date1").Range.Text = today(0).ToString
    document.Bookmarks("Date2").Range.Text = today(0).ToString
    document.Bookmarks("Date3").Range.Text = today(0).ToString
    document.Bookmarks("Approver").Range.Text = mycallerPreview.mycallerSelect2.txtChecked.Text.ToString
    document.Bookmarks("Number2").Range.Text = mycallerPreview.mycallerSelect2.txtReportNumber.Text.ToString
    document.Bookmarks("Number1").Range.Text = mycallerPreview.mycallerSelect2.txtReportNumber.Text.ToString
    document.Bookmarks("Vendor").Range.Text = mycallerPreview.previewInst.txtVendor.Text.ToString
    document.Bookmarks("Test1").Range.Text = mycallerPreview.mycallerSelect2.txtReportTitle.Text.ToString
    document.Bookmarks("TestTitle").Range.Text = mycallerPreview.mycallerSelect2.txtReportTitle.Text.ToString
    If mycallerPreview.mycallerSelect2.cmbName.SelectedIndex <> -1 Then
        document.Bookmarks("Writer").Range.Text = mycallerPreview.mycallerSelect2.cmbName.SelectedItem.ToString
        document.Bookmarks("Reviewer").Range.Text = mycallerPreview.mycallerSelect2.cmbName.SelectedItem.ToString
    End If
    If mycallerPreview.mycallerSelect2.cmbQuote.SelectedIndex <> -1 Then
        document.Bookmarks("Quote").Range.Text = mycallerPreview.mycallerSelect2.cmbQuote.SelectedItem.ToString
    End If

All bookmarks in my word document are filled in until it reaches the combo box references which are at the bottom of the "DoWork" handler. Any suggestions?

Update:

As suggested, I tried thread synchronization ...

    Dim x As New Thread(AddressOf buildReport)
    x.Start()

This doesn't solve my problem but gave me the following exception:

Cross-thread operation not valid: Control 'cmbName' accessed from a thread other than the thread it was created on.

Revised:

 'garbage collects and initializes progress bar to default values
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
    'create list of objects to pass through ThreadStart Method
    Dim list As New List(Of Object)
    If mycallerPreview.mycallerSelect2.cmbName.SelectedIndex <> -1 Then
        list.Add(mycallerPreview.mycallerSelect2.cmbName.SelectedItem.ToString)
    End If
    If mycallerPreview.mycallerSelect2.cmbQuote.SelectedIndex <> -1 Then
        list.Add(mycallerPreview.mycallerSelect2.cmbQuote.SelectedItem.ToString)
    End If
    list.Add(ProgressBar1)

    Dim x As New Thread(AddressOf buildReport)
    x.Start(list)
    MessageBox.Show("Build Complete")
    GC.Collect()
    GC.WaitForPendingFinalizers()
    GC.Collect()
    GC.WaitForPendingFinalizers()
End Sub

'builds the report
Public Sub buildReport(list_temp As Object)
    Dim progress As New ProgressBar
    progress = list_temp(2)
    progress.Visible = True
    Application.EnableVisualStyles()
    progress.Style = ProgressBarStyle.Marquee
    progress.MarqueeAnimationSpeed = 10
    Dim list As List(Of Object) = list_temp
    Dim app As word.Application = New word.Application
    Dim document As word.Document
    Dim today As String()
    app.Visible = True
    document = app.Documents.Add("K:\ETL Test Files\" & mycallerPreview.previewInst.txtYear.Text & "\" & mycallerPreview.previewInst.txtVendor.Text & "\" & mycallerPreview.previewInst.txtReport.Text & "\Test Report\Report Data\ReportTemplate.doc") 'open up template

    'add info to pre-made bookmarks
    today = Date.Today.ToString.Split(" ")
    document.Bookmarks("Date").Range.Text = today(0).ToString
    document.Bookmarks("Date1").Range.Text = today(0).ToString
    document.Bookmarks("Date2").Range.Text = today(0).ToString
    document.Bookmarks("Date3").Range.Text = today(0).ToString
    document.Bookmarks("Approver").Range.Text = mycallerPreview.mycallerSelect2.txtChecked.Text.ToString
    document.Bookmarks("Number2").Range.Text = mycallerPreview.mycallerSelect2.txtReportNumber.Text.ToString
    document.Bookmarks("Number1").Range.Text = mycallerPreview.mycallerSelect2.txtReportNumber.Text.ToString
    document.Bookmarks("Vendor").Range.Text = mycallerPreview.previewInst.txtVendor.Text.ToString
    document.Bookmarks("Test1").Range.Text = mycallerPreview.mycallerSelect2.txtReportTitle.Text.ToString
    document.Bookmarks("TestTitle").Range.Text = mycallerPreview.mycallerSelect2.txtReportTitle.Text.ToString
    document.Bookmarks("Writer").Range.Text = list(0).ToString
    document.Bookmarks("Reviewer").Range.Text = list(0).ToString
    document.Bookmarks("Quote").Range.Text = list(1).ToString

Upvotes: 0

Views: 263

Answers (1)

Michael Z.
Michael Z.

Reputation: 1473

To clarify the issue you are having:
You need to access a ComboBox and a ProgressBar from another thread. You originally used BackgroundWorker which apparently either swallowed your cross-thread error or you swallowed it in a Try-Catch. Either way, you changed it to Thread and made the Cross Thread Operation Not Valid visible.

This error "Cross-Thread Operation Not Valid" arises when you try to access a user control from a thread other than its own. It's important to be able to modify these controls so how do we do it?

First, you modify the method you want to be async to accept a parameter. This should be an object so you can pass as much information into your async that will be needed for the task.

Here is your modified method to include the object as a parameter.

Public Sub buildReport(list_temp As Object)

In your code you passed in the ComboxBox text rather than a reference to the ComboBox. This is why that part does work. Then you pass in a reference to your ProgressBar. When you accessed the progressbar from your async method, you did so without invoking a delegate. What this means is that you have to create a method on the UI thread that updates your control. You then declare a delegate that will be called from the async method.

Here is an example of a button starting a thread that updates the TextBox text. You'll need a TextBox and a Button for this example.

First, you need to declare a delegate and an instance of that delegate. You'll also need to create the method that modifies the control you want because you need to pass that method name into the delegate instance declaration.

Public Delegate Sub SetTextBoxDelegate(Text As String)
    Public SetTextbox_UI_Thread As SetTextBoxDelegate = New SetTextBoxDelegate(AddressOf SetTextBox)

Public Sub SetTextBox(Text As String)
    TextBox1.Text = Text
End Sub

Now here is the button click which starts the thread:

Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
    Dim t As System.Threading.Thread = New Threading.Thread(AddressOf DoStuff)
    t.Start()
End Sub

As you can see, it is starting a thread using the method DoStuff(). This is the method that invokes our delegate (if needed) to update the textbox.

Public Sub DoStuff()
    System.Threading.Thread.Sleep(3000)
    If TextBox1.InvokeRequired Then
        TextBox1.Invoke(SetTextbox_UI_Thread, "Hello")
    Else
        TextBox1.Text = "Hello"
    End If
End Sub

Note that I first checked if InvokeRequired = True because you can call this method from the UI thread so then you could just access the controls as you normally would.

Upvotes: 1

Related Questions