Click here to Skip to main content
13,732,829 members
Click here to Skip to main content
Add your own
alternative version

Tagged as

Stats

9.9K views
154 downloads
24 bookmarked
Posted 5 Jan 2018
Licenced MIT

Passing Parameters to a Running Application in WPF

, 5 Jan 2018
Rate this:
Please Sign up or sign in to vote.
Passing Parameters to a running application in WPF

Introduction

Passing parameters to a running application is a great feature, but this is something that does not come pre-built-in in WPF. You have to implement it yourself.

There are many approaches of how to implement this. Most of these implementations make my hair stand on (like writing to a file and using the FileSystemWatcher to detect changes).

When I was still new to WPF, I encountered this exact problem. I had a similar solution for WinForms though and I tried to port it to WPF somehow. I eventually succeeded, but since then, I never had an application that required such a feature. When I started my Cauldron project, I decided to also add this feature to the WPF application base class. In case you just need this specific feature (not a whole framework)... here it is...

But first ... Where do we need this feature? Well, take Photoshop as an example. Let us assume Photoshop is running already and now you open a psd-file by double-clicking on it. What happens now is that the running Photoshop instance will open the psd-file instead of a second Photoshop instance. Obviously, what happened here is that the running Photoshop instance was instructed to open the file. The same will also happen if we manually type in the console 'Photoshop.exe C:\testfile.psd'.

This is the behaviour we want also for our WPF application.

Defining the Behaviour

To not overcomplicate it, let us assume that our application is single instance. If the application is executed, it has to check if any other instance of itself is already running. If this is not the case, then the application should proceed on loading and handling the parameters passed by void Main.

If the application is executed and it detects that an instance of itself is already running, then the instance should try to send the parameters passed by void Main to the existing instance and exit afterwards.

Implementation

For our application, to be able to receive message from "itself", we need to add a hook to the window message. In WinForms, this is very easy because it is pre-implemented, but in WPF, we have to implement everything ourselves.

For this implementation, we need the following: A modified COPYDATASTRUCT, WM_COPYDATA and SetForegroundWindow.

In order to retrieve the message we have received, we have to marshal unmanaged data to the COPYDATASTRUCT structure.

internal static class UnsafeNative
{
      [DllImport("user32.dll")]
      public static extern bool SetForegroundWindow (IntPtr hWnd);

      [StructLayout(LayoutKind.Sequential)]
      private struct COPYDATASTRUCT
      {
         public IntPtr dwData;
         public int cbData;

         [MarshalAs(UnmanagedType.LPWStr)]
         public string lpData;
      }
      
      private const int WM_COPYDATA = 0x004A;

      public static string GetMessage(int message, IntPtr lParam)
      {
          if (message == UnsafeNative.WM_COPYDATA)
          {
              try
              {
                  var data = Marshal.PtrToStructure<UnsafeNative.COPYDATASTRUCT>(lParam);
                  var result = string.Copy(data.lpData);
                  return result;
              }
              catch
              {
                  return null;
              }
          }

          return null;
      }
}

The try-catch will prevent a program crash if we received invalid or malformed data, since normally the lpData member of COPYDATASTRUCT is expecting an IntPtr. This happens, for example, if another program is also sending data with the WM_COPYDATA message id that is not in the format we have expected.

To keep it simple, we will just add our 'listener' implementation directly to the MainWindow.

public MainWindow()
{
      InitializeComponent();
      this.Loaded += (s, e) =>
      {
            MainWindow.WindowHandle = new WindowInteropHelper(Application.Current.MainWindow).Handle;
            HwndSource.FromHwnd(MainWindow.WindowHandle)?.AddHook(new HwndSourceHook(HandleMessages));
      };
}

public static IntPtr WindowHandle { get; private set; }

internal static void HandleParameter(string[] args)
{
      // Do stuff with the args
}

