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

비밀번호

댓글 작성자
 




1  2  3  4  5  6  7  8  [9]  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13718정성태8/27/20247455오류 유형: 921. Visual C++ - error C1083: Cannot open include file: 'float.h': No such file or directory [2]
13717정성태8/26/20247057VS.NET IDE: 192. Visual Studio 2022 - Windows XP / 2003용 C/C++ 프로젝트 빌드
13716정성태8/21/20246770C/C++: 167. Visual C++ - 윈도우 환경에서 _execv 동작 [1]
13715정성태8/19/20247403Linux: 78. 리눅스 C/C++ - 특정 버전의 glibc 빌드 (docker-glibc-builder)
13714정성태8/19/20246770닷넷: 2295. C# 12 - 기본 생성자(Primary constructors) (책 오타 수정) [3]
13713정성태8/16/20247515개발 환경 구성: 721. WSL 2에서의 Hyper-V Socket 연동
13712정성태8/14/20247231개발 환경 구성: 720. Synology NAS - docker 원격 제어를 위한 TCP 바인딩 추가
13711정성태8/13/20248088Linux: 77. C# / Linux - zombie process (defunct process) [1]파일 다운로드1
13710정성태8/8/20248018닷넷: 2294. C# 13 - (6) iterator 또는 비동기 메서드에서 ref와 unsafe 사용을 부분적으로 허용파일 다운로드1
13709정성태8/7/20247770닷넷: 2293. C# - safe/unsafe 문맥에 대한 C# 13의 (하위 호환을 깨는) 변화파일 다운로드1
13708정성태8/7/20247567개발 환경 구성: 719. ffmpeg / YoutubeExplode - mp4 동영상 파일로부터 Audio 파일 추출
13707정성태8/6/20247797닷넷: 2292. C# - 자식 프로세스의 출력이 4,096보다 많은 경우 Process.WaitForExit 호출 시 hang 현상파일 다운로드1
13706정성태8/5/20247923개발 환경 구성: 718. Hyper-V - 리눅스 VM에 새로운 디스크 추가
13705정성태8/4/20248175닷넷: 2291. C# 13 - (5) params 인자 타입으로 컬렉션 허용 [2]파일 다운로드1
13704정성태8/2/20248137닷넷: 2290. C# - 간이 dotnet-dump 프로그램 만들기파일 다운로드1
13703정성태8/1/20247466닷넷: 2289. "dotnet-dump ps" 명령어가 닷넷 프로세스를 찾는 방법
13702정성태7/31/20247885닷넷: 2288. Collection 식을 지원하는 사용자 정의 타입을 CollectionBuilder 특성으로 성능 보완파일 다운로드1
13701정성태7/30/20248146닷넷: 2287. C# 13 - (4) Indexer를 이용한 개체 초기화 구문에서 System.Index 연산자 허용파일 다운로드1
13700정성태7/29/20247771디버깅 기술: 200. DLL Export/Import의 Hint 의미
13699정성태7/27/20248273닷넷: 2286. C# 13 - (3) Monitor를 대체할 Lock 타입파일 다운로드1
13698정성태7/27/20248225닷넷: 2285. C# - async 메서드에서의 System.Threading.Lock 잠금 처리파일 다운로드1
13697정성태7/26/20247934닷넷: 2284. C# - async 메서드에서의 lock/Monitor.Enter/Exit 잠금 처리파일 다운로드1
13696정성태7/26/20247471오류 유형: 920. dotnet publish - error NETSDK1047: Assets file '...\obj\project.assets.json' doesn't have a target for '...'
13695정성태7/25/20247462닷넷: 2283. C# - Lock / Wait 상태에서도 STA COM 메서드 호출 처리파일 다운로드1
13694정성태7/25/20247923닷넷: 2282. C# - ASP.NET Core Web App의 Request 용량 상한값 (Kestrel, IIS)
13693정성태7/24/20247249개발 환경 구성: 717. Visual Studio - C# 프로젝트에서 레지스트리에 등록하지 않은 COM 개체 참조 및 사용 방법파일 다운로드1
1  2  3  4  5  6  7  8  [9]  10  11  12  13  14  15  ...