Windows 7 リボンフレームワークの概要とスケルトンプログラムの作成
リボンフレームワークとは何か?
Windows リボンフレームワーク (Windows Ribbon Framework) は一言で言えば、 従来の Windows のメニュー、ツールバー、タスクペインなどを置き換える、 新しいコマンドフレームワークといえます。と、いってもなんだかわかりにくいと思いますので、実際に見てみるとこうなります。
従来の (Windows Vista 以前の)「ペイント」は次のようなユーザーインターフェイスでした。
Windows Vista 以前の従来の UI のペイント
この従来形は、Windows ユーザーにはお馴染みだと思います。Windows 7 からはペイントの UI も大きく変わり、 次のように変更されています。
Windows 7 からの新しいペイント
リボンフレームワークでは、新しいペイントの UI に見られるような タブ切り替え的なメニュー、ツールバーのコンビネーションを簡単に実装できるようになります。
実装方法 ~ やや面倒・・・ XAML、COM、Windows API・・・
上で「簡単に実装できる」と書きましたが、この美しい(?)見栄えから考えれば十分「簡単に」と言えると思いますが、 実際のところ、XAML、COM プログラミング、Windows API プログラミングの併せ技になりますので、少々面倒くさいといえば面倒くさいです。 いったん枠組みを理解してしまえばよいと思いますので、そこまで頑張ってください。
この資料では、Windows リボンフレームワークの骨子の部分を理解し、単純な枠組みを作るすることを目標としています。
開発環境準備
Windows リボンフレームワークは Windows Vista SP2 以降に導入されています。開発環境としては、Windows 7 SDK が必要です。 マイクロソフトのダウンロードセンターから、Windows 7 SDK (SDK v7.0) 以降をダウンロードしてインストールしてください。
それから、私がちょっと手間取ってしまったのは、開発環境のバージョンの構成です。
Windows SDK Configuration Tool というツールが付属しているのですが、私はこれは Visual Studio と統合するときだけのものかと思っていました。 しかし今回私の環境ではコマンドプロンプトからビルドするときでも、このツールを使って SDK バージョンを v7.0 としておかないと、UIRibbon.h など、 Windows 7 特有のヘッダファイルが読み込まれませんでした。私の環境の問題なのか何なのか良くわかりませんが、もしヘッダファイルが読み込まれない場合などは、 このツールで SDK バージョンを再構成してみてください。
必要な環境はたったこれだけです。さっそく、単純なスケルトンコードを作ってみましょう。
UI の定義 ~ マークアップバイナリの作成と取り込み
リボンは、XAML (Extensible Application Markup Language, ザムル) で UI を定義します。 XAML をリボンマークアップコンパイラ (UICC.EXE) でコンパイルして、リボンのマークアップバイナリ (*.bml) を作成します。
例えば ribbonapp.xml という XAML ファイルをコンパイルして、マークアップバイナリ ribbonapp.bml、リソーススクリプト ribbonapp.rc、 ヘッダファイル ids.h を生成する場合は、次のコマンドを実行します。
> UICC RibbonApp.xml RibbonApp.bml /header:ids.h /res:RibbonApp.rc
マークアップバイナリはリソーススクリプト (*.rc) から、UIFILE として取り込みます。
bml と rc を作れれば通常通りリソースコンパイラで、リソースファイル (*.res) を作れば OK です。
リボンフレームワークのスケルトンコード
ここでは非常に単純なスケルトンコードとして、次のようなウィンドウを表示してみましょう。
XAML をリボンマークアップコンパイラで処理する
まず XAML ファイルを用意します。次のコードを RibbonApp.xml として保存します。
<?xml version='1.0' encoding='utf-8'?> <Application xmlns="http://schemas.microsoft.com/windows/2009/Ribbon"> <Application.Commands> <Command Name="cmdExit" LabelTitle="Exit application" /> </Application.Commands> <Application.Views> <Ribbon> <Ribbon.Tabs> <Tab> <Group> <Button CommandName="cmdExit" /> </Group> </Tab> </Ribbon.Tabs> </Ribbon> </Application.Views> </Application>
この XAML を次のコマンドで処理します。
> UICC RibbonApp.xml RibbonApp.bml /header:ids.h /res:RibbonApp.rc
いくつか警告がでますが、ここでは無視して OK です。正常終了すれば、RibbonApp.bml、ids.h、RibbonApp.rc が生成されているはずです。
スケルトンの実装
ヘッダーファイル RibbonApp.h は次の通りです。
#pragma once #include <UIRibbon.h> #include <UIRibbonPropertyHelpers.h> #include "ids.h" ///////////////////////////////////////////////////////////////////////////// LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); BOOL InitializeFramework(HWND hWnd); void DestroyFramework(); ///////////////////////////////////////////////////////////////////////////// class CCommandHandler : public IUICommandHandler { public: static HRESULT CreateInstance(IUICommandHandler **ppCommandHandler); // IUnknown STDMETHODIMP_(ULONG) AddRef(); STDMETHODIMP_(ULONG) Release(); STDMETHODIMP QueryInterface(REFIID iid, void** ppv); // IUICommandHandler STDMETHOD(UpdateProperty)( UINT nCmdID, REFPROPERTYKEY key, const PROPVARIANT* ppropvarCurrentValue, PROPVARIANT* ppropvarNewValue ); STDMETHOD(Execute)( UINT nCmdID, UI_EXECUTIONVERB verb, const PROPERTYKEY* key, const PROPVARIANT* ppropvarValue, IUISimplePropertySet* pCommandExecutionProperties ); private: CCommandHandler() : m_cRef(1) { } LONG m_cRef; }; ///////////////////////////////////////////////////////////////////////////// class CApplication : public IUIApplication { public: static HRESULT CreateInstance(IUIApplication **ppApplication); // IUnknown STDMETHOD_(ULONG, AddRef()); STDMETHOD_(ULONG, Release()); STDMETHOD(QueryInterface(REFIID iid, void** ppv)); // IUIApplication STDMETHOD(OnCreateUICommand)( UINT nCmdID, UI_COMMANDTYPE typeID, IUICommandHandler** ppCommandHandler); STDMETHOD(OnViewChanged)( UINT viewId, UI_VIEWTYPE typeId, IUnknown* pView, UI_VIEWVERB verb, INT uReasonCode); STDMETHOD(OnDestroyUICommand)( UINT32 commandId, UI_COMMANDTYPE typeID, IUICommandHandler* commandHandler); private: CApplication() : m_cRef(1), m_pCommandHandler(NULL) { } ~CApplication() { if (m_pCommandHandler) { m_pCommandHandler->Release(); m_pCommandHandler = NULL; } } LONG m_cRef; IUICommandHandler * m_pCommandHandler; };
実装部 RibbonApp.cpp は次の通りです。
#include "RibbonApp.h" TCHAR g_lpszClassName[] = TEXT("_Skelton_"); HWND g_hWnd = NULL; IUIFramework *g_pFramework = NULL; IUIApplication *g_pApplication = NULL; ////////////////////////////////////////////////////////////////////////// int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { HRESULT hr = CoInitialize(NULL); if (FAILED(hr)) { return FALSE; } WNDCLASSEX wcl; wcl.cbSize = sizeof(WNDCLASSEX); wcl.hInstance = hInstance; wcl.lpszClassName = g_lpszClassName; wcl.lpfnWndProc = WndProc; wcl.style = NULL; wcl.hIcon = LoadIcon(NULL, IDI_APPLICATION); wcl.hIconSm = NULL; wcl.hCursor = LoadCursor(NULL, IDC_ARROW); wcl.lpszMenuName = NULL; wcl.cbClsExtra = 0; wcl.cbWndExtra = 0; wcl.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH); if(!RegisterClassEx(&wcl)) { return FALSE; } g_hWnd = CreateWindow( g_lpszClassName, TEXT("Window Title"), WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL); if(!g_hWnd) { return FALSE; } ShowWindow( g_hWnd, nShowCmd); UpdateWindow( g_hWnd ); MSG Msg; while(GetMessage(&Msg, NULL, 0, 0)) { TranslateMessage(&Msg); DispatchMessage(&Msg); } CoUninitialize(); return Msg.wParam; } ////////////////////////////////////////////////////////////////////////// LRESULT CALLBACK WndProc( HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; switch(Msg) { case WM_CREATE: if( !InitializeFramework( hWnd ) ) { return -1; } break; case WM_DESTROY: DestroyFramework(); PostQuitMessage(0); break; case WM_PAINT: hdc = BeginPaint (hWnd, &ps); EndPaint( hWnd, &ps ); default: return DefWindowProc(hWnd, Msg, wParam, lParam); } return FALSE; } ///////////////////////////////////////////////////////////////////////////// BOOL InitializeFramework(HWND hWnd) { HRESULT hr = CoCreateInstance( CLSID_UIRibbonFramework, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&g_pFramework) ); if (FAILED(hr)) { return FALSE; } hr = CApplication::CreateInstance(&g_pApplication); if (FAILED(hr)) { return FALSE; } hr = g_pFramework->Initialize(hWnd, g_pApplication); if (FAILED(hr)){ return FALSE; } hr = g_pFramework->LoadUI( GetModuleHandle(NULL), TEXT("APPLICATION_RIBBON")); if (FAILED(hr)){ return FALSE; } return TRUE; } ///////////////////////////////////////////////////////////////////////////// void DestroyFramework() { if (g_pFramework) { g_pFramework->Destroy(); g_pFramework->Release(); g_pFramework = NULL; } if (g_pApplication) { g_pApplication->Release(); g_pApplication = NULL; } } ///////////////////////////////////////////////////////////////////////////// HRESULT CApplication::CreateInstance( IUIApplication **ppApplication ) { if (!ppApplication) { return E_POINTER; } *ppApplication = NULL; HRESULT hr = S_OK; CApplication* pApplication = new CApplication(); if ( pApplication != NULL ) { *ppApplication = static_cast<IUIApplication *>(pApplication); } else { hr = E_OUTOFMEMORY; } return hr; } ///////////////////////////////////////////////////////////////////////////// STDMETHODIMP_(ULONG) CApplication::AddRef() { return InterlockedIncrement(&m_cRef); } ///////////////////////////////////////////////////////////////////////////// STDMETHODIMP_(ULONG) CApplication::Release() { LONG cRef = InterlockedDecrement(&m_cRef); if (cRef == 0) { delete this; } return cRef; } ///////////////////////////////////////////////////////////////////////////// STDMETHODIMP CApplication::QueryInterface(REFIID iid, void** ppv) { if (iid == __uuidof(IUnknown)) { *ppv = static_cast<IUnknown*>(this); } else if (iid == __uuidof(IUIApplication)) { *ppv = static_cast<IUIApplication*>(this); } else { *ppv = NULL; return E_NOINTERFACE; } AddRef(); return S_OK; } ///////////////////////////////////////////////////////////////////////////// STDMETHODIMP CApplication::OnCreateUICommand( UINT nCmdID, UI_COMMANDTYPE typeID, IUICommandHandler** ppCommandHandler ) { if (NULL == m_pCommandHandler) { HRESULT hr = CCommandHandler::CreateInstance(&m_pCommandHandler); if (FAILED(hr)) { return hr; } } return m_pCommandHandler->QueryInterface(IID_PPV_ARGS(ppCommandHandler)); } ///////////////////////////////////////////////////////////////////////////// STDMETHODIMP CApplication::OnViewChanged( UINT viewId, UI_VIEWTYPE typeId, IUnknown* pView, UI_VIEWVERB verb, INT uReasonCode ) { HRESULT hr = E_NOTIMPL; if (UI_VIEWTYPE_RIBBON == typeId) { switch (verb) { case UI_VIEWVERB_CREATE: hr = S_OK; break; case UI_VIEWVERB_SIZE: { IUIRibbon* pRibbon = NULL; UINT uRibbonHeight; hr = pView->QueryInterface(IID_PPV_ARGS(&pRibbon)); if (SUCCEEDED(hr)) { hr = pRibbon->GetHeight(&uRibbonHeight); pRibbon->Release(); } } break; case UI_VIEWVERB_DESTROY: hr = S_OK; break; } } return hr; } ///////////////////////////////////////////////////////////////////////////// STDMETHODIMP CApplication::OnDestroyUICommand ( UINT32 nCmdID, UI_COMMANDTYPE typeID, IUICommandHandler* commandHandler ) { return E_NOTIMPL; } ///////////////////////////////////////////////////////////////////////////// HRESULT CCommandHandler::CreateInstance( IUICommandHandler **ppCommandHandler ){ if (!ppCommandHandler) { return E_POINTER; } *ppCommandHandler = NULL; HRESULT hr = S_OK; CCommandHandler* pCommandHandler = new CCommandHandler(); if (pCommandHandler != NULL) { *ppCommandHandler = static_cast<IUICommandHandler *>(pCommandHandler); } else { hr = E_OUTOFMEMORY; } return hr; } ///////////////////////////////////////////////////////////////////////////// STDMETHODIMP_(ULONG) CCommandHandler::AddRef() { return InterlockedIncrement(&m_cRef); } ///////////////////////////////////////////////////////////////////////////// STDMETHODIMP_(ULONG) CCommandHandler::Release() { LONG cRef = InterlockedDecrement(&m_cRef); if (cRef == 0) { delete this; } return cRef; } ///////////////////////////////////////////////////////////////////////////// STDMETHODIMP CCommandHandler::QueryInterface(REFIID iid, void** ppv) { if (iid == __uuidof(IUnknown)) { *ppv = static_cast<IUnknown*>(this); } else if (iid == __uuidof(IUICommandHandler)) { *ppv = static_cast<IUICommandHandler*>(this); } else { *ppv = NULL; return E_NOINTERFACE; } AddRef(); return S_OK; } ///////////////////////////////////////////////////////////////////////////// STDMETHODIMP CCommandHandler::UpdateProperty( UINT nCmdID, REFPROPERTYKEY key, const PROPVARIANT* ppropvarCurrentValue, PROPVARIANT* ppropvarNewValue ){ return E_NOTIMPL; } ///////////////////////////////////////////////////////////////////////////// STDMETHODIMP CCommandHandler::Execute( UINT nCmdID, UI_EXECUTIONVERB verb, const PROPERTYKEY* key, const PROPVARIANT* ppropvarValue, IUISimplePropertySet* pCommandExecutionProperties ) { return S_OK; }
makefile は次の通りです。
TARGETNAME=RibbonApp RSC=rc.exe LINK32=link.exe OUTDIR=.\chk ALL : "$(OUTDIR)\$(TARGETNAME).exe" "$(OUTDIR)" : if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" # compile CPP_PROJ=\ /MT\ /W3\ /Fo"$(OUTDIR)\\"\ /Fd"$(OUTDIR)\\"\ /c\ /Zi\ /DWIN32\ /DUNICODE\ /D_UNICODE\ #resource compiler RSC_PROJ=/l 0x411 /fo"$(OUTDIR)\$(TARGETNAME).res" /d "_DEBUG" #link LINK32_FLAGS=\ user32.lib\ gdi32.lib\ ole32.lib\ /subsystem:windows\ /pdb:"$(OUTDIR)\$(TARGETNAME).pdb"\ /out:"$(OUTDIR)\$(TARGETNAME).exe"\ /DEBUG LINK32_OBJS= \ "$(OUTDIR)\$(TARGETNAME).obj"\ "$(OUTDIR)\$(TARGETNAME).res" "$(OUTDIR)\$(TARGETNAME).exe" : "$(OUTDIR)" $(LINK32_OBJS) $(LINK32) $(LINK32_FLAGS) $(LINK32_OBJS) .c{$(OUTDIR)}.obj:: $(CPP) $(CPP_PROJ) $< .cpp{$(OUTDIR)}.obj:: $(CPP) $(CPP_PROJ) $< .rc{$(OUTDIR)}.res:: $(RSC) $(RSC_PROJ) $<
上記を同じディレクトリに保存して、nmake すれば chk サブディレクトリに RibbonApp.exe が作成されるはずです。
尚、上記実装部の内容については、IUIApplication インターフェイスや IUICommandHandler インタフェイスなどのリボンフレームワークに特有の部分もありますが、 特に AddRef や QueryInterface など、COM プログラミングに馴染みのない方にはかなり疑問の残る内容と思いますので、 また別の機会に解説を書こうと思います。
とりあえず、この資料では上記ファイルから上にスクリーンショットを示したような、リボン付きのスケルトンがビルドできたら成功としてください。