Як зробити Xamarin Studio трішки краще?

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 дебажится проект.

Створення проекту
Для створення проекту в 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 досить обширні і, мабуть, обмежуються лише вашими потребами, так що не соромтеся спрощувати життя собі і колегам.

На цьому все, сподіваюся стаття стане вам у пригоді. Дякую, що прочитали!

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.