Итак, прошло уже полтора года с тех пор как я начал разрабатывать мобильные приложения с помощью 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 достаточно обширные и, пожалуй, ограничиваются лишь вашими потребностями, так что не стесняйтесь упрощать жизнь себе и коллегам.
На этом все, надеюсь статья вам пригодится. Спасибо, что прочитали!
Newbilius
За статью спасибо! Наверняка пригодится.
Но текущая проблема Xamarin Studio (для меня) не только в отсутствии каких-то важных фич, но и в нестабильности. В моей практике не редки ситуации, когда начинает постоянно отваливаться debugger, перестаёт отзываться какая-нибудь из менюшек, перестают открываться xib-файлы, перестаёт работать меню навигации по stacke trace и т.п. Лечится вся эта гадость только перезапуском студии или чисткой её кэша. Ситуация которую я бы простил бесплатному продукту, но которые совершенно вымораживают в коммерческом продукте. Инструментарий того же Unity 3D отлажен в разы лучше, а стоит заметно меньше…
Pocheshire Автор
К сожалению, с этим трудно поспорить. Ругаемся на баги Xamarin Studio всей командой. Остается только надеятся на улучшения в последующих версиях, потому что сама идея Xamarin'а очень и очень хороша.
arkamax
У конкурентов (Embarcadero RAD Studio) не особенно лучше — постоянные вылеты среды, отсутствие банальных фич, глюки на уровне «непонятно, кто тестировал», и исправлять никто не торопится.
P.S. «Deleted successfully» (последнее слово — наречие).
Pocheshire Автор
Спасибо, исправил.
KilgortTraut
Я работал раньше с mac Mini слабого, было похожее на то, что вы пишите. Сейчас работаю с Mac Book Pro Retina и вообще багов, вылетов нету. Возможно собака просто прожорливая. Но я сейчас перешел на Parallels + Visual Studio и через билд хост все ок, данный проект на Xamarin Forms, потому xib файлы не редактирую. Но отдельно скажу, что Xamarin Forms полное уг, в нем есть только примитивные контролы и сделать вменяемый кросс платформ дизайн геморрой полнейший, все приходиться делать все равно через platform specific вьюхи(renderers в терминах Xamarin Forms).
Unity3d тоже глючный кстати и использует Monodevelop(та же Xamarin Studio) и я так же с ним работаю через Visual Studio + Unity VS Tools.