Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일

(시리즈 글이 6개 있습니다.)
VS.NET IDE: 77. Visual Studio 확장(VSIX) 만드는 방법
; https://www.sysnet.pe.kr/2/0/1515

VS.NET IDE: 78. Visual Studio 확장으로 XmlCodeGenerator 제작하는 방법
; https://www.sysnet.pe.kr/2/0/1518

VS.NET IDE: 115. Visual Studio 확장(VSIX)을 이용해 사용자 메뉴 추가하는 방법
; https://www.sysnet.pe.kr/2/0/11184

VS.NET IDE: 116. Visual Studio 확장(VSIX)을 이용해 사용자 메뉴 추가하는 방법 (2) - 동적 메뉴 구성
; https://www.sysnet.pe.kr/2/0/11185

VS.NET IDE: 117. Visual Studio 확장(VSIX)을 이용해 사용자 매크로를 추가하는 방법
; https://www.sysnet.pe.kr/2/0/11186

VS.NET IDE: 165. Visual Studio 2022를 위한 Extension 마이그레이션
; https://www.sysnet.pe.kr/2/0/12682




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)
12815정성태8/30/202115783개발 환경 구성: 602. WSL 2 - docker-desktop-data, docker-desktop (%LOCALAPPDATA%\Docker\wsl\data\ext4.vhdx) 파일을 다른 디렉터리로 옮기는 방법
12814정성태8/30/202120198.NET Framework: 1110. C# 11 - 인터페이스 내에 정적 추상 메서드 정의 가능 (DIM for Static Members) [2]파일 다운로드1
12813정성태8/29/202117281.NET Framework: 1109. C# 10 - (11) Lambda 개선파일 다운로드1
12812정성태8/28/202116626.NET Framework: 1108. C# 10 - (10) 개선된 #line 지시자
12811정성태8/27/202116503Linux: 44. 윈도우 개발자를 위한 리눅스 fork 동작 방식 설명 (파이썬 코드)
12810정성태8/27/202115538.NET Framework: 1107. .NET Core/5+에서 동적 컴파일한 C# 코드를 (Breakpoint도 활용하며) 디버깅하는 방법 - #line 지시자파일 다운로드1
12809정성태8/26/202115482.NET Framework: 1106. .NET Core/5+에서 C# 코드를 동적으로 컴파일/사용하는 방법 [1]파일 다운로드1
12808정성태8/25/202117129오류 유형: 758. go: ...: missing go.sum entry; to add it: go mod download ...
12807정성태8/25/202117899.NET Framework: 1105. C# 10 - (9) 비동기 메서드가 사용할 AsyncMethodBuilder 선택 가능파일 다운로드1
12806정성태8/24/202114449개발 환경 구성: 601. PyCharm - 다중 프로세스 디버깅 방법
12805정성태8/24/202116151.NET Framework: 1104. C# 10 - (8) 분해 구문에서 기존 변수의 재사용 가능파일 다운로드1
12804정성태8/24/202116319.NET Framework: 1103. C# 10 - (7) Source Generator V2 APIs
12803정성태8/23/202116811개발 환경 구성: 600. pip cache 디렉터리 옮기는 방법
12802정성태8/23/202117248.NET Framework: 1102. .NET Conf Mini 21.08 - WinUI 3 따라해 보기 [1]
12801정성태8/23/202116839.NET Framework: 1101. C# 10 - (6) record class 타입의 ToString 메서드를 sealed 처리 허용파일 다운로드1
12800정성태8/22/202117212개발 환경 구성: 599. PyCharm - (반대로) 원격 프로세스가 PyCharm에 디버그 연결하는 방법
12799정성태8/22/202117557.NET Framework: 1100. C# 10 - (5) 속성 패턴의 개선파일 다운로드1
12798정성태8/21/202118861개발 환경 구성: 598. PyCharm - 원격 프로세스를 디버그하는 방법
12797정성태8/21/202116222Windows: 197. TCP의 MSS(Maximum Segment Size) 크기는 고정된 것일까요?
12796정성태8/21/202117223.NET Framework: 1099. C# 10 - (4) 상수 문자열에 포맷 식 사용 가능파일 다운로드1
12795정성태8/20/202117559.NET Framework: 1098. .NET 6에 포함된 신규 BCL API - 스레드 관련
12794정성태8/20/202116915스크립트: 23. 파이썬 - WSGI를 만족하는 최소한의 구현 코드 및 PyCharm에서의 디버깅 방법 [1]
12793정성태8/20/202117703.NET Framework: 1097. C# 10 - (3) 개선된 변수 초기화 판정파일 다운로드1
12792정성태8/19/202118860.NET Framework: 1096. C# 10 - (2) 전역 네임스페이스 선언파일 다운로드1
12791정성태8/19/202115651.NET Framework: 1095. C# COM 개체를 C++에서 사용하는 예제 [3]파일 다운로드1
12790정성태8/18/202119578.NET Framework: 1094. C# 10 - (1) 구조체를 생성하는 record struct파일 다운로드1
... 31  32  33  34  35  36  37  38  39  40  41  42  43  44  [45]  ...