【问题标题】:VB.NET: "AddHandler" by specifying Handler function as a variableVB.NET:“AddHandler”通过将 Handler 函数指定为变量
【发布时间】:2019-08-26 06:20:55
【问题描述】:

我正在为 Autodesk Inventor(一个 3D CAD 程序)编写插件。该插件将一堆内部命令添加到 Inventor,并为每个命令创建一个工具栏按钮。我需要将每个命令的 Execute 事件连接到我的加载项中的处理程序函数。

我创建了一个“MyCommand”类,其中包含定义命令所需的所有信息。我还创建了一个 List(Of MyCommand) ,其中包含每个命令的定义。最后,我可以遍历每个命令并创建内部 Inventor 命令定义,以及将按钮添加到工具栏。但是,我不知道如何将命令与它的处理函数关联在循环内

下面的示例代码说明了我所追求的:

Sub CreateCommands()
    Dim oCommands As New List(Of MyCommand)

    oCommands.Add(New MyCommand("DrawLogoCmd", "Draw Logo", "DrawLogoSub"))
    oCommands.Add(New MyCommand("PublishDrawingCmd", "Publish Drawing", "PublishDrawingSub"))
    ' [Dozens more commands]

    For Each oCommand As MyCommand In oCommands
        ' Code for adding internal command definition and button to Inventor. This is working fine.
        Dim oCommandDef As Inventor.CommandDefinition = InventorApp.CommandDefinitions.Add(oCommand.InternalDefinitionName, oCommand.DisplayName)
        InventorApp.ToolbarButtons.Add(oCommandDef)

        ' Associate command definition with handler function
        ' ===== THIS IS THE LINE I NEED TO FIGURE OUT =====
        AddHandler oCommandDef.OnExecute, AddressOf oCommand.HandlerSubName
    Next
End Sub

Sub DrawLogoSub()
    ' [My add-in's code to draw logo in Inventor]
End Sub

Sub PublishDrawingSub()
    ' [My add-in's code to publish drawing in Inventor]
End Sub

Class MyCommand
    Public InternalDefinitionName As String
    Public DisplayName As String
    Public HandlerSubName As String

    Sub New(InternalDefinitionName As String, DisplayName As String, HandlerSubName As String)
        With Me
            .InternalDefinitionName = InternalDefinitionName
            .DisplayName = DisplayName
            .HandlerSubName = HandlerSubName
        End With
    End Sub
End Class

那么,这可能吗?有没有办法使用它的名称作为字符串来获取“AddressOf”我的函数?或者,有什么方法可以在我的 MyCommand 类中存储对函数本身的引用,并将其传递给 AddressOf 运算符?

或者,除了“AddHandler/AddressOf”之外还有其他方法可以吗?

欢迎提出任何建议。

