In this article, you will learn to associate an application with a file extension and when a file with this extension is double clicked on Windows Explorer, automatically run the application or use the already running instance and open the file in it.
Introduction
This article and the sample code demonstrates how to associate an application with a file extension (e.g., .mytxt) and when a file with this extension is double clicked on Windows Explorer, automatically run the application or use the already running instance and open the file in it.
Steps required to accomplish this are:
- Associating a file extension with your application and make windows trigger your application when a file with this format is double clicked.
- Detecting if any other instance of your application is already running.
- If there is no other instance (this is the only instance running), then open the file in this instance of your application.
- If there is one instance already running, then make that already running application open this file and exit the new one.
Associating Your Application With a File Extension
Windows reads file extension associations from "HKEY_CLASSES_ROOT
" registry. You need to add a few keys to this registry in order to tell Windows that it should use your application by default, for opening this type of files. So when user double clicks a file with this extension, Windows automatically triggers your application and passes the file path as the first argument to your application. Below is the helper class that performs file association.
static class FileAssociationHelper
{
public static void AssociateFileExtension
(string fileExtension, string name, string description, string appPath)
{
RegistryKey _extensionKey = Registry.ClassesRoot.CreateSubKey(fileExtension);
_extensionKey.SetValue("", name);
RegistryKey _formatNameKey = Registry.ClassesRoot.CreateSubKey(name);
_formatNameKey.SetValue("", description);
_formatNameKey.CreateSubKey("DefaultIcon").SetValue("", "\"" + appPath + "\",0");
RegistryKey _shellActionsKey = _formatNameKey.CreateSubKey("Shell");
_shellActionsKey.CreateSubKey("open").CreateSubKey("command").SetValue
("", "\"" + appPath + "\" \"%1\"");
_extensionKey.Close();
_formatNameKey.Close();
_shellActionsKey.Close();
SHChangeNotify(0x08000000, 0x0000, IntPtr.Zero, IntPtr.Zero);
}
[DllImport("shell32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern void SHChangeNotify
(uint wEventId, uint uFlags, IntPtr dwItem1, IntPtr dwItem2);
}
fileExtension
is the extension you want to associate (e.g., ".mytxt") name
is the name of your format (e.g., "MyTextFile
") description
is the description for your format appPath
is the full path of your application that will be triggered by Windows
Sample usage for this method is:
FileAssociationHelper.AssociateFileExtension
(".mytxt", "MyTxtFile", "Simple text file", Application.ExecutablePath);
Detecting the Running Instances of Your Application
Detecting if any instance of your application is running can be done in many ways. The sample code provides two solutions for instance detection.
The first and simplest one is getting the list of all the processes that has the same name with yours. If the number of found processes is greater than 1, that means there is at least one more instance that is running. Sample code is as follows:
public static bool CheckInstancesFromRunningProcesses()
{
Process _currentProcess = Process.GetCurrentProcess();
Process[] _allProcesses = Process.GetProcessesByName(_currentProcess.ProcessName);
if (_allProcesses.Length > 1)
return true;
return false;
}
The second way of detecting other instances is using a kernel mode synchronization object. In this example, I used named Mutex
. Sample code tries to acquire the named Mutex
whose name is "OpenWithSingleInstance
". If this is the only instance, then the lock will be acquired, otherwise not.
public static bool CheckInstancesUsingMutex()
{
Mutex _appMutex = new Mutex(false, "OpenWithSingleInstance");
if (!_appMutex.WaitOne(1000))
return true;
return false;
}
Opening the File in the Already Running Instance
Because we want to run only a single instance of our application and open all the files in this instance, we need to communicate with the already running instance and make it open the file. Communication can be done in many ways, TCP connection, named pipe, sending a window message...
In this example, I used window messages for passing the file name to the running instance. Windows has a special window message code and data structure for passing custom data to another window via window messages; WM_COPYDATA
and COPYDATASTRUCT
.
Definition SendMessage
Win32 API method, WM_COPYDATA
message code and COPYDATASTRUCT
structure is as follows:
const int WM_COPYDATA = 0x004A;
[DllImport("user32", EntryPoint = "SendMessageA")]
private static extern int SendMessage(IntPtr Hwnd, int wMsg, IntPtr wParam, IntPtr lParam);
[StructLayout(LayoutKind.Sequential)]
struct COPYDATASTRUCT
{
public IntPtr dwData;
public int cbData;
public IntPtr lpData;
}
And the SendDataMessage
helper method that is used for sending string
messages to the main window of a process is:
public static void SendDataMessage(Process targetProcess, string msg)
{
IntPtr _stringMessageBuffer = Marshal.StringToHGlobalUni(msg);
COPYDATASTRUCT _copyData = new COPYDATASTRUCT();
_copyData.dwData = IntPtr.Zero;
_copyData.lpData = _stringMessageBuffer;
_copyData.cbData = msg.Length * 2;
IntPtr _copyDataBuff = IntPtrAlloc(_copyData);
SendMessage(targetProcess.MainWindowHandle, WM_COPYDATA, IntPtr.Zero, _copyDataBuff);
Marshal.FreeHGlobal(_copyDataBuff);
Marshal.FreeHGlobal(_stringMessageBuffer);
}
The next step is sending the full path of the file to the running instance in the entry point of the application, before showing any user interface to the user, then exiting the new instance and let the running instance open the file.
static class Program
{
[STAThread]
static void Main(params string[] args)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
if (SingleInstanceHelper.CheckInstancesUsingMutex() && args.Length > 0)
{
Process _otherInstance = SingleInstanceHelper.GetAlreadyRunningInstance();
MessageHelper.SendDataMessage(_otherInstance, args[0]);
return;
}
Application.Run(new Form1(args.Length > 0 ? args[0] : null));
}
}
The final step is of course watching for the file message in the running instance and opening the requested file. You can do it by overriding the WndProc
method of the main window whose type is System.Windows.Forms.Form
.
protected override void WndProc(ref Message m)
{
if (m.Msg == MessageHelper.WM_COPYDATA)
{
COPYDATASTRUCT _dataStruct = Marshal.PtrToStructure<COPYDATASTRUCT>(m.LParam);
string _strMsg = Marshal.PtrToStringUni(_dataStruct.lpData, _dataStruct.cbData / 2);
openFileInTabControl(_strMsg);
}
base.WndProc(ref m);
}
The openFileInTabControl
method creates a new TabPage
for this new file, then reads all the text content of the file, puts it into a TextBox
and places this TextBox
to the newly created TabPage
.
private void openFileInTabControl(string filePath)
{
string _strFileData = File.ReadAllText(filePath);
TabPage _tabPage = new TabPage(Path.GetFileNameWithoutExtension(filePath));
TextBox _textBox = new TextBox();
_textBox.Multiline = true;
_textBox.Dock = DockStyle.Fill;
_textBox.Text = _strFileData;
_tabPage.Controls.Add(_textBox);
tabControl1.TabPages.Add(_tabPage);
}
After loading the file, you may also want to restore and activate the application if it is minimized or behind the other windows.
Sample Application
Sample application consists of a single Form
and three helper classes for file association, detecting running instances and sending a string
message to the main window of a process.

"Register File Extension" button associates ".mytxt" file extension with this sample application. After associating the file extension, close the sample application and double click the files "Test_file1.mytxt" and then "Test_file2.mytxt" which are in the sample codes folder. The two files should be opened in the single instance of the application and you should see a similar window as above.
"Unregister File Extension" button disassociates the file format by removing the registry keys for this format.
Alternative Solution for Single Instance Requirement
Microsoft.Net BCL has a helper class for ensuring only one instance of an application can be run at the same time. Thanks to Ralph Lechterbeck for informing me about the existing of this library.
Unfortunately, the helper class is in the library named "Microsoft.VisualBasic.dll". Although the name is misleading, this library can be safely referenced and used in C# projects. I could not find any reasonable answer why Microsoft guys put all these useful utilities into a library whose name contains "VisualBasic
".
Here is the alternative sample code:
namespace OpenWithSingleInstance
{
static class Program
{
[STAThread]
static void Main(params string[] args)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
SingleInstanceController controller = new SingleInstanceController();
controller.Run(args);
}
}
public class SingleInstanceController : WindowsFormsApplicationBase
{
public SingleInstanceController()
{
IsSingleInstance = true;
StartupNextInstance += this_StartupNextInstance;
}
void this_StartupNextInstance(object sender, StartupNextInstanceEventArgs e)
{
Form1 form = MainForm as Form1;
form.OpenFileInTabControl(e.CommandLine.Count > 0 ? e.CommandLine[0] : null);
}
protected override void OnCreateMainForm()
{
MainForm = new Form1(this.CommandLineArgs.Count > 0 ?
this.CommandLineArgs[0] : null);
}
}
}
Although the usage is simple, the code under this functionality is a bit complicated. WindowsFormsApplicationBase
class uses "memory mapped files", "tcp communication" and "wait handles" for ensuring only a single instance can run and passing the command line arguments to the running instance.
History
- 14th June, 2017
- 16th June, 2017
- "Unregister File Extension" button is added
- Sample source code updated
- 22nd June, 2017
- "Alternative solution for single instance requirement" part is added, special thanks to Ralph Lechterbeck
- Manually calculated the size of the message string