将文件扩展名与应用程序关联

Posted

技术标签:

【中文标题】将文件扩展名与应用程序关联【英文标题】:Associate File Extension with Application 【发布时间】:2011-02-10 12:46:26 【问题描述】:

我已经编写了一个编辑特定文件类型的程序,我希望用户可以选择在启动时将我的应用程序设置为该文件类型的默认编辑器(因为我不需要安装程序)。

我试图通过向 HKEY_CLASSES_ROOT 添加一个密钥来编写一个可重用的方法来为我关联一个文件(最好在任何操作系统上,尽管我正在运行 Vista),并且我正在将它与我的应用程序一起使用,但是它好像没用。

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();

知道为什么它不起作用吗?一个示例使用可能是

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

如果我使用 regedit 执行相同操作,则使用“CurrentUser”的方法部分似乎可以工作,但使用我的应用程序却不行。

【问题讨论】:

你试过以管理员身份运行你的程序吗? UAC 意味着您的应用不会以管理员身份运行,除非您明确要求它。你运行 Vista,Vista 包括 UAC。您能否再次检查程序是否以管理员身份运行? 我试过“以管理员身份运行”加上 UAC 已经关闭,但程序运行后文件仍然没有关联。 我认为您的方法中倒数第三行可能不正确。我认为您不想将“CurrentUser”设置为子项。 相关问题Filetype association with application (C#) 【参考方案1】:

答案比我预期的要简单得多。 Windows 资源管理器对 open with 应用程序有自己的覆盖,我试图在最后几行代码中对其进行修改。如果您只是删除资源管理器覆盖,则文件关联将起作用。

我还告诉资源管理器,我通过使用 P/Invoke 调用非托管函数 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
    var 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);

【讨论】:

我知道这是旧的,您可能已经发现了这一点,但我注意到在这段代码段和您的第一篇文章中,CurrentUser 的第一行 = 您已将 .ucs 扩展名硬编码到您的OpenSubKey() 调用。 对我来说效果很好!但请注意 - 需要管理权限 删除 .png 等已知扩展名的子键正在工作,但只要我通知资源管理器,它就会恢复 UserChoice。如何停止资源管理器恢复 UserChoice?【参考方案2】:

这是一个完整的例子:

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;
    

【讨论】:

这为我节省了很多时间。在 Windows 10 上完美运行。干得好! 如果能添加一个删除文件关联的函数就好了。【参考方案3】:

您可以通过 托管 方式进行操作via ClickOnce。自己不要对注册表大惊小怪。这可以通过项目属性 => 发布 => 选项 => 文件关联的 VS2008 及更高版本(包括 Express)中的工具(即无 xml)获得

【讨论】:

不错的答案,但不幸的是我正在使用 VS2005,所以我必须等到我得到 VS2010 吗? 这很好,但是,您如何使用此方法并设置自定义命令行参数?这个方法强制你使用app.exe "%1",但是如果我想让它使用app.exe /config "%1"呢?【参考方案4】:

上面的解决方案不适用于 Windows 10。 这是我为当前用户使用 %localappdata%\MyApp\MyApp.exe 打开扩展名为 .myExt 的文件的解决方案。读取cmets后优化。

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

【讨论】:

来自this answer: "RegistryKey 类实现了IDisposable,因此您应该将您的密钥包装在using 语句中。" 或者,您也可以完成后应在RegistryKey 上致电Close or Dispose。这意味着如本例所示链接对CreateSubKey 的调用是个坏主意 同意你的看法,它更简洁,但了解我可以从上面的代码中得到的最坏的副作用是什么仍然很有趣。想法? 如果您不释放 unmanaged 资源(例如,RegistryKey),您的应用程序将遭受内存泄漏。请参阅Relation between resource leaks and memory leaks and performance 和IDisposable Interface。请注意,问题和接受的答案中的代码示例都包括根据需要调用RegistryKey.Close 哎呀。花 30 秒看这段代码,全错了。 User_Extension 键在 CURRENT_USER 中创建一个 .ext 键。应该是CLASSES_ROOT。我不再照顾那件事了。如果这是错误的,那么其余的也可能是错误的。 这对我有用,开箱即用,tyvm。【参考方案5】:

如果您将密钥写入HKEY_CURRENT_USER\Software\Classes 而不是HKEY_CLASSES_ROOT,则在 Vista 及更高版本下无需管理员权限即可工作。

【讨论】:

【参考方案6】:

您使用的是旧版本的 Visual Studio,Vista 会将您的程序视为“旧版”Windows 应用程序。并重定向您所做的注册表写入。在你的程序中包含a manifest,这样你就可以看到Vista了。 VS2008 及更高版本会自动包含此清单。

请注意,这仍然无法为您的用户解决问题,她不太可能在关闭 UAC 的情况下运行您的应用。您需要编写一个单独的应用程序,该应用程序具有链接清单并要求管理员权限。它需要将 requestedExecutionLevel 设置为 requireAdministrator 的清单。

【讨论】:

我无法将清单添加到项目中,因为实际的 exe 不在我的项目中。有什么办法让它出现以便我可以添加资源? (去添加 -> 现有项目并选择 obj 文件夹中的 .exe 只是复制它) 如果这真的是一个遗留应用程序,你可能会破坏事情。但您可以使用 mt.exe SDK 工具注入清单。【参考方案7】:

如果您使用的是 Visual Studio 2015,请安装设置和部署扩展。创建一个安装向导,然后将您的 .exe 文件附加到它。在解决方案资源管理器中右键单击您的主程序转到-view,-file types,然后右键单击文件类型并选择添加新文件类型。根据需要更改所有属性,然后构建 MSI 安装程序。

注意:我重新阅读了您的问题并意识到您不需要安装程序。很抱歉,尽管您应该考虑使用一个,因为它可以为您的程序提供更多自定义。

【讨论】:

【参考方案8】:

将文件扩展名与您自己的程序相关联的实际方法:

using Microsoft.Win32;
using System;
using System.IO;
using System.Runtime.InteropServices;

private static void RegisterForFileExtension(string extension, string applicationPath)
    
        RegistryKey FileReg = Registry.CurrentUser.CreateSubKey("Software\\Classes\\" + extension);
        FileReg.CreateSubKey("shell\\open\\command").SetValue("", applicationPath + " %1");
        FileReg.Close();

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

【讨论】:

【参考方案9】:

一行

FileReg.CreateSubKey("shell\open\command").SetValue("", applicationPath + " %1");

应该修改为

FileReg.CreateSubKey("shell\open\command").SetValue("", $"\"applicationPath\" \"%1\"");

如果您不希望路径中的空格出现问题,例如:

C:\我的文件夹\我的文件.txt

【讨论】:

以上是关于将文件扩展名与应用程序关联的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 .desktop 文件(在 linux 下)将文件扩展名与应用程序相关联?

将特定的文件扩展名与我的 delphi 应用程序相关联 - 有任何库吗?

Windows 7文件扩展名关联

cpack:如何在安装过程中将程序与文件扩展名关联?

将文件类型与 Chrome 打包应用程序相关联

如何将文件扩展名关联到 C# 中的当前可执行文件