Introduction
CustomFileExtension is a control that helps you define a file extension for your custom file types. This component is not visible on a form, but below it.
What is a file association?
File association is a set of Registry keys that determine how your new file type will behave, what icon it is going to have and with what application it is going to be run. This can be done without the CustomFileExtension
control as well, but this way is easier and faster than writing code yourself every time.
It is all about creating the Registry keys in the right place.
Theory
Here is some theoretical explanation:
Inside HKEY_CLASSES_ROOT, you need to create a new key with the name of the desired extension. E.g. '.cp' (without the quotes). In the data field of the newly created key, enter MyApplication, which is going to be like a reference to that extension inside the Registry.
Still inside HKEY_CLASSES_ROOT, add a new key with the name of the reference, which is MyApplication. In the data field of this key, you can enter some text which is going to be a description of that file type in Explorer. To have an icon, we need to add a subkey inside our MyApplication key, called DefaultIcon. The data field of the DefaultIcon subkey is going to be the path to the icon you want your file type to have. Enclose the icon path like this: "icon_path". You can also have an icon that is inside the resource of the Exe or DLL file, like this:
"application_path",x
where x is the index of the icon inside the resource.
Finally, we need to specify the path to the application so that the application can open your file type.
Inside the MyApplication key, create another subkey called shell. Inside the shell subkey, create another subkey called open. In the data field of the open subkey, enter some text that will be displayed in the context menu when you right click the file with your .cp extension.
And lastly, create another subkey inside the open subkey, called command. In the command subkey, we can set the full path to the application .exe so the file type is connected to the application like so:
"application_path" "%1"
%1 acts like an argument variable so if we double click on a file with a .cp extension, we will pass the full path to that clicked file so we can open it inside our application.
The Registry should look something like this:
Extension part
Handler part
Open with...
There is an option to specify an application to open your custom format. For example, you can edit a .html file in Notepad, but in Visual Studio as well. The same functionality you can have for your own application, using this control.
The theory behind this is just adding the OpenWithProgids subkey of the extension key, and inside the OpenWithProgids subkey, start adding string values of handlers to reference different applications.
After that, you need to create a key that matches each handler on the same level as your extension. Each of those keys must have a subkey shell. Then, shell must have a subkey open, and open must have a subkey command. Inside the command subkey, you need to enter the full path to other applications, with %1 at the end.
When you use the RegisterFileTypes()
method, the control will assign the association for the selected extension, and if you have selected applications, OpenWithProgids
will be filled.
Open files in the same application instance
You can also make your program to open files in already running applications instead of running a new instance of the application. Communication is between the processes, and is sent with the SendMessage
Windows API.
No Registry messing around this time.
Two new methods are added:
SendFilePath
which has the args
parameter to be passed and checks for the application instances, prepare the file paths, and then sends the file paths to the running application if the application is running. The method returns true
if there is more than one instance of the application. GetData
is another method that is used on the receiving end. Takes a data argument type of string
and returns an array of file paths.
Enough about the theory and the conspiracy theory. Let's see the code.
Source code
The source code is pretty simple. We are taking advantage of the Registry
class. So you need to add a using
statement:
using Microsoft.Win32;
About the code.
Registry.SetValue("HKEY_CLASSES_ROOT\.cp", "", "MyApplication");
Registry.SetValue("HKEY_CLASSES_ROOT\MyApplication", "", "Some description");
Registry.ClassesRoot.CreateSubKey("DefaultIcon");
if (embeddedIcon)
Registry.SetValue("HKEY_CLASSES_ROOT\MyApplication\DefaultIcon",
"", "Application_Path", "Icon_Position");
else
Registry.SetValue("HKEY_CLASSES_ROOT\MyApplication\DefaultIcon", "", "Icon_Path");
Registry.SetValue("HKEY_CLASSES_ROOT\MyApplication\shell", "", "");
Registry.SetValue("HKEY_CLASSES_ROOT\MyApplication\shell\open", "", "Open me");
Registry.SetValue("HKEY_CLASSES_ROOT\MyApplication\shell\open\command",
"", "Application_Path" ""%1""");
After executing this code, you need to inform Explorer about the changes. I am not sure if there's an equivalent function in .NET for this, but you can use PInvoke to call the SHChangeNotify
function, that will inform the Explorer about the change. You can call the function with DllImport
, but first, add another using
statement:
using System.Runtime.InteropServices;
and the required enums which can be found here [^] and here [^].
And after we add the association, all we need is to remove the association.
Registry.ClassesRoot.DeleteSubKey(ext);
Registry.ClassesRoot.DeleteSubKeyTree(handler);
Open with...
We have explained this option, but let us see the code for it.
for (int i = 0; i < openWith.Count; i++)
{
string app = openWith[i].AppPath;
string originalApp = app;
app = app.Substring(app.LastIndexOf("\\") + 1);
app = app.Substring(0, app.LastIndexOf('.'));
string runHandler = "CustomFileExtension." + app + ".Run.Handler";
Registry.ClassesRoot.CreateSubKey(ext + "\\OpenWithProgids");
Registry.SetValue(HKCR + ext + "\\OpenWithProgids", runHandler, "");
Registry.SetValue(HKCR + runHandler, "", "");
Registry.SetValue(HKCR + runHandler + "\\shell", "", "");
Registry.SetValue(HKCR + runHandler + "\\shell\\open", "", "");
Registry.SetValue(HKCR + runHandler + "\\shell\\open\\command", "", "\"" +
originalApp + @""" ""%1""");
}
This code is in the RegisterFileType()
method. When you register a new extension you have selected, the application will be added to the Open with dialog.
When you call the RemoveFileType()
method, it will delete all the used handlers for the Open with dialog.
Open files in the same application instance
All we have to do in the SendFilePath
method is to combine the file paths, list the current running process, and send the message.
Process proc = Process.GetCurrentProcess();
Process[] processes = Process.GetProcessesByName(proc.ProcessName);
if (processes.Length == 0)
return false;
string message = "";
for (int i = 0; i < args.Length; i++)
message += args[i] + ";";
byte[] data = Encoding.Default.GetBytes(message);
int dataLen = data.Length;
COPYDATASTRUCT cds;
cds.dwData = (IntPtr)100;
cds.lpData = message;
cds.cbData = dataLen + 1;
if (processes.Length > 1)
{
foreach (Process p in processes)
{
if (p.Id != proc.Id)
{
SendMessage(p.MainWindowHandle, WM_COPYDATA, 0, ref cds);
return true;
}
}
}
In your application, you are using this method like this:
CustomFileExtensionControl.CustomFileExtension ctf =
new CustomFileExtensionControl.CustomFileExtension();
if (!ctf.SendFilePath(args))
Application.Run(new Form1(args));
So when the application is already running, we are passing the arguments to the running application. In your application, you also need to add a WndProc
event which will be called when the control sends the message.
protected override void WndProc(ref Message message)
{
if (message.Msg == WM_COPYDATA)
{
COPYDATASTRUCT mystr = (COPYDATASTRUCT)message.GetLParam(typeof(COPYDATASTRUCT));
Type mytype = mystr.GetType();
mystr = (COPYDATASTRUCT)message.GetLParam(mytype);
string [] args = customFileExtension1.GetData(mystr.lpData);
OpenFile(args);
}
base.WndProc(ref message);
}
We can see here the use of the GetData
method which will enable the OpenFile
function to read the files.
Thanks to scosta_FST for asking a question about opening files in running applications.
Setting up
Setting up this control is pretty straightforward. After you've dragged a component onto a form, select it and set the properties. Here is an explanations of the available properties:
This is the final application file name along with the extension (.exe). E.g. MyApplication.exe must match with the actual application name.
This will be displayed in Explorer when you select the list view on Tiles. It'll show the description, e.g., CP's File.
If set to true
, the icon is used from inside the .exe file in the resource, and then you have to set the IconPosition
as well to select the right icon in the resource.
This is the actual extension of your custom file type which will be used to open that file with your application (ApplicationName
).
This is used to connect the extension key in the Registry with the corresponding key, to open the extension file name with your application.
Just like ApplicationName
, IconName
is the name of your icon along with the extension (.ico) that you want your file type to have. E.g., MyIcon.ico.
This is the position of your icons inside the Exe's resource file (or .dll). 0 being the first icon, 1 second, and so on.
Set this property to have a custom text when you open a context menu by right clicking your custom file type. It is the default action which is the same as double clicking the file.
This is a collection of file paths to the applications that have to appear in the Open with dialog.
ApplicationName
Description
EmbeddedIcon
Extension
Handler
IconName
IconPosition
OpenText
OpenWith
Using it
Using this control is pretty straightforward. To add the component to a form, right click on the Toolbar and select Choose items... A dialog will open, click on Browse, and find the .dll file and click OK. Now you can drag and drop the component to the form. Select the control and set the properties, and then you can associate a file type with the RegisterFileType()
function, and use RemoveFileType()
to remove the association. Like so: customFileExtension.RegisterFileType()
and customFileExtension.RemoveFileType()
.
And that's all there is to it.
Version history
- 26.03.2010 - First initial version.
- 08.04.2010 - Added Open with capabilities.
- 20.04.2010 - Can now open files in a running application instance.