2

I am trying my best to figure out how to go about this error i am reciving:

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

I have a backgroundworker thats extracting cells from an excel worksheet and placing them into the listview.

Upon form load i do this:

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    Call createListView()
End Sub

Private Sub createListView()
    ListView1.View = View.Details
    ListView1.GridLines = True
    ListView1.FullRowSelect = True
    ListView1.HideSelection = False
    ListView1.MultiSelect = False

    ListView1.Columns.Add("Column Name", 150)
    ListView1.Columns.Add("Column Number", 150)
End Sub

Then i call the backgroundworker after the user selects a file:

If openFileDialog1.ShowDialog() = DialogResult.OK Then
        stFilePathAndName = openFileDialog1.FileName
        ProgressBar1.Style = ProgressBarStyle.Marquee
        BGWxml2excel = New System.ComponentModel.BackgroundWorker
        BGWxml2excel.WorkerReportsProgress = True
        BGWxml2excel.WorkerSupportsCancellation = True
        BGWxml2excel.RunWorkerAsync()
End If

Then i process with getting the excel column count and values so that i can populate the listview with it:

Private Sub BGWxml2excel_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BGWxml2excel.DoWork
    Call xml2Excel(stFilePathAndName)
End Sub

Private Sub xml2Excel(ByRef theDirOfFile As String)
    Dim xlsApp As Excel.Application
    Dim xlsWB As Excel.Workbook
    Dim xlsSheet As Excel.Worksheet
    Dim columnCount As Integer = 0

    xlsApp = New Excel.Application
    xlsApp.Visible = False
    xlsApp.DisplayAlerts = False

    xlsWB = xlsApp.Workbooks.OpenXML(Filename:=theDirOfFile, LoadOption:=XlXmlLoadOption.xlXmlLoadImportToList)
    xlsSheet = xlsWB.Worksheets(1)
    xlsSheet.Select()
    columnCount = xlsSheet.UsedRange.Columns.Count

    Dim lvi As New ListViewItem
    Dim x As Integer = 1

    Do Until x = columnCount + 1
        lvi.Text = xlsSheet.Cells(1, x).value
        lvi.SubItems.Add(x)

        ListView1.Items.Add(lvi)
        x = x + 1
    Loop

    'xlsSheet.SaveAs("c:\_tempExcelFile.xlsx", FileFormat:=51, CreateBackup:=False)
    xlsWB.Close()
    xlsApp.Quit()    
End Sub

The error is on this line:

ListView1.Items.Add(lvi)

What can i do in order to correct this odd proglem?

Thanks!

David

StealthRT
  • 10,108
  • 40
  • 183
  • 342

2 Answers2

0

The problem is that only the UI thread can update the UI. Because you are adding an item to the ListView from your worker, you are getting this exception.

To fix this, you could store items that you want to add to the listview in a shared variable (one to which both the UI thread and your worker have access), put a timer on your form (so the UI threads hits the tick event handler) and add the items within the tick handler.

Watered-down example (in C#, because it's a lot faster for me :-) ):

private List<ListViewItem> _itemsToBeAdded = new List<ListViewItem>();
private readonly object _lockObject = new object();

// worker method:
private void xml2Excel(string input)
{
    // do some processing...
    ListViewItem lvi = new ListViewItem();
    // set up lvi

    lock(_lockObject)
    {
        _itemsToBeAdded.Add(lvi);
    }
}

private void timer1_Tick(object sender, EventArgs e)
{
    lock(_lockObject)
    {
        foreach(var item in _itemsToBeAdded)
        {
            ListView1.Add(item);
        }
    }
}
Mark Avenius
  • 13,679
  • 6
  • 42
  • 50
0

Only the UI thread can access the UI. Your background worker is on another thread. You'll need to use .InvokeRequired/.Invoke to get back to the UI thread.

http://msdn.microsoft.com/en-us/library/ms171728(v=vs.80).aspx

The event handler's for the background worker will be raised on the main thread; you can safely alter the UI there. But actually in the background thread, you'll have to invoke. Like this:

Delegate Sub SetTextCallback([text] As String)

Private Sub SetText(ByVal [text] As String)

        ' InvokeRequired required compares the thread ID of the
        ' calling thread to the thread ID of the creating thread.
        ' If these threads are different, it returns true.
        If Me.textBox1.InvokeRequired Then
            Dim d As New SetTextCallback(AddressOf SetText)
            Me.Invoke(d, New Object() {[text]})
        Else
            Me.textBox1.Text = [text]
        End If
    End Sub
Rob P.
  • 14,921
  • 14
  • 73
  • 109