My pages

by Sergey Vinyarsky

VSCT - как и с чем это едят

Как я и обещал в прошлый раз, давайте сегодня разберемся, каким же образом наше расширение может добавить команды и меню в 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 сделать контекстное меню. То есть фрагмент

должен быть изменен на

И вот результат:

В следующий раз рассмотрим иконки, шорткаты и все остальное, что покажется важным. Спасибо за внимание. :)