Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 2개 있습니다.)

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" 라는 메뉴가 보입니다.

vsix_menu_1.png

여기서 재미있는 것은 현재까지 아무런 소스 코드도 실행되지 않는다는 점입니다. "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




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 7/13/2021]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 31  [32]  33  34  35  36  37  38  39  40  41  42  43  44  45  ...
NoWriterDateCnt.TitleFile(s)
12827정성태9/4/20218286스크립트: 27. 파이썬 - 웹 페이지 데이터 수집을 위한 scrapy Crawler 사용법 요약
12826정성태9/3/202110475.NET Framework: 1113. C# 10 - (12) 문자열 보간 성능 개선 [1]파일 다운로드1
12825정성태9/3/20218054개발 환경 구성: 603. GoLand - WSL 환경과 연동
12824정성태9/2/202117147오류 유형: 760. 파이썬 tensorflow - Dst tensor is not initialized. 오류 메시지
12823정성태9/2/20216837스크립트: 26. 파이썬 - PyCharm을 이용한 fork 디버그 방법
12822정성태9/1/202112056오류 유형: 759. 파이썬 tensorflow - ValueError: Shapes (...) and (...) are incompatible [2]
12821정성태9/1/20217630.NET Framework: 1112. C# - .NET 6부터 공개된 ISpanFormattable 사용법
12820정성태9/1/20217950VC++: 147. Golang - try/catch에 대응하는 panic/recover [1]파일 다운로드1
12819정성태8/31/20218044.NET Framework: 1111. C# - FormattableString 타입
12818정성태8/31/20217284Windows: 198. 윈도우 - 작업 관리자에서 (tensorflow 등으로 인한) GPU 연산 부하 보는 방법
12817정성태8/31/20219841스크립트: 25. 파이썬 - 윈도우 환경에서 directml을 이용한 tensorflow의 AMD GPU 사용 방법
12816정성태8/30/202115212스크립트: 24. 파이썬 - tensorflow 2.6 NVidia GPU 사용 방법 [2]
12815정성태8/30/20218345개발 환경 구성: 602. WSL 2 - docker-desktop-data, docker-desktop (%LOCALAPPDATA%\Docker\wsl\data\ext4.vhdx) 파일을 다른 디렉터리로 옮기는 방법
12814정성태8/30/202110647.NET Framework: 1110. C# 11 - 인터페이스 내에 정적 추상 메서드 정의 가능 (DIM for Static Members) [2]파일 다운로드1
12813정성태8/29/20218863.NET Framework: 1109. C# 10 - (11) Lambda 개선파일 다운로드1
12812정성태8/28/20218487.NET Framework: 1108. C# 10 - (10) 개선된 #line 지시자
12811정성태8/27/20218732Linux: 44. 윈도우 개발자를 위한 리눅스 fork 동작 방식 설명 (파이썬 코드)
12810정성태8/27/20217489.NET Framework: 1107. .NET Core/5+에서 동적 컴파일한 C# 코드를 (Breakpoint도 활용하며) 디버깅하는 방법 - #line 지시자파일 다운로드1
12809정성태8/26/20218158.NET Framework: 1106. .NET Core/5+에서 C# 코드를 동적으로 컴파일/사용하는 방법 [1]파일 다운로드1
12808정성태8/25/20219419오류 유형: 758. go: ...: missing go.sum entry; to add it: go mod download ...
12807정성태8/25/20219364.NET Framework: 1105. C# 10 - (9) 비동기 메서드가 사용할 AsyncMethodBuilder 선택 가능파일 다운로드1
12806정성태8/24/20216979개발 환경 구성: 601. PyCharm - 다중 프로세스 디버깅 방법
12805정성태8/24/20218227.NET Framework: 1104. C# 10 - (8) 분해 구문에서 기존 변수의 재사용 가능파일 다운로드1
12804정성태8/24/20219017.NET Framework: 1103. C# 10 - (7) Source Generator V2 APIs
12803정성태8/23/20218574개발 환경 구성: 600. pip cache 디렉터리 옮기는 방법
12802정성태8/23/20218894.NET Framework: 1102. .NET Conf Mini 21.08 - WinUI 3 따라해 보기 [1]
... 31  [32]  33  34  35  36  37  38  39  40  41  42  43  44  45  ...