private static IntPtr HandleMessages
(IntPtr handle, int message, IntPtr wParameter, IntPtr lParameter, ref Boolean handled)
{
      var data = UnsafeNative.GetMessage(message, lParameter);

      if (data != null)
      {
            if (Application.Current.MainWindow == null)
                  return IntPtr.Zero;

            if (Application.Current.MainWindow.WindowState == WindowState.Minimized)
                  Application.Current.MainWindow.WindowState = WindowState.Normal;

            UnsafeNative.SetForegroundWindow(new WindowInteropHelper
                                            (Application.Current.MainWindow).Handle);

            var args = data.Split(' ');
            HandleParameter(args);
            handled = true;
      }

      return IntPtr.Zero;
}

We need the window handle to be able to hook to the WndProc. To be able to get the window handle of our WPF window, we have to wait until the window is loaded. In the HandleMessages method, we take care of restoring the window if it is minimized and also we are setting the focus to our window to alert the user. The HandleParameter method will handle the parameters passed via window message and also the parameters passed via void Main. More to that later.

So now our application is ready to listen to the window messages. Let us now implement the sender.

For this implementation, we will add the pinvoke SendMessage and our SendMessage implementation to our UnsafeNative class.

[DllImport("User32.dll", EntryPoint = "SendMessage")]
private static extern int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, ref COPYDATASTRUCT lParam);

public static void SendMessage(IntPtr hwnd, string message)
{
    var messageBytes = Encoding.Unicode.GetBytes(message);
    var data = new UnsafeNative.COPYDATASTRUCT
    {
        dwData = IntPtr.Zero,
        lpData = message,
        cbData = messageBytes.Length + 1 /* +1 because of \0 string termination */
    };

    if (UnsafeNative.SendMessage(hwnd, WM_COPYDATA, IntPtr.Zero, ref data) != 0)
        throw new Win32Exception(Marshal.GetLastWin32Error());
}

Now we have a "sender" and a "listener", it's time to implement the void Main. In order to do that, we have to modify our WPF application a bit.

  • Remove the StartupUri="MainWindow.xaml" line from App.xaml.
  • Change the App.xaml's build action to Page instead of ApplicationDefinition.
  • Add the following class:
public static class Program
{
      [STAThread]
      public static void Main(string[] args)
      {
            var proc = Process.GetCurrentProcess();
            var processName = proc.ProcessName.Replace(".vshost", "");
            var runningProcess = Process.GetProcesses()
                .FirstOrDefault(x => (x.ProcessName == processName || 
                                x.ProcessName == proc.ProcessName || 
                                x.ProcessName == proc.ProcessName + ".vshost") && x.Id != proc.Id);

            if (runningProcess == null)
            {
                  var app = new App();
                  app.InitializeComponent();
                  var window = new MainWindow();
                  MainWindow.HandleParameter(args);
                  app.Run(window);
  
                  MainWindow.HandleParameter(args);
                  return; // In this case we just proceed on loading the program
            }

            if (args.Length > 0)
                  UnsafeNative.SendMessage(runningProcess.MainWindowHandle, string.Join(" ", args));
      }
}

If the application is started in Visual Studio, the application's process name has a .vshost suffix. We have to also consider this while searching for a running instance of our application. This is what we are doing in the first half of the code.

If the application does not find any running instance besides its own, it will just proceed on loading the main window and pass the parameters to our centralized parameter handler. If the application does find a running instance, the application will send the parameters using SendMessage to that instance instead.

Test the Code

Now let us test if our code is working as expected.

To be able to see the passed parameters, we are adding a TextBlock to MainWindow.xaml and also the following code to our parameter handler.

internal static void HandleParameter(string[] args)
{
      if (Application.Current?.MainWindow is MainWindow mainWindow)
            mainWindow.textBlock.Text = string.Join("\r\n", args);
}

Let us start the application without a parameter first.

This is what we see now.

Let us start the application again; this time with parameters (don't close the previous one).

The running instance of our application is now showing the parameters.

The implementation works as expected.

The Icing on Top - URL Protocol

I think everybody is familiar with those mailto: links in emails and websites. If you click on those, it will open your mail application, creates a new mail and adds the email-address to the email receiver.

Wouldn't it be nice if our application can do that too? Here is how.

We will register our application to Windows to be the handler of the codeprojectsample url protocol. The process can be automated, but for this sample, we will be doing it on foot.

Add the following entries to the Windows registry.

[HKEY_CLASSES_ROOT\codeprojectsample]
@="URL:codeprojectsample"
"URL Protocol"=""

[HKEY_CLASSES_ROOT\codeprojectsample\DefaultIcon]
@="D:\\Example\\WpfApp1\\bin\\Debug\\WpfApp1.exe,0"

[HKEY_CLASSES_ROOT\codeprojectsample\shell]

[HKEY_CLASSES_ROOT\codeprojectsample\shell\open]

[HKEY_CLASSES_ROOT\codeprojectsample\shell\open\command]
@="D:\\Example\\WpfApp1\\bin\\Debug\\WpfApp1.exe %1"

Don't forget to modify the paths.

Let us test if this works... Type the following in your browser.

Our application executes and shows the following:

Now let us type the following in the browser:

codeprojectsample:Newstuff

Now the running instance is showing the following:

Great! It works.

It can be better though...

Let us modify our parameter handler a bit (don't forget to add a reference to System.Web).

internal static void HandleParameter(string[] args)
{
      if (Application.Current?.MainWindow is MainWindow mainWindow)
      {
            if (args != null && args.Length > 0 && args[0]?.IndexOf
               ("codeprojectsample", StringComparison.CurrentCultureIgnoreCase) >= 0 && 
                 Uri.IsWellFormedUriString(args[0], UriKind.RelativeOrAbsolute))
            {
                  var url = new Uri(args[0]);
                  var parsedUrl = HttpUtility.ParseQueryString(url.Query);
                  mainWindow.textBlock.Text = $"Article Id: {parsedUrl.Get("artid")}\r\nName: 
                                              {parsedUrl.Get("name")}";
            }
            else
                  mainWindow.textBlock.Text = string.Join("\r\n", args);
      }
}

So now... if we type the following in the browser:

codeprojectsample:youdecidewhattodo?artid=2322&name=Hello%20CSharp

Then our application will show the following:

Conclusion

In my opinion, as long as your application is Windows-only, this implementation of parameter passing is the best option. I have seen implementations ranging from shared files to shared memory and also using sockets and pipes ... I did not really like any of these, because Windows is already offering a built-in solution. Why not use it?

Links

License

This article, along with any associated source code and files, is licensed under The MIT License

Share

About the Author

Alex Schunk
Germany Germany
No Biography provided

You may also be interested in...

Pro
Pro

Comments and Discussions

 
QuestionShowInTaskBar False Pin
Creswell Gould5-Oct-18 14:37
memberCreswell Gould5-Oct-18 14:37 
QuestionSame functionality without showing program on taskbar Pin
26-Sep-18 23:14
member26-Sep-18 23:14 
QuestionSolution without MainWindow Pin
LuWa8922-Feb-18 22:52
memberLuWa8922-Feb-18 22:52 
Questionuse the App.unsafeNative::sendMessage to make XAML App the logger for the powershell scripts Pin
SERGUEIK11-Jan-18 9:16
memberSERGUEIK11-Jan-18 9:16 
PraiseRe: use the App.unsafeNative::sendMessage to make XAML App the logger for the powershell scripts Pin
Alex Schunk15-Jan-18 2:16
memberAlex Schunk15-Jan-18 2:16 
QuestionYou got my 5! Pin
jackmos9-Jan-18 3:03
professionaljackmos9-Jan-18 3:03 
AnswerRe: You got my 5! Pin
Alex Schunk15-Jan-18 2:00
memberAlex Schunk15-Jan-18 2:00 
QuestionGreat Pin
andy nikolov5-Jan-18 9:23
memberandy nikolov5-Jan-18 9:23 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web05-2016 | 2.8.180920.1 | Last Updated 5 Jan 2018
Article Copyright 2018 by Alex Schunk
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid