헬마입니다.

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

글의 원문은 이곳입니다.

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

우리의 컨텍스트 메뉴 표시하기의 첫번째 시도에서 알 수 있던 한가지 버그는 등록정보 대화상자가 여러분이 클릭한 위치에 나타나지 않는다는 것이다. 등록정보가 대화상자가 미친게 아니다. 단지 등록정보 대화상자는 마우스가 클릭된 위치를 모르고 있을뿐이고 여러분이 등록정보 대화상자에게 위치를 알려주면 된다.

          CMINVOKECOMMANDINFOEX info = { 0 };
          info.cbSize = sizeof(info);
          info.fMask = CMIC_MASK_UNICODE | CMIC_MASK_PTINVOKE;
          info.hwnd = hwnd;
          info.lpVerb  = MAKEINTRESOURCEA(iCmd - SCRATCH_QCM_FIRST);
          info.lpVerbW = MAKEINTRESOURCEW(iCmd - SCRATCH_QCM_FIRST);
          info.nShow = SW_SHOWNORMAL;
          info.ptInvoke = pt;

여러분은 CMINVOKECOMMANDINFOEX 구조체의 fMask 에 CMIC_MASK_PTINVOKE 플래그를 설정하고 ptInvoke 멤버에 위치를 넣고 실행하는 것으로 위치를 알려줄 수 있다.

이렇게 바꾸면 등록정보가 대화상자가 더 이상 임의의 위치에 나타나는 대신 여러분이 클릭한 위치에 나타나는 것을 관찰할 수 있다.

다음 시간에는, 우리의 샘플 프로그램의 또 다른 문제점을 잡도록 하겠다.

Published Thursday, September 23, 2004 7:00 AM by oldnewthing

댓글을 달아 주세요

헬마입니다.

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

글의 원문은 이곳입니다.

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

고정된 동사를 실행하는 대신에, 이번에는 사용자에게 컨텍스트 메뉴에서 항목을 고르도록 요청하고 선택한 항목을 실행해보겠습니다.

OnContextMenu 함수를 아래와 같이 수정합니다.

#define SCRATCH_QCM_FIRST 1
#define SCRATCH_QCM_LAST  0x7FFF

#undef HANDLE_WM_CONTEXTMENU
#define HANDLE_WM_CONTEXTMENU(hwnd, wParam, lParam, fn) \
    ((fn)((hwnd), (HWND)(wParam), GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)), 0L)

// WARNING! Incomplete and buggy! See discussion
void OnContextMenu(HWND hwnd, HWND hwndContext, int xPos, int yPos)
{
  POINT pt = { xPos, yPos };
  if (pt.x == -1 && pt.y == -1) {
    pt.x = pt.y = 0;
    ClientToScreen(hwnd, &pt);
  }

  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))) {
        int iCmd = TrackPopupMenuEx(hmenu, TPM_RETURNCMD,
                                    pt.x, pt.y, hwnd, NULL);
        if (iCmd > 0) {
          CMINVOKECOMMANDINFOEX info = { 0 };
          info.cbSize = sizeof(info);
          info.fMask = CMIC_MASK_UNICODE;
          info.hwnd = hwnd;
          info.lpVerb  = MAKEINTRESOURCEA(iCmd - SCRATCH_QCM_FIRST);
          info.lpVerbW = MAKEINTRESOURCEW(iCmd - SCRATCH_QCM_FIRST);
          info.nShow = SW_SHOWNORMAL;
          pcm->InvokeCommand((LPCMINVOKECOMMANDINFO)&info);
        }
      }
      DestroyMenu(hmenu);
    }
    pcm->Release();
  }
}

첫번째 변화는 WM_CONTEXTMENU 메시지 토론에서 가져온 것이고 HANDLE_WM_CONTEXTMENU 메시지를 수정하는 것입니다.

두번째 변화는 두번째 문제인데, 키보드로 실행한 컨텍스트 메뉴에 대한 특별한 처리에 관한 것입니다. 우리가 키보드로 실행된 컨텍스트 메뉴를 받으면, 우리는 마우스 포인터를 클라이언트 영역의 (0,0) 지역으로 옮깁니다. 이렇게 하면 컨텍스트 메뉴가 정상적인 위치에 출력되도록 할 수 있습니다. ( 만약 우리가 객체를 가진 컨테이너라면, 선택된 하위 객체의 위치에 출력하는 것이 더 나은 선택입니다. )

세번째 변화는 실제적으로 우리가 하려는 주제입니다 : 사용자에게 컨텍스트 메뉴를 출력하고, 결과를 얻고, 결과대로 행동하는 거죠.

저는 여러분이 TrackPopupMenuEx 함수 에 많이 익숙하다고 가정합니다. 여기서 우리는 TPS_RETURNCMD 플래그를 설정해서 우리의 창으로 사용자가 선택한 항목이 WM_COMMAND 메시지로 보내지는 대신 함수의 반환값으로 요구합니다.

SCRATCH_QCM_FIRST 의 값이 0이 아닌 1이라는 사실이 눈여겨볼 점입니다. 만약 이 값이 0이라면, 우리는 사용자가 선택한 항목 0과 사용자가 메뉴를 취소한 것을 구분할 수 없게됩니다.

우리는 사용자가 메뉴에서 항목을 선택했다고 확신하고, CMINVOKECOMMANDEX 구조체 에 두개의 동사 필드에 사용자 선택과 ptInvoke 멤버를 통해 실행 위치를 가르키키도록 구조체를 채웁니다.

알림 : 여러분이 메뉴 ID 를 통해 명령을 실행할 때 여러분은 반드시 항목의 시작점으로부터 상대적인 간격(offset) 을 IContextMenu::QueryContextMenu 에게 넘겨줘야합니다. 이것이 우리가 SCRATCH_QCM_FIRST 값을 빼는 이유입니다.

여러분이 이 프로그램을 실행하면, 제대로 작동하지 않는 몇가지를 발견할 수 있을겁니다. 가장 확실한 것중은 연결 프로그램(Open With) 와 보내기(Send To) 가 작동하지 않을 것이고, 더 많은 미묘한 버그도 있을겁니다. 이에 대해서는 며칠 후 언급할 것입니다.

Published Wednesday, September 22, 2004 7:00 AM by oldnewthing

댓글을 달아 주세요

헬마입니다.

이란은 제가 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

댓글을 달아 주세요

헬마입니다.

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

글의 원문은 이곳입니다.
--------------------------------------------------------------------------------

인터페이스란 것은 계약이지만 이러한 계약은 양쪽 당사자 모두에게 해당된다는 것을 기억해야합니다. 대부분의 시간동안 인터페이스를 읽을 때 계약의 요청자 입장에서 보게되지만, 때때로 수행자 측면에서 계약을 읽는 것이 도움이 된다.

예를 들어, 제어판 어플리케이션을 위한 인터페이스를 보자.

거의 대부분의 시간을 이 문서를 읽는 동안 "난 제어판 어플리케이션을 작성하고 있다구" 라고 중엉거릴 것이다. 자, 예를 들어, 이 문서는 다음과 같이 말하고 있다.

제어판 어플리케이션을 처음으로 불러오는 어플리케이션을 제어할 때, 어플리케이션은 CPlApplet 함수에 대한 주소를 얻고 함수를 호출하고 메시지를 전달하는데 이 주소를 사용한다.

"난 제어판 어플리케이션을 작성하고 있다구" 라고 중엉걸릴때 이 의미는 "이크, CPlApplet 이라고 불리는 함수를 만들고 메시지를 받을 수 있도록 이 함수를 외부에 노출해야겠네" 이다.

하지만, "난 제어판 어플리케이션을 호스팅하구 있어" 라고 중엉거린다면, 이 의미는 " 이크, 어플리케이션의 CPlApplet 함수의 주소를 얻기 위해 GetProcAddress() 를 호출하고 이 함수에 메시지를 전송할 수 있군." 이다.

유사하게, "메시지 처리" 섹션에서 제어 어플리케이션에서 제어판 어플리케이션으로 보내지는 메시지의 목록을 나타내고 있다. 만약 "난 제어판 어플리케이션을 작성하고 있어" 라면 "이크, 이 방법대로 이러한 메시지들을 받을 준비를 해야겠네"이다. 하지만, "난 제어판 어플리케이션을 호스팅하고 있어" 라면 목록에서 이 방법대로 이러한 메시지들을 보내야 겠군" 이다.

  마지막으로, "제어 어플리케이션은 FreeLibrary 함수를 호출하여 제어판 어플리케이션을 해제한다" 에서 "난 제어판 어플리케이션을 작성하고 있어" 라면 "종료에 대비해야겠군" 일테지만, "난 제어판 어플리케이션을 호스팅하고 있어" 라면 "DLL 을 해제하는 바로 그곳이군" 이다.

자 이제 시작해보자. 언제나처럼 우리의 프로그램으로 시작하고 WinMain 을 변경한다:

#include <cpl.h>


int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev,
                   LPSTR lpCmdLine, int nShowCmd)
{
  HWND hwnd;

  g_hinst = hinst;

  if (!InitApp()) return 0;

  if (SUCCEEDED(CoInitialize(NULL))) {/* In case we use COM */

      hwnd = CreateWindow(
          "Scratch",                      /* Class Name */
          "Scratch",                      /* Title */
          WS_OVERLAPPEDWINDOW,            /* Style */
          CW_USEDEFAULT, CW_USEDEFAULT,   /* Position */
          CW_USEDEFAULT, CW_USEDEFAULT,   /* Size */
          NULL,                           /* Parent */
          NULL,                           /* No menu */
          hinst,                          /* Instance */
          0);                             /* No special parameters */

      if (hwnd) {
        TCHAR szPath[MAX_PATH];
        LPTSTR pszLast;
        DWORD cch = SearchPath(NULL, TEXT("access.cpl"),
                     NULL, MAX_PATH, szPath, &pszLast);
        if (cch > 0 && cch < MAX_PATH) {
          RunControlPanel(hwnd, szPath);
      }
    }

    CoUninitialize();
  }

  return 0;
}

창을 나타내고 메시지 루프에 들어가는 대신, 우리는 제어판을 호스팅하는 것처럼 시작할 것이다. 오늘의 우리의 희생양은 (내게 필요한 옵션 제어판인) access.cpl 이다. 경로에서 프로그램을 찾은 후 RunControlPanel 에게 무거운 짐을 수행하도록 요청한다.

void RunControlPanel(HWND hwnd, LPCTSTR pszPath)
{
  // Maybe this control panel application has a custom manifest
  ACTCTX act = { 0 };
  act.cbSize = sizeof(act);
  act.dwFlags = 0;
  act.lpSource = pszPath;
  act.lpResourceName = MAKEINTRESOURCE(123);
  HANDLE hctx = CreateActCtx(&act);
  ULONG_PTR ulCookie;
  if (hctx == INVALID_HANDLE_VALUE ||
      ActivateActCtx(hctx, &ulCookie)) {


    HINSTANCE hinstCPL = LoadLibrary(pszPath);
    if (hinstCPL) {
      APPLET_PROC pfnCPlApplet = (APPLET_PROC)
        GetProcAddress(hinstCPL, "CPlApplet");
      if (pfnCPlApplet) {
        if (pfnCPlApplet(hwnd, CPL_INIT, 0, 0)) {
          int cApplets = pfnCPlApplet(hwnd, CPL_GETCOUNT, 0, 0);
          //  We're going to run application zero
          //  (In real life we might show the user a list of them
          //  and let them pick one)
          if (cApplets > 0) {
            CPLINFO cpli;
            pfnCPlApplet(hwnd, CPL_INQUIRE, 0, (LPARAM)&cpli);
            pfnCPlApplet(hwnd, CPL_DBLCLK, 0, cpli.lData);
            pfnCPlApplet(hwnd, CPL_STOP, 0, cpli.lData);
          }
        }
        pfnCPlApplet(hwnd, CPL_EXIT, 0, 0);
      }

      FreeLibrary(hinstCPL);
    }

    if (hctx != INVALID_HANDLE_VALUE) {
      DeactivateActCtx(0, ulCookie);
      ReleaseActCtx(hctx);
    }

  }
}

일단 빨간 줄로 된것은 무시하자, 이에 대해선 후에 다시 얘기할 것이다.

우리가 한 것은 모두 호스트 측면에서 사양을 읽어 구현한 것이다. 그래서 라이브러리를 불러오고, 진입점을 찾은 후 CPL_INIT 로 호출한 다음에 CPL_GETCOUNT 로 함수를 호출했다. 만약 CPL 파일 안에 어떠한 제어판 어플리케이션이 들어있다면, 우리는 첫번째 것을 물어볼 수 있고, 어플리케이션을 더블 클릭하고 멈추게 한다. 결국 모든 일이 끝나면, 우리는 호스트 규칙에 따라 정리할 수 있으것이다. ( 일반적으로, CPL_EXIT 라는 메시지를 보냄으로써 ).

자 이제 빨간 부분을 제외한 모든 부분이 끝났다. 그럼 저 부분은 무엇인가?

빨간 부분은 사용자 메니페스트(manifest) 를 가지고 있는 제어판 어플리케이션을 지원하기위한 것이다. 이 부분은 윈도 XP에서 새로워진 것이고 MSDN의 이 부분에서 설명하고 있다.

만약 "RunDll32.exe 에 의해 실행되는 제어판 또는 DLL 에서 ComCtl32 버전 6 사용하기" 섹션을 내려받는다면 자원 번호 123 으로 취급되는 메니페스트를 첨부함으로써 제어판 호스트 측에 메니페스트를 제공할 수 있다는 것을 알 수 있다. 자, 이제 붉은 코드가 무슨 일을 하는지 보자 : 이 코드는 메니페스트를 불러와서 활성화시킨 후 제어판 어플리케이션이 메니페스트 활성화된 채로 동작할 수 있도록 불러오고 후에 자원을 해제한다. 만약 메니페스트가 없다면, CreateActCtx 는 INVALID_HANDLE_VALUE 를 반환한다. 우리는 이러한 오류를 취급하지 않았다 왜냐하면 아직 많은 프로그램이 메니페스트를 제공하지 않기 때문이다.

Published Friday, December 26, 2003 8:39 AM by oldnewthing

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

영어 공부 차원에서 하는 것이라 매우 미흡합니다. 오역등이 있다면 바로 바로 지적해주세요.

댓글을 달아 주세요

  1. BlogIcon toko jaket online 2012.11.08 11:36 Address Modify/Delete Reply

    당신의 작문 능력과 더불어 웹 로그에 대한 레이아웃과 정말 영감을 함께 느낀다. 이 보상 주제뿐만 아니라 그것을 스스로를 수정 아세요? 어쨌든 우수한 우수한 기록을 유지, 그것은 모양과 같은 환상적인 블로그 오늘 취할 드문 것 ..

  2. BlogIcon sweater 2012.12.10 19:05 Address Modify/Delete Reply

    이 주제 재료 내부에 개발 된 것으로 나타납니다 모든 일들과 함께 모든 관점은 일반적으로 상대적으로 새로 고침하는 중입니다. 그럼에도 불구하고, 내가 뭐라 구요,하지만 전체 제안에 더 힘을 실어주지 마, 모든이 적은 것도을 자극하지. 당신의 설명은 실제로 완전히 정당하지 현실에서 정말 일반적으로 포인트 완전히 특정하여 자기 없습니다 것을 나에게 보인다. 어떤 경우에는 내가 좋아하는 읽어 않았습니다.