【问题讨论】:

    标签: vb.net event-handling


    【解决方案1】:

    是的,您可以使用反射按名称找到方法,但我不推荐它。 AddHandler 命令通常被赋予AddressOf 一个方法,但它也可以接受一个委托,只要它符合事件的签名。因此,除非您需要通过字符串来执行此操作,否则我建议您改为执行此类操作。假设事件匹配正常的EventHandler委托:

    Sub CreateCommands()
        Dim oCommands As New List(Of MyCommand)
    
        oCommands.Add(New MyCommand("DrawLogoCmd", "Draw Logo", AddressOf DrawLogo))
        oCommands.Add(New MyCommand("PublishDrawingCmd", "Publish Drawing", AddressOf PublishDrawing))
        ' [Dozens more commands]
    
        For Each oCommand As MyCommand In oCommands
            ' Code for adding internal command definition and button to Inventor. This is working fine.
            Dim oCommandDef As Inventor.CommandDefinition = InventorApp.CommandDefinitions.Add(oCommand.InternalDefinitionName, oCommand.DisplayName)
            InventorApp.ToolbarButtons.Add(oCommandDef)
    
            ' Associate command definition with handler function
            AddHandler oCommandDef.OnExecute, oCommand.Handler
        Next
    End Sub
    
    Sub DrawLogo(sender As Object, e As EventArgs)
        ' [My add-in's code to draw logo in Inventor]
    End Sub
    
    Sub PublishDrawing(sender As Object, e As EventArgs)
        ' [My add-in's code to publish drawing in Inventor]
    End Sub
    
    Class MyCommand
        Public InternalDefinitionName As String
        Public DisplayName As String
        Public Handler As EventHandler
    
        Sub New(InternalDefinitionName As String, DisplayName As String, Handler As EventHandler)
            With Me
                .InternalDefinitionName = InternalDefinitionName
                .DisplayName = DisplayName
                .Handler = Handler
            End With
        End Sub
    End Class
    

    如果你真的需要通过字符串来做,你可以像这样使用反射(注意我使用NameOf而不是将方法名称硬编码为字符串文字,只是为了让它更安全):

    Sub CreateCommands()
        Dim oCommands As New List(Of MyCommand)
    
        oCommands.Add(New MyCommand("DrawLogoCmd", "Draw Logo", NameOf(DrawLogo)))
        oCommands.Add(New MyCommand("PublishDrawingCmd", "Publish Drawing", NameOf(PublishDrawing)))
        ' [Dozens more commands]
    
        For Each oCommand As MyCommand In oCommands
            ' Code for adding internal command definition and button to Inventor. This is working fine.
            Dim oCommandDef As Inventor.CommandDefinition = InventorApp.CommandDefinitions.Add(oCommand.InternalDefinitionName, oCommand.DisplayName)
            InventorApp.ToolbarButtons.Add(oCommandDef)
    
            ' Associate command definition with handler function
            AddHandler oCommandDef.OnExecute, Sub(sender As Object, e As EventArgs) CallMethod(oCommand.Handler)
        Next
    End Sub
    
    Private Sub CallMethod(name As String)
        Me.GetType().GetMethod(name).Invoke(Me, Nothing)
    End Sub
    
    Sub DrawLogo()
        ' [My add-in's code to draw logo in Inventor]
    End Sub
    
    Sub PublishDrawing()
        ' [My add-in's code to publish drawing in Inventor]
    End Sub
    

    【讨论】:

    • 很好的建议,谢谢。我正在尝试第一个,但在AddHandler oCommandDef.OnExecute, oCommand.Handler 行上,oCommand.Handler 部分给出了以下错误:“'EventHandler' 类型的值不能转换为'ButtonDefinitionSink_OnExecuteEventHandler'。”。我尝试使用“Sink”类型而不是“EventHandler”,但它没有定义。而且我找不到任何关于它的文档,无论是在 vb.net 文档还是 Inventor 中。有什么想法吗?
    • 在对象浏览器中搜索它。它必须是在 Audodesk 库中公开为公共类型的自定义委托类型。如果找不到,请查看Inventor.CommandDefinition 类中OnExecute 事件的定义,它应该会显示它的完全限定名称。
    • 谢谢,我在 Inventor 节点下的对象浏览器中找到了它。我不确定为什么它会在那里列出,但 IntelliSense 不会提出来……无论如何,看起来这会起作用(或者,至少,编译)。如果它功能正常,我会在这里发布。同时,您能解释一下为什么不建议使用反射吗?会导致哪些类型的问题或不良行为?
    • 反射绕过了编译器的类型检查。换句话说,如果您更改了方法的名称,但忘记更改字符串文字,它仍然可以编译,但会在运行时抛出异常。
    • 它工作正常!我很激动。这将使添加命令和按钮变得更加容易。我不得不调整一两个其他的东西,比如将WithEvents 添加到oCommands 声明中。但在那之后,我可以简单地通过定义它们的内部名称、显示名称和处理程序子来添加多个按钮。感谢您的帮助。
    【解决方案2】:

    您希望代码将字符串映射到方法引用。我不再 100% 清楚语法,但它可能看起来像这样:

    Dim actionMap As Dictionary(Of string, Action(Of Object, EventArgs))
    actionMap.Add("DrawLogo", AddressOf DrawLogo)
    actionmap.Add("PublishDrawing", AddressOf PublishDrawing)
    

    然后For Each 循环可以像这样使用字典:

    For Each oCommand As MyCommand In oCommands
        Dim oCommandDef As Inventor.CommandDefinition = InventorApp.CommandDefinitions.Add(oCommand.InternalDefinitionName, oCommand.DisplayName)
        InventorApp.ToolbarButtons.Add(oCommandDef)
    
        AddHandler oCommandDef.OnExecute, actionMap(oCommand.HandlerSubName)
    Next
    

    当我在这里时,我将添加从 VB6/VBScript 到 .Net 的迁移,Microsoft 更改了他们的 VB 样式指南。他们现在不再推荐像 o 这样的匈牙利疣,而是根据 .Net 更强烈地使用类型的方式和更好的工具支持特别推荐反对这些疣。前缀只是不添加它们以前的值,现在您可以直接写 command 而不是 oCommand,这往往更具可读性。

    【讨论】:

    • 感谢词典的建议。 Steven 的建议需要更少的开销,所以我将首先遵循它。如果它不起作用,我会尝试字典。
    • 另外,感谢有关“疣”的提示。我喜欢尽可能坚持标准。所以你是说即使我有一个名为“Command”的类型,我也可以说Dim Command As Command
    • @DRoam 是的。变量名称可以与类型名称相同,没有任何问题。编译器根据上下文知道你是指类型还是变量。
    • VB.Net 允许名称匹配,尽管这不是很好的做法。最好给变量一个不同的名称,也可以传达一些上下文。使用问题中类型名称的示例代码将是 Dim command As MyCommandDim commands As New List(Of MyCommand)
    • 很棒的提示,谢谢。很高兴知道这一点。很难打破匈牙利符号的习惯,但我会尽力而为;)
    猜你喜欢
    • 1970-01-01
    • 2013-05-20
    • 2021-01-16
    • 2011-07-12
    • 1970-01-01
    • 2019-09-26
    • 1970-01-01
    • 1970-01-01
    • 2017-03-14
    相关资源
    最近更新 更多