Zine.net online

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

ucel.net

Robimy sobie addina – część II

Można by przypuszczać, że skoro wypuszczona w 2002 roku wersja Visual Studio ma „.net” w nazwie, to bazuje w większej części na technologii .NET. Cóż, tak naprawdę, przynajmniej jeśli chodzi o programowanie rozszerzeń środowiska, nazwa powinna mieć końcówkę .COM zamiast .NET. Bo Visual Studio .NET miało dotneta tylko w nazwie, a tak naprawdę cały model obiektowy funkcjonował,jak i poprzednich wersjach, na bazie interfejsów COM-owskich. Konsekwencją tego jest fakt, że środowisko nie jest tak intuicyjne jak powinno być. Ot chociażby interfejs użytkownika dla addinu: zarówno okienko z ustawieniami jak i okienko sterujące musiały być kontrolkami ActiveX.

Ironicznie, pierwsza edycja nowego Visual Studio bez dotneta w nazwie, Visual Studio 2005, zaczęła być tą naprawdę dotnetową. A przynajmniej pod względem projektowania interfejsu użytkownika dla addinów. Koniec z modyfikowaniem rejestru, koniec z kontrolkami ActiveX / od tej pory używamy plików XML i natywnych kontrolek wyprowadzanych z System.Windows.Forms.Control. Ale nie koniec niestety z COM-em. W każdej chwili wyskoczyć może COMException, a wszystkie praktycznie interfejsy, które będziemy używać to interfejsy COM-owskie. Przypominam, że warunkiem widzialności addina dla Visual Studio było opakowanie assembly atrybutem ComVisible. Polecam się też przyjrzeć w jaki sposób ustawiany był obrazek na pasku narzędzi. Tak, tak, IPictureDisp to bynajmniej nie jest czysto dotnetowski interfejs.

Ale dość tego już nieco przydługawego wstępu, wracajmy do naszego addina. Zanim jednak zacznę opisywać projektowanie interfejsu użytkownika – mała uwaga. Zdaję sobie sprawę, że tak naprawdę kolejność projektowania aplikacji jest odwrotna – czyli najpierw projektuje się funkcjonalność a potem interfejs, ale opisując technologię chcę umożliwić czytelnikom jak najszybsze przejście do własnych prób, a w tym wypadku szeroko rozumiana funkcjonalność addinu jest chyba najmniej interesująca.

Interfejs użytkownika najwygodniej upakować jest w okienku narzędziowym, takim w jakim oferowane są standardowe narzędzia VS, jak Solution Explorer, Class View czy Server Explorer. Samo stworzenie takiego okienka nie jest trudne. Po pierwsze, tworzymy nową kontrolkę, wyprowadzoną z UserControl i umieszczamy na niej potrzebne nam akcesoria:


Dla wygody i logicznego podziału addinu wszystkie elementy interfejsu użytkownika umieścimy w oddzielnej bibliotece, ZineVersion.UI.dll. Nie muszę chyba dodawać, że w opcjach projektu należy zaznaczyć haczyk przy „Register for COM Interop". Dodatkowo w przypadku tej kontrolki bardzo ważne jest by jej klasa posiadała atrybut ComVisible z wartością true. Po co – o tym za chwilę. Do projektu dodać musimy jeszcze odnośniki do EnvDTE.dll i EnvDTE80.dll oraz zdefiniować właściwości ToolWindow i DTE, gdzie będziemy przechowywać odpowiednio informacje o hoście kontrolki i środowisku:

public Window2 ToolWindow { get; set; }
public DTE2 DTE { get; set; }

Aby wyświetlić właśnie zdefiniowaną kontrolke musimy wrócić do klasy Connect i jej metody Exec(). Zaimplementowanego tam beepa zastępujemy następującym kodem:

if (CmdName == "ZineVersion.Connect.ZineTool")
{
  
if (ctl != null)
   {
      ctl.ToolWindow.Visible =
true;
      Handled =
true;
   }
  
else
  
{
     
Window2 wnd = null;
     
object refObj = null;
     
string assemblyLocation = Assembly.GetCallingAssembly().Location;
     
string currentDir = Path.GetDirectoryName(assemblyLocation);

     
try
     
{
        
Windows2 wnds = (Windows2)(_applicationObject.Windows);
         wnd = wnds.CreateToolWindow2(
                    _addInInstance,
                    
Path.Combine(currentDir, "ZineVersion.UI.dll"),
                   
"ZineVersion.UI.ZineWindowCtl",
                   
"Zine Version",
                    ZineGuid.ToString(
"B"),
                   
ref refObj) as Window2;
         ctl = refObj
as IZineVersionCtl;
         ctl.ToolWindow = wnd;
         ctl.DTE = _applicationObject;
         wnd.Visible =
true;
         Handled =
true;
      }
     
catch (Exception ex)
      {
        
Debug.Write(ex.Message);
      }
   }
}

Dodatkowo w nagłówku będziemy potrzebować kilku deklaracji:

private Guid ZineGuid = new Guid("{6E20BA3F-D4DC-4a48-A219-72BB8BB4BEEE}");
private IZineVersionCtl ctl;

Krótkie omówienie tego, co się powyżej dzieje. Aktywując addin mamy do czynienia z dwoma mozliwymi sytuacjami:

  1. okienko addinu było już wcześniej otwarte i teraz jest zamknięte – wystarczy je więc ponownie pokazać

  2. otwieramy okienko poraz pierwszy dla tej instancji VS

W drugim przypadku musimy utworzyć najpierw okienko hostujące kontrolkę – robimy to używając metody CreateToolWindow2(). Różni się ona od obecnej w poprzednich wersjach Visual Studio metody CreateToolWindow() tym, że poprawnie obsługuje hostowanie kontrolek .NET – poprzednia wymagała kontrolki ActiveX.

Ponieważ kontrolka znajduje się w innym pliku, wymagane jest podanie ścieżki do niego. Jeśli klasa kontrolki nie zostałaby oznaczona jako ComVisible, to obiekt reprezentujący kontrolkę nie zostałby zwrócony – wartość refObj będzie równa NULL. Stąd też moja wcześniejsza uwaga na temat tego atrybutu. Klucz ZineGuid jest używany do identyfikacji kontrolki w VS i może mieć dowolną wartość. Co ciekawe, jest on przekazywany jako łańcuch znaków i to łańcuch w konkretnym formacie – próba użycia wyniku standardowego wywołania ToString() skończy się mało mówiącym wyjątkiem COM Exception.

Zwracany obiekt refObj to instancja nowoutworzonej kontrolki. Jeśli z jakiegoś powodu nie chcemy dodawać odwołania do System.Windows.Forms.dll do biblioteki addinu można interesującą nas funkcjonalność opakować w interfejs, tak jak zostało to zaprezentowane powyżej. Po wszystkim pozostaje jedynie ustawić atrybut Visible utworzonej kontrolki na true i cieszyć się nowym okienkiem w systemie, które możemy sobie zadokować np. przy Solution Explorerze. Tytuł okienka pojawi się wówczas w zakładce pod okienkiem, obok mało interesującego obrazka. Aby zmienić ten obrazek należy użyć metody SetTabPicture(). Oczekuje ona bitmapy w formacie IPicture. Co ciekawe format ten nie obsługuje przezroczystości, więc jakiś kolor musi zostać ustalony jako przezroczysty. Jaki? Oto jest pytanie...

W wersji 2002/2003 był to kolor (0, 254, 0). W wersji 2005 i 2008 do RC były to kolory (254, 0, 254) i (255, 0, 255). W wersji finalnej żaden z nich nie działa i nikt nie wie dlaczego. Osobiście wypróbowałem kolor biały i stwierdziłem że nieźle zlewa się on z tłem zakładki.

...
ctl.DTE = _applicationObject;
wnd.SetTabPicture(GetTabPicture());
wnd.Visible =
true;
...

private object GetTabPicture()
{
  
Bitmap img = Properties.Resources.BitmapVersionTool;
  
Color transparent = Color.FromArgb(0, 255, 255, 255);

  
if (_applicationObject.Version == "8.0")
      transparent =
Color.FromArgb(254, 0, 254);

  
for (int x = 0; x < img.Width; x++)
     
for (int y = 0; y < img.Height; y++)
        
if (img.GetPixel(x, y) == Color.FromArgb(192, 192, 192))
            img.SetPixel(x, y, transparent);

   stdole.
IPicture ret = Support.ImageToIPicture(img) as stdole.IPicture;
  
return ret;
}

W powyższym kodzie zastępuję domyślny kolor tła (szary) kolorem białym. Uwaga: żeby trick zadziałał, plik musi być 24-bitową bitmapą.

Update

Zgodnie z sugestią Wojtka wypróbowałem, bez przekonania przyznaję, metodę MakeTransparent(). Jakie było moje zdziwienie, gdy okazało się, że metoda działa możecie sobie sami wyobrazić. Co ciekawe, zostało to najprawdopodobniej zmienione w wersji RTM Visual Studio 2008, bo wersja RC (nie wspominając już VS 2005) potrzebowała jeszcze konkretnego koloru i wywołanie metody MakeTransparent() nie przynosiło żadnego skutku.

Tak więc nasza metoda GetTabPicture() może zostać nieco zmodyfikowana:

private object GetTabPicture()
{
  
Bitmap img = Properties.Resources.BitmapVersionTool;
  
Color transparent = Color.FromArgb(0, 254, 0, 254);

  
if (_applicationObject.Version == "8.0")
   {
     
for (int x = 0; x < img.Width; x++)
        
for (int y = 0; y < img.Height; y++)
           
if (img.GetPixel(x, y) == Color.FromArgb(192, 192, 192))
               img.SetPixel(x, y, transparent);
   }
  
else
     
img.MakeTransparent(Color.FromArgb(192, 192, 192));

   stdole.
IPicture ret = Support.ImageToIPicture(img) as stdole.IPicture;
  
return ret;
}

Mógłbym tutaj jeszcze na koniec dodać, że najprostsze rozwiązania są najmniej oczywiste...

c.d.n.

Opublikowane 14 kwietnia 2008 16:26 przez ucel
Attachment(s): ZineVersion.zip

Komentarze:

 

Wojciech Gebczyk said:

a'propo utransparentowiania bitmapy tp jest metoda MakeTransparent(Color). Moze ta metoda przyda sie tobie?

kwietnia 14, 2008 17:11
 

ucel said:

To bylo po prostu za trywialne zebym na to wpadl... szczegolnie, ze ta metoda dziala najwyrazniej dopiero od VS 2008 RTM.

Co ciekawe, pytalem tez na forum MSDN i tam ludzie tez nie mieli pojecia jak wprowadzic przezroczystosc do obrazka. W kazdym razie dzieki za sugestie i poprawiam tekst.

kwietnia 15, 2008 11:00
Komentarze anonimowe wyłączone
W oparciu o Community Server (Personal Edition), Telligent Systems