Opening a specific file format with a single instance of an application

0
101

Download source

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 running the application or using the already running instance and opening the file in it.

Steps required to accomplish this are;

  1. Associating a file extension with your application and make windows trigger your application when a file with this format is double clicked.
  2. 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 e 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. Sample code provides two solution for instance detection.

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;
}

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 a 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 a 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 = Encoding.Unicode.GetByteCount(msg);
 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 requsted 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);

 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 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.

LEAVE A REPLY