EternalWindows
シェル拡張 / プレビュー ハンドラ
サンプルはこちら

プレビュー ハンドラは、特定の拡張子を持ったファイルのプレビューを表示する場合に使用します。 エクスプローラーでプレビューを表示するには、整理/レイアウト/からプレビューペインを選択します。

プレビュー ハンドラのオブジェクトは、次に示す4つのインターフェースを実装する必要があります。

class CPreviewHandler : public IPreviewHandler, public IObjectWithSite, public IOleWindow, public IInitializeWithStream
{
public:
	STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject);
	STDMETHODIMP_(ULONG) AddRef();
	STDMETHODIMP_(ULONG) Release();
	
	STDMETHODIMP SetWindow(HWND hwnd, const RECT *prc);
	STDMETHODIMP SetRect(const RECT *prc);
	STDMETHODIMP DoPreview(VOID);
	STDMETHODIMP Unload(VOID);
	STDMETHODIMP SetFocus(VOID);
	STDMETHODIMP QueryFocus(HWND *phwnd);
	STDMETHODIMP TranslateAccelerator(MSG *pmsg);

	STDMETHODIMP SetSite(IUnknown *pUnkSite);
	STDMETHODIMP GetSite(REFIID riid, void **ppvSite);

	STDMETHODIMP GetWindow(HWND *phwnd);
	STDMETHODIMP ContextSensitiveHelp(BOOL fEnterMode);

	STDMETHODIMP Initialize(IStream *pstream, DWORD grfMode);

	CPreviewHandler();
	~CPreviewHandler();

private:
	LONG                 m_cRef;
	HWND                 m_hwndParent;
	HWND                 m_hwndPreview;
	RECT                 m_rc;
	IUnknown             *m_pSite;
	IPreviewHandlerFrame *m_pFrame;	
};

class CClassFactory : public IClassFactory
{
public:
	STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject);
	STDMETHODIMP_(ULONG) AddRef();
	STDMETHODIMP_(ULONG) Release();
	
	STDMETHODIMP CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppvObject);
	STDMETHODIMP LockServer(BOOL fLock);
};

extern void LockModule(BOOL bLock);
extern BOOL CreateRegistryKey(HKEY hKeyRoot, LPTSTR lpszKey, LPTSTR lpszValue, LPTSTR lpszData);

SetWindowからTranslateAcceleratorまでがIPreviewHandlerのメソッド、SetSiteとGetSiteがIObjectWithSiteのメソッド、 GetWindowとContextSensitiveHelpがIOleWindowのメソッド、InitializeがIInitializeWithStreamのメソッドになります。 IInitializeWithStreamの代わりに、IInitializeWithFileやIInitializeWithItemを実装することもできそうですが、 これらのIIDはQueryInterfaceに送られてきません。

プレビュー ハンドラが実装することになるメソッド数は非常に多いため、 まずはメソッドが呼ばれる順番を確認してみましょう。 プレビューウインドウが表示されるまでの順番は次の通りです。

Initialize
SetSite
SetWindow
DoPreview
SetRect(初回のみ)

DoPreviewという関数が非常に重要で、ここでプレビューウインドウを作成することになります。 ただし、このプレビューウインドウはフォルダの子ウインドウという位置づけですから、 これを作成するには親ウインドウのハンドルが事前に必要となります。 この親ウインドウのハンドルは、SetWindowから受け取ることになります。

STDMETHODIMP CPreviewHandler::SetWindow(HWND hwnd, const RECT *prc)
{
	m_hwndParent = hwnd;
	m_rc = *prc;

	if (m_hwndPreview != NULL) {
		SetParent(m_hwndPreview, m_hwndParent);
		SetWindowPos(m_hwndPreview, NULL, m_rc.left, m_rc.top, m_rc.right - m_rc.left, m_rc.bottom - m_rc.top, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
	}

	return S_OK;
}

hwndに親ウインドウのハンドルが格納されています。 よって、これをDoPreviewで参照できるようにメンバ変数へ保存しておきます。 prcには、プレビューウインドウとして適切なサイズが格納されているため、 これもDoPreviewで参照できるようにメンバ変数へ保存しておきます。 通常、SetWindowの時点ではプレビューウインドウが作成されていないため、 後続のif文が真になることはありませんが、 もし真になった場合はプレビューウインドウの親ウインドウをhwndに設定します。 そして、prcを基にウインドウのサイズを設定しなければならないようです。

DoPreviewでは、先にも述べたようにプレビューウインドウを作成することになります。

STDMETHODIMP CPreviewHandler::DoPreview(VOID)
{
	if (m_hwndPreview != NULL)
		return E_FAIL;
	
	m_hwndPreview = CreateWindowEx(0, TEXT("EDIT"), TEXT("Preview Data"), WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL, m_rc.left, m_rc.top, m_rc.right - m_rc.left, m_rc.bottom - m_rc.top, m_hwndParent, 0, 0, NULL);
	
	if (m_hwndPreview == NULL)
		return HRESULT_FROM_WIN32(GetLastError());

	return S_OK;
}

プレビューウインドウは子ウインドウとして作成されますから、 第4引数にはWS_CHILDとWS_VISIBLEを指定することになります。 また、第9引数には親ウインドウのハンドルを指定します。 第3引数は実際にコントロールに表示される文字列になりますから、 実際の開発ではファイルから読み取った文字列を指定することになります。 第2引数のウインドウクラスについては基本的に自由ですが、 今回はエディットコントロールを示すEDITを指定しています。 この場合は、ES_MULTILINEで複数行入力を可能にし、 ES_WANTRETURNで改行を可能にしておくべきでしょう。 文字の入力をさせたくない場合は、これらの代わりにES_READONLYを指定するようにします。

初めてプレビューウインドウを表示した場合は、DoPreviewの後にSetRectが呼ばれることになります。 これは、初回時におけるSetWindowのprcが0初期化されており、 正しい幅と高さが格納されていないのが原因であると思われます。 よって、SetRectでウインドウサイズを正しく設定することになります。

STDMETHODIMP CPreviewHandler::SetRect(const RECT *prc)
{
	m_rc = *prc;
	
	if (m_hwndPreview != NULL)
		SetWindowPos(m_hwndPreview, NULL, m_rc.left, m_rc.top, m_rc.right - m_rc.left, m_rc.bottom - m_rc.top, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
	
	return S_OK;
}

prcにプレビューウインドウの新しいサイズが格納されているため、これをメンバ変数に保存しておきます。 プレビューウインドウは既に作成されているはずですからif文は真になり、 SetWindowPosによってプレビューウインドウのサイズが設定されることになります。 ちなみに、SetRectは親ウインドウを最大化した場合などにも呼ばれます。

エディットコントロールのようなフォーカス持つウインドウを作成した場合、 フォーカスが割り当てられている状態でキーを押すと、TranslateAcceleratorが呼ばれます。

STDMETHODIMP CPreviewHandler::TranslateAccelerator(MSG *pmsg)
{
	HRESULT hr = S_FALSE;
	
	if (m_pFrame != NULL)
		hr = m_pFrame->TranslateAccelerator(pmsg);

	return hr;
}

m_pFrameの型はIPreviewHandlerFrameであり、これはSetSiteで取得することができます。 IPreviewHandlerFrame::TranslateAcceleratorを呼び出すというのは、 押下されたキーを親ウインドウに処理させることを意味しており、 たとえばBackキーを押した場合はフォルダの階層が変化することになります。 こうした動作が不要な場合は、IPreviewHandlerFrame::TranslateAcceleratorを呼び出さずにS_FALSEを返します。

TranslateAcceleratorでIPreviewHandlerFrame::TranslateAcceleratorを呼び出すと、 QueryFocusが呼ばれることになります。 このメソッドは、フォルダ上のファイルでキーを押した場合にも呼ばれます。

STDMETHODIMP CPreviewHandler::QueryFocus(HWND *phwnd)
{
	HRESULT hr = E_INVALIDARG;
	
	if (phwnd != NULL) {
		*phwnd = GetFocus();
		if (*phwnd != NULL)
			hr = S_OK;
		else
			hr = HRESULT_FROM_WIN32(GetLastError());
	}

	return hr;
}

QueryFocusでは、現在フォーカスを持っているウインドウのハンドルを第1引数に格納することになっています。 よって、GetFocusの戻り値を受け取るだけで構いません。

フォルダ上でTabキーを押して、プレビューウインドウにフォーカスが割り当てられようとするとSetFocusが呼ばれます。

STDMETHODIMP CPreviewHandler::SetFocus(VOID)
{
	if (m_hwndPreview == NULL)
		return S_FALSE;
	
	::SetFocus(m_hwndPreview);

	return S_OK;
}

SetFocusでは、Windows APIのSetFocusを呼び出すだけで問題ありません。

プレビューの対象となっているファイルから別のファイルに切り替えた場合や、 フォルダを閉じた場合はUnloadが呼ばれることになります。

STDMETHODIMP CPreviewHandler::Unload(VOID)
{
	if (m_hwndPreview != NULL) {
		DestroyWindow(m_hwndPreview);
		m_hwndPreview = NULL;
	}

	return S_OK;
}

Unloadでは、プレビューウインドウをDestroyWindowで破棄することになります。 また、他のメソッドにてウインドウが破棄されていることを特定できるように、m_hwndPreviewにNULLを格納しておきます。 Unloadが制御を返しても、必ずDLLがアンロードされるとは限らないことに注意してください。

IObjectWithSiteのメソッドは、次のように実装することになります。

STDMETHODIMP CPreviewHandler::SetSite(IUnknown *pUnkSite)
{
	HRESULT hr;

	if (m_pSite != NULL) {
		m_pSite->Release();
		m_pSite = NULL;
	}

	m_pSite = pUnkSite;

	hr = m_pSite->QueryInterface(IID_PPV_ARGS(&m_pFrame));
	
	return hr;
}

STDMETHODIMP CPreviewHandler::GetSite(REFIID riid, void **ppvSite)
{
	*ppvSite = m_pSite;

	return S_OK;
}

SetSiteは、prevhost.exeがプレビューハンドラにIUnknownのポインタを渡すために呼ばれます。 このIUnknownで識別されるオブジェクトはIPreviewHandlerFrameを実装しているため、 これをQueryInterfaceで取得してメンバ変数に保存しておきます。 また、IUnknownのポインタもGetSiteで返せるように保存しておきます。

IOleWindowのメソッドは、次のように実装することになります。

STDMETHODIMP CPreviewHandler::GetWindow(HWND *phwnd)
{
	if (phwnd == NULL)
		return E_INVALIDARG;

	*phwnd = m_hwndParent;

	return S_OK;
}

STDMETHODIMP CPreviewHandler::ContextSensitiveHelp(BOOL fEnterMode)
{
	return E_NOTIMPL;
}

GetWindowがどのようなタイミングで呼ばれるかは分かりませんが、 通常はプレビューウインドウの親ウインドウのハンドルを返すようにします。 ContextSensitiveHelpは、実装が必須でないためE_NOTIMPLを返すだけで構いません。

IInitializeWithStreamのメソッドは、Initializeのみです。

STDMETHODIMP CPreviewHandler::Initialize(IStream *pstream, DWORD grfMode)
{
	return S_OK;
}

今回はS_OKを返しているだけですが、 実際の開発ではpstreamのReadを呼び出すことになるでしょう。 これにより、プレビューの対象となっているファイルからデータを取得できますから、 これをプレビューウインドウのタイトルとして設定することができます。

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

HKEY_CLASSES_ROOT
  CLSID
    <オブジェクトのCLSID> AppID = {6d2b5079-2f0b-48dd-ab7f-97cec514d30b}

HKEY_CLASSES_ROOT
  <特定の拡張子>
    shellex
      {8895b1c6-b41f-4c1c-a562-0d564250836f} (既定) = オブジェクトのCLSID

HKEY_LOCAL_MACHINE
  SOFTWARE
    Microsoft
      Windows
        CurrentVersion
          PreviewHandlers オブジェクトのCLSID = 独自のハンドラ名

プレビュー ハンドラのDLLは、ファイルがプレビュー表示される際にprevhost.exeによってロードされ、 フォルダを閉じた際にアンロードされます。 DLLが認識されるためには、登録後にシェルを再起動する必要があります。 AppIDというエントリを作成するのが珍しい処理ですが、これは必須となっています。 PreviewHandlersキーに対して行うのは、オブジェクトのCLSIDを持ったサブキーを作成することではなく、 オブジェクトのCLSIDを持ったエントリの作成です。 よって、アンインストール時に呼び出すのは、SHDeleteKeyではなくSHDeleteValueになります。 間違っても、PreviewHandlersキーに対してSHDeleteKeyを実行してはいけません。


戻る