Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Custom file extension control

4.83/5 (27 votes)
28 Aug 2017CPOL7 min read 1   3.4K  
Easily assign your extension to your application and set an icon.

Image 1

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

custom-file-extension/ss101.png

Handler part

custom-file-extension/ss100.png

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.

custom-file-extension/ss112.png

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:

C#
using Microsoft.Win32;

About the code.

C#
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:

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

C#
Registry.ClassesRoot.DeleteSubKey(ext);
Registry.ClassesRoot.DeleteSubKeyTree(handler);

Open with...

We have explained this option, but let us see the code for it.

C#
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, "");

    // openwith handler
    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.

C#
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:

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

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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)