Visual Studio Package – doplnění

Jan Holan       21.10.2011       Visual Studio, Architektura       12460 zobrazení

V březnu (to ten čas letí) jsem psal příspěvek jak tvořit doplněk do Visual Studia. Nyní po několika úpravách našeho vlastního doplňku bych se k tomuto tématu vrátil a popsal několik dalších funkcí a postřehů, které se můžou hodit při tvorbě doplňku.

Zobrazení MessageBoxu

Napíšeme si funkci ShowMessageBox pro volání standartního Visual Studio Message boxu pro zobrazení libovolné zprávy z extension package a funkci ShowException pro zobrazení chybové zprávy. Funkce přidáme do třídy našeho package:

#region action methods
public DialogResult ShowMessageBox(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton, string helpTopic)
{
    var UIShell = (IVsUIShell)base.GetService(typeof(SVsUIShell));

    OLEMSGBUTTON msgbtn = MapButtons(buttons);
    OLEMSGDEFBUTTON msgdefbtn = MapDefaultButton(defaultButton);
    OLEMSGICON msgicon = MapIcon(icon);
    int pnResult = 0;
    Guid clsid = Guid.Empty;
    UIShell.ShowMessageBox(0, ref clsid, caption, text, helpTopic, 0, msgbtn, msgdefbtn, msgicon, 0, out pnResult);
    return (DialogResult)pnResult;
}

public DialogResult ShowException(Exception exception)
{
    return ShowMessageBox(exception.Message, "Package Error", MessageBoxButtons.OK, MessageBoxIcon.Hand, MessageBoxDefaultButton.Button1, exception.HelpLink);
}
#endregion

#region private member functions
private static OLEMSGBUTTON MapButtons(MessageBoxButtons buttons)
{
    switch (buttons)
    {
        case MessageBoxButtons.OK:
            return OLEMSGBUTTON.OLEMSGBUTTON_OK;
        case MessageBoxButtons.OKCancel:
            return OLEMSGBUTTON.OLEMSGBUTTON_OKCANCEL;
        case MessageBoxButtons.AbortRetryIgnore:
            return OLEMSGBUTTON.OLEMSGBUTTON_ABORTRETRYIGNORE;
        case MessageBoxButtons.YesNoCancel:
            return OLEMSGBUTTON.OLEMSGBUTTON_YESNOCANCEL;
        case MessageBoxButtons.YesNo:
            return OLEMSGBUTTON.OLEMSGBUTTON_YESNO;
        case MessageBoxButtons.RetryCancel:
            return OLEMSGBUTTON.OLEMSGBUTTON_RETRYCANCEL;
    }

    throw new ArgumentException("buttons");
}

private static OLEMSGDEFBUTTON MapDefaultButton(MessageBoxDefaultButton defaultButton)
{
    if (defaultButton == MessageBoxDefaultButton.Button1)
    {
        return OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST;
    }
    else if (defaultButton == MessageBoxDefaultButton.Button2)
    {
        return OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_SECOND;
    }
    else if (defaultButton == MessageBoxDefaultButton.Button3)
    {
        return OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST;
    }

    throw new ArgumentException("defaultButton");
}

private static OLEMSGICON MapIcon(MessageBoxIcon icon)
{
    switch (icon)
    {
        case MessageBoxIcon.None:
            return OLEMSGICON.OLEMSGICON_NOICON;
        case MessageBoxIcon.Hand:
            return OLEMSGICON.OLEMSGICON_CRITICAL;
        case MessageBoxIcon.Question:
            return OLEMSGICON.OLEMSGICON_QUERY;
        case MessageBoxIcon.Exclamation:
            return OLEMSGICON.OLEMSGICON_WARNING;
        case MessageBoxIcon.Asterisk:
            return OLEMSGICON.OLEMSGICON_INFO;
    }

    throw new ArgumentException("icon");
}
#endregion

Options dialog

Přidání karty do Visual Studio options dialogu s našimi volbami pro Extension lze provést docela jednoduše. V našem extension vytvoříme třídu, která bude dědit z Microsoft.VisualStudio.Shell.DialogPage. Do této třídy umístíme public vlastnosti, ty se pak již automaticky budou zobrazovat v property gridu naší options stránky v dialogu. U vlastností můžeme uvést atributy Category, DisplayName a Description, tím určíme kde a s jakým textem má být volba zobrazena. Pokud potřebujeme při potvrzení options dialogu provést nějakou akci, použijeme metodu OnApply. Příklad takové třídy může vypadat takto:

[Guid("B2722762-E01B-474F-9B04-114428C76A2A")]
internal class GereralOptionPage : DialogPage
{
    #region member varible and default property initialization
    private bool m_ShowDestroyCommand = true;
    private bool m_FileIconChange = true;
    #endregion

    #region property getters/setters
    [Category("TFSSC Explorer Extension Features")]
    [DisplayName("Destroy Command")]
    [Description("Enable Destroy menu command.")]
    public bool ShowDestroyCommand
    {
        get { return m_ShowDestroyCommand; }
        set { m_ShowDestroyCommand = value; }
    }

    [Category("TFSSC Explorer Extension Features")]
    [DisplayName("Files icon change")]
    [Description("Enable icon change on branched files in the file list of Source Control Explorer.")]
    public bool FileIconChange
    {
        get { return m_FileIconChange; }
        set { m_FileIconChange = value; }
    }
    #endregion

    protected override void OnApply(PageApplyEventArgs e)
    {
        base.OnApply(e);

        //Refresh file list of Source Control Explorer
        ExtensionPackage.VersionControlExt.Explorer.ShowDeletedItems = !ExtensionPackage.VersionControlExt.Explorer.ShowDeletedItems;
        ExtensionPackage.VersionControlExt.Explorer.ShowDeletedItems = !ExtensionPackage.VersionControlExt.Explorer.ShowDeletedItems;
    }
}

Třídu musíme pro extension package zavést, to se provede přidáním atributu ProvideOptionPage k třídě našeho ExtensionPackage:

[ProvideOptionPage(typeof(GereralOptionPage), "TFSSC Explorer Extension", "Gereral", 0, 0, false)]

Abychom dále zjistili jak je hodnota volby nastavená, potřebujeme se na daný option z kódu odkázat, to provedeme takto:

GereralOptionPage options = (GereralOptionPage)this.GetDialogPage(typeof(GereralOptionPage));

if (options.FileIconChange)
{
    ...
}

Pozn.: Pokud v kódu podle nějaké volby např. měníme viditelnost některého menu commandu, nesmíme zapomenout v jeho definici ve vsct souboru uvést příznak DynamicVisibility:

<CommandFlag>DynamicVisibility</CommandFlag>

Také je možné vytvořit složitější Option Page dialogu, kde lze např. umístit vlastní UserControl, více o tomto tématu se lze dočíst zde.

Context Menu

Minule jsme si ukázali jak v souboru vsct nadefinovat položky k nějakému již existujícímu menu. Nyní si ukážeme definici, která vytvoří celé nové Context Menu s položkami:

<Commands package="guidTFSSCExplorerExtensionPkg">
  <!--This is the sub-section that defines the menus and toolbars.-->
  <Menus>
    <!-- To define an element in this group you need an ID, a parent group, a display priority,
          a menu type, a name and a default text. -->
    <!-- Source Control Explorer Drag-Drop Context Menu -->
    <Menu guid="guidTFSSCExplorerExtensionCmdSet" id="imnuExplorerDragContext" priority="0x0000" type="Context">
      <Parent guid="guidTFSSCExplorerExtensionCmdSet" id="0"/>
      <Strings>
        <ButtonText>Source Control Explorer Drag-Drop Context Menu</ButtonText>
        <CommandName>TFSSCExplorerDragDropContextMenu</CommandName>
      </Strings>
    </Menu>
  </Menus>

  <Groups>
    <!--Context menu group -->
    <Group guid="guidTFSSCExplorerExtensionCmdSet" id="igrpExplorerDragContext" priority="0x0100">
      <Parent guid="guidTFSSCExplorerExtensionCmdSet" id="imnuExplorerDragContext"/>
    </Group>
  </Groups>

  <Buttons>
    <!--Source Control Explorer Drag-Drop ContextMenu Buttons-->
    <Button guid="guidTFSSCExplorerExtensionCmdSet" id="icmdExplorerDragMove" priority="0x0100" type="Button">
      <Parent guid="guidTFSSCExplorerExtensionCmdSet" id="igrpExplorerDragContext" />
      <Strings>
        <CommandName>TfsContextExplorerDragMove</CommandName>
        <ButtonText>Move</ButtonText>
      </Strings>
    </Button>
    <Button guid="guidTFSSCExplorerExtensionCmdSet" id="icmdExplorerDragBranch" priority="0x0200" type="Button">
      <Parent guid="guidTFSSCExplorerExtensionCmdSet" id="igrpExplorerDragContext" />
      <Strings>
        <CommandName>TfsContextExplorerDragBranch</CommandName>
        <ButtonText>Branch</ButtonText>
      </Strings>
    </Button>
    <Button guid="guidTFSSCExplorerExtensionCmdSet" id="icmdExplorerDragCopy" priority="0x0300" type="Button">
      <Parent guid="guidTFSSCExplorerExtensionCmdSet" id="igrpExplorerDragContext" />
      <Strings>
        <CommandName>TfsContextExplorerDragCopy</CommandName>
        <ButtonText>Copy</ButtonText>
      </Strings>
    </Button>
  </Buttons>
</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="imnuExplorerDragContext" value="0x1001" />
    <IDSymbol name="igrpExplorerDragContext" value="0x1010" />
    <IDSymbol name="icmdExplorerDragMove" value="0x1100" />
    <IDSymbol name="icmdExplorerDragBranch" value="0x1200" />
    <IDSymbol name="icmdExplorerDragCopy" value="0x1300" />
  </GuidSymbol>
</Symbols>

Pro použití tohoto context menu nám ještě zbývají dvě věci, které provedeme v kódu. První je zaregistrování obslužných metod jednotlivých příkazů menu, druhá je kód pro vlastní volání zobrazení context menu:

#region constants
private const int imnuExplorerDragContext = 0x1001;    //Context Menu
private const int icmdExplorerDragMove = 0x1100;
private const int icmdExplorerDragBranch = 0x1200;
private const int icmdExplorerDragCopy = 0x1300;
#endregion

public void Setup()
{
    //Register Context Menu commands
    var menuCommandIDMove = new CommandID(GuidList.guidTFSSCExplorerExtensionCmdSet, icmdExplorerDragMove);
    var menuItemMove = new OleMenuCommand(ExplorerDragMove_MenuInvokeCallback, menuCommandIDMove);
    ExtensionPackage.MenuCommandService.AddCommand(menuItemMove);

    var menuCommandIDBranch = new CommandID(GuidList.guidTFSSCExplorerExtensionCmdSet, icmdExplorerDragBranch);
    var menuItemBranch = new OleMenuCommand(ExplorerDragBranch_MenuInvokeCallback, menuCommandIDBranch);
    ExtensionPackage.MenuCommandService.AddCommand(menuItemBranch);

    var menuCommandIDCopy = new CommandID(GuidList.guidTFSSCExplorerExtensionCmdSet, icmdExplorerDragCopy);
    var menuItemCopy = new OleMenuCommand(ExplorerDragCopy_MenuInvokeCallback, menuCommandIDCopy);
    ExtensionPackage.MenuCommandService.AddCommand(menuItemCopy);
}

private void ExplorerDragMove_MenuInvokeCallback(object sender, EventArgs e)
{
    ...
}

private void ExplorerDragBranch_MenuInvokeCallback(object sender, EventArgs e)
{
    ...
}

private void ExplorerDragCopy_MenuInvokeCallback(object sender, EventArgs e)
{
    ...
}

...
    //Show Drag-Drop Context Menu
    try
    {
        var menuCommandID = new CommandID(GuidList.guidTFSSCExplorerExtensionCmdSet, imnuExplorerDragContext);
        ExtensionPackage.MenuCommandService.ShowContextMenu(menuCommandID, e.X, e.Y);
    }
    catch (Exception ex)
    {
        ShowException(ex);
    }

Všimněte si, že konstanty ID menu a jednotlivých commandů odpovídají tomu, tak jsou nadefinované symboly ve vsct  souboru.

Visual Studio Gallery

Ještě závěrem zmíním několik poznámek k publikaci vlastního VS rozšíření do Visual Studio Gallery.

  • Licenci, která se u doplňku v galerii zobrazuje, je nutné přidat už přímo jako jeho součást. Jedná se o txt nebo rtf soubor, který se navolí v souboru .vsixmanifest v sekci License Terms podobně jako se zde přidává např. icona a náhledový obrázek.
  • Do vlastní galerie se pak nahrává soubor balíčku .vsix, jedná se o soubor formátu zip, ve kterém je popisný xml soubory .pkgdef, .vsixmanifest, license, logo a vlastní binární dll soubor. Tento balíček je při kompilaci automaticky vytvořen pokud ve vlastnostech projektu na záložce VSIX zaškrtnete volbu Create VSIX Container during build.
  • Po nahrání do galerie není doplněk hned veřejně viditelný, k tomu je nutné ještě potvrdit tlačítkem Publish.
  • Při vytvoření nové verze doplňku, změníme číslo verze v souboru .vsixmanifest a nový vsix znovu nahrajeme do galerie. Pokud byl již doplněk nainstalován, tak je automaticky v Extension Manageru Visual Studia nabízen k instalaci update doplňku.

Více je popsáno v odkazech zde a zde.

 

hodnocení článku

0       Hodnotit mohou jen registrované uživatelé.

 

Nový příspěvek

 

                       
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