Итак, прошло уже полтора года с тех пор как я начал разрабатывать мобильные приложения с помощью Xamarin и C#. За это время ребята из Xamarin основательно поработали над своей IDE, так что от связки iMac-Parallels Desktop-Visual Studio-Android я с радостью отказался в пользу iMac-Xamarin-Genymotion. Однако, Xamarin Studio все еще находится на том уровне, когда некоторые действия приходится выполнять вручную, но что делать, если это приходится совершать 5, 10, 15 и более раз за день? Ответ простой – проапгрейдить Xamarin Studio, написав Add-in, который будет делать всю работу за тебя. В этой статье я расскажу как создать простой Add-in и куда двигаться, если нужно что-то посерьезнее.
Подготовка
Для создания Add-in'а потребуется:
- Xamarin Studio;
- Addin maker.
Список не длинный. Addin maker устанавливается следующим образом:
- Открываем Xamarin Studio и заходим в менеджер дополнений
- В менеджере дополнений выбираем вкладку Gallery и вбиваем в поисковую строку Addin maker. Как только менеджер найдет Add-in, устанавливаем его
Если вдруг менеджер дополнений такой Add-in не нашел, берем его здесь.
После установки Addin Maker перезапускаем Xamarin Studio. Теперь все готово для того чтобы начать.
Цель создаваемого Add-in
Основная и единственная цель Add-in, о создании которого я расскажу, будет заключаться в удалении bin и obj папок в основной директории проекта нажатием на одну кнопку. Работая с Xamarin Studio на Mac, такую операцию порой приходится выполнять по несколько раз в час, вне зависимости от того под Android или под iOS дебажится проект.
Cоздание проекта
Для создания проекта в Xamarin Studio выбираем New Solution > C# > Xamarin Studio > Xamarin Studio Addin, вводим имя и выбираем расположение:
После создания проекта его структура должна выглядеть следующим образом:
AddinInfo и AssemblyInfo, как ясно из названия, несут в себе информацию об Add-in и сборке соответственно. В Manifest.addin же как раз описывается модель создаваемого расширения. Поэтому открываем его и прописываем следующие строки:
<?xml version="1.0" encoding="UTF-8"?>
<ExtensionModel>
<Extension path = "/MonoDevelop/Ide/Commands">
<Command id = "BinObjCleaner.BinObjCleanerCommands.DeleteBinObj"
_label = "Delete bin/obj"
_description = "Delete /bin and /obj folders in every solution project"
defaultHandler = "BinObjCleaner.DeleteBinObjHandler" />
</Extension>
<Extension path = "/MonoDevelop/Ide/MainMenu/Build">
<CommandItem id="BinObjCleaner.BinObjCleanerCommands.DeleteBinObj" />
</Extension>
</ExtensionModel>
А теперь по порядку. Строка:
<Extension path = "/MonoDevelop/Ide/Commands">
Она говорит о том, в какую категорию попадает Add-in. Так как создаем кнопку, категорией будет «Commands», если бы создавали шаблон файла, то в качестве категории нужно было бы указать «FileTemplates» (конечно же двумя категориями дело не ограничивается). Идем дальше.
<Command id = "BinObjCleaner.BinObjCleanerCommands.DeleteBinObj"
_label = "Delete bin/obj"
_description = "Delete /bin and /obj folders in every solution project"
defaultHandler = "BinObjCleaner.DeleteBinObjHandler" />
Здесь задается:
- id’шник кнопки, представляет из себя путь (с указанием namespace’а) до enum’а с типом кнопки, т.е. {namespace}.{enum_name}.{enum_type_name};
- label — текст, который будет отображаться;
- description — описание, что кнопка умеет делать;
- defaultHandler — указываем путь до класса, который будет содержать в себе логику работы кнопки, т.е. {namespace}.{class_name}.
Следующие строки:
<Extension path = "/MonoDevelop/Ide/MainMenu/Build">
<CommandItem id="BinObjCleaner.BinObjCleanerCommands.DeleteBinObj" />
Определяют расположение Add-in’а (в нашем случае кнопки) с конкретным id в IDE. Т.е., после запуска Add-in’а, кнопку мы должны увидеть здесь:
Продолжаем двигаться к этой цели. Необходимо теперь создать пустой enum (Клик правой кнопкой мыши на проекте > Add > New file)
И прописать в нем:
using System;
namespace BinObjCleaner
{
public enum BinObjCleanerCommands
{
DeleteBinObj
}
}
Это мы добавили в проект enum для следующей строчки Manifest.addin'а:
<Command id = «BinObjCleaner.BinObjCleanerCommands.DeleteBinObj"
Дальше, создаем пустой класс аналогично enum'у:
Теперь мы добавили класс, который указали в Manifest.addin'е как обработчик для кнопки:
defaultHandler = "BinObjCleaner.DeleteBinObjHandler"
Наследуем его от CommandHandler’а и переопределяем два метода — Run и Update. Должно получится следующее:
using System;
using MonoDevelop.Components.Commands;
using MonoDevelop.Ide;
using MonoDevelop.Core;
namespace BinObjCleaner
{
public class DeleteBinObjHandler : CommandHandler
{
protected override void Run ()
{
}
protected override void Update (CommandInfo info)
{
}
}
}
Переопределение метода Update необходимо для управления видимостью кнопки, т.е. если не открыто ни одно решение, то кнопка показываться не должна, так как тогда не понятно в каком именно решении нужно удалить bin и obj папки. Для этого нужно добавить в метод Update одну строку:
info.Visible = IdeApp.Workspace.IsOpen;
В переопределении метода Run будет содержаться вся логика, связанная с удалением bin и obj папок. Вызывается этот метод после нажатия на кнопку. Перед удалением папок нужно проверить запущено решение или начата ли его сборка, а также что эти операции завершены. Добавим в метод Run следующий код:
var projectOperation = IdeApp.ProjectOperations;
var isBuild = projectOperation.IsBuilding (projectOperation.CurrentSelectedSolution);
var isRun = projectOperation.IsRunning (projectOperation.CurrentSelectedSolution);
if (!isBuild && !isRun
&& IdeApp.ProjectOperations.CurrentBuildOperation.IsCompleted
&& IdeApp.ProjectOperations.CurrentRunOperation.IsCompleted)
{
}
Таким образом мы получим список операций, которые могут выполнятся над решением, проверим не началась/завершилась ли сборка решения, не был ли начат/закончен запуск решения.
После того как мы убедились, что удаление bin и obj папок ничего не сломает, необходимо получить список проектов, входящих в решение, для этого внутрь if'а добавим следующую строчку:
var solutionItems = projectOperation.CurrentSelectedSolution.Items;
Теперь, когда мы знаем список проектов в решении, остается лишь пройтись по пути до каждого проекта, добавляя «/bin» или «/obj» и удаляя эти папки, предварительно проверив их существование:
foreach (var item in solutionItems)
{
var binPath = item.BaseDirectory.FullPath + "/bin";
if (FileService.IsValidPath (binPath) && FileService.IsDirectory (binPath))
FileService.DeleteDirectory (binPath);
var objPath = item.BaseDirectory.FullPath + "/obj";
if (FileService.IsValidPath (objPath) && FileService.IsDirectory (objPath))
FileService.DeleteDirectory (objPath);
}
В итоге DeleteBinObjHandler должен выглядеть следующим образом:
using System;
using MonoDevelop.Components.Commands;
using MonoDevelop.Ide;
using MonoDevelop.Core;
namespace BinObjCleaner
{
public class DeleteBinObjHandler : CommandHandler
{
protected override void Run ()
{
var projectOperation = IdeApp.ProjectOperations;
var isBuild = projectOperation.IsBuilding (projectOperation.CurrentSelectedSolution);
var isRun = projectOperation.IsRunning (projectOperation.CurrentSelectedSolution);
if (!isBuild && !isRun
&& IdeApp.ProjectOperations.CurrentBuildOperation.IsCompleted
&& IdeApp.ProjectOperations.CurrentRunOperation.IsCompleted)
{
IdeApp.Workbench.StatusBar.BeginProgress("Deleting /bin and /obj folders");
var solutionItems = projectOperation.CurrentSelectedSolution.Items;
foreach (var item in solutionItems)
{
var binPath = item.BaseDirectory.FullPath + "/bin";
if (FileService.IsValidPath (binPath) && FileService.IsDirectory (binPath))
FileService.DeleteDirectory (binPath);
var objPath = item.BaseDirectory.FullPath + "/obj";
if (FileService.IsValidPath (objPath) && FileService.IsDirectory (objPath))
FileService.DeleteDirectory (objPath);
}
IdeApp.Workbench.StatusBar.EndProgress ();
IdeApp.Workbench.StatusBar.ShowMessage ("Deleted successfully");
}
}
protected override void Update (CommandInfo info)
{
info.Visible = IdeApp.Workspace.IsOpen;
}
}
}
Все, что касается статус бара IDE (IdeApp.Workbench.StatusBar), нужно для отображения пользователю визуального уведомления, что процесс удаления начался или закончился. Выглядит так:
На этом с созданием проекта все, можно собирать решение, упаковывать его в .mpack и публиковать в официальный репозиторий или распространить среди коллег.
Публикация Add-in
Если вы, как и я, создаете Add-in для внутреннего пользования, то достаточно будет лишь выполнить в консоле команду «mdtool setup pack BinObjCleaner.dll»:
И распространить .mpack (если собираете с Mac, то найдете его в домашней папке) среди нуждающихся. Установка производится через менеджер дополнений в Xamarin Studio с помощью установки из файла (Xamarin Studio > Add-in Manager > Install from file).
Если же вы решите выкладывать в официальный репозиторий, то я, к сожалению, могу помочь только ссылкой, т.к. сколько я ни старался, пробиться через многочисленные ошибки при сборке мне не удалось.
Куда двигаться дальше? Вместо заключения
После написания простого Add-in’а, когда я осознал возможности расширения Xamarin Studio, я решил написать шаблон решения и проекта для внутренней разработки с дополнительной логикой, например, c автоматической регистрацией контроллеров при их создании (самая частая ошибка наших новичков – забыть зарегистрировать контроллер :-) ). Исходники этого Add-in и Add-in’а, рассмотренного в статье, можно найти на гитхабе здесь и здесь.
В написании обоих мне сильно помогали и помогают следующие ресурсы:
- База статей MonoDevelop www.monodevelop.com/developers/articles
- Описание MonoDevelop API www.monodevelop.com/developers/articles/api-overview
- Больше всего способствуют успеху примеры Add-in'ов на гитхабе, например, github.com/aBothe/Mono-D
- Руководство по созданию Add-in от Xamarin developer.xamarin.com/guides/cross-platform/getting_started/extending_xamarin_studio_with_addins
Возможности расширения Xamarin Studio достаточно обширные и, пожалуй, ограничиваются лишь вашими потребностями, так что не стесняйтесь упрощать жизнь себе и коллегам.
На этом все, надеюсь статья вам пригодится. Спасибо, что прочитали!