我正在尽力弄清楚如何解决这个错误:
跨线程操作无效:控制'ListView1'从其创建的线程以外的线程访问。
我有一个后台工作程序,它从excel工作表中提取单元格并将它们放入listview。
在表单加载时,我这样做:
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
然后我在用户选择文件后调用backgroundworker:
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
然后我处理获取excel列数和值,以便我可以用它填充listview:
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
错误在这一行:
ListView1.Items.Add(lvi)
我能做些什么才能纠正这个奇怪的问题?
谢谢!
大卫
答案 0 :(得分:0)
问题是只有UI线程才能更新UI。因为您要从工作人员向ListView
添加项目,所以您将收到此异常。
要解决此问题,您可以将要添加到列表视图中的项目存储在共享变量(UI线程和工作者都可以访问的变量)中,在表单上放置一个计时器(以便UI线程点击tick事件处理程序)并在tick处理程序中添加项目。
淡化示例(在C#中,因为它对我来说要快得多:-)):
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);
}
}
}
答案 1 :(得分:0)
只有UI线程可以访问UI。你的后台工作者在另一个线程上。您需要使用.InvokeRequired / .Invoke来返回UI线程。
http://msdn.microsoft.com/en-us/library/ms171728(v=vs.80).aspx
后台工作程序的事件处理程序将在主线程上引发;你可以安全地改变那里的用户界面。但实际上在后台线程中,你必须调用。像这样:
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