1

更新:我认为这与 MainForm 的窗口句柄的延迟实例化有关 - 但无法弄清楚这将如何导致此处看到的行为。

应用程序通过第 3 方 COM 接口请求数据,提供回调以处理结果。在回调中,需要更新 UI - 但更新没有按预期工作。就好像 MainForm 的值副本已创建,何时MainForm.DataReady直接跨线程调用或调用,但 UI 更新在从事件处理程序执行时按预期工作。你能解释一下为什么吗?

(注意:AppDomain.CurrentDomain.Id总是1在 MainForm 或 ClassB 中检查。)

初始代码- 从 ClassB 实例调用 DataReady,而 MainForm 中没有 InvokeRequred /Delegate /Invoke 逻辑。应用程序 UI 更改按预期工作,MainFormSomeListControl.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

向 DataReady 添加了 InvokeRequred 逻辑,仍然直接从 ClassB 调用。InvokeRequired 永远不会为真,应用程序 UI 更改按预期工作,MainFormSomeListControl.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

MainForm.DataReady直接从 ClassB调用得到异常:“在创建窗口句柄之前,不能对控件调用 Invoke 或 BeginInvoke。” 直到我强制创建窗口句柄。然后它与以前的行为相同,即 InvokeRequired 永远不会为真,应用程序 UI 更改按预期工作,MainFormSomeListControl.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
4

1 回答 1

3

对比:

  A.getData() 

和:

  If Not MainForm.IsHandleCreated Then

您在第一条语句中使用了正确的面向对象编程语法。A 是一个对象。Form.IsHandleCreated 属性是一个实例属性,它需要左侧的对象名称。但是,您使用了类型名称。MainForm 不是对象,它是代码中的一种类型。

这是可能的,这是一个非常讨厌的 VB.NET 功能。它的存在是为了帮助 VB6 程序员转向 VB.NET 编码,VB6 强烈建议使用表单的类型名称。在 VB4 实现任何类似对象之前从 VB1 继承的语法。

现在这肯定是一种方便。您可以通过简单地使用类型名称来引用另一个类中的表单对象。请注意您没有使用A 对象的便利。您通过将其设置为全局变量并将其存储在模块中来解决它。这也不会赢得任何价格,但确实允许您在任何课程中引用 A。

问题是,当您开始在另一个线程中使用假表单对象时,这种便利变得致命。你没想到的是这个对象有<ThreadLocal>范围。换句话说,当你在工作线程中使用它时,你会得到一个MainForm 类的对象。这个表单对象是不可见的,你从来没有调用过它的 Show() 方法。并不是说这会起作用,线程不会泵送消息循环,因此表单不会正确地绘制自己。您观察到的另一个副作用是其 InvokeRequired 属性不起作用。它返回 False。没错,表单是在工作线程上创建的,因此您实际上不必使用 BeginInvoke()。并不是说这也行得通,它仍然是错误的对象,而不是用户正在查看的对象。

因此,一种 Q&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。当您遇到异常时会提醒您。

于 2013-01-11T22:51:13.490 回答