Большинство из нас используют Visual Studio при программировании на C#. Вы также можете использовать Visual Studio Code, но я предпочитаю Visual Studio. Последняя версия Visual Studio — 2022, а версия сообщества бесплатна для использования, но в ней отсутствует несколько несущественных функций. Хотя Visual Studio содержит множество инструментов и опций, некоторые из них всегда отсутствуют. Вы можете расширить эту IDE с помощью расширений. Но знаете ли вы, что можете создавать собственные расширения Visual Studio с помощью C#?

В этой статье я собираюсь показать вам основы создания расширения для Visual Studio. Я использую предварительную версию сообщества Visual Studio 2022, но эта статья также работает для Visual Studio 2019, если она у вас есть.

Расширение объяснил

Расширение используется для расширения чего-либо (да!). Вы везде видите расширения. Даже палка для селфи — это форма расширения. В этом случае это продолжение вашей руки, чтобы сделать селфи. Расширение обычно ставится или к чему-то подключается.

Программные приложения используют множество расширений. Расширение добавляет дополнительные функции или функции к существующей части программного обеспечения. Таким образом, нам не нужно менять ядро ​​приложения, а просто расширять его с помощью кода. Можно даже сказать, что плагины — это форма расширения.

Visual Studio знакома с расширениями. На самом деле многие окна и инструменты, которые вы используете в Visual Studio, являются расширениями. В нем даже есть пункт меню «Управление расширениями», где вы можете просматривать и устанавливать новые расширения или удалять те, которые вы больше не используете.

Цель этой статьи

Может быть, это хорошая идея, чтобы рассказать вам, какова цель этой статьи. Ну, в предыдущих версиях Visual Studio была возможность автоматически генерировать GUID. Каким-то образом они удалили это, или я больше не могу найти. Я использую GUID для секретных ключей и прочего. Это отличный пример, чтобы показать вам, как создать расширение.

Таким образом, цель состоит в том, чтобы создать GUID через расширение. Но разделю:

  • Сначала я покажу вам, как можно выполнить расширение или код с помощью пункта меню.
    Я добавлю пункт меню в меню "Инструменты". Когда вы щелкаете пункт меню, GUID будет помещен в код, где находится курсор.
  • Во-вторых, я покажу вам, как создать окно инструментов.
    В этом окне будет список и кнопка. Когда вы нажимаете кнопку, создается 10 GUID. Вы можете дважды щелкнуть GUID, и этот GUID будет помещен в ваш код и удален из списка.

Подготовка

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

Откройте установщик Visual Studio и выберите «Изменить» в своей версии Visual Studio. Да, вы можете установить несколько версий Visual Studio.

После того, как вы нажали «Изменить», прокрутите вниз, пока не увидите «Другие наборы инструментов», пока не увидите «Разработка расширения Visual Studio». Если этот флажок установлен, вам не нужно ничего делать, просто выйдите из установщика Visual Studio и перейдите к следующей главе. Если он не отмечен, отметьте его и нажмите кнопку «Изменить» (правый нижний угол).

Это не займет много времени. Если вы возьмете кофе, он будет готов до того, как вы вернетесь. Если только вы не читаете это на кухне или у вас на столе нет кофеварки…

Проект VSIX

Если вы запустите Visual Studio после установки, вы можете создать новый проект. Найдите «Проект VSIX». Этот проект создан для тех, кто хочет создать расширение для Visual Studio. В нем будет все, что вам нужно для начала. Я назову свой проект GuidGenerator.

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

Возможно, вы заметили, что вам не нужно было выбирать версию .NET при создании нового проекта. Это связано с тем, что расширения создаются с помощью .NET 4.7.2. Я не мог найти причину, но я думаю, что это как-то связано с проблемами совместимости. Visual Studio будет работать на определенной версии, и с каждой новой версией .NET или C# сложно обновлять всю IDE… Думаю, но это только предположение.

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

Обозреватель решений

Давайте посмотрим на файлы, которые приносит нам новый проект.

Я не буду обсуждать их все, а только те, которые выделяются. Например, GuidGeneratorPackage.cs. Это класс, который содержит некоторую информацию о расширении, которое вы хотите создать. Расширения создаются с помощью пакетов. И каждому пакету нужен уникальный идентификатор, которым является PackageGuidString.

Что бы вы ни делали, не меняйте PackageGuidString или единственный метод внутри этого класса. Это может привести к сбою вашего расширения или даже к сбою Visual Studio.

