image

Итак, прошло уже полтора года с тех пор как я начал разрабатывать мобильные приложения с помощью 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 устанавливается следующим образом:
  1. Открываем Xamarin Studio и заходим в менеджер дополнений

    image
  2. В менеджере дополнений выбираем вкладку Gallery и вбиваем в поисковую строку Addin maker. Как только менеджер найдет Add-in, устанавливаем его

    image


Если вдруг менеджер дополнений такой 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, вводим имя и выбираем расположение:

image

После создания проекта его структура должна выглядеть следующим образом:

image

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’а, кнопку мы должны увидеть здесь:

image

Продолжаем двигаться к этой цели. Необходимо теперь создать пустой enum (Клик правой кнопкой мыши на проекте > Add > New file)

image

И прописать в нем:

using System;??

namespace BinObjCleaner
?{?
    public enum BinObjCleanerCommands?
    {
?        DeleteBinObj?
    }
?}

Это мы добавили в проект enum для следующей строчки Manifest.addin'а:

<Command id = «BinObjCleaner.BinObjCleanerCommands.DeleteBinObj"

Дальше, создаем пустой класс аналогично enum'у:

image

Теперь мы добавили класс, который указали в 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), нужно для отображения пользователю визуального уведомления, что процесс удаления начался или закончился. Выглядит так:

image

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

Публикация Add-in


Если вы, как и я, создаете Add-in для внутреннего пользования, то достаточно будет лишь выполнить в консоле команду «mdtool setup pack BinObjCleaner.dll»:

image

И распространить .mpack (если собираете с Mac, то найдете его в домашней папке) среди нуждающихся. Установка производится через менеджер дополнений в Xamarin Studio с помощью установки из файла (Xamarin Studio > Add-in Manager > Install from file).
Если же вы решите выкладывать в официальный репозиторий, то я, к сожалению, могу помочь только ссылкой, т.к. сколько я ни старался, пробиться через многочисленные ошибки при сборке мне не удалось.

Куда двигаться дальше? Вместо заключения


После написания простого Add-in’а, когда я осознал возможности расширения Xamarin Studio, я решил написать шаблон решения и проекта для внутренней разработки с дополнительной логикой, например, c автоматической регистрацией контроллеров при их создании (самая частая ошибка наших новичков – забыть зарегистрировать контроллер :-) ). Исходники этого Add-in и Add-in’а, рассмотренного в статье, можно найти на гитхабе здесь и здесь.
В написании обоих мне сильно помогали и помогают следующие ресурсы:

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

На этом все, надеюсь статья вам пригодится. Спасибо, что прочитали!

Комментарии (5)


  1. Newbilius
    22.04.2015 19:17
    +1

    За статью спасибо! Наверняка пригодится.

    Но текущая проблема Xamarin Studio (для меня) не только в отсутствии каких-то важных фич, но и в нестабильности. В моей практике не редки ситуации, когда начинает постоянно отваливаться debugger, перестаёт отзываться какая-нибудь из менюшек, перестают открываться xib-файлы, перестаёт работать меню навигации по stacke trace и т.п. Лечится вся эта гадость только перезапуском студии или чисткой её кэша. Ситуация которую я бы простил бесплатному продукту, но которые совершенно вымораживают в коммерческом продукте. Инструментарий того же Unity 3D отлажен в разы лучше, а стоит заметно меньше…


    1. Pocheshire Автор
      22.04.2015 19:43

      не только в отсутствии каких-то важных фич, но и в нестабильности

      К сожалению, с этим трудно поспорить. Ругаемся на баги Xamarin Studio всей командой. Остается только надеятся на улучшения в последующих версиях, потому что сама идея Xamarin'а очень и очень хороша.


      1. arkamax
        23.04.2015 03:01

        У конкурентов (Embarcadero RAD Studio) не особенно лучше — постоянные вылеты среды, отсутствие банальных фич, глюки на уровне «непонятно, кто тестировал», и исправлять никто не торопится.

        P.S. «Deleted successfully» (последнее слово — наречие).


        1. Pocheshire Автор
          23.04.2015 04:13

          Спасибо, исправил.


    1. KilgortTraut
      28.04.2015 16:21

      Я работал раньше с 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.