Zine.net online

Witaj na Zine.net online Zaloguj się | Rejestracja | Pomoc
w Szukaj

ucel.net

Robimy sobie addina – część I

Visual Studio oferuje kilka sposobów rozszerzenia własnej funkcjonalności. Często wykonywane polecenia można nagrać sobie w postaci makra. Akcje wykonywane w ramach projektu można zamknąć w postaci asystenta (bądź Wizarda jak kto woli). Bardziej skomplikowane polecenia i akcje można natomiat zaimplementować w addinie. I o tym jak stworzyć własnego addina chciałbym Wam napisać w krótkiej serii artykułów.
Sposób tworzenia i konfiguracji Addinów zmienił sie między wersjami 7.1 i 8.0 środowiska Visual Studio. Ja tu skupię się na stworzeniu addina dla Visual Studio 2005/2008, jeśli jednak będzie takie zapotrzebowanie mogę pokrótce przedstawić też wersję dla środowiska Visual Studio .NET.
Addin przeze mnie opisywany będzie służyć konkretnemu celowi: zmianie wersji projektu napisanego w C++. Informacja o wersji może być zapisana albo w odpowiednim pliku *.rc albo w pliku nagłówkowym o nazwie zapisanej w opcjach środowiska. Zmiana wersji może nastąpić albo poprzez akcję użytkownika wewnątrz środowiska, albo automatycznie, podczas udanej kompilacji odpowiedniej konfiguracji projektu. Oczywiście część wizualna addinu powinna umożliwiać zmianę wersji więcej niż jednego projektu na raz.

Raz, dwa, trzy, zaczynamy...

Są dwa podejścia utworzenia nowego addina. Pierwsze polega na wybraniu odpowiedniego typu nowego projektu:



Drugie, nazywane przeze mnie „podejściem Ch. Petzolda” (kto zna jego książki z pewnością odgadnie dlaczego), polega na stworzeniu addina od zera, zaczynając od pustego projektu typu Class Library. I to podejście wykorzystam, gdyż pozwoli mi ono opisać dokładnie strukturę addina.

Do utworzonego, pustego Class Library należy najpierw dodać referencje do kilku plików:
  • EnvDTE
  • EnvDTE80
  • EnvDTE90 (jeśli zamierzamy korzystać z obiektów specyficznych tylko dla VS 2008)
  • Extensibility
  • Microsoft.VisualStudio.CommandBars
  • stdole
Będą one potrzebne zarówno do implementacji addinu jak i interakcji ze środowiskiem VS.

Podstawowa klasa addina, stanowiąca łącze z VS, nazywa się zazwyczaj Connect i musi implementować interfejsy IDTExtensibility2 i IDTCommandTarget. Niech więc tak nazywa się pierwszy plik nowoutworzonego projektu:

using System;
using Extensibility;
using EnvDTE;
namespace ZineVersion
{
  public class Connect : IDTExtensibility2, IDTCommandTarget
  {
    public Connect()
    {
    }
    #region IDTExtensibility2 Members
    public void OnAddInsUpdate(ref Array custom)
    {
    }
    public void OnBeginShutdown(ref Array custom)
    {
    }
    public void OnConnection(object Application,
                             ext_ConnectMode ConnectMode,
                             object AddInInst,
                             ref Array custom)
    {
    }
    public void OnDisconnection(ext_DisconnectMode RemoveMode,
                                ref Array custom)
    {
    }
    public void OnStartupComplete(ref Array custom)
    {
    }
    #endregion
    #region IDTCommandTarget Members
    public void QueryStatus(string CmdName,
                            vsCommandStatusTextWanted NeededText,
                            ref vsCommandStatus StatusOption,
                            ref object CommandText)
    {
    }
    public void Exec(string CmdName,
                     vsCommandExecOption ExecuteOption,
                     ref object VariantIn,
                     ref object VariantOut,
                     ref bool Handled)
    {
    }
#endregion
  }
}


Pokrótce omówię poszczególne metody interfejsu IDTExtensiibility2:
  • OnAddInsUpdate() jest używana do zaprogramowania zależności od innych addinów. Przykładowo piszemy dwa addiny A1 i A2 i definiujemy sobie, że tylko jeden z nich może być aktywny. Implementujemy więc metodę OnAddInsUpdate i monitorujemy sobie zmiany zbioru aktywnych addinów;
  • OnBeginShutdown() jest uruchamiana wraz z zamykaniem środowiska. Można ją wykorzystać, by zwolnić zasoby używane przez addin;
  • OnConnection() jest uruchamiana gdy addin jest ładowany do VS. Na podstawie parametru ConnectMode można ustalić w jakim kontekście addin jest ładowany. Tej metodzie przyjrzymy się bliżej w dalszym ciągu artykułu;
  • OnDisconnection() jest uruchamiana wraz z usunięciem addina ze środowiska. Podobnie do OnBeginShutdown() można ją wykorzystać do zwolnienia zasobów używanych przez addin;
  • OnStartupComplete() jest uruchamiana wówczas, gdy uruchomione zostanie środowisko Visual Studio. Metoda ta zostanie oczywiście uruchomiona tylko w przypadku addinów, które ładują się razem ze środowiskiem.
Zazwyczaj podczas rejestracji addinu w systemie jest on powiązywany z poleceniem VS – istniejącym bądź nowym, definiowanym tylko i wyłącznie na potrzeby modułu. Obsługą tego polecenia zajmują się metody interfejsu IDTCommandTarget. QueryStatus() określa status polecenia w danym momencie. Exec() jest używane do implementacji samego polecenia – tą metodą też zajmiemy się za chwilę.

No to implementujemy...

W pierwszej kolejności implementujemy metodę OnConnection(). Jak już wspomniałem wyżej, jest ona czymś w rodzaju entry point addina. Poprzez jej parametry otrzymujemy referencje do obiektu reprezentującego instancję Visual Studio jak i obiektu reprezentującego instancję addinu:

private DTE2 _applicationObject;
private AddIn _addInInstance;
public void OnConnection(object Application,
                         ext_ConnectMode ConnectMode,
                         object AddInInst,
                         ref Array custom)
{
    _applicationObject = (DTE2)Application;
    _addInInstance = (AddIn)AddInInst;


Teraz należy właściwie zinterpretować wartość parametru ConnectMode. W tej chwili istotna dla nas będzie wartość ext_ConnectMode.ext_cm_UISetup. Metoda OnConnection() uruchamiana jest z tą wartością tylko wtedy, gdy addin jest konfugurowany w VS, np. przy pierwszym uruchomieniu po instalacji. To co w tym momencie należałoby wykonać, to utworzenie nowego polecenia uruchamiającego nasz kod oraz powiązanie go z menu lub paskiem narzędzi:

// Instalacja addina
if (ConnectMode == ext_ConnectMode.ext_cm_UISetup)
{
    object[] contextUIGUIDs = new object[] { };
    Commands2 commands = (Commands2)_applicationObject.Commands;
    try
    {
        // Utworz polecenie
        Command command = commands.AddNamedCommand2(
                _addInInstance,
                "ZineTool",
                "",
                "Uruchamia ZineTool",
                true,
                59,
                ref contextUIGUIDs,
                (int)vsCommandStatus.vsCommandStatusEnabled+
                (int)vsCommandStatus.vsCommandStatusSupported,
                (int)vsCommandStyle.vsCommandStylePict,
                vsCommandControlType.vsCommandControlTypeButton);
        // Teraz znajdz odpowiedni pasek narzedzi
        CommandBar commandBar;
        CommandBars bars = (CommandBars)_applicationObject.CommandBars;
        try
        {
            commandBar = bars["ZineBar"] as CommandBar;
        }
        catch (ArgumentException)
        {
            // Pasek narzedzi nie istnieje. Wiec go utworzmy.
            commandBar = commandBars.Add("ZineBar", 1, false, false);
        }
        // Dodaj kontrolke do p.aska
        CommandBarButton button = command.AddControl(commandBar, 1) as CommandBarButton;
        // I zmien jej obrazek
        button.Picture = Support.ImageToIPictureDisp(
                         Properties.Resources.BitmapVersionTool) as stdole.StdPicture;
        button.Mask = Support.ImageToIPictureDisp(
                      Properties.Resources.BitmapVersionToolMask) as stdole.StdPicture;
    }
    catch (ArgumentException)
    {
        // Jesli jestesmy tutaj, to najpewniej komenda juz istnieje
    }
}


Stworzyliśmy więc nowy pasek narzędzi, umieściliśmy na nim przycisk i powiązaliśmy go z poleceniem (uwaga!) ZineVersion.Connect.ZineTool. To co nam w tym momencie zostało, to implementacja kodu tego polecenia. Tę wykonujemy w funkcji Exec():

public void Exec(string CmdName,
                 vsCommandExecOption ExecuteOption,
                 ref object VariantIn,
                 ref object VariantOut,
                 ref bool Handled)
{
    Handled = false;
    if (ExecuteOption == vsCommandExecOption.vsCommandExecOptionDoDefault)
    {
         if (CmdName == "ZineVersion.Connect.ZineTool")
        {
            Console.Beep();
            Handled = true;
        }
    }
}


Na koniec, w metodzie QueryStatus(), określamy status utworzonego przez nas polecenia. Bez implementacji tej metody wyżej zdefiniowane polecenie dałoby się uruchomić tylko jeden raz.

public void QueryStatus(string CmdName,
                        vsCommandStatusTextWanted NeededText,
                        ref vsCommandStatus StatusOption,
                        ref object CommandText)
{
  if (NeededText == vsCommandStatusTextWanted.vsCommandStatusTextWantedNone)
    if (CmdName == "ZineVersion.Connect.ZineTool")
      StatusOption = (vsCommandStatus)vsCommandStatus.vsCommandStatusSupported | vsCommandStatus.vsCommandStatusEnabled;
}


I to prawie wszystko, tylko...

Jak to teraz uruchomić???

Instalacja addinów już w środowisku Visual Studio 2005 stała się bardzo łatwa (w porównaniu do poprzedniej wersji) i nie zmieniło się to w wersji obecnej. Polega ona na spreparowaniu odowiedniego pliku o rozszerzeniu .AddIn, wprowadzeniu do niego informacji o addinie i umieszczeniu go w katalogu <Moje Dokumenty>\Visual Studio 2008\Addins. Jeśli katalog nie istnieje, to należy go utworzyć. Przykładowy plik dla naszego addinu przedstawiam poniżej:

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Extensibility xmlns="http://schemas.microsoft.com/AutomationExtensibility">
  <HostApplication>
    <!-- Pozwol uruchomic addina w VS 2005 -->
    <Name>Microsoft Visual Studio</Name>
    <Version>8.0</Version>
  </HostApplication>
  <HostApplication>
    <!-- Pozwol uruchomic addina w VS 2008 -->
    <Name>Microsoft Visual Studio</Name>
    <Version>9.0</Version>
  </HostApplication>
  <Addin>
    <FriendlyName>ZineVersion</FriendlyName>
    <Description>ZineVersion - narzedzie do zarzadzania wersjami projektow
    </Description>
    <Assembly>e:\Private\Zine\ZineVersion\ZineVersion\bin\Debug\ZineVersion.dll
    </Assembly>
    <FullClassName>ZineVersion.Connect</FullClassName>
    <CommandPreload>1</CommandPreload>
    <CommandLineSafe>1</CommandLineSafe>
    <LoadBehavior>0</LoadBehavior>
  </Addin>
</Extensibility>


Poza tym docelowa assembly z addinem musi posiadać atrybut ComVisible. To wystarczy aby addin bezproblemowo ładował się wraz z uruchomieniem środowiska Visual Studio. Debugging addina jest też prosty. W tym celu w zakładce Debug ustawiamy Start Action na Start external program i wybieramy ścieżkę do pliku startowego Visual Studio. Następnie polecam zapisanie konfiguracji i skopiowanie jej jako Debug Reset. W tej skopiowanej dodajemy parametr startowy „/resetaddin ZineVersion.Connect”. Umożliwi nam to sterowanie kodem uruchamianym przy przyłączaniu addina – Visual Studio uruchomione z tym parametrem zachowa się tak jak przy pierwszym uruchomieniu addina i wywoła metodę OnConnection z parametrem ConnectMode = UISetup.

I to wszystko. Po uruchomieniu nowej instancji Visual Studio konieczne może być dostosowanie wyświetlanych pasków narzędzi i aktywacja ZineBar. Po kliknięciu znajdującego się tam przycisku usłyszymy krótkie beep.

W drugiej części (jeśli chcecie, żeby druga część powstała) opiszę jak do addinu dodać interfejs użytkownika – okienko narzędziowe, okienko opcji oraz jak porozumiewać się z użytkownikiem.


Opublikowane 21 lutego 2008 14:22 przez ucel
Attachment(s): ZineVersion.zip

Komentarze:

 

Wojciech Gebczyk said:

Przydatne wiadomosci, dzieki!

lutego 21, 2008 16:13
 

ucel.net said:

Visual Studio oferuje kilka sposobów rozszerzenia własnej funkcjonalności. Często wykonywane polecenia można nagrać sobie w postaci makra. Akcje wykonywane w ramach projektu można zamknąć w postaci asystenta (bądź Wizarda jak kto woli). Bardziej skomplikowane

lutego 22, 2008 10:33
 

nuwanda said:

Mnie też się podobało. Ciekawy wstęp. I oczywiście, czekam na następną część, z którą chętnie się zapoznam. BTW słyszeliście o Visual Studio Shell? To naprawdę faja opcja jeżeli chodzi o budowę własnych narzędzi, a także integrację z VS. http://msdn2.microsoft.com/en-us/vsx2008/products/bb933751.aspx

lutego 22, 2008 10:48
 

Marcin Hoppe said:

Podobno za pomocą Visual Studio Shell można zbudować sobie nieistniejący produkt, czyli Visual F# :). Z braku obsługi add-inów w wersjach Express jest to fajna opcja, żeby pobawić się F# jeśli ktoś nie ma pełnego Visual Studio.

lutego 26, 2008 09:08
Komentarze anonimowe wyłączone
W oparciu o Community Server (Personal Edition), Telligent Systems