EternalWindows
シェル拡張 / プロパティシート ハンドラ
サンプルはこちら

プロパティシート ハンドラは、特定の拡張子を持ったファイルのプロパティダイアログに、 独自のプロパティシートを追加したい場合に使用します。 たとえば次の図の"MyPage"は、独自に追加されたプロパティシートです。

プロパティシート ハンドラのオブジェクトは、IShellPropSheetExtとIShellExtInitを実装する必要があります。 IShellPropSheetExtはダイアログにプロパティシートを追加するために使用し、 IShellExtInitはハンドラを初期化するために使用します。

class CShellPropSheetExt : public IShellPropSheetExt, public IShellExtInit
{
public:
	STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject);
	STDMETHODIMP_(ULONG) AddRef();
	STDMETHODIMP_(ULONG) Release();

	STDMETHODIMP AddPages(LPFNADDPROPSHEETPAGE pfnAddPage, LPARAM lParam);
	STDMETHODIMP ReplacePage(UINT uPageID, LPFNADDPROPSHEETPAGE pfnReplacePage, LPARAM lParam);

	STDMETHODIMP Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID);

	CShellPropSheetExt();
	~CShellPropSheetExt();

private:
	LONG  m_cRef;
	TCHAR m_szFilePath[MAX_PATH];
};

AddPagesからReplacePageまでがIShellPropSheetExtのメソッドであり、 InitializeがIShellExtInitのメソッドになります。 ReplacePageは、コントールパネルオブジェクトのプロパティシートを書き換える場合に使用するため、 通常は呼ばれることはありません。

Initializeは、プロパティダイアログが表示される段階になると呼ばれます。 ここでは、プロパティの表示対象となっているファイルを特定することになります。

STDMETHODIMP CShellPropSheetExt::Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID)
{
	HRESULT   hr;
	FORMATETC formatetc;
	STGMEDIUM medium;
	LPTSTR    lpszFilePath;

	formatetc.cfFormat = RegisterClipboardFormat(CFSTR_FILENAME);
	formatetc.ptd      = NULL;
	formatetc.dwAspect = DVASPECT_CONTENT;
	formatetc.lindex   = -1;
	formatetc.tymed    = TYMED_HGLOBAL;

	hr = pdtobj->GetData(&formatetc, &medium);
	if (FAILED(hr))
		return E_FAIL;

	lpszFilePath = (LPTSTR)GlobalLock(medium.hGlobal);
	lstrcpy(m_szFilePath, lpszFilePath);
	GlobalUnlock(medium.hGlobal);

	ReleaseStgMedium(&medium);

	return S_OK;
}

第2引数のIDataObjectを使用すれば、データを任意のフォーマットで取得できます。 FORMATETC構造体は、データをどのようなフォーマットで要求するかを格納し、 cfFormatにRegisterClipboardFormat(CFSTR_FILENAME)の戻り値を指定すれば、 ファイルパスを取得できるようになります。 tymedにTYMED_HGLOBALを指定していることから、データはグローバルメモリとして返されるため、 medium.hGlobalをGlobalLockに指定すれば、ファイルパスを参照できるようになります。 今回は、このファイルパスをメンバ変数に保存しているだけですが、 実際の開発ではファイルからデータを読み取って、ダイアログの内容に反映させることになるでしょう。

Initializeの後にはAddPagesが呼ばれることになります。 このメソッドでは、独自のプロパティシートをダイアログに追加することになります。

STDMETHODIMP CShellPropSheetExt::AddPages(LPFNADDPROPSHEETPAGE pfnAddPage, LPARAM lParam)
{
	PROPSHEETPAGE  psp;
	HPROPSHEETPAGE hPage;

	psp.dwSize        = sizeof(PROPSHEETPAGE);
	psp.dwFlags       = PSP_USETITLE | PSP_USECALLBACK | PSP_USEREFPARENT;
	psp.hInstance     = g_hinstDll;
	psp.pszTemplate   = MAKEINTRESOURCE(IDD_DIALOG1);
	psp.pszTitle      = TEXT("MyPage");
	psp.pfnDlgProc    = (DLGPROC)DialogProc;
	psp.lParam        = (LPARAM)this;
	psp.pfnCallback   = PropSheetPageProc;
	psp.pcRefParent   = (PUINT)&g_lLocks;

	hPage = CreatePropertySheetPage(&psp);
	if (hPage == NULL)
		return E_OUTOFMEMORY;
	
	if (!pfnAddPage(hPage, lParam)) {
		DestroyPropertySheetPage(hPage);
		return E_FAIL;
	}

	AddRef();

	return S_OK;
}

プロパティシートを作成するには、PROPSHEETPAGE構造体を初期化してCreatePropertySheetPageを呼び出します。 hInstanceにはDLLのインスタンスハンドルを指定し、pszTemplateにはダイアログリソースのID、 pfnDlgProcはダイアログのプロシージャのアドレスを指定します。 また、dwFlagsに指定している各定数により、 pszTitleにプロパティシートの名前、pfnCallbackにコールバック関数のアドレス、 pcRefParentにDLLのロック数を指定することになります。 プロパティシートのハンドルを取得したらこれをpfnAddPageに指定し、 これが成功した場合はダイアログにプロパティシートが追加されたことになります。

先のInitializeでは、PROPSHEETPAGE.lParamにオブジェクトのアドレスを指定していましたが、 このような場合はAddRefを呼び出して参照カウントを上げておく必要があります。 理由は、AddPagesが制御を返すとオブジェクトのReleaseが呼ばれるからであり、 AddRefを呼び出していないとこの時点でオブジェクトが破棄されてしまうからです。 lParamにオブジェクトのアドレスを指定するということは、 ダイアログプロシージャでもオブジェクトを参照したいということですから、 ダイアログが表示されている間はオブジェクトを破棄させるわけにはいきません。 ダイアログプロシージャは、次のような実装になります。

INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static CShellPropSheetExt *p = NULL;

	switch (uMsg) {

	case WM_INITDIALOG: {
		LPPROPSHEETPAGE lpPage = (LPPROPSHEETPAGE)lParam;		
		p = (CShellPropSheetExt *)lpPage->lParam;
		return TRUE;
	}

	case WM_NOTIFY: {
		LPNMHDR lpNmhdr = (LPNMHDR)lParam;

		if (lpNmhdr->code == PSN_APPLY)
			MessageBox(NULL, TEXT("OKボタンが押されました。"), TEXT("OK"), MB_OK);
		else if (lpNmhdr->code == PSN_RESET)
			MessageBox(NULL, TEXT("ダイアログが閉じられました。"), TEXT("OK"), MB_OK);
		else
			;
		break;
	}

	default:
		break;

	}

	return FALSE;
}

プロパティダイアログで独自のプロパティシートを選択した場合は、 ダイアログプロシージャにWM_INITDIALOGが送られます。 通常はここで、コントロールの初期値を設定したりするでしょう。 また、lParamにはPROPSHEETPAGE構造体のアドレスが格納されており、 その中身はAddPagesで初期化したPROPSHEETPAGE構造体の同様になっています。 よって、lParamを通じてオブジェクトのアドレスを取得することができます。 ダイアログプロシージャで処理する他のメッセージとしては、 コントロールからの通知を受け取るためのWM_COMMANDが挙げられるでしょう。 このメッセージでLOWORD(wParam)を参照すれば、通知を行ったコントロールのIDを特定できます。 また、上記しているようにWM_NOTIFYを処理すれば、 ダイアログが閉じられるタイミングを特定できます。

ダイアログプロシージャにオブジェクトのアドレスを渡す場合は、 オブジェクトの参照カウントを下げる処理もDLLが行うことになります。 このタイミングは、PROPSHEETPAGE.pfnCallbackに指定したコールバック関数で通知されます。

UINT CALLBACK PropSheetPageProc(HWND hwnd, UINT uMsg, LPPROPSHEETPAGE ppsp)
{
	if (uMsg == PSPCB_RELEASE) {
		CShellPropSheetExt *p = (CShellPropSheetExt *)ppsp->lParam;
		p->Release();
	}

	return 1;
}

uMsgがPSPCB_RELEASEである場合に、オブジェクトのReleaseを呼び出します。 これにより、ダイアログの表示が終了した段階でオブジェクトが破棄され、 DLLはアンロードされることになります。

プロパティシート ハンドラを登録するには、次に示すレジストリキーを作成することになります。

HKEY_CLASSES_ROOT
 <特定の拡張子> (既定) = オブジェクトのProgID

HKEY_CLASSES_ROOT
  <オブジェクトのProgID>
    shellex
      PropertySheetHandlers
        <独自のハンドラ名> (既定) = オブジェクトのCLSID

プロパティシート ハンドラのDLLは、プロパティダイアログを表示する段階になるとシェルにロードされ、 ダイアログの表示が終了するとアンロードされます。


戻る