更新:我认为它与MainForm的窗口句柄的延迟实例化有关 - 但是还没有能够解决这将导致这里看到的行为的方式。 < / p>
应用程序通过第三方COM接口请求数据,提供回调以处理结果。在回调中,UI需要更新 - 但更新不能按预期工作。这就好像创建了MainForm的值副本,当调用MainForm.DataReady
或直接跨线程调用时,但UI更新在从事件处理程序执行时按预期工作。你能解释一下原因吗?
(注意:AppDomain.CurrentDomain.Id
始终为1
,无论是在MainForm还是在ClassB中进行检查。)
初始代码 - 在MainForm中没有InvokeRequred / Delegate / Invoke逻辑的情况下从ClassB实例调用DataReady。应用程序UI更改按预期工作,MainForm SomeListControl.EmptyListMsg = "Not Available"
更改不会“粘贴”(就像应用于MainForm的单独副本一样)
Module AppGlobals
Public WithEvents A As ClassA
End Module
Partial Friend Class MyApplication
Private Sub MyApplication_Startup(ByVal sender As Object,
ByVal e As StartupEventArgs) Handles Me.Startup
A = New ClassA()
End Sub
End Class
Class MainForm
private sub getData
ToggleWait(True)
SomeListControl.Clear()
A.getData() 'Sets up the com object & callback
end sub
Public Sub DataReady()
ToggleWait(False)
' Do something with the data
End Sub
Private Sub ToggleWait(toggle as Boolean)
Application.UseWaitCursor = False
if toggle then
SomeListControl.EmptyListMsg = "Not Available"
else
SomeListControl.EmptyListMsg = "Please Wait"
end if
End Sub
End Class
Class ClassA
public sub getData()
Dim ComObj as New ComObject
Call ComObj.setClient(New ClassB)
End Sub
End Class
Class ClassB
Implements IComObjectClient
sub getdata_callback(results() as Object) handles IComObjectClient.getdata_callback
' Get the results
MainForm.DataReady()
end sub
End Class
将InvokeRequred逻辑添加到DataReady,仍然直接从ClassB 调用。 InvokeRequired永远不会成立,应用程序UI更改按预期工作,MainForm SomeListControl.EmptyListMsg = "Not Available"
更改不会“粘贴”(就像应用于MainForm的单独副本一样)
Class MainForm
Public Delegate Sub DataReadyDelegate(ByVal toggle As Boolean)
...
Public Sub DataReady()
If InvokeRequired Then
Invoke(New DataReadyDelegate()
Else
ToggleWait(False)
' Do something with the data
End If
End Sub
...
End Class
直接从ClassB调用MainForm.DataReady
得到异常:“在创建窗口句柄之前,无法在控件上调用Invoke或BeginInvoke。”直到我强制创建窗口句柄。然后它与以前的行为相同,即,InvokeRequired永远不会成立,应用程序UI更改按预期工作,MainForm SomeListControl.EmptyListMsg = "Not Available"
更改不会“粘贴”(就像应用于MainForm的单独副本一样)
Class ClassB
Implements IComObjectClient
Public Delegate Sub DataReadDelegate()
sub getdata_callback(results() as Object) handles IComObjectClient.getdata_callback
' Get the results
If Not MainForm.IsHandleCreated Then
' This call forces creation of the control's handle
Dim handle As IntPtr = MainForm.Handle
End If
MainForm.Invoke(New DataReadyDelegate(AddressOf MainForm.DataReady))
end sub
End Class
从事件处理程序执行在ClassA和ClassB中定义的自定义'获取数据'事件。 ClassA侦听ClassB.got_data_event并引发ClassA.got_data_event,MainForm侦听ClassA.got_data_event并通过调用DataReady()来处理它。这样做 - InvokeRequired为true,Invoke已被执行,Application UI和MainForm UI更改按预期工作。
Class MainForm
Public Delegate Sub DataReadyDelegate()
...
Public Sub DataReady()
If InvokeRequired Then
Invoke(New DataReadyDelegate()
Else
ToggleWait(False)
' Do something with the data
End If
End Sub
Public Sub _GotData_HandleEvent(ByVal resultMessage As String)
DataReady()
End Sub
Private Sub MainForm_Load(sender As Object, e As EventArgs) Handles Me.Load
...
ToggleWait(False)
AddHandler A.GotData, AddressOf _GotData_HandleEvent
...
End Sub
...
End Class
答案 0 :(得分:3)
对比:
A.getData()
使用:
If Not MainForm.IsHandleCreated Then
您在第一个语句中使用了正确的面向对象编程语法。 A是对象。 Form.IsHandleCreated属性是实例属性,它需要左侧的对象名称。但是,您使用了类型名称。 MainForm不是一个对象,它是代码中的一个类型。
这是可能的一个非常讨厌的VB.NET功能。它的存在是为了帮助VB6程序员转向VB.NET编码,强烈鼓励VB6使用表单的类型名称。在VB4实现类似对象的任何东西之前从VB1继承的语法。
现在这肯定是一种便利。您只需使用类型名称即可引用另一个类中的表单对象。请注意您如何不使用A对象获得这种便利。您通过将其作为全局变量将其存储在模块中来解决它。这也没有赢得任何价格,但确实允许你在任何一个班级中引用A.
问题是,当你开始在另一个线程中使用伪形式对象时,这种便利会变成致命。您没有指望的是此对象具有<ThreadLocal>
范围。换句话说,当您在工作线程中使用它时,您将获得MainForm类的 new 对象。此表单对象不可见,您从未调用其Show()方法。并不是说这会起作用,线程不会引发消息循环,因此表单不会正确地绘制自己。您观察到的另一个副作用是它的InvokeRequired属性不起作用。它返回False。正确地说,表单是在工作线程上创建的,因此您实际上不必使用BeginInvoke()。并非这也可以,它仍然是错误的对象,而不是用户正在看的对象。
因此,一个Q&amp; D解决方法是使用表单对象对A对象执行相同操作,将其存储在全局变量中:
Module AppGlobals
Public WithEvents A As ClassA
Public MainWindow As MainForm
End Module
并从类构造函数初始化它:
Class MainForm
Sub New()
InitializeComponent()
MainWindow = Me
End Sub
'' etc..
End Class
现在您可以在课程中引用MainWindow。并且您获得了用户正在查看的MainForm类的实际实例的引用。并从MainWindow.InvokeRequired获取正确的返回值。
这将解决您的问题,但它仍然很丑陋且容易出错。正确的方式如下:
Public Class MainForm
Private Shared MainWindow As MainForm
Public Shared ReadOnly Property Instance() As MainForm
Get
'' Return a reference to the one-and-only instance of MainForm
If MainWindow Is Nothing Then
'' It doesn't exist yet so create an instance
'' Creating one on a worker thread will never work, so complain
If System.Threading.Thread.CurrentThread.GetApartmentState() <> Threading.ApartmentState.STA Then
Throw New InvalidOperationException("Cannot create a window on a worker thread")
End If
New MainForm()
End If
Return MainWindow
End Get
End Property
Protected Overrides Sub OnFormClosed(ByVal e As System.Windows.Forms.FormClosedEventArgs)
'' Ensure that the one-and-only instance is now Nothing since it closed
MyBase.OnFormClosed(e)
MainWindow = Nothing
End Sub
Sub New()
'' Creating more than once instance of this form can't work, so complain
If MainWindow IsNot Nothing Then Throw New InvalidOperationException("Cannot create more than one instance of the main window")
InitializeComponent()
'' We need to keep track of this instance since the Instance property returns it
MainWindow = Me
End Sub
'' etc...
End Class
现在,您可以在类的任何位置使用MainForm.Instance,例如MainForm.Instance.InvokeRequired。当你因异常而弄错时,你会被提醒。