Pull to refresh

Как сделать Xamarin Studio чуточку лучше?

Reading time7 min
Views14K
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 достаточно обширные и, пожалуй, ограничиваются лишь вашими потребностями, так что не стесняйтесь упрощать жизнь себе и коллегам.

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

Articles