Еще один интересный файл — source.extension.vsixmanifest. Те, кто когда-либо работал над приложением для Android (я работал), могут знать о манифесте. Это немного то же самое. Я не знаю, есть ли они в приложениях для iPhone, так как я никогда не работал с ними.

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

Отладка

Одна из вещей, которые мне не очень нравятся, это когда я начинаю запускать или отлаживать расширение. Попробуйте, просто запустите расширение или пакет. Я буду ждать…

Да, он запускает другой экземпляр Visual Studio. Это может быть очень медленно, если ваша система не обновлена. Особенно, когда у вас мало оперативной памяти. Но если подумать, то логично запустить новую Visual Studio. Как еще вы хотите проверить свою посылку?

Если вы собираетесь создать много логики (код C#) в своем расширении, отделите эту логику от пакета и создайте модульные тесты. Таким образом, вы можете проверить, работает ли логика, которая является самой большой частью вашего пакета. Таким образом, вам нужно будет запускать экземпляр Visual Studio только тогда, когда вы работаете над стилем.

Создать пункт меню

Самая простая часть — это создание пункта меню, доступ к которому можно получить через меню Visual Studio.

Пункт меню и логика, стоящая за ним, называются командой. Чтобы создать команду, нам нужно добавить файл Command. Вы можете сделать это, щелкнув проект правой кнопкой мыши и выбрав Добавить => Новый элемент.

В более новых версиях Visual Studio это показывает вам простое диалоговое окно. Здесь вы можете ввести имя файла, который хотите создать, и Visual Studio попытается угадать, что это за файл. Но нам нужен конкретный шаблон с скаффолд-кодом. Если вы видите этот экран, просто нажмите кнопку Показать все шаблоны.

В диалоговом окне найдите команду. Я назову его «GuidGeneratorCommand». Нажмите «Добавить», чтобы создать новую команду.

После добавления команды были добавлены два новых файла: GuidGeneratorCommand.cs и GuidGeneratorPackage.vsct. Последний — для структуры и поведения графических частей. Вы можете установить имя, значок, описание и в каком меню и панели инструментов должны появиться. Он в формате XML и нуждается в некоторой настройке.

GuidGenerateCommand.cs содержит C#, отсюда и расширение cs. Вот где происходит волшебство. Конструктор вызывается из метода InitializeAsync. Параметрами являются пакет, определенный в GuidGeneratePackage.cs, и commandService. CommandService основан на OleMenuCommandService, классе, который добавляет обработчики для команд меню.

Давайте запустим этот пакет и посмотрим, что произойдет. Ничего особенного, верно? Что ж, открываем меню Инструменты:

Тада! И когда вы нажимаете на нее:

Но где выполняется этот код? В методе Execute в файле GuidGeneratorCommand.cs.

Запуск приложения Windows

Давайте изменим поведение этого пункта меню. Откройте GuidGeneratorCommand.cs и найдите метод Execute (отправитель объекта, EventArgs e). Это метод события, как вы можете видеть, взглянув на параметры.

Удалите все строки кода в этом методе, кроме для ThreadHelper.ThrowIfNotOnUIThread(). Затем измените метод следующим кодом:

private void Execute(object sender, EventArgs e)
{
    ThreadHelper.ThrowIfNotOnUIThread();

    Process proc = new Process();
    proc.StartInfo.FileName = "notepad.exe";
    proc.Start();
}

Возможно, вы захотите добавить ссылку на System.Diagnostics.

Теперь снова запустите пакет и щелкните пункт меню Invoke GuidGeneratorCommand. Вы увидите, что Notepad.exe был открыт. Потрясающий! Но это всего лишь небольшая демонстрация. Нам нужны GUID!

Управление редактором

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

private void Execute(object sender, EventArgs e)
{
    ThreadHelper.ThrowIfNotOnUIThread();

    DTE dte = (DTE)ServiceProvider.GetServiceAsync(typeof(DTE)).Result;

    TextSelection selection = (TextSelection)dte.ActiveDocument.Selection;
    selection.Text = Guid.NewGuid().ToString();
}

Итак, сначала я получаю DTE, который зависит от конфигурации. С помощью ServiceProvider мы можем получить его. Затем я получаю активный документ и из него выбираю выделенный текст. Этот выделенный текст имеет свойство Text, которое мы можем изменить. Я меняю выбор на новый GUID.

Вот и все! Если вы запустите этот пакет сейчас, создадите простое консольное приложение в отлаживаемом экземпляре Visual Studio и выделите текст, вы можете щелкнуть пункт меню Invoke GuidGeneratorCommand и увидеть волшебство!

Вы также можете просто щелкнуть где-нибудь в документе. Выбор не нужен.

Объяснение ThreadHelper

В предыдущих примерах кода вы видели функцию ThreadHelper.ThrowIfNotOnUIThread(). Это необходимо. Ваш пакет все равно запустится без него, но вы получите предупреждение (и C# и .NET любят предупреждения).

Имя уже должно быть подсказкой. Киньте если не на UI нить. Это означает, что оно не будет работать, если это расширение используется в командной среде. Противоположным является ThreadHelper.ThrowIfOnUIThread(), что означает, что вы не можете использовать эту команду внутри пользовательского интерфейса, такого как Visual Studio.

Замените ThrowIfNotOnUIThread на ThrowIfOnUIThread и посмотрите, что произойдет.

Создание окна инструментов

Далее рассмотрим, как мы можем создать окно инструментов. Вы знаете, как Solution Explorer или Toolbox. Но для GUID давайте не будем воссоздавать то, что уже есть.

Чтобы создать окно инструментов, нам нужно сделать что-то похожее на то, что мы сделали с командой: Добавить новый элемент. Для этого есть два шаблона: окно инструментов или окно асинхронных инструментов. Разница в асинхронности. Последний можно использовать только в Visual Studio версии 17.0 и выше.

Я буду создавать «обычное» окно инструментов и назову его GuidGeneratorToolWindow.

После того, как вы нажали «Добавить», будут созданы 3 файла:

  • GuidGeneratorToolWindow.cs
    Это основа окон инструментов. Класс наследуется от ToolWindowPane, что позволяет создавать собственное окно инструментов.
    Заголовок — это текст, который будет отображаться в верхней части окна инструментов. Я меняю его на «Генератор GUID».
  • GuidGeneratorToolWindowCommand.cs
    Это тот же тип класса, что и GuidGeneratorCommand.cs, который мы только что обсуждали. За исключением того, что мы ничего там не меняем.
    Не меняйте Execute, потому что это откроет графический интерфейс окна инструмента. Он попытается найти GuidGeneratorToolWindowx, определенный в GuidGeneratorToolWindow.cs, и показать окно инструмента.
  • GuidGeneratorToolWindowControl.xaml
    Окну инструментов нужен графический интерфейс, и они (думаю, Microsoft) решили использовать XAML в качестве языка интерфейса для окон инструментов. В XAML нет ничего плохого, но мне он не нравится, это личное дело.
    Во всяком случае, именно здесь мы проектируем окно инструмента.

Тестовый забег

Ничего не меняя в коде, я просто запущу это и посмотрю, что произойдет. Окно инструментов не открывается по умолчанию, и наше окно инструментов не находится в меню «Инструменты». Он должен быть в меню «Вид». Но вам нужно открыть «Другие окна», и вы найдете там GenerateGuidToolWindow.

Само окно инструментов не очень интересное. Это просто кнопка, и когда вы нажимаете на нее, появляется окно сообщения… Круто. Но теперь вы можете перетаскивать и закреплять окно инструментов в любом месте.

Изменение дизайна

Давайте покончим с этим и изменим дизайн окна инструментов. Я хочу список и кнопку. Когда я нажимаю кнопку, список получает 10 случайных GUID. Затем, когда я дважды щелкаю GUID, его нужно вставить в мой редактор и удалить из списка. Легкий!

Вот XAML, который я использую:

<UserControl x:Class="GuidGenerator.GuidGeneratorToolWindowControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:vsshell="clr-namespace:Microsoft.VisualStudio.Shell;assembly=Microsoft.VisualStudio.Shell.15.0"
             Background="{DynamicResource {x:Static vsshell:VsBrushes.WindowKey}}"
             Foreground="{DynamicResource {x:Static vsshell:VsBrushes.WindowTextKey}}"
             mc:Ignorable="d"
             d:DesignHeight="300" d:DesignWidth="300"
             Name="MyToolWindow">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"></RowDefinition>
            <RowDefinition Height="50"></RowDefinition>
        </Grid.RowDefinitions>
        <ListBox Name="lstGuids" Grid.Row="0" MouseDoubleClick="lstGuids_MouseDoubleClick"/>
        <Button Content="Button" Grid.Row="1" Click="button1_Click"/>
    </Grid>
</UserControl>

А это код C# для файла XAML:

public partial class GuidGeneratorToolWindowControl : UserControl
{
    public GuidGeneratorToolWindowControl()
    {
        this.InitializeComponent();
    }

    private void button1_Click(object sender, RoutedEventArgs e)
    {
        lstGuids.Items.Clear();

        for (int i = 0; i < 10; i++)
            lstGuids.Items.Add(Guid.NewGuid().ToString());
    }

    private void lstGuids_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {
        ThreadHelper.ThrowIfNotOnUIThread();

        DTE dte = Package.GetGlobalService(typeof(DTE)) as DTE;

        var selection = (TextSelection)dte.ActiveDocument.Selection;
        selection.Text = lstGuids.SelectedValue.ToString();

        lstGuids.Items.Remove(lstGuids.Items[lstGuids.SelectedIndex]);
    }
}

Я думаю, что код довольно прост, особенно код C#. 1stGuids_MouseDoubleClick делает то же самое, что и предыдущий пример с GuidCommand: добавление GUID из списка в активный документ.

А вот как это выглядит, когда я запускаю пакет и открываю окно инструментов:

Я открываю консольное приложение, которое использовал раньше, открываю Program.cs и помещаю курсор куда-нибудь в документ. Затем я дважды щелкаю GUID. Смотрите, как происходит волшебство!

Распространение пакета

Прежде чем мы сможем использовать расширение в обычной Visual Studio (не в отладочной), нам нужно добавить некоторые метаданные в source.extension.vsixmanifest. Я думаю, что большинство параметров говорят сами за себя, и созданное ими окно дизайна очень помогает. Не так давно нам приходилось делать это вручную и с помощью XML. Одна крошечная опечатка заставила все взорваться.

Изменение подписи

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

Откройте GuidGeneratorPackage.vsct и найдите Вызвать GuidGenerator. В моем файле он находится в строке 59. Этот фрагмент XML полностью посвящен пункту меню. Вы можете изменить текст здесь на Вставить GUID или на любой другой.

<Button guid="guidGuidGeneratorPackageCmdSet" id="GuidGeneratorCommandId" priority="0x0100" type="Button">
  <Parent guid="guidGuidGeneratorPackageCmdSet" id="MyMenuGroup" />
  <Icon guid="guidImages" id="bmpPic1" />
  <Strings>
    <ButtonText>Insert GUID</ButtonText>
  </Strings>
</Button>

Другое родительское меню

Я не думаю, что пункт Вставить GUID должен находиться в меню Инструменты. Я думаю, было бы лучше переместить его в меню Правка. Если вы посмотрите на приведенный выше XML, вы заметите Parent с идентификатором MyMenuGroup. Это ссылка на группу в разделе Группы, расположенную прямо над кнопками.

Чтобы изменить меню, просто измените идентификатор родителя в этой группе. Текущий идентификатор: IDM_VS_MENU_TOOLS. Немного сложно угадать, какой идентификатор предназначен для меню «Правка». Итак, вот список вариантов:

Нам нужна редакция. который является идентификатором IDM_VS_MENU_EDIT.

<Groups>
  <Group guid="guidGuidGeneratorPackageCmdSet" id="MyMenuGroup" priority="0x0600">
    <!--<Parent guid="guidSHLMainMenu" id="IDM_VS_MENU_TOOLS" />-->
      <Parent guid="guidSHLMainMenu" id="IDM_VS_MENU_EDIT" />
  </Group>
</Groups>

Установка пакета

Хорошо, теперь все готово. Находим установочный файл. Для этого нет экспорта или генерации исполняемого файла. Итак, вот что вам нужно сделать:

  1. Перестройте свое решение и убедитесь, что все работает.
  2. Откройте проект расширения в проводнике.
  3. Затем перейдите в bin -> debug.
  4. Найдите файл VSIX или расширение Microsoft Visual Studio.
  5. Закройте Visual Studio.
  6. Скопируйте это или дважды щелкните по нему.
  7. Установите расширение.
  8. Запустите Visual Studio.
  9. Сделанный!

Заключение

Ну вот и все! Основы создания расширения в Visual Studio 2022 Community Preview! Не так сложно, правда? Конечно, вы можете делать гораздо больше, чем просто генерировать и использовать некоторые GUID. Но я предпочитаю, чтобы мои статьи были короткими, и лучше пишу новую, если читатели хотят узнать больше.

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