【问题标题】:Pass Command Line to first instance of a Single Instance App将命令行传递给单实例应用程序的第一个实例
【发布时间】:2015-03-03 12:23:57
【问题描述】:

当用户使用注册表在 Windows 资源管理器中右键单击文件时,我已经实现了上下文菜单。文件地址将作为命令行传递给应用程序。解析没问题。

如何实现类似于“添加到 Windows Media Player 播放列表”的功能?它不会打开应用程序的另一个实例,而是在同一个打开的窗口上工作并将其添加到列表中?

【问题讨论】:

    标签: .net vb.net


    【解决方案1】:

    根据应用的启动方式,有 2 种方法可以做到这一点。

    方法一:使用VB App Framework和MainForm

    这是最简单的,因为您主要只需要为应用程序事件添加一些代码。首先,向您的主窗体添加一个方法,以接收来自应用程序后续实例的新参数:

    Public Class MyMainForm       ' note the class name of the form
        ...
    
        Public Sub NewArgumentsReceived(args As String())
            ' e.g. add them to a list box
            If args.Length > 0 Then
                lbArgs.Items.AddRange(args)
            End If
        End Sub
    

    下一步:

    • 打开项目属性
    • 选中“制作单一实例”选项
    • 在底部,点击查看应用程序事件
    • 这将像其他任何窗口一样打开一个新的代码窗口;在左侧下拉菜单中选择MyApplication EventsStartupNextInstance 在右边。

    在这里,我们找到主窗体并将命令行参数发送到我们创建的方法:

    Private Sub MyApplication_StartupNextInstance(sender As Object,
                    e As ApplicationServices.StartupNextInstanceEventArgs) _
                         Handles Me.StartupNextInstance
    
        Dim f = Application.MainForm
        '  use YOUR actual form class name:
        If f.GetType Is GetType(MyMainForm) Then
            CType(f, MyMainForm).NewArgumentsReceived(e.CommandLine.ToArray)
        End If
    
    End Sub
    

    注意:不要试图从Application.OpenForms 中提取主窗体。有几次我在集合中找不到打开的表格,所以我不再依赖它了。 Application.MainForm 也比较简单。

    就是这样 - 当一个新实例运行时,它的命令行参数应该被传递给表单并显示在列表框中(或者按照你的方法处理)。


    方法二:从Sub Main开始

    这更复杂,因为从 Sub Main 启动您的应用程序意味着不使用提供 StartupNextInstance 事件的 VB 应用程序框架。解决方案是继承WindowsFormsApplicationBase 以提供所需的功能。

    首先,给你的主窗体起一个有意义的名字,然后像上面那样添加NewArgumentsReceived(args As String())

    对于那些不知道的人,这里是如何从Sub Main() 启动您的应用程序:

    • 向您的应用添加一个名为“程序”的模块
    • 给它添加一个Public Sub Main()
    • 转到项目 -> 属性 -> 应用程序
    • 取消选中Enable Application Framework
    • 选择新的“Sub Main”作为启动对象

    模块实际上可以命名为任何名称,Program 是 VS 用于 C# 应用程序的约定。 Sub Main 的代码将在我们创建类之后。以下大部分内容来自旧的 MSDN 文章或博客或其他内容。

    Imports Microsoft.VisualBasic.ApplicationServices
    Imports System.Collections.ObjectModel
    
    Public Class SingleInstanceApp
        ' this is My.Application
        Inherits WindowsFormsApplicationBase
    
        Public Sub New(mode As AuthenticationMode)
            MyBase.New(mode)
            InitializeApp()
        End Sub
    
        Public Sub New()
            InitializeApp()
        End Sub
    
        ' standard startup procedures we want to implement
        Protected Overridable Sub InitializeApp()
            Me.IsSingleInstance = True
            Me.EnableVisualStyles = True
        End Sub
    
        ' ie Application.Run(frm):
        Public Overloads Sub Run(frm As Form)
            ' set mainform to be used as message pump
            Me.MainForm = frm
            ' pass the commandline
            Me.Run(Me.CommandLineArgs)
        End Sub
    
        Private Overloads Sub Run(args As ReadOnlyCollection(Of String))
            ' convert RO collection to simple array
            ' these will be handled by Sub Main for the First instance
            '    and in the StartupNextInstance handler for the others
            Me.Run(myArgs.ToArray)
        End Sub
    
        ' optional: save settings on exit
        Protected Overrides Sub OnShutdown()
    
            If My.Settings.Properties.Count > 0 Then
                My.Settings.Save()
            End If
    
            MyBase.OnShutdown()
        End Sub
    End Class
    

    请注意,App Framework 可以为我们做的三件主要事情(“启用 XP 样式”、“制作单一实例”和“退出时保存设置”)都已计算在内。现在,对Sub Main进行一些修改:

    Imports Microsoft.VisualBasic.ApplicationServices
    Imports System.Collections.ObjectModel
    
    Module Program  
        ' this app's main form
        Friend myForm As MyMainForm
    
        Public Sub Main(args As String())
            ' create app object hardwired to SingleInstance
            Dim app As New SingleInstanceApp()
    
            ' add a handler for when a second instance tries to start
            ' (magic happens there)
            AddHandler app.StartupNextInstance, AddressOf StartupNextInstance
    
            myForm = New MyMainForm
    
            ' process command line args here for the first instance
            ' calling the method you added to the form:
            myForm.NewArgumentsReceived(args)
    
            ' start app
            app.Run(myForm)   
        End Sub
    
        ' This is invoked when subsequent instances try to start.
        '    grab and process their command line 
        Private Sub StartupNextInstance(sender As Object, 
                            e As StartupNextInstanceEventArgs)
    
            ' ToDo: Process the command line provided in e.CommandLine.
            myForm.NewArgumentsReceived(e.CommandLine.ToArray)
    
        End Sub   
    
    End Module
    

    SingleInstanceApp 类可以与任何Sub Main 样式的应用程序一起重用,并且该方法中的代码主要是复制粘贴样板文件,除了NewArgumentsReceived 方法的表单引用和实际名称。


    测试

    编译应用程序,然后使用命令窗口,向应用程序发送一些命令行参数。我用过:

    C:\Temp>单实例“First Inst”苹果蝙蝠猫

    这将正常启动应用程序,并显示参数。那么:

    C:\Temp>singleinstance "Next Inst" ziggy zoey zacky
    C:\Temp>singleinstance "Last Inst" 111 222 3333

    您使用哪种方法都没有关系 - 它们的工作方式相同。结果:

    请注意,根据安全设置,您的防火墙可能会请求使用任一方法连接到其他计算机的应用程序的权限。这是实例如何发送或侦听其他人的参数的结果。至少在我的情况下,我可以拒绝连接权限,并且一切正常。

    【讨论】:

      【解决方案2】:

      @Plutonix 解决方案非常高效和优雅。

      但是如果您的程序经历了多个表单,即如果主表单在程序执行期间可以更改,例如,如果您有一个登录表单,然后是一个主表单,或者一系列非模态表单,Application.MainForm 并不总是相同的表单,并且可能事先不知道(硬编码)

      Plutonix 代码假定它是已知的并对其进行硬编码。 在这种情况下,您可能希望能够随时接收 NewArguments,无论您的应用程序当时采用哪种形式。

      扩展 Plutonix 解决方案有两种解决方案:

      1) 反复将 Application.MainForm 强制为代码中的特定表单(我尚未对此进行测试,但 Application.MainForm 是读/写的,因此它可以工作)。

      2) 最优雅的是在所有可能成为 MainForm 的表单上实现一个接口:

      创建基本界面:

      Public Interface INewArgumentsReceived
          Sub NewArgumentsReceived(args As String())
      End Interface
      

      将 MyApplication_StartupNextInstance 的 @Plutonix 代码修改为:

      Private Sub MyApplication_StartupNextInstance(sender As Object, e As ApplicationServices.StartupNextInstanceEventArgs) Handles Me.StartupNextInstance
                  Dim f = Application.MainForm
      
                  If f.GetType.GetInterfaces.Contains(GetType(INewArgumentsReceived)) Then
                      CType(f, INewArgumentsReceived).NewArgumentsReceived(e.CommandLine.ToArray)
                  Else
                      MsgBox("The current program state can't receive new requests.",, vbExclamation)
                  End If
      

      现在在所有可能成为主窗体的窗体上,实现 INewArgumentsReceived 接口:

      Public Class FormA: Implements INewArgumentsReceived
      
          Public Sub NewArgumentsReceived(args As String()) Implements INewArgumentsReceived.NewArgumentsReceived
               MsgBox("Got new arguments")
          End Sub
      

      使用接口的另一个好处是我们可以检查当前的 Application.MainForm 是否实现了它并且能够接收它。

      如果当前 Application.MainForm 没有实现接口,它会优雅地失败并显示信息性消息。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-04-29
        • 1970-01-01
        • 2017-05-30
        • 1970-01-01
        • 1970-01-01
        • 2016-09-24
        • 1970-01-01
        相关资源
        最近更新 更多