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.