我有两节课。
Public Class MainForm
Private Project As clsProject
Private Sub btnDo_Click
...
Backgroundworker.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Project = New clsProject
End Sub
和MainForm中的两个方法
Public Shared Sub setLabelTxt(ByVal text As String, ByVal lbl As Label)
If lbl.InvokeRequired Then
lbl.Invoke(New setLabelTxtInvoker(AddressOf setLabelTxt), text, lbl)
Else
lbl.Text = text
End If
End Sub
Public Delegate Sub setLabelTxtInvoker(ByVal text As String, ByVal lbl As Label)
end class
我想从clsProject构造函数更新MainForm的标签。
MainForm.setLabelTxt("Getting prsadasdasdasdasdry..", MainForm.lblProgress)
但它不会更新它们。 我做错了什么?
答案 0 :(得分:2)
问题是您正在使用全局MainForm
实例来访问后台线程中的标签:
Public Class clsProject
Public Sub New()
' When accessing MainForm.Label1 on the next line, it causes an exception
MainForm.setLabelTxt("HERE!", MainForm.Label1)
End Sub
End Class
调用MainForm.setLabelTxt
是可以的,因为这是一个共享方法,所以它不会通过全局实例来调用它。但是,当您访问Label1
属性时,那就是利用VB.NET的技巧来访问表单的全局实例。在非UI线程中显然不允许通过该auto-global-instance变量(总是与该类型共享同一名称)使用该表单。执行此操作时,它会抛出InvalidOperationException
,并显示以下错误消息:
创建表单时出错。有关详细信息,请参阅Exception.InnerException。错误是:ActiveX控件'8856f961-340a-11d0-a96b-00c04fd705a2'无法实例化,因为当前线程不在单线程单元中。
我猜你没有看到错误的原因是因为你正在某个地方捕获异常并且你只是忽略它。如果您停止使用该全局实例变量,则错误消失并且可以正常工作。例如,如果您将构造函数更改为:
Public Class clsProject
Public Sub New(f As MainForm)
' The next line works because it doesn't use the global MainForm instance variable
MainForm.setLabelTxt("HERE!", f.Label1)
End Sub
End Class
然后,在MainForm
中,你必须这样称呼它:
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Project = New clsProject(Me) ' Must pass Me
End Sub
不允许使用后台线程中的全局实例,但是当我们使用后台线程中的相同标签时,不经过该全局变量就可以工作。
所以很明显你不能使用后台线程中的全局MainForm
变量,但可能不清楚的是,永远使用它是个坏主意。首先,它令人困惑,因为它与MainForm
类型的名称相同。更重要的是,它是一个全局变量,如果可以避免的话,任何形式的全球状态几乎总是不好的做法。
虽然上面的例子确实解决了这个问题,但它仍然是一种非常糟糕的方式。更好的选择是将setLabelTxt
方法传递给clsProject
对象,或者更好的是clsProject
只需在需要更改标签时引发事件。然后,MainForm
可以简单地监听这些事件并在它们发生时处理它们。最终,clsProject
类可能是某种业务类,无论如何都不应该进行任何类型的UI工作。
答案 1 :(得分:1)
尝试:
Me.Invoke(...)
而不是lbl.Invoke(...
。我不得不这样做。这是我的实施:
Delegate Sub SetTextDelegate(ByVal args As String)
Private Sub SetTextBoxInfo(ByVal txt As String)
If txtInfo.InvokeRequired Then
Dim md As New SetTextDelegate(AddressOf SetTextBoxInfo)
Me.Invoke(md, txt)
Else
txtInfo.Text = txt
End If
End Sub
这对我有用。
答案 2 :(得分:1)
您无法直接从BackgroundWorker
对GUI元素执行任何操作。 “克服”的一种方法是强制通过Me.Invoke
从主线程执行给定的动作;但这不是理想的程序。此外,您的代码混合了主窗体和外部类(+共享/非共享对象),这使得整个结构不太稳固。
一个可靠的工作解决方案依赖于特定的BGW方法来处理GUI元素;例如:ProgressChanged Event
。示例代码:
Public Class MainForm
Private Project As clsProject
Public Shared bgw As System.ComponentModel.BackgroundWorker
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
bgw = BackgroundWorker1 'Required as far as you want to called it from a Shared method
BackgroundWorker1.WorkerReportsProgress = True
BackgroundWorker1.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker1_DoWork(sender As System.Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Project = New clsProject
End Sub
Public Shared Sub setLabelTxt(ByVal text As String)
bgw.ReportProgress(0, text) 'You can write any int as first argument as far as will not be used anyway
End Sub
Private Sub BackgroundWorker1_ProgressChanged(sender As Object, e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
Me.Label1.Text = e.UserState 'You can access the given GUI-element directly
Me.Label1.Update()
End Sub
End Class
Public Class clsProject
Public Sub New()
MainForm.setLabelTxt("Getting prsadasdasdasdasdry..")
End Sub
End Class