2010-04-21 5 views
36

Я написал программу, которая редактирует определенный тип файла, и я хочу дать пользователю возможность установить мое приложение в качестве редактора по умолчанию для этого типа файлов (поскольку я не хочу установщика) на старте.Связанное расширение файла с помощью приложения

Я попытался написать повторно используемый метод, который связывает файл для меня (желательно на любой ОС, хотя я использую Vista), добавляя ключ к HKEY_CLASSES_ROOT и использую его с моим приложением, но он, похоже, не работает.

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

Часть метода, который использует «CurrentUser», кажется, работает, если я делаю то же самое, используя Regedit, но с помощью моего приложения нет.

+0

ты попытался запустить программу в качестве администратора? –

+0

Я системный администратор, так что да. – User2400

+0

UAC означает, что ваше приложение не запускается как admin, если вы его явно не требуете. Вы запускаете Vista, Vista включает UAC. Можете ли вы проверить, работает ли программа как администратор? –

ответ

26

Ответ был намного проще, чем я ожидал. Проводник Windows имеет свое собственное переопределение для open с приложением, и я пытался изменить его в последних строках кода. Если вы просто удалите переопределение проводника, то ассоциация файлов будет работать.

Я также сказал исследователю, что я изменил ассоциацию файлов с помощью вызова неуправляемого функции 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); 
+8

Я знаю, что это уже давно, и вы, возможно, уже поймали это, но я заметил в этом сегменте кода и в вашем первом сообщении, что первая строка CurrentUser = у вас есть расширение .ucs, жестко закодированное в ваш вызов OpenSubKey(). –

+0

Работал очень хорошо для меня! но обратите внимание - требуется административные права. –

+0

. Удаление подраздела для известных расширений, таких как .png, работает, но как только я уведомляю об этом, он восстанавливает UserChoice. Как я могу остановить проводник, восстанавливающий UserChoice? – HGMamaci

17

Вы можете сделать это в via ClickOnce. Не суетитесь с реестром самостоятельно. Это доступно с помощью инструментария (т.е. без xml) в VS2008 и выше (включая Express) в Project Properties => Publish => Options => Ассоциации файлов

+2

Хороший ответ, но, к сожалению, я использую VS2005, так что буду ждать, пока я получу VS2010? – User2400

+0

Это хорошо и все, но как вы используете этот метод и задаете собственные аргументы командной строки? Этот метод заставляет вас использовать 'app.exe '% 1" ', но что, если бы я хотел, чтобы он выполнял' app.exe/config'% 1 "'? – Andy

3

Вы используете старую версию Visual Studio, Vista будет лечить ваша программа как «унаследованное» приложение Windows. И перенаправлять сделанные вами записи в реестре. Включите a manifest в свою программу, чтобы вы смотрели Vista. Этот манифест автоматически включается VS2008 и вверх.

Опасайтесь, что это все еще не решит проблему для вашего пользователя, она вряд ли сможет запустить ваше приложение с выключенным UAC. Вам нужно будет написать отдельное приложение, в котором есть манифест, связанный и запрашивающий права администратора. Ему нужен манифест с запрошенным параметром ExecutionLevel для requireAdministrator.

+0

Я не могу добавить манифест в проект, потому что фактический exe не в моем проекте. Любой способ сделать так, чтобы я мог добавить ресурс? (переход к существующему элементу -> существующий элемент и выбор файла .exe в папке obj просто копирует его) – User2400

+0

Вероятно, вы нарушите это, если это действительно устаревшее приложение. Но вы можете ввести манифест с помощью инструмента mt.exe SDK. –

7

Если вы пишете ключи в HKEY_CURRENT_USER\Software\Classes вместо HKEY_CLASSES_ROOT, это должно работать без прав администратора под Vista и позже.

3

Решения выше не работает для меня с окнами 10. Вот мое решение, чтобы открыть файл с .myExt % localappdata% \ MyApp \ MyApp.exe для текущего пользователя. Оптимизирован после чтения комментариев.

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

Из [этого ответа] (http://stackoverflow.com/a/13535039/1497596): * «Класс' RegistryKey' реализует 'IDisposable', поэтому вы должны обернуть свои ключи в' using' statement. * * Или , в качестве альтернативы вы должны вызвать [Закрыть или удалить] (http://stackoverflow.com/a/37059244/1497596) в 'RegistryKey', когда вы закончите с ним. Это означает, что ** цепочка вызовов на 'CreateSubKey', как показано в этом примере, является плохой идеей **. – DavidRR

+0

Согласитесь, что вы чище, но все же интересно понять, какой худший побочный эффект я могу ожидать от кода выше. Идеи? – sofsntp

+0

Если вы не распоряжаетесь * неуправляемым * ресурсом (например, 'RegistryKey'), ваше приложение будет страдать от утечек памяти. См. [Связь между утечками ресурсов и утечками памяти и производительностью] (http://stackoverflow.com/q/11879701/1497596) и [IDisposable Interface] (https://msdn.microsoft.com/en-us/library/System .IDisposable.aspx). Обратите внимание, что примеры кода как в вопросе, так и в принятом ответе включают вызовы 'RegistryKey.Close' по мере необходимости. – DavidRR

3

Если вы используете Visual Studio 2015, то установите расширение установки и развертывания. Создайте мастер установки, а затем прикрепите к нему свой файл .exe. Щелкните правой кнопкой мыши свою основную программу в проводнике решений, перейдите в типы -view, -file, а затем щелкните правой кнопкой мыши по типам файлов и выберите новый тип файла. Измените все свойства на свои нужды, а затем создайте установщик MSI.

ПРИМЕЧАНИЕ: Я перечитываю ваш вопрос и понял, что вы не хотите установщика. Извините за это, хотя вы должны использовать его, потому что он дает вам больше настроек по сравнению с вашей программой.

6

Вот полный пример:

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([email protected]"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; 
    } 
+0

Спасибо, это прекрасно! Он работает в Windows 10 – zoran