Passing Parameters to a Running Application in WPF






4.89/5 (13 votes)
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 ofApplicationDefinition
. - 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
- This implementation is also available as a Nuget package: Cauldron.Win32.WPF.ParameterPassing.
- You can also find an implementation sample here.
- There is also a Nuget package to register the url protocol programmatically: Cauldron.Win32.Net.
- A sample is also available here.