Violet Tape некоторые мысли о разработке на платформе .Net

20Сен/102

Register file extension

Для одного из проектов понадобилось мне создать ассоциацию между файлами и программой. Не важно сейчас, это новое какое-нибудь расширение в духе .myextension или же вы хотите переопределить открывание .mp3 на свою программу. Понятное дело, что вы не хотите озадачивать пользователя запуском непонятно чего или там отрыть файло специфическим образом, или запускать reg файл. Все надо сделать программно, по желанию пользователя, или же предложить ему переназначить открывать файлы по умолчанию с помощью вашей программы.

Итак, цель – программно зарегистрировать любое расширение на вашу программу.

Поиск по интернету дает в основном ссылки на статью http://www.codeproject.com/KB/dotnet/System_File_Association.aspx где есть библиотека по работе с регистрацией расширений. Скачав библиотеку, я увидел насколько велика она и монструозна, и решил написать свое, мелкое и попроще, а то я путаюсь в таком обилии всего =)

Фактическая работа

В .Net нет ничего готового для того, чтобы зарегистрировать расширение на приложение. Но можно повторить в коде то, как это содержится в системе. Ниже представлен наиболее общий вид регистрации расширения для приложения с помощью reg файла.

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\.myextension]
@="app.myextension"

[HKEY_CLASSES_ROOT\app.myextension]
@="Text Document"

[HKEY_CLASSES_ROOT\app.myextension\DefaultIcon]
@="%SystemRoot%\\SysWow64\\imageres.dll,-102"

[HKEY_CLASSES_ROOT\app.myextension\shell]

[HKEY_CLASSES_ROOT\app.myextension\shell\open]

[HKEY_CLASSES_ROOT\app.myextension\shell\open\command]
@="%SystemRoot%\\System32\\notepad.exe" \"%1\""

[HKEY_CLASSES_ROOT\app.myextension\shell\print]

[HKEY_CLASSES_ROOT\app.myextension\shell\print\command]
@="%SystemRoot%\\System32\\notepad.exe" /p \"%1\""

Т.е. должно получится две ветки. %1 – является указателем на выбранный файл.

Данный код иллюстрирует случай, кода вы устанавливаете ассоциацию для всех пользователей на компьютере, и для этого вам потребются права администратора. Для того, чтобы установить ассоциацию для одного пользователя, необходимо добавить данные в HKEY_CURRENT_USER\Software\Classes – для этого хвтати обычных прав исполнения.

Если у вас уникальное расширение и регистрация будет происходить при установке, то лучше воспользовать свойствами установщика и задать все ассоциации там. Это осветим как-нибудь потом, когда будем рассматривать установщик базовый в деталях.

Реализация

Фактически надо только записать данные в реестр. Другой вопрос как это сделать красиво! =)

Я думаю что было бы полезно иметь структуру, которая будет содержать все необходимые данные для создания ассоциации. Нам понадобятся:

  • Имя приложения
  • Путь до приложения
  • Путь до иконки (может быть путем до приложения, если не задавать другого)
  • Само расширение
  • Набор действий с файлом

Т.е. класс примерно следующего вида:

public class FileAssociateInfo {
   public string ApplicationName;
   public string ApplicationPath;
   public string ApplicationIconPath;
   private string fileExtension;
   public string FileExtension {
      get { return fileExtension; }
      set { fileExtension = FormatExtension(value); }
   }

   public string RegMarker {
      get { return string.Format("{0}{1}", ApplicationName, FileExtension); }
   }

   public List<FileAction> FileActions = new List<FileAction>();

   private static string FormatExtension(string info) {
      return info.StartsWith(".") ? info : "." + info;
   }
}

Набор действий с файлом, представляется тут тоже в виде класса. Т.к. судя по reg файлу необходимо будет задать имя команды и саму команду. Класс для этого может выглядеть следующим образом:

public class FileAction {
   public string Name;
   public string Command;
}

Очень просто! =)

Может конечно во мне есть излишняя любовь ко всякого рода спец классам, но здесь я дописал еще один относительно небольшой класс постройки FileAssociateInfo.

FileAssociateInfoBuilder поможет создать все необходимые данные. На мой взгляд итоговый код стал получше.

Т.е. стало возможно написать

var builder = new FileAssociateInfoBuilder("vt_notepad", @"c:\Windows\System32\notepad.exe")
      .AddCommand("Open", "", PassSelectedFile.ToApplication)
      .AddExtension(extension);

В конструктор передается имя приложения, путь до него, и далее задаются команды, с указанием, для файла или нет. В процессе я подумал, что можно сделать возможность добавления нескольких расширений, но пока отказался от этого.

PassSelectedFile это энумератор (черт, опять забыл как это по-русски будет) который указывает, передается файл или нет.

public enum PassSelectedFile {
    ToApplication,
    Nowhere
}

Мне кажется имя и значения вполне себе аутеничные! ;)

Сам билдер тоже прост, я надеюсь что для вас тоже.

public class FileAssociateInfoBuilder {
   private readonly string applicationPath;
   private readonly FileAssociateInfo fileAssociateInfo;

   public FileAssociateInfoBuilder(string applicationName, string applicationPath) {
      this.applicationPath = applicationPath;
      fileAssociateInfo = new FileAssociateInfo {
            ApplicationName = applicationName,
            ApplicationPath = applicationPath,
            ApplicationIconPath = applicationPath,
      };
   }

   public FileAssociateInfoBuilder AddExtension(string extension) {
      fileAssociateInfo.FileExtension = extension;
      return this;
   }

   public FileAssociateInfoBuilder AddCommand(string commandName, string arg, PassSelectedFile argValue) {
      fileAssociateInfo.FileActions.Add(new FileAction {
             Name = commandName,
             Command = string.Format("\"{0}\" {1} {2}",
                      applicationPath,
                      arg,
                      argValue == PassSelectedFile.ToApplication ? "\"%1\"" : "")
             });
      return this;
   }

   public FileAssociateInfo Result() {
      return fileAssociateInfo;
   }
}

После этого можно перейти к самому важному классу, собственно сервису который создаст все ключи в реестре.

Собственно создание ключей

public void CreateAssociation(FileAssociateInfo info) {
   if(Exists(info.FileExtension)) return;

    var classes = Registry.CurrentUser.CreateSubKey("Software\\Classes");
    classes.CreateSubKey(info.FileExtension).SetValue("", info.RegMarker);

    var mainKey = classes.CreateSubKey(info.RegMarker);
    mainKey.SetValue("", string.Format("{0} files", info.ApplicationName));
    mainKey.CreateSubKey("DefaultIcon").SetValue("", info.ApplicationIconPath);

    var shell = mainKey.CreateSubKey("Shell");

    info.FileActions.ForEach(action => {
          var command = shell.CreateSubKey(action.Name);
          command.SetValue("", action.Name);
          command.CreateSubKey("Command").SetValue("", action.Command);
        }
    );
}

Ничего сверхестественного, никакого rocket-science, просто последовательно создаем ключи в реестре. Сразу после этого все будет работать.

Удаление пройдет еще более просто, надо знать расширение и все. Остальное можно вычислить.

public void DeleteAssociation(string fileExtension) {
    var classes = Registry.CurrentUser.CreateSubKey("Software\\Classes");

    var mainKey = classes.CreateSubKey(fileExtension).GetValue("").ToString();

    classes.DeleteSubKeyTree(fileExtension);
    classes.DeleteSubKeyTree(mainKey);
}

На этом весь основной код закончен.

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

Развитие

Можно делать проверки на существование регистрации расширения. Можно получать все расширения в системе. Можно создавать свои действия для абсолютно всех файлов, если дописать действия для расширения «*». Если не лень, то можно сделать сервис который будет делать регистрацию в зависимости от привелегий пользователя, т.е. либо для всех пользователей если права администратора, либо только для одного пользователя.

Вообще данная технология и знания нам очень пригодятся в следующих статьях.  Так что будьте в курсе )

Исходные коды

Hard’n’heavy!

Комментарии (2) Пинги (0)
  1. Интересно и позновательно, а будет еще что-то по этой теме?

  2. A formidable share, I simply given this onto a colleague who was doing slightly evaluation on this. And he the truth is purchased me breakfast as a result of I found it for him.. smile. So let me reword that: Thnx for the treat! However yeah Thnkx for spending the time to debate this, I really feel strongly about it and love reading more on this topic. If doable, as you become experience, would you mind updating your weblog with more particulars? It is highly helpful for me. Huge thumb up for this weblog put up!


Оставить комментарий


Нет обратных ссылок на эту запись.