Rueben.Ramirez Rueben.Ramirez - 6 months ago 32
Vb.net Question

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

Answer

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 accesses the progressbar from your async method, yhou 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.