【发布时间】:2015-03-03 12:23:57
【问题描述】:
当用户使用注册表在 Windows 资源管理器中右键单击文件时,我已经实现了上下文菜单。文件地址将作为命令行传递给应用程序。解析没问题。
如何实现类似于“添加到 Windows Media Player 播放列表”的功能?它不会打开应用程序的另一个实例,而是在同一个打开的窗口上工作并将其添加到列表中?
【问题讨论】:
当用户使用注册表在 Windows 资源管理器中右键单击文件时,我已经实现了上下文菜单。文件地址将作为命令行传递给应用程序。解析没问题。
如何实现类似于“添加到 Windows Media Player 播放列表”的功能?它不会打开应用程序的另一个实例,而是在同一个打开的窗口上工作并将其添加到列表中?
【问题讨论】:
根据应用的启动方式,有 2 种方法可以做到这一点。
这是最简单的,因为您主要只需要为应用程序事件添加一些代码。首先,向您的主窗体添加一个方法,以接收来自应用程序后续实例的新参数:
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 Events; StartupNextInstance 在右边。在这里,我们找到主窗体并将命令行参数发送到我们创建的方法:
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
模块实际上可以命名为任何名称,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
您使用哪种方法都没有关系 - 它们的工作方式相同。结果:
请注意,根据安全设置,您的防火墙可能会请求使用任一方法连接到其他计算机的应用程序的权限。这是实例如何发送或侦听其他人的参数的结果。至少在我的情况下,我可以拒绝连接权限,并且一切正常。
【讨论】:
@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 没有实现接口,它会优雅地失败并显示信息性消息。
【讨论】: