Associate File Extension with Application

2018-12-31 18:00发布

问题:

I\'ve written a program that edits a specific filetype , and I want to give the user the option to set my application as the default editor for this filetype (since I don\'t want an installer) on startup.

I\'ve tried to write a re-useable method that associates a file for me (preferably on any OS, although I\'m running Vista) by adding a key to HKEY_CLASSES_ROOT, and am using it with my application, but it doesn\'t seem to work.

public static void SetAssociation(string Extension, string KeyName, string OpenWith, string FileDescription)
{
    RegistryKey BaseKey;
    RegistryKey OpenMethod;
    RegistryKey Shell;
    RegistryKey CurrentUser;

    BaseKey = Registry.ClassesRoot.CreateSubKey(Extension);
    BaseKey.SetValue(\"\", KeyName);

    OpenMethod = Registry.ClassesRoot.CreateSubKey(KeyName);
    OpenMethod.SetValue(\"\", FileDescription);
    OpenMethod.CreateSubKey(\"DefaultIcon\").SetValue(\"\", \"\\\"\" + OpenWith + \"\\\",0\");
    Shell = OpenMethod.CreateSubKey(\"Shell\");
    Shell.CreateSubKey(\"edit\").CreateSubKey(\"command\").SetValue(\"\", \"\\\"\" + OpenWith + \"\\\"\" + \" \\\"%1\\\"\");
    Shell.CreateSubKey(\"open\").CreateSubKey(\"command\").SetValue(\"\", \"\\\"\" + OpenWith + \"\\\"\" + \" \\\"%1\\\"\");
    BaseKey.Close();
    OpenMethod.Close();
    Shell.Close();

    CurrentUser = Registry.CurrentUser.CreateSubKey(@\"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\\" + Extension);
    CurrentUser = CurrentUser.OpenSubKey(\"UserChoice\", RegistryKeyPermissionCheck.ReadWriteSubTree, System.Security.AccessControl.RegistryRights.FullControl);
    CurrentUser.SetValue(\"Progid\", KeyName, RegistryValueKind.String);
    CurrentUser.Close();
}

Any idea why it doesn\'t work? An example use might be

SetAssociation(\".ucs\", \"UCS_Editor_File\", Application.ExecutablePath, \"UCS File\"); 

The part of the method that uses \"CurrentUser\" seems to work if I do the same using regedit, but using my application it doesn\'t.

回答1:

The answer was a lot simpler than I expected. Windows Explorer has its own override for the open with application, and I was trying to modify it in the last lines of code. If you just delete the Explorer override, then the file association will work.

I also told explorer that I had changed a file association by calling the unmanaged function SHChangeNotify()

public static void SetAssociation(string Extension, string KeyName, string OpenWith, string FileDescription)
{
    // The stuff that was above here is basically the same

    // Delete the key instead of trying to change it
    CurrentUser = Registry.CurrentUser.OpenSubKey(\"Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Explorer\\\\FileExts\\\\\" + Extension, true);
    CurrentUser.DeleteSubKey(\"UserChoice\", false);
    CurrentUser.Close();

    // Tell explorer the file association has been changed
    SHChangeNotify(0x08000000, 0x0000, IntPtr.Zero, IntPtr.Zero);
}

[DllImport(\"shell32.dll\", CharSet = CharSet.Auto, SetLastError = true)]
public static extern void SHChangeNotify(uint wEventId, uint uFlags, IntPtr dwItem1, IntPtr dwItem2);


回答2:

You can do that in a managed way via ClickOnce. No fussing with the registry yourself. This is available via tooling (i.e. no xml) in VS2008 and above (including Express) on Project Properties => Publish => Options => File Associations



回答3:

Here\'s a complete example:

public class FileAssociation
{
    public string Extension { get; set; }
    public string ProgId { get; set; }
    public string FileTypeDescription { get; set; }
    public string ExecutableFilePath { get; set; }
}

public class FileAssociations
{
    // needed so that Explorer windows get refreshed after the registry is updated
    [System.Runtime.InteropServices.DllImport(\"Shell32.dll\")]
    private static extern int SHChangeNotify(int eventId, int flags, IntPtr item1, IntPtr item2);

    private const int SHCNE_ASSOCCHANGED = 0x8000000;
    private const int SHCNF_FLUSH = 0x1000;

    public static void EnsureAssociationsSet()
    {
        var filePath = Process.GetCurrentProcess().MainModule.FileName;
        EnsureAssociationsSet(
            new FileAssociation
            {
                Extension = \".ucs\",
                ProgId = \"UCS_Editor_File\",
                FileTypeDescription = \"UCS File\",
                ExecutableFilePath = filePath
            });
    }

    public static void EnsureAssociationsSet(params FileAssociation[] associations)
    {
        bool madeChanges = false;
        foreach (var association in associations)
        {
            madeChanges |= SetAssociation(
                association.Extension,
                association.ProgId,
                association.FileTypeDescription,
                association.ExecutableFilePath);
        }

        if (madeChanges)
        {
            SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_FLUSH, IntPtr.Zero, IntPtr.Zero);
        }
    }

    public static bool SetAssociation(string extension, string progId, string fileTypeDescription, string applicationFilePath)
    {
        bool madeChanges = false;
        madeChanges |= SetKeyDefaultValue(@\"Software\\Classes\\\" + extension, progId);
        madeChanges |= SetKeyDefaultValue(@\"Software\\Classes\\\" + progId, fileTypeDescription);
        madeChanges |= SetKeyDefaultValue($@\"Software\\Classes\\{progId}\\shell\\open\\command\", \"\\\"\" + applicationFilePath + \"\\\" \\\"%1\\\"\");
        return madeChanges;
    }

    private static bool SetKeyDefaultValue(string keyPath, string value)
    {
        using (var key = Registry.CurrentUser.CreateSubKey(keyPath))
        {
            if (key.GetValue(null) as string != value)
            {
                key.SetValue(null, value);
                return true;
            }
        }

        return false;
    }


回答4:

If you write the keys into HKEY_CURRENT_USER\\Software\\Classes instead of HKEY_CLASSES_ROOT, this should work without administrator privileges under Vista and later.



回答5:

Solution above did not work for me with Windows 10. Here is my solution to open files with the .myExt extension with %localappdata%\\MyApp\\MyApp.exe for current user. Optimised after reading comments.

 String App_Exe = \"MyApp.exe\";
 String App_Path = \"%localappdata%\";
 SetAssociation_User(\"myExt\", App_Path + App_Exe, App_Exe);

 public static void SetAssociation_User(string Extension, string OpenWith, string ExecutableName)
 {
    try {
                using (RegistryKey User_Classes = Registry.CurrentUser.OpenSubKey(\"SOFTWARE\\\\Classes\\\\\", true))
                using (RegistryKey User_Ext = User_Classes.CreateSubKey(\".\" + Extension))
                using (RegistryKey User_AutoFile = User_Classes.CreateSubKey(Extension + \"_auto_file\"))
                using (RegistryKey User_AutoFile_Command = User_AutoFile.CreateSubKey(\"shell\").CreateSubKey(\"open\").CreateSubKey(\"command\"))
                using (RegistryKey ApplicationAssociationToasts = Registry.CurrentUser.OpenSubKey(\"Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\ApplicationAssociationToasts\\\\\", true))
                using (RegistryKey User_Classes_Applications = User_Classes.CreateSubKey(\"Applications\"))
                using (RegistryKey User_Classes_Applications_Exe = User_Classes_Applications.CreateSubKey(ExecutableName))
                using (RegistryKey User_Application_Command = User_Classes_Applications_Exe.CreateSubKey(\"shell\").CreateSubKey(\"open\").CreateSubKey(\"command\"))
                using (RegistryKey User_Explorer = Registry.CurrentUser.CreateSubKey(\"Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Explorer\\\\FileExts\\\\.\" + Extension))
                using (RegistryKey User_Choice = User_Explorer.OpenSubKey(\"UserChoice\"))
                {
                    User_Ext.SetValue(\"\", Extension + \"_auto_file\", RegistryValueKind.String);
                    User_Classes.SetValue(\"\", Extension + \"_auto_file\", RegistryValueKind.String);
                    User_Classes.CreateSubKey(Extension + \"_auto_file\");
                    User_AutoFile_Command.SetValue(\"\", \"\\\"\" + OpenWith + \"\\\"\" + \" \\\"%1\\\"\");
                    ApplicationAssociationToasts.SetValue(Extension + \"_auto_file_.\" + Extension, 0);
                    ApplicationAssociationToasts.SetValue(@\"Applications\\\" + ExecutableName + \"_.\" + Extension, 0);
                    User_Application_Command.SetValue(\"\", \"\\\"\" + OpenWith + \"\\\"\" + \" \\\"%1\\\"\");
                    User_Explorer.CreateSubKey(\"OpenWithList\").SetValue(\"a\", ExecutableName);
                    User_Explorer.CreateSubKey(\"OpenWithProgids\").SetValue(Extension + \"_auto_file\", \"0\");
                    if (User_Choice != null) User_Explorer.DeleteSubKey(\"UserChoice\");
                    User_Explorer.CreateSubKey(\"UserChoice\").SetValue(\"ProgId\", @\"Applications\\\" + ExecutableName);
                }
                SHChangeNotify(0x08000000, 0x0000, IntPtr.Zero, IntPtr.Zero);
            }
            catch (Exception excpt)
            {
                //Your code here
            }
        }

  [DllImport(\"shell32.dll\", CharSet = CharSet.Auto, SetLastError = true)]
  public static extern void SHChangeNotify(uint wEventId, uint uFlags, IntPtr dwItem1, IntPtr dwItem2);


回答6:

You are using an old version of Visual Studio, Vista is going to treat your program as a \"legacy\" Windows app. And redirect the registry writes you make. Include a manifest in your program so you\'ll look Vista-aware. This manifest is automatically included by VS2008 and up.

Beware that this still won\'t solve the problem for your user, she\'s very unlikely to run your app with UAC turned off. You\'ll need to write a separate app that has a manifest as linked and asks for administrator privileges. It needs the manifest with requestedExecutionLevel set to requireAdministrator.



回答7:

If you\'re using Visual Studio 2015 then install the setup and deployment extension. Create a Setup Wizard, and then attach your .exe file to it. Right click your main program in the solution explorer go to -view, -file types, and then right click on the file types and select add new file type. Change all the properties to your needs and then build the MSI installer.

NOTE: I re-read your question and realized that you did not want an installer. Sorry about that, although you should consider using one because it gives you a lot more customization over your program(s).