헬마입니다.

이란은 제가 The Old New Thing 을 읽으면서 관심가지는 글들을 번역해서 올려놓는 란입니다.

글의 원문은 이곳입니다.

-----------------------------------------------------------------------------------------

대부분의 문서는 쉘 컨텍스트메뉴 구조체에 접근하여 컨텍스트 메뉴를 제공하는 방법에 대하여 설명하고 있다. 만약 다른 관점에서 문서 읽기를 해보았다면 컨텍스트 메뉴를 어떻게 호스팅해야할 지도 알 수 있을것이다. ( 이 문서는 11 개의 시리즈 문서와 3개의 여담 중 첫번째이다. 네, 11개 문서에요요 --  난 우발적인 기분전환을 시도해볼 것이다. )

IContextMenu 를 사용하는 일반적인 패턴은 아래와 같다:

아래의 글들은 IContextMenu 구현자의 관점에서 컨텍스트 메뉴처리기 제작하기 를 설명한 것이다.

쉘은 최초에 IContextMenu::QueryContextMenu 를 호출한다. 쉘은 컨텍스트 메뉴에 항목을 추가하기 위해 함수에서 사용할 수 있는 HMENU 값을 전달한다. 사용자가 명령 중에 하나를 선택하면 마이크로소프트 윈도 탐색기의 상태표시줄에 출력할 도움 문자열을 얻기위해 IContextMenu::GetCommandString 이 호출된다. 사용자가 처리기의 항목들 중 하나를 클릭하면 쉘은 IContextMenu::InvokeCommand 를 호출한다. 처리기는 그러면 적절한 명령을 실행하게된다.

자 이번에는 IContextMenu 호스트로서 해야할 것들이 무엇인지 다른 관점에서 보자 :

IContextMenu 호스트 는 최초에 IContextMenu::QueryContextMenu 함수를 호출한다. 호스트측은 함수가 컨텍스트 메뉴에 항목을 추가하는데 사용할 수 있는 HMENU 핸들 값을 넘겨준다. 사용자가 명령 중에 하나를 선택하면,  IContextMenu::GetCommandString 를 호출하여 호스트측의 상태표시줄에 표시할 도움 문자열을 얻어온다. 사용자가 처리기의 항목 중 하나를 클릭하면 IContextMenu 호스트 는 IContextMenu::InvokeCommand 를 호출하고 처리기는 적절한 명령을 실행한다.

컨텍스트 메뉴 문서의 이 새로운 설명에 대한 결과를 살펴보는 것은 앞으로 몇 주 동안의 우리의 관심이 될 것이다.
 
  자, 이제 시작해봐요. 언제나 그랬듯이, 갖다 붙이는 프로그램으로 시작합니다. 나는 당신이 쉘 네임스페이스와 pidl 들에 이미 익숙하다고 가정하고 컨텍스트 메뉴의 이슈에만 집중할 것입니다.

#include <shlobj.h>

HRESULT GetUIObjectOfFile(HWND hwnd, LPCWSTR pszPath, REFIID riid, void **ppv)
{
  *ppv = NULL;
  HRESULT hr;
  LPITEMIDLIST pidl;
  SFGAOF sfgao;
  if (SUCCEEDED(hr = SHParseDisplayName(pszPath, NULL, &pidl, 0, &sfgao))) {
    IShellFolder *psf;
    LPCITEMIDLIST pidlChild;
    if (SUCCEEDED(hr = SHBindToParent(pidl, IID_IShellFolder,
                                      (void**)&psf, &pidlChild))) {
      hr = psf->GetUIObjectOf(hwnd, 1, &pidlChild, riid, NULL, ppv);
      psf->Release();
    }
    CoTaskMemFree(pidl);
  }
  return hr;
}

이 간단한 함수는 경로를 받아서 쉘 UI 객체를 얻습니다. 우리는 SHParseDisplayName 를 사용해서 경로를 pidl 로 변환 후 SHBindToParent 를 사용하여 Pidl 의 부모와 바인딩한다. 그런 다음,  IShellFolder::GetUIObjectOf 를 사용해서 부모에게 자식의 UI 객체를 요청한다. 저는 당신이 이러한 쉘 네임스페이스에서의 작업을 코웃음으로 할 정도로 익숙하다고 가정하고 있습니다.

(SHParseDisplayName 과 SHBindToParent 도우미 함수는 스스로 하지 않은 일은 아무것도 해주지 않습니다. 저 함수들은 그저 타이핑 횟수를 줄여주는 것 뿐입니다. 일단, 쉘 네임스페이스를 사용해서 의미있는 시간을 사용하기 시작하면 위와 같은 작은 함수들의 라이브러리를 만들게 됩니다.)

우리의 첫번째 단계에서, 우리는 사용자가 오른쪽 버튼을 눌렀을 때 파일에서 "Play" 동사를 실행시킬 것입니다. ( 왜 오른쪽버튼 이냐구요? 이 프로그램의 차후 버전에서는 컨텍스트 메뉴를 표시할 것이기 때문입니다.)  

#define SCRATCH_QCM_FIRST 1
#define SCRATCH_QCM_LAST  0x7FFF

void OnContextMenu(HWND hwnd, HWND hwndContext, UINT xPos, UINT yPos)
{
  IContextMenu *pcm;
  if (SUCCEEDED(GetUIObjectOfFile(hwnd, L"C:\\Windows\\clock.avi",
                   IID_IContextMenu, (void**)&pcm))) {
    HMENU hmenu = CreatePopupMenu();
    if (hmenu) {
      if (SUCCEEDED(pcm->QueryContextMenu(hmenu, 0,
                             SCRATCH_QCM_FIRST, SCRATCH_QCM_LAST,
                             CMF_NORMAL))) {
        CMINVOKECOMMANDINFO info = { 0 };
        info.cbSize = sizeof(info);
        info.hwnd = hwnd;
        info.lpVerb = "play";
        pcm->InvokeCommand(&info);
      }
      DestroyMenu(hmenu);
    }
    pcm->Release();
  }
}

    HANDLE_MSG(hwnd, WM_CONTEXTMENU, OnContextMenu);

  위의 점검 사항에서 보았던 것처럼, 우리는 처음에 IContextMenu 를 만들고 IContextMenu::QueryContextMenu 를 호출하여 인터페이스를 초기화하고 있습니다. 주목할 점은, 우리가 메뉴를 표시할 의도가 없음에도 불구하고 여전히 팝업 메뉴를 만들고 있는 이유는 IContextMenu::QueryContextMenu 가 메뉴 핸들을 요구하기 때문입니다. 그러나 우리는 실제적으로 결과로 받은 메뉴를 표시하지 않습니다. 사용자에게 메뉴에서 항목을 선택하라고 요청하는 대신에 우리는 사용자가 "Play" 를 선택했다고 만들고, CMINVOKECOMMANDINFO 구조체 에 내용을 채우고 실행시킵니다.

  그러나 우리는 어떻게 "Play" 라는 적합한 동사가 있다는 것을 알 수 있을까요? 이 경우에 우리는 "clock.avi" 라는 파일을 하드코딩했고 AVI 파일은 "Play" 라는 동사가 있다는 것을 알고 있기때문입니다. 물론, 이것이 범용적으로 작동하지는 않습니다. 실행할 기본동사를 얻어오는 것을 하기 전에 사용자에게 실행할 동사를 요청하는 것보다 더 쉬운 단계를 해보자. 이러한 연습은 실제적으로 우리를 혼란스럽게 할 수 도 있지만, 우리는 차후에 이러한 기본동사에 관한 문제를 다룰 것입니다.

만약 위의 코드가 원하던 모든 것( 파일에서 고정된 동사를 호출하는 것 ) 이라면 나머지 컨텍스트 메뉴에 관한 기사들을 읽어볼 필요가 없습니다. 이 코드는 ShellExecuteEx 함수 에 SEE_MASK_INVOKEIDLIST 플래스와 함계 호출하는 것과 일치합니다.

Published Monday, September 20, 2004 7:00 AM by oldnewthing

댓글을 달아 주세요