Как я и обещал в прошлый раз, давайте сегодня разберемся, каким же образом наше расширение может добавить команды и меню в Visual Studio. Студия, как правило, не загружает в память расширение до тех пор, пока оно не потребуется для выполнения какого-либо действия, например, пока пользователь не кликнет по пункту меню. Это означает, что студия отображает меню расширения до загрузки самого расширения. Как это реализовано? Данные меню хранятся в сборке как бинарный ресурс и студия берет информацию из него без загрузки всего остального. При компиляции расширения в этот ресурс помещается CTO-файл (Command Table Output), который в свою очередь является результатом компиляции VSCT-файла (Visual Studio Command Table). VSCT-компилятор - независимая от основного компилятора сущность, вызов которой мы должны в явном виде прописать в csProj-файл, как это показано ниже:
и
Мастер создания проекта Visual Studio Package делает это сам, если поставить одну из галочек, указывающих, что мы хотим добавить визуальные контролы:
VSCT-файл содержит XML-данные. Корневой элемент называется CommandTable:
Как вы видите, компания Microsoft предоставляет XSD-cхему, найти ее можно локально после установки Visual Studio SDK. На моей VS2013 она оказалась в C:\Program Files\Microsoft Visual Studio 12.0\XML\Schemas\VSCT.xsd
Признаюсь честно, я вообще не представляю, что с этим файлом делать. Посмотреть атрибуты конкретного элемента? Да, можно. Проверить готовый файл на валидность? Пожалуйста. Но разобраться со сложным форматом с нуля по схеме мне еще не разу не удавалось.
Обратите внимание, что указание пространства имен обязательно, иначе файл не скомпилируется. К тому же указание пространства имен включает в редакторе IntelliSence.
Элемент CommandTable в общем случае содержит следующие элементы:
Далеко не все элементы обязательны, некоторые из них выполняют свои, довольно узкие, задачи. Поэтому давайте лучше рассмотрим конкретные примеры и те элементы, которые для них потребуются.
Пример: Добавление команды в меню Edit
Добавим в меню Edit команду Package menu item.
Готовый VSCT-файл имеет следующий вид:
Из всех элементов нам понадобились только Extern, Commands и Symbols.
Элемент Symbols
Все меню, группы и команды идентифицируются по паре значений - guid и id, где id - целое 32-разрядное число. Поскольку человеку неудобно работать с числовыми значениями, каждому гуиду и каждому идентификатору присваиваются наименования. Эти соответствия прописываются вручную в Symbols в виде
Элементы IDSymbol вложены в GuidSymbol, поскольку целочисленные идентификаторы уникальны только в пределах GUID’а. Соответственно, у другого GUID’а могут быть такие же числовые идентификаторы.
GUID’ы, очевидно, должны быть уникальны. Идентификаторы - любые значения, какие вам нравится, но уникальные в пределах GUID’а.
Наименования тоже любые, для удобства обычно пишут разные префиксы, но это не обязательно.
Элемент Symbols традиционно размещается в конце файла.
Элемент Commands
Здесь определяются все меню и команды. В нашем случае мы имеем один элемент Group и один элемент Button.
Button - это команда. Действительно, название сбивает с толку, поскольку команда совершенно не обязательно будет кнопкой. В нашем случае это пункт меню.
Если быть точнее, то команда - это опубликованное именованное действие. Это действие может быть вызвано другим расширением или набрано с клавиатуры в Command Window (попробуйте набрать там Edit.Packagemenuitem и нажать Enter, запустится наша команда). Мы для удобства пользователя привязываем к действию визуальный контрол, но в общем случае это не требуется.
Group (группа) - это смысловое объединение команд. Команды, входящие в каждую из групп, отделяются от остальных команд сепараторами. В нашем случае в группу входит только одна команда, поэтому мы получили один пункт меню с сепараторами сверху и снизу.
Взаимные отношения сущностей задаются через элементы Parent, то есть Родитель. Родителем Button прописан guid и id группы.
Родителем группы может быть только меню. Мы разместили группу в меню Edit задав ей соответствующего родителя
Здесь guidSHLMainMenu - GUID всех команд главного меню Visual Studio, а IDM_VS_MENU_EDIT идентификатор меню Edit. Откуда берутся эти константы? Из заголовочного файла stdidcmd.h, который мы включили в начале.
При необходимости найдите у себя этот файл, наименования определений там говорят сами за себя. На своей машине я обнаружил его в C:\Program Files (x86)\Microsoft Visual Studio 12.0\VSSDK\VisualStudioIntegration\Common\Inc
Значения Priority определяют местоположение данной команды или группы в группе или меню соответственно. Поиграйтесь с Priority в строке
Местоположение нашей группы с командой в меню Edit будет меняться. Чем меньше приоритет, тем обычно выше и левее отображается сущность.
Пример: Добавление подменю в меню Edit с двумя командами
В прошлом примере у нас не было собственного меню, мы встраивали голую команду. Давайте теперь добавим подменю, в котором разместим две команды.
Результирующий файл имеет следующий вид:
Появился элемент Menu. В нем задано наименование, под которым это подменю будет отображаться (элемент ButtonText) и родитель MyMenuGroup.
Еще раз вспомним:
- Команды входят в группу
- Группа входит в меню
- Меню входит в группу
То есть мы реализовали так:
- В меню Edit помещаем группу MyMenuGroup
- В группу MyMenuGroup помещаем меню MySubMenu
- В меню MySubMenu помещаем группу MySubMenuGroup
- В группу MySubMenuGroup помещаем команды cmdidMyCommand1 и cmdidMyCommand2
Казалось бы, зачем создавать MyMenuGroup, если в нем всего один элемент - наше меню, но правила жесткие, меню может быть только в группе. И команды тоже. Если командам напрямую прописать MySubMenu, то меню не отобразится вообще, так как с точки зрения Visual Studio оно вроде как пустое получается - без групп.
Пример: Добавление контекстного меню
До сих пор мы добавляли свои контролы к главному меню, однако совершенно очевидным образом мы можем расширить и контекстные меню в разных окнах. Давайте перенесем подменю с двумя командами из предыдущего примера в контекстное меню редактора кода. Для этого достаточно родителем MyMenuGroup сделать контекстное меню. То есть фрагмент
должен быть изменен на
И вот результат:
В следующий раз рассмотрим иконки, шорткаты и все остальное, что покажется важным.
Спасибо за внимание. :)