Visual Studio Package

Jan Holan       08.03.2011       Visual Studio, Architektura       13003 zobrazení

V minulém příspěvku jsem psal o našem doplňku do Visual Studia, nyní se podíváme, jak se takový VS Extension Package dá vyrobit. Visual Studio podporuje dva rozdílné  způsoby jak vytvořit rozšíření, první možnost je Visual Studiu Add-In, druhá možnost je právě Visual Studio Package.

Jednodušší Add-In umožňuje pomoci kódu, který se spustí při startu VS, nějakým způsobem začlenit Vaše rozšíření do Visual Studia, většinou tím že, v tomto kódu vytvoříte a zaregistrujete novou volbu do některého existujícího VS menu.

Naproti tomu VS Package umožnuje kromě tvorby nových položek menu i vytvoření vlastního tool okna, nebo různá rozšíření VS code editoru. Menu a podobné prvky se zde ovšem nevytváří kódem, ale vše je potřeba v Package nejprve popsat  deklarací v XML souboru. Vlastní package se také nemusí nutně spouštět už při startu Visual Studia, ale až když je potřeba tj. např. až když vyvoláte některou volbu menu, kterou package přidal (o tom ještě podrobněji níže). Výhodou pak může být například jednodušší distribuce, při buildu  se automaticky celý package přímo zapakuje do .vsix souboru, kterým lze rovnou nainstalovat, nebo lze package vložit do Visual Studio Galerie a přes Extension Manager ve VS dohledat.

Ještě jedno upozornění, celá záležitost okolo Visual Studio Package je dosti složitá, já zde popíšu pouze základy tj. vytvoření vlastního package a nakonfigurování položek menu, ostatní funkce zde popsány nebudou (na vytvoření našeho doplňku také potřeba nebyli). Také převod z VS Add-in na Package není možný, je nutné doplněk napsat znovu. Pro představu příklad, celý náš doplněk jsem nejprve vytvořil pouze jako Add-In což i s funkcionalitou oken ve WPF a logikou volání TFS trvalo cca tři dny, a asi stejně dlouho my pak trvalo Add-In přepsat na VS Package.

A nyní už k vlastní tvorbě, pro vytvoření Package je do VS 2010 nutné doinstalovat Visual Studio 2010 SDK (soubor VsSDK_sfx.exe), který do okna New Project doplní volbu Visual Studio Package. Při vytváření projektu tohoto typu, je nejprve spuštěn Wizard, ve kterém kromě jména, popisu a jiných informací o package, zvolíme co bude obsahovat – volby Menu Command, Tool Window a Custom Editor. Po zvolení Menu Command bude vytvořený projekt obsahovat tyto soubory:

source.extension.vsixmanifest – soubor popisující vsix soubor package, jedná se o informace pro instalaci package a informace, které se zobrazí v Extension Manageru.
<Jméno>Package.cs – Hlavní třída package zděděná z třídy Microsoft.VisualStudio.Shell.Package.
<Jméno>.vsct – XML sobor popisující definice voleb menu, jejich vlastnosti, icony atd.
GuidList.cs – Statická třída s konstantami, definujíci GUID identifikátory používané v Package.
VSPackage.resx – resource soubor s texty a iconou package.

Všechny položky jednotlivých menu, kontext menu a toolbarů jsou ve Visual Studiu definované jako příkazy (commands). (Nemusí se jednat pouze o volby položek menu, ale i např. comboboxy atd.) Nové příkazy, které Package implementuje musíme také jako jeho součást definovat, to se provádí pomoci .vsct souboru (Visual Studio Command Table configuration). Jedná se o XML soubor, jehož dvě hlavní části jsou sekce Commands a Symbols. Celé si to vysvětlíme na příkladu, který přidává položky do kontext menu v okně Source Control Exploreru.

<CommandTable xmlns="http://schemas.microsoft.com/VisualStudio/2005-10-18/CommandTable" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  ...
  <Commands package="guidTFSSCExplorerExtensionPkg">
    ...
    <Groups>
      <Group guid="guidTFSSCExplorerExtensionCmdSet" id="igrpExplorerContextDestroy" priority="0x1000">
        <Parent guid="guidHatPackageCmdSet" id="imnuSccExplorerContext"/>
      </Group>
    </Groups>
    
    <!--Buttons section. -->
    <Buttons>
      <Button guid="guidTFSSCExplorerExtensionCmdSet" id="icmdExplorerBranchToFolder" priority="0x0250" type="Button">
        <Parent guid="guidHatPackageCmdSet" id="igrpExplorerContextBranch" />
        <Icon guid="guidImages" id="bmpPicBranchToFolder" />
        <CommandFlag>DefaultDisabled</CommandFlag>
        <Strings>
          <CommandName>TfsContextExplorerBranchToFolder</CommandName>
          <ButtonText>Branch to Folder...</ButtonText>
        </Strings>
      </Button>
      <Button guid="guidTFSSCExplorerExtensionCmdSet" id="icmdExplorerMergeFromSources" priority="0x0251" type="Button">
        <Parent guid="guidHatPackageCmdSet" id="igrpExplorerContextBranch" />
        <Icon guid="guidImages" id="bmpPicMergeFromSources" />
        <CommandFlag>DefaultDisabled</CommandFlag>
        <Strings>
          <CommandName>TfsContextExplorerMergeFromSources</CommandName>
          <ButtonText>Merge From Sources...</ButtonText>
        </Strings>
      </Button>
      <Button guid="guidTFSSCExplorerExtensionCmdSet" id="icmdExplorerMoveToFolder" priority="0x0350" type="Button">
        <Parent guid="guidHatPackageCmdSet" id="igrpExplorerContextBranch" />
        <Icon guid="guidImages" id="bmpPicMoveToFolder" />
        <CommandFlag>DefaultDisabled</CommandFlag>
        <Strings>
          <CommandName>TfsContextExplorerMoveToFolder</CommandName>
          <ButtonText>Move to Folder...</ButtonText>
        </Strings>
      </Button>
      
      <Button guid="guidTFSSCExplorerExtensionCmdSet" id="icmdExplorerShowDeletedItems" priority="0x0100" type="Button">
        <Parent guid="guidTFSSCExplorerExtensionCmdSet" id="igrpExplorerContextDestroy" />
        <Icon guid="guidImages" id="bmpPicShowDeletedItems" />
        <Strings>
          <CommandName>TfsContextExplorerShowDeletedItems</CommandName>
          <ButtonText>Show / hide deleted items</ButtonText>
        </Strings>
      </Button>
      <Button guid="guidTFSSCExplorerExtensionCmdSet" id="icmdExplorerDestroy" priority="0x0200" type="Button">
        <Parent guid="guidTFSSCExplorerExtensionCmdSet" id="igrpExplorerContextDestroy" />
        <Icon guid="guidImages" id="bmpPicDestroy" />
        <CommandFlag>DefaultDisabled</CommandFlag>
        <Strings>
          <CommandName>TfsContextExplorerDestroy</CommandName>
          <ButtonText>Destroy</ButtonText>
        </Strings>
      </Button>
    </Buttons>
    
    <!--The bitmaps section is used to define the bitmaps that are used for the commands.-->
    <Bitmaps>
      <Bitmap guid="guidImages" href="Properties\ButtonsImages.bmp" usedList="bmpPicBranchToFolder, bmpPicMergeFromSources, bmpPicMoveToFolder, bmpPicShowDeletedItems, bmpPicDestroy"/>
    </Bitmaps>
  </Commands>
  
  <Symbols>
    <!-- This is the package guid. -->
    <GuidSymbol name="guidTFSSCExplorerExtensionPkg" value="{4725a13f-c9e1-4c4a-9779-bdb9ec5311d9}" />
    
    <!-- This is the guid used to group the menu commands together -->
    <GuidSymbol name="guidTFSSCExplorerExtensionCmdSet" value="{3ec04835-c2d5-4978-b9f9-4e62ad5384d2}">
      <IDSymbol name="icmdExplorerBranchToFolder" value="0x0100" />
      <IDSymbol name="icmdExplorerMergeFromSources" value="0x0200" />
      <IDSymbol name="icmdExplorerMoveToFolder" value="0x0300" />
      <IDSymbol name="igrpExplorerContextDestroy" value="0x1020" />
      <IDSymbol name="icmdExplorerShowDeletedItems" value="0x0400" />
      <IDSymbol name="icmdExplorerDestroy" value="0x0500" />
    </GuidSymbol>

    <!--Source Control Explorer-->
    <GuidSymbol name="guidHatPackageCmdSet" value="{ffe1131c-8ea1-4d05-9728-34ad4611bda9}">
      <IDSymbol name="imnuSccExplorerContext" value="0x1011" />
      <IDSymbol name="igrpExplorerContextBranch" value="0x1115" />
    </GuidSymbol>

    <GuidSymbol name="guidImages" value="{b8a063ce-0941-4fba-9734-6ffc0ab2f12c}" >
      <IDSymbol name="bmpPicBranchToFolder" value="1" />
      <IDSymbol name="bmpPicMergeFromSources" value="2" />
      <IDSymbol name="bmpPicMoveToFolder" value="3" />
      <IDSymbol name="bmpPicShowDeletedItems" value="4" />
      <IDSymbol name="bmpPicDestroy" value="5" />
    </GuidSymbol>
  </Symbols>
</CommandTable>

Nejprve se zaměříme se na sekci Buttons, kde jsou definované nové položky menu, vypadají takto:

Menu

Definice první položky “Branch to Folder” začíná takto:

<Button guid="guidTFSSCExplorerExtensionCmdSet" id="icmdExplorerBranchToFolder" priority="0x0250" type="Button">
  <Parent guid="guidHatPackageCmdSet" id="igrpExplorerContextBranch" />

Atribut guid vždy odkazuje na identifikátor definovaný jako GuidSymbol v sekci Symbols (níže v souboru). U elementu Button se jedná o guid určující skupinu příkazů (command set), která spojuje více příkazů v package. V tomto Package máme jednu takovou skupinu označenou identifikátorem guidTFSSCExplorerExtensionCmdSet, aby byla hodnota GUIDu přístupná i z kódu, je kromě sekce GuidSymbol ještě uvedena v konstantě v souboru GuidList.cs.

Další atribut id určuje identifikaci daného elementu, náš první Button má tedy id zvolené icmdExplorerBranchToFolder, vlastní hodnotu identifikátoru opět najdeme definovanou až v sekci Symbols, tentokrát elementem IDSymbol umístěném pod konkrétním GuidSymbol. Atribut priority určuje pořadí prvků v sekci menu a type="Button" určuje, že položka je typu tlačítko (tj. volba menu).

Nejdůležitější podelement Parent specifikuje, ve které části a jakého menu bude daná položka zobrazena. Každé menu nebo podmenu je totiž rozděleno do více částí - sekcí (Menu Groups), jsou odděleny i graficky čárami v menu. Atribut id u elementu Parent odkazuje na symbol určující tuto sekci menu a atribut guid odkazuje na command set ke kterému menu Group patří. Naše první položka Branch to Folder je umístěna v context menu Source Conrol Explorer okna, proto je zde uveden id symbol igrpExplorerContextBranch jehož hodnota odpovídá sekci v tomto menu (od volby Branching and Merging po volbu Apply Label viz obr.). Guid guidHatPackageCmdSet odpovídá command setu v package pro TFS Source Control, kde je menu definováno (konkrétně v assembly Microsoft.VisualStudio.TeamFoundation.VersionControlUI.dll).

Asi vás nyní zajímá, jak se hodnoty id, guid a priority získají, když víme že chceme svojí novou položku přidat do kdovíjakého existujícího menu ve VS (v našem případě to bylo menu okna Source Control). (Výchozí projekt pro Package nám v tom moc nepomůže, protože pouze jednoduše vytváří položku v Tool Menu). Nejjednodušší způsob je použít VSCTPowerToy (doplněk do Visual Studia, který lze stáhnout zde), tento nástroj umožňuje prohledat všechny definované příkazy, menu, toolbarů apod. a zjistit jejich symbolická jména, id a guid identifikátory. Obrázek ukazuje příklad zobrazení pro naší Branch to Folder položku.

Update: Protože tento doplněk již delší dobu není dostupný pro nové Visual Studio 2012, provedl jsem jeho neoficiální recompilaci s rozšířením pro prohledání i VS 2012 Registry Hive. Již se nejedná o doplněk do VS, ale jen o samostatný program, to ale nijak nebrání v jeho používání. Ke stažení je dostupný zde

VSCTPowerToy

Další podelementy Button definice už jsou vcelku jasné, Icon určuje odkaz na id symbol a guid obrázku, Strings/CommandName určuje název příkazu, Strings/ButtonText titulek příkazu a flag DefaultDisabled v elementu CommandFlag určuje, že položka bude v základním zobrazení nedostupná (vyšedlá), její zpřístupnění pak nastavíme už kódem.

Shrňme si to ještě jednou na příkladu položky "Show / hide deleted items", její id je icmdExplorerShowDeletedItems ve stejném command setu. Na rozdíl od minulých položek je její umístění nastaveno na novou sekci menu pojmenovanou id igrpExplorerContextDestroy, jejíž definice se nachází v sekci Groups, a protože je definována v tomto package, je guid nastaven také na stejný command set jako položky menu. V elementu parent Group položky je uvedeno id celého context menu, ve kterém sekce je (imnuSccExplorerContext), a guid na package tohoto menu - tedy opět package TFS Source Control (guidHatPackageCmdSet).

Poslední část .vsct souboru, kterou jsme ještě nezmínili je sekce Bitmaps, kde je definováno jaké icony budou mít jednotlivé nové položky menu, které přidáváme. Icony všech voleb jsou zde naskládány do jednoho obrázku (určen atributem href), každá icona v něm má velikost 16x16 a jejich symboly jsou určeny atributem usedList v pořadí z leva doprava, tak jak jsou umístěny v obrázku (opět jsou pak ještě definovány v sekci GuidSymbol).
Příklad pro našich 5 voleb vypadá takto:

ButtonsImages

Zajímavostí je v jakém musí být obrázek formátu, buď 32bit RGBA bitmap (podporuje nastavení průhlednosti), nebo 24bit bitmap s růžovou (fuchsia) barvou jako transparentní podklad a dále některé zdroje uvádějí, že lze neoficiálně použít i klasický png  (sám jsem nezkoušel). Já jsem v mém Package zvolil pouze tu 24bit bitmapu, protože jsem na 32bit nenašel editor, který by to podporoval a přitom bych ho uměl používat.

Věřte že nyní máme nejtěžší část za sebou a zbytek tvorby Package, který už bude naštěstí klasicky v C# kódu, bude již jednodušší.

Protože každá položka menu, která vyvolává příkaz pak v kódu funguje podobně (jen ten vlastní kód příkazu je jiný), vytvořil jsem si základní třídu ExtensionCommand, ze které všechny moje příkazy dědí.

public abstract class ExtensionCommand : IExtensionComponent
{
    #region IExtensionComponent Members
    public virtual void Setup()
    {
        // Create the command for the menu item.
        var menuCommandID = new CommandID(this.MenuCmdSetGuid, this.ID);
        var menuItem = new OleMenuCommand(MenuInvokeCallback, MenuChangeCallback, MenuBeforeQueryStatusCallback, menuCommandID);
        ExtensionPackage.MenuCommandService.AddCommand(menuItem);
    }
    #endregion

    #region action methods
    protected virtual void QueryStatus(OleMenuCommand command)
    {
        command.Supported = true;
        command.Visible = true;
        command.Enabled = false;
        command.Checked = false;
    }

    protected virtual void Execute(OleMenuCommand command) { }
    #endregion

    #region property getters/setters
    public abstract Guid MenuCmdSetGuid
    {
        get;
    }

    public abstract int ID
    {
        get;
    }
    #endregion

    #region private member functions
    private void MenuInvokeCallback(object sender, EventArgs e)
    {
        try
        {
            var command = sender as OleMenuCommand;
            this.Execute(command);
        }
        catch (Exception ex)
        {
            Trace.WriteLine(ex.ToString());
        }
    }

    private void MenuChangeCallback(object sender, EventArgs e) { }

    private void MenuBeforeQueryStatusCallback(object sender, EventArgs e)
    {
        try
        {
            var command = sender as OleMenuCommand;
            this.QueryStatus(command);
        }
        catch (Exception ex)
        {
            Trace.WriteLine(ex.ToString());
        }
    }
    #endregion
}

A interface IExtensionComponent definující pouze metodu Setup.

public interface IExtensionComponent
{
    void Setup();
}

Ještě příklad jednoho příkazu, tak např. ShowDeleted.

public class ShowDeleted : ExtensionCommand
{
    #region ExtensionCommand Members
    public override Guid MenuCmdSetGuid
    {
        get { return GuidList.guidTFSSCExplorerExtensionCmdSet; }
    }

    public override int ID
    {
        get { return 0x0400; }
    }

    protected override void QueryStatus(OleMenuCommand command)
    {
        base.QueryStatus(command);

        if (ExtensionPackage.VersionControlExt == null || ExtensionPackage.VersionControlExt.Explorer == null)
        {
            command.Visible = false;
            command.Checked = false;
            return;
        }

        command.Enabled = true;
        command.Checked = ExtensionPackage.VersionControlExt.Explorer.ShowDeletedItems;
    }

    protected override void Execute(OleMenuCommand command)
    {
        ExtensionPackage.VersionControlExt.Explorer.ShowDeletedItems = !ExtensionPackage.VersionControlExt.Explorer.ShowDeletedItems;
    }
    #endregion
}

Takže každý command přes naší base třídu ExtensionCommand vrací Guid (metoda MenuCmdSetGuid) a ID, tyto dvě položky musí souhlasit s hodnotami definovaných v symbolech ve .vsct souboru. Base třída v metodě Setup, která se volá při inicializaci Package, z těchto hodnot sestaví objekt CommandID a zaregistruje příkaz metodou AddCommand na objektu MenuCommandService.

Při registraci se také nastaví metoda MenuInvokeCallback, která se volá pří spuštění příkazu, a metoda MenuBeforeQueryStatusCallback, která slouží pro změnu vlastností příkazu jako jsou Enabled, Visible nebo Checked. Ty volají metody Execute a QueryStatus konkrétního příkazu. V příkazu ShowDeleted metoda Execute mění nastavení ShowDeletedItems Source Controlu, a metoda QueryStatus zde nastavuje nejen vlastnosti Visible a Enabled, ale i vlastnost Checked podle vlastnosti ShowDeletedItems (tím je zobrazeno, zda je volba zapnutá).

Zbývá kód vlastního Package, ten by normálně byl ve třídě <Jméno>Package zděděné ze třídy Package. Já jsem ovšem kód, který je obecný i pro jiný package přesunul to třídy ExtensionPackage a až z té bude konkrétní package zděděny. Třída také obsahuje pomocné statické proměnné (jako MenuCommandService, Extensibility, DTE2), které jsou využívány ve třídě ExtensionCommand a jednotlivých příkazů.

/// <summary>
/// VS Extension Package
/// </summary>
public class ExtensionPackage : Package
{
    #region member varible and default property initialization
    private List<IExtensionComponent> extensionComponents = new List<IExtensionComponent>();

    private static ExtensionPackage s_Instance;

    public static IVsExtensibility Extensibility { get; private set; }
    public static DTE2 DTE2 { get; private set; }
    public static VersionControlExt VersionControlExt { get; private set; }

    public static IVsUIShell UIShell { get; private set; }
    public static MenuCommandService MenuCommandService { get; private set; }
    #endregion

    #region constructors and destructors
    /// <summary>
    /// Package constructor.
    /// Inside this method you can place any initialization code that does not require 
    /// any Visual Studio service because at this point the package object is created but 
    /// not sited yet inside Visual Studio environment. The place to do all the other 
    /// initialization is the Initialize method.
    /// </summary>
    public ExtensionPackage()
    {
        Trace.WriteLine("Package constructor");

        Extensibility = (IVsExtensibility)Package.GetGlobalService(typeof(IVsExtensibility));
        DTE2 = (DTE2)Extensibility.GetGlobalsObject(null).DTE;

        VersionControlExt = DTE2.GetObject("Microsoft.VisualStudio.TeamFoundation.VersionControl.VersionControlExt") as VersionControlExt;

        //Load Extension Components
        var thisAssembly = Assembly.GetExecutingAssembly();
        Type[] types = thisAssembly.GetTypes();
        foreach (Type t in types)
        {
            if (t.GetInterface("IExtensionComponent") != null && t.Name != "ExtensionCommand")
            {
                try
                {
                    Trace.WriteLine(string.Format("Creating {0} component", t.Name));

                    object obj = t.Assembly.CreateInstance(t.FullName);
                    var extensionComponent = (IExtensionComponent)obj;
                    this.extensionComponents.Add(extensionComponent);
                }
                catch (Exception ex)
                {
                    Trace.WriteLine(ex.ToString());
                }
            }
        }
    }
    #endregion

    #region action methods
    /// <summary>
    /// Initialization of the package; this method is called right after the package is sited, so this is the place
    /// where you can put all the initilaization code that rely on services provided by VisualStudio.
    /// </summary>
    protected override void Initialize()
    {
        s_Instance = this;
        base.Initialize();

        Trace.WriteLine("Package Initialize");

        UIShell = (IVsUIShell)base.GetService(typeof(SVsUIShell));
        MenuCommandService = base.GetService(typeof(IMenuCommandService)) as OleMenuCommandService;

        //Setup Extension Components
        foreach (var ec in this.extensionComponents)
        {
            try
            {
                Trace.WriteLine(string.Format("Entering {0} Setup", ec.ToString()));

                ec.Setup();
            }
            catch (Exception ex)
            {
                Trace.WriteLine(ex.ToString());
            }
        }
    }

    /// <summary>
    /// Get Extension Package Service
    /// </summary>
    public static object GetPackageService(Type serviceType)
    {
        if (s_Instance != null)
        {
            return s_Instance.GetService(serviceType);
        }

        return null;
    }
    #endregion
}

Třída nejprve v konstruktoru naplní některé pomocné statické proměnné a vytvoří všechny objekty zděděné z interface IExtensionComponent co jsou v package assembly. Dále se v metodě Initialize nastaví zbývající proměnné a na vytvořené componenty se zavolá metoda Setup. Tento mechanizmus umožňuje rozšíření package např. o nový příkaz pouze přidáním nové třídy, a hlavní kód package není nutné měnit.

Třída našeho package pak už stačí pouze definovat s prázdnou implementací, jsou zde ale nutné uvést všechny potřebné atributy (definující název a iconu při registraci a package guid).

// This attribute tells the PkgDef creation utility (CreatePkgDef.exe) that this class is a package.
[PackageRegistration(UseManagedResourcesOnly = true)]
// This attribute is used to register the informations needed to show the this package
// in the Help/About dialog of Visual Studio.
[InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)]
// This attribute is needed to let the shell know that this package exposes some menus.
[ProvideMenuResource("Menus.ctmenu", 1)]
[Guid(GuidList.guidTFSSCExplorerExtensionPkgString)]
public sealed class TFSSCExplorerExtensionPackage : ExtensionPackage { }

Tím by se mohlo zdát, že máme hotovo, je zde ale ještě jedna záležitost. Jde o to, kdy Visual Studio package načte (vyvolá metodu Initialize), ve výchozím nastavení je package načten až když je potřeba, tj. v tomto případě až když novou položkou menu vyvoláme jeho příkaz. To nám může vyhovovat v případě že package implementuje pouze nové příkazy, tool window apod.

Součástí mého Package je ale ještě funkčnost, která doplňuje do okna Source Controlu podporu drag & drop a další funkce Source Controlu. K tomu je potřeba provést vlastní inicializaci (zaregistrování událostí okna a Source Controlu) už při startu. Funkčnosti jsou implementované třídou založené přímo na IExtensionComponent interface (a ne třídě ExtensionCommand), a inicializace je v metodě Setup, kterou package z metody Initialize zavolá.

Jak ale zajistit, aby se metoda Initialize package třídy vyvolala hned při spuštění Visual Studia? (Jedná se o chování, které VS Add-In měl jako jediné možné.) Protože řešení tohoto problému není nikde moc dobře popsáno, trvalo mi asi půlden, než jsem našel odpověď. A nakonec se ukázalo, že stačí třídu package (v mém případě TFSSCExplorerExtensionPackage) rozšířit o atribut ProvideAutoLoad, a jako hodnotu nastavit GUID, který určuje, kdy je potřeba mít package už načten. V mém  případě to jsou hodnoty NoSolution a SolutionExists, jejich odpovídající GUID string jsem našel ve třídě Microsoft.VisualStudio.VSConstants.UICONTEXT. Třída finálně tedy vypadá takto:

// This attribute tells the PkgDef creation utility (CreatePkgDef.exe) that this class is a package.
[PackageRegistration(UseManagedResourcesOnly = true)]
// This attribute is used to register the informations needed to show the this package
// in the Help/About dialog of Visual Studio.
[InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)]
// This attribute is needed to let the shell know that this package exposes some menus.
[ProvideMenuResource("Menus.ctmenu", 1)]
[Guid(GuidList.guidTFSSCExplorerExtensionPkgString)]
[ProvideAutoLoad(Microsoft.VisualStudio.VSConstants.UICONTEXT.NoSolution_string)]
[ProvideAutoLoad(Microsoft.VisualStudio.VSConstants.UICONTEXT.SolutionExists_string)]
public sealed class TFSSCExplorerExtensionPackage : ExtensionPackage { }

Ještě si všimněte že, protože potřebuji nastavit hodnoty dvě, tak jsem zde atribut uvedl dvakrát.

Na úplný závěr ještě uvedu některé zdroje, které jsem při implementaci Package použil:
DiveDeeper's blog:
http://dotneteers.net/blogs/divedeeper/archive/2010/03/02/VisualStudioPackages.aspx
http://dotneteers.net/blogs/divedeeper/archive/2010/05/23/vs-2010-package-development-chapter-2-commands-menus-and-toolbars.aspx http://dotneteers.net/blogs/divedeeper/archive/2008/03/02/LearnVSXNowPart14.aspx
David DeWinter:
http://blogs.rev-net.com/ddewinter/2008/03/14/dynamic-menu-commands-in-visual-studio-packages-part-1/ http://blogs.rev-net.com/ddewinter/2008/04/05/dynamic-menu-commands-in-visual-studio-packages-part-3/ The Visual Studio Blog:
http://blogs.msdn.com/b/visualstudio/archive/2009/12/09/building-and-publishing-an-extension-for-visual-studio-2010.aspx AllenD's WebLog (How to get a VS Package to load):
http://blogs.msdn.com/b/allend/archive/2006/05/04/590055.aspx

 

hodnocení článku

1 bodů / 1 hlasů       Hodnotit mohou jen registrované uživatelé.

 

Nový příspěvek

 

pro koho????

Funguje i pro visual studio 2012 EXPRESS ?????

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Samotné Express edice podporovány nejsou, protože tam nejsou doplňky tohoto typu podporovány žádné.

Ale tento doplněk nemá nastavené vyžadování specifické edice, pouze prostředí Integrated Shell. Takže i když použijete klidně jen samotnou instalaci Team Explorer for Microsoft Visual Studio (která je také zdarma), tak tam doplněk fungovat bude.

http://www.microsoft.com/en-us/download/...

nahlásit spamnahlásit spam 0 odpovědětodpovědět

mno nečekal jsem tak rychlou odpověď teď jsem o tom psal kamarádovi přes skype a domluvil mi že se mi samosebou poptá jinak já teprve v této kapitole co se týká programování začínám tzn každopádně každý doplněk se hodí. (Jak by ne) každopádně děkuji za odpověď a za každou další radu budu dost vděčný zde si něco více počtu a pak se když tak na Vás obrátím s pozdravem Revan :-)[nechci bejt známej] :-)

nahlásit spamnahlásit spam 0 odpovědětodpovědět

RE: Visual Studio Package

Thank you, even though I don't speak the language I managed to reuse the code! Thanks for the English comments!

Kristof

nahlásit spamnahlásit spam 0 odpovědětodpovědět
                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:
Heslo:

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback