Visual Studio 확장(VSIX)을 이용해 사용자 메뉴 추가하는 방법
이에 대해서는 다음의 문서에 자세하게 나와 있습니다.
Creating an Extension with a Menu Command
; https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2015/extensibility/creating-an-extension-with-a-menu-command
방법은 정말 쉽습니다. Visual Studio에서 새 프로젝트를 선택해 "Extensibility" / "VSIX Project" 유형의 프로젝트를 생성한 다음, 새 항목 추가를 이용해 "Extensibility" / "Custom Command"를 선택하면 됩니다. 그럼, (파일 이름을 MacroCommand로 지정한 경우) 다음의 파일 3개가 자동으로 추가됩니다.
- MacroCommand.cs
- MacroCommandPackage.cs
- MacroCommandPackage.vsct
이 상태의 기본 소스 그대로 빌드하고 실행하면 디버그 용도의 "Visual Studio"가 별도로 뜨면서 "Tools" 메뉴에 "Invoke MacroCommand" 라는 메뉴가 보입니다.
여기서 재미있는 것은 현재까지 아무런 소스 코드도 실행되지 않는다는 점입니다. "Invoke MacroCommand" 메뉴 항목이 보이는 것은 단지 "MacroCommandPackage.vsct"에 명시된 메뉴 관련 XML 설정이 있기 때문입니다.
<Buttons>
<Button guid="guidMacroCommandPackageCmdSet" id="MacroCommandId" priority="0x0100" type="Button">
<Parent guid="guidMacroCommandPackageCmdSet" id="MyMenuGroup" />
<Icon guid="guidImages" id="bmpPic1" />
<Strings>
<ButtonText>Invoke MacroCommand</ButtonText>
</Strings>
</Button>
</Buttons>
즉, Visual Studio는 .vsct 파일로부터 Tools 메뉴에 보일 사용자 정의 메뉴 정보를 미리 알 수 있기 때문에 "MacroCommand.cs", "MacroCommandPackage.cs" 파일에 구현된 C# 코드가 단 한 줄도 실행되지 않고도 초기 메뉴를 보여줄 수 있는 것입니다.
그렇다면 코드가 언제 실행될까요? 바로, Visual Studio에서 "Tools" / "Invoke MacroCommand" 메뉴를 한 번은 실행해야 비로소 MacroCommandPackage.cs의 MacroCommandPackage 생성자가 실행되고,
// MacroCommandPackage.cs
public MacroCommandPackage()
{
}
이어 MacroCommandPackage.Initialize 메서드가 실행됩니다.
// MacroCommandPackage.cs
protected override void Initialize()
{
MacroCommand.Initialize(this);
base.Initialize();
}
보는 바와 같이 이제야 MacroCommand.cs 파일의 MacroCommand.Initialize가 실행되고,
// MacroCommand.cs
public static void Initialize(Package package)
{
Instance = new MacroCommand(package);
}
이를 통해 마침내 MacroCommand 인스턴스가 생성될 수 있습니다.
소스 코드 실행 절차를 봤으니, 메뉴 이름을 바꾸는 것도 한번 해볼까요? ^^
Changing the Text of a Menu Command
; https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2015/extensibility/changing-the-text-of-a-menu-command
일단, Visual Studio 실행 시점부터 보이는 메뉴 이름을 바꾸려면 .vsct 파일의 ButtonText를 수정해야 합니다.
<Button guid="guidMacroCommandPackageCmdSet" id="MacroCommandId" priority="0x0100" type="Button">
<Parent guid="guidMacroCommandPackageCmdSet" id="MyMenuGroup" />
<Icon guid="guidImages" id="bmpPic1" />
<Strings>
<ButtonText>Invoke MacroCommand</ButtonText>
</Strings>
</Button>
반면, "실행 중"에 동적으로 메뉴 이름을 바꾸고 싶다면 이제 코딩을 해야 합니다. 우선, 해당 메뉴의 이름이 실행 중에 바뀔 수 있음을 .vsct의 Button에 CommandFlag 노드를 이용해 명시해야 합니다.
<Button guid="guidMacroCommandPackageCmdSet" id="MacroCommandId" priority="0x0100" type="Button">
<Parent guid="guidMacroCommandPackageCmdSet" id="MyMenuGroup" />
<Icon guid="guidImages" id="bmpPic1" />
<CommandFlag>TextChanges</CommandFlag>
<Strings>
<ButtonText>Invoke MacroCommand</ButtonText>
</Strings>
</Button>
그런 다음, 코드에서 다음과 같이 기존의 MenuCommand 타입을 OleMenuCommand로 바꾼 후 BeforeQueryStatus 이벤트를 구독해야 합니다.
private MacroCommand(Package package)
{
if (package == null)
{
throw new ArgumentNullException("package");
}
this.package = package;
OleMenuCommandService commandService = this.ServiceProvider.GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
if (commandService != null)
{
var menuCommandID = new CommandID(CommandSet, CommandId);
var menuItem = new OleMenuCommand(this.MenuItemCallback, menuCommandID);
menuItem.BeforeQueryStatus += MenuItem_BeforeQueryStatus;
commandService.AddCommand(menuItem);
}
}
예상할 수 있듯이, BeforeQueryStatus 이벤트에서 원하는 조건에 따라 자유롭게 메뉴의 Text 속성을 변경하면 됩니다.
private void MenuItem_BeforeQueryStatus(object sender, EventArgs e)
{
var myCommand = sender as OleMenuCommand;
if (null != myCommand)
{
myCommand.Text = "New Text";
}
}
자, 이제 실행을 해볼까요? ^^ 처음에는 "Tools" / "Invoke MacroCommand"가 뜹니다. 왜냐하면 이 시점에는 그 어떤 코드도 실행되지 않은 상태이고 단지 .vsct에 명시된 내용에 따라 Visual Studio가 환경을 구성하기 때문입니다. 최소 한 번은 이 메뉴를 사용자가 명시적으로 선택해 실행해 줘야 MacroCommand 클래스의 코드들이 실행되고 "menuItem.BeforeQueryStatus += MenuItem_BeforeQueryStatus" 이벤트 핸들러가 연결될 수 있습니다. 그렇게 한 번 선택해 준 이후부터는 MenuItem_BeforeQueryStatus 코드에 따라 메뉴 이름이 "New Text"로 바뀌어 나타납니다.
그런데, 영 마음에 들지 않습니다. Visual Studio 로딩 시부터 내가 만든 VSIX 확장의 코드가 실행되면 좋겠는데요. 바로 이에 대한 설명이 다음의 문서에 나옵니다.
Loading VSPackages
; https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2015/extensibility/loading-vspackages
따라서, 모든 상황에서 무조건 Visual Studio를 실행시키만 하면 MacroCommandPackage를 로딩하도록 만들고 싶다면 다음과 같이 ProvideAutoLoad 특성을 적용해 볼 수 있습니다.
[PackageRegistration(UseManagedResourcesOnly = true)]
[InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)] // Info on this package for Help/About
[ProvideMenuResource("Menus.ctmenu", 1)]
[ProvideAutoLoad(UIContextGuids80.EmptySolution)]
[ProvideAutoLoad(UIContextGuids80.NoSolution)]
[ProvideAutoLoad(UIContextGuids80.SolutionExists)]
[Guid(MacroCommandPackage.PackageGuidString)]
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "pkgdef, VS and vsixmanifest are valid VS terms")]
public sealed class MacroCommandPackage : Package
{
// ... [생략] ...
}
이렇게 변경해 주면, Visual Studio가 실행되자마자 MacroCommandPackage 객체가 생성되고 MenuCommand.cs의 코드도 실행되어 처음부터 메뉴가 "New Text"로 나오는 것을 확인할 수 있습니다.
(
첨부 파일은 이 글의 예제 코드를 포함합니다.)
마지막으로, VSIX 디버그 용으로 실행되는 Visual Studio는 다음의 명령어로 직접 실행해 보는 것도 가능합니다.
"C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\IDE\devenv.exe" /rootsuffix Exp
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]