使用.NET类库显示表单以响应COM事件会冻结表单

时间:2018-07-16 22:00:41

标签: c# .net vb.net com

我正在创建一个.NET组件,我想使用它来简化与支持COM Automation接口的应用程序的工作。该组件的功能之一是它将显示对话框以从用户那里收集信息。但是,在我需要使用的工作流程中,对话框会部分显示,然后冻结。

我已经创建了我使用Excel所做的简化版本。我实际上正在使用具有COM Automation接口的另一个应用程序,但是该问题是一个普遍的问题,可以使用大多数人应该使用的Excel进行重现。这是各个部分的简要说明。我正在用Visual Basic编写此代码,但假定C#也会发生相同的问题,尽管我尚未对此进行测试。

.NET类库项目“ MyCmd”

MyDef类

创建MyDef类的实例时,它将获取并保存当前活动的Excel工作表。它还侦听Excel Sheet对象的SelectionChange事件。作为对SelectionChange事件的响应,它将创建“ MyCmd”类的一个实例(如下所述),并触发自定义CommandCreated事件,并传递创建的MyCmd对象。

它还支持一个名为ForceEvent的公共方法,该方法与SelectionChange事件具有相同的作用。这用于使用不同的代码路径来测试功能。

Imports Microsoft.Office.Interop.Excel

Public Class MyDef
    Private WithEvents _sheet As Microsoft.Office.Interop.Excel.Worksheet
    Public Event CommandCreated(ByVal command As MyCmd)

    Public Sub New()
        _sheet = g_app.ActiveSheet
    End Sub

    Public Sub ForceEvent()
        Dim cmd As New MyCmd()
        RaiseEvent CommandCreated(cmd)
    End Sub

    Private Sub _sheet_SelectionChange(Target As Range) Handles _sheet.SelectionChange
        Me.ForceEvent()
    End Sub
End Class

MyCmd类

包含一个方法的类,该方法创建对话框的新实例并显示它。

Public Class MyCmd
    Private _form As Form = Nothing

    Public Sub ShowDialog()
        If _form Is Nothing Then
            _form = New CommandDialog
        End If

        _form.Visible = True
    End Sub
End Class

我正在测试的对话框是一个窗体(名为“ CommandDialog”),上面有几个按钮,但后面没有代码。我只是测试看看表单是否按预期显示。

全局模块

我还使用下面的代码连接到Excel并获取Excel Application对象。它按预期工作。

Public Module Globals
    Private _app As Microsoft.Office.Interop.Excel.Application = Nothing

    Public Function g_app() As Object
        If Not _app Is Nothing Then
            Return _app
        Else
            Try
                _app = GetObject(, "Excel.Application")
            Catch ex As Exception
                MsgBox("Excel must be running")
                Return Nothing
            End Try
            Return _app
        End If
    End Function
End Module

.NET类库项目“ MyCmd”

我还有另一个名为“ CommandFramework”的项目,该项目正在使用MyCmd类库。第二个项目是Windows Form App,其中包含两个按钮和MyDef支持的CommandCreated事件的事件处理程序。整个代码如下。

单击第一个按钮时,它将创建MyDef类的实例,由于WithEvent声明,该实例也为CommandCreated事件设置了事件处理程序。单击第二个按钮将调用ForceEvent方法,该方法将引发CommandCreated事件并显示对话框。我还可以在活动的Excel工作表中单击一个单元格,该单元格将引发SelectionChange事件并导致调用ForceEvent方法。但是,在这种情况下,对话框仅部分显示并被冻结。除了触发器(单击第二个按钮或接收Excel SelectionChange事件)外,代码流是相同的。第二种情况导致冻结。

Public Class CommandFramework
    Private WithEvents _cmdDef As MyCommand.MyDef = Nothing

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        _cmdDef = New MyCommand.MyDef
    End Sub

    Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
        _cmdDef.ForceEvent()
    End Sub

    Private Sub cmdDef_CommandCreated(command As MyCommand.MyCmd) Handles _cmdDef.CommandCreated
        command.ShowDialog()
    End Sub
End Class

有人对问题的根源和解决方案有任何想法吗?我的猜测是这是某种线程问题,并且存在僵局,但我对该领域了解不足,甚至无法开始诊断或尝试其他事情。

1 个答案:

答案 0 :(得分:1)

尝试继承处理StandardOleMarshalObject中的COM事件的所有类:

public class Page {
  private int pageNum;

  public Page(int pageNum) {
    this.pageNumber = pageNum;
  }

  public int getPageNum() {
    return pageNum;
  }

这将确保您在最初创建.NET对象的同一线程(大概是主STA线程)上接收事件回调。看看是否有帮助。

请注意,它仍可能会出现死锁或重新入库问题,这是COM编组中的典型问题,特别是如果您从Excel事件处理程序中调用另一个Excel API。理想情况下,您应该尽快从事件处理程序中返回,并且可能使用Public Class MyDef Inherits System.Runtime.InteropServices.StandardOleMarshalObject ... Public Class CommandFramework Inherits System.Runtime.InteropServices.StandardOleMarshalObject SynchronizationContext.Current.Post()之类的方法异步处理需要完成的任何事情。但是在异步情况下,您仍然应该考虑可能的可重入性,例如当您仍在处理上一个调用时再次调用事件处理程序时。

我有一些相关的问题: