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

비밀번호

댓글 작성자
 




... 61  62  63  64  65  66  [67]  68  69  70  71  72  73  74  75  ...
NoWriterDateCnt.TitleFile(s)
11967정성태7/1/201911666개발 환경 구성: 446. Synology NAS를 Windows 10에서 iSCSI로 연결하는 방법
11966정성태6/30/201911035Math: 62. 활성화 함수에 따른 뉴런의 출력을 그리드 맵으로 시각화파일 다운로드1
11965정성태6/30/201911885.NET Framework: 846. C# - 2차원 배열을 1차원 배열로 나열하는 확장 메서드파일 다운로드1
11964정성태6/30/201913329Linux: 20. C# - Linux에서의 Named Pipe를 이용한 통신
11963정성태6/29/201913071Linux: 19. C# - .NET Core Unix Domain Socket 사용 예제
11962정성태6/27/201910755Math: 61. C# - 로지스틱 회귀를 이용한 선형분리 불가능 문제의 분류파일 다운로드1
11961정성태6/27/201910320Graphics: 37. C# - PLplot - 출력 모음(Family File Output)
11960정성태6/27/201911102Graphics: 36. C# - PLplot의 16색 이상을 표현하는 방법과 subpage를 이용한 그리드 맵 표현
11959정성태6/27/201912242Graphics: 35. matplotlib와 PLplot의 한글 처리
11958정성태6/25/201916568Linux: 18. C# - .NET Core Console로 리눅스 daemon 프로그램 만드는 방법 [6]
11957정성태6/24/201915637Windows: 160. WMI 쿼리를 명령행에서 간단하게 수행하는 wmic.exe [2]
11956정성태6/24/201913678Linux: 17. CentOS 7에서 .NET Core Web App 실행 환경 구성 [1]
11955정성태6/20/201912002Math: 60. C# - 로지스틱 회귀를 이용한 분류파일 다운로드1
11954정성태6/20/201911412오류 유형: 550. scp - sudo: no tty present and no askpass program specified
11953정성태6/20/201910253오류 유형: 549. The library 'libhostpolicy.so' required to execute the application was not found in '...'
11952정성태6/20/201911016Linux: 16. 우분투, Centos의 Netbios 호스트 이름 풀이 방법
11951정성태6/20/201913853오류 유형: 548. scp 연결 시 "Permission denied" 오류 및 "WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!" 경고
11950정성태6/18/201912645.NET Framework: 845. C# - 윈도우 작업 관리자와 리소스 모니터의 메모리 값을 구하는 방법
11949정성태6/18/20199069오류 유형: 547. CoreCLR Profiler 예제 프로젝트 빌드 시 컴파일 오류 유형
11948정성태6/17/201911351Linux: 15. 리눅스 환경의 Visual Studio Code에서 TFS 서버 연동
11947정성태6/17/201912638Linux: 14. 리눅스 환경에서 TFS 서버 연동
11946정성태6/17/201913232개발 환경 구성: 445. C# - MathNet으로 정규 분포를 따르는 데이터를 생성, PLplot으로 Histogram 표현파일 다운로드1
11945정성태6/17/201911625Linux: 13. node.js에서 syslog로 출력하는 방법
11944정성태6/16/201918396Linux: 12. Ubuntu 16.04/18.04에서 node.js 최신 버전 설치 방법
11943정성태6/15/201911207.NET Framework: 844. C# - 박싱과 언박싱 [1]
11942정성태6/13/201917632개발 환경 구성: 444. 로컬의 Visual Studio Code로 원격 리눅스 머신에 접속해 개발하는 방법 [1]
... 61  62  63  64  65  66  [67]  68  69  70  71  72  73  74  75  ...