Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pass Command Line to first instance of a Single Instance App

Tags:

.net

vb.net

I have already implemented context menu to appear when a user right-clicks a file in windows explorer using Registry. The file address will be passed to the application as command lines. Parsing it is no problem.

How can I implement that is similar to "Add To Windows Media Player Playlist"? It does not open another instance of the app but works on the same open window and adds it to a list?

like image 789
Earlee Avatar asked Mar 03 '15 12:03

Earlee


People also ask

How do I make sure that only one instance of my application runs at a time C#?

The best way of accomplishing this is using a named mutex. Create the mutex using code such as: bool firstInstance; Mutex mutex = new Mutex(false, "Local\\" + someUniqueName, out firstInstance); // If firstInstance is now true, we're the first instance of the application; // otherwise another instance is running.

What is a single instance application?

A Single Instance application is an application that limits the program to run only one instance at a time. This means that you cannot open the same program twice.


2 Answers

There are 2 ways to do this depending on how your app starts.

Method 1: Using VB App Framework and a MainForm

This is the easiest because you mainly just need to add some code for an Application event. First, add a method to your main form to receive new arguments from subsequent instances of your app:

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

Next:

  • Open Project Properties
  • Check the "Make Single Instance" option
  • At the bottom, click View Application Events
  • This will open a new code window like any other; Select MyApplication Events in the left drop down; and StartupNextInstance in the right one.

Here, we find the main form and send the command line arguments to the method we created:

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

Note: Do not try to fish the main form out of Application.OpenForms. A few times I've had it fail to find an open form in the collection, so I have quit relying on it. Application.MainForm is also simpler.

That's it - when a new instance runs, its command line args should be passed to the form and displayed in the listbox (or processed however your method sees fit).


Method 2: Starting From Sub Main

This is more complicated because starting your app from a Sub Main means that the VB Application Framework is not used, which provides the StartupNextInstance event. The solution is to subclass WindowsFormsApplicationBase to provide the functionality needed.

First, give your main form a meaningful name and add something like the NewArgumentsReceived(args As String()) as above.

For those not aware, here is how to start your app from Sub Main():

  • Add a module named 'Program' to your app
  • Add a Public Sub Main() to it.
  • Go to Project -> Properties -> Application
  • Uncheck Enable Application Framework
  • Select your new "Sub Main" as the Startup Object

The module can actually be named anything, Program is the convention VS uses for C# apps. The code for Sub Main will be later after we create the class. Much of the following originated from an old MSDN article or blog or something.

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

Note that the three main things the App Framework can do for us ("Enable XP Styles", "Make Single Instance" and "Save Settings on Exit") are all accounted for. Now, some modifications to 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

The SingleInstanceApp class can be reused with any Sub Main style app, and the code in that method is mainly a copy-paste boilerplate affair except for perhaps the form reference and actual name of the NewArgumentsReceived method.


Testing

Compile the app, then using a command window, send some commandline arguments to the app. I used:

C:\Temp>singleinstance "First Inst" apple bats cats

This starts the app as normal, with the arguments shown. Then:

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

It doesnt matter which approach you use - they both work the same. The Result:

enter image description hereenter image description here

Note that depending on security settings, your firewall may request permission for apps using either method to connect to other computers. This is a result of how an instance sends or listens for the arguments from others. At least with mine, I can deny permission to connect and everything still works fine.

like image 117
Ňɏssa Pøngjǣrdenlarp Avatar answered Oct 21 '22 08:10

Ňɏssa Pøngjǣrdenlarp


@Plutonix solution is quite efficient and elegant.

However if you program goes through multiple Forms i.e. if the Main Form can change during program execution, for example if you have a login form and then a main form, or a sequence of non-modal forms, Application.MainForm won't always be the same form and may not be known beforehand (hard coded).

Plutonix code assumes it is known and hard codes it. In this case, you may want to be able to receive the NewArguments at all times, in whichever form is active at the time in your application.

There are 2 solutions to extend Plutonix solution:

1) Repeatedly force Application.MainForm to a specific form in code (I haven't tested this but Application.MainForm is Read/Write so it could work).

2) The most elegant is to implement an Interface on all forms that can possibly become the MainForm:

Create the Basic interface:

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

Modify @Plutonix code for MyApplication_StartupNextInstance to:

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

Now on all possible forms that can become the Main Form, implement the INewArgumentsReceived Interface:

Public Class FormA: Implements INewArgumentsReceived

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

The other advantage of using the Interfaces is that we can check if the current Application.MainForm implements it and is able to receive it.

If the current Application.MainForm does not implement the Interface it fails gracefully with an informational message.

like image 1
Pedro Ramilo Avatar answered Oct 21 '22 07:10

Pedro Ramilo