では早速、サンプルアプリケーションのソースを公開します。
サンプルアプリケーションの概要は、以下の通り。
・開発環境はVisual Studio2005 VC++ 6.0
・ダイアログ無しベース。画面はMicrosoft Expression Blend 2で作成した、XAML(ざむる)を呼び出す事で実現。
・2つの画面を用意。画面は交互に行き来が可能。
・画面遷移時にデスクトップが見えてしまう事を防ぐ為に、背景用のXAMLを常に表示。
・各画面にボタンを用意し、ボタンクリック、キーアップ、キーダウンを取れるようにしています。
・windows messageのHookにより、100msタイマ、ジェスチャ(フリック等)に対応。
開発中に見つかった不可解な問題と、回避策についても一部言及しています。
ソース全文のダウンロードはこちらから。
http://nagisa.okoshi-yasu.net/blog/Subproject1.zip
まずは定義から。
次にWinMainです。
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
HRESULT retcode;
if (!XamlRuntimeInitialize())
return -1;
if (FAILED(retcode=GetXRApplicationInstance(&g_Mainapp)))
return -1;
if (FAILED(retcode=g_Mainapp->AddResourceModule((HINSTANCE)hInstance)))
return -1;
XRWindowCreateParams wp;
ZeroMemory(&wp, sizeof(XRWindowCreateParams));
wp.Style = WS_BORDER;
wp.pTitle = L"TEST";
wp.Left = 0;
wp.Top = 0;
wp.pHookProc = HookProc;
XRXamlSource xamlsrc;
//背景用スレッド起動
ghBackThreadHandle = CreateThread(NULL, 0, ThreadBackGroundUI, NULL, 0, NULL);
\\不可解な現象その1。通常のwindowsなら以下の方法でマウスカーソルを非表示に出来ますが、CE+Expression Blendでは何故か、画面遷移の瞬間だけカーソルが出てしまいます。これを避けるために、画面サイズギリギリの場所にマウスカーソルを飛ばして、見えなくしています。ただし、画面サイズよりも大きい場所を指定すると、指定が無効になってしまうので、サイズギリギリの800,480にする必要があります。このためよく見ると、右下に1ドットだけカーソルが見えていて、画面遷移時に出たり消えたりしています。ダサい対処法。
while(ShowCursor(FALSE) >= 0); //マウスカーソル消去 これだけでは画面遷移時にカーソルが出てしまう
//マウスカーソルを画面外に飛ばしてしまうことで、非表示とする
RECT recta;
recta.top = recta.bottom = 800;
recta.right = recta.left = 480;
ClipCursor((const RECT *)&recta);
\\ここからが、繰り返し処理。1画面を表示-破棄を繰り返します。
for( ; gPanelNo != -1; ) {
\\XAMLをメモリにロード
xamlsrc.SetFile(gc_PanelFileTBL[gPanelNo]);
if (FAILED(retcode=g_Mainapp->CreateHostFromXaml(&xamlsrc, &wp, &g_UiMainvhost))) {
break;
}
IXRFrameworkElementPtr root;
//ルートエレメントの作成
if (FAILED(retcode=g_UiMainvhost->GetRootElement(&root))) {
break;
}
int i;
EventHandler handler;
//ボタン登録
\\各画面に応じたボタンイベントを作成
for( i = 0; gc_ButtonNameTBL[gPanelNo][i][0] != 0x00; i++) {
//名前からオブジェクト取得
if (FAILED(retcode=root->FindName(gc_ButtonNameTBL[gPanelNo][i], &m_btnControl[i])))
{
continue;
}
//クリックイベント登録
if (FAILED(retcode=CreateDelegate(&handler,&EventHandler::OnClickEvent, &g_ButtonEventTBL[i]))) {
continue;
}
if (FAILED(retcode=m_btnControl[i]->AddMouseLeftButtonDownEventHandler(g_ButtonEventTBL[i])))
{
continue;
}
//キーダウンイベント登録
if (FAILED(retcode=CreateDelegate(&handler,&EventHandler::KeyDownEvent, &g_ButtonDownEventTBL[i]))) {
continue;
}
if (FAILED(retcode=m_btnControl[i]->AddMouseLeftButtonDownEventHandler(g_ButtonDownEventTBL[i])))
{
continue;
}
//キーアップイベント登録
if (FAILED(retcode=CreateDelegate(&handler,&EventHandler::KeyUpEvent, &g_ButtonUpEventTBL[i]))) {
continue;
}
if (FAILED(retcode=m_btnControl[i]->AddMouseLeftButtonUpEventHandler(g_ButtonUpEventTBL[i])))
{
continue;
}
}
\\各画面に応じたテキストブロックイベントを作成。これらを応用すれば、どんなオブジェクトのイベントでも、作れるようになると思います。
//テキストブロック登録
for( i = 0; gc_TextBoxNameTBL[gPanelNo][i][0] != 0x00; i++) {
//名前からオブジェクト取得
if (FAILED(retcode=root->FindName(gc_TextBoxNameTBL[gPanelNo][i], &m_txtblockControl[i])))
{
continue;
}
}
SYSTEMTIME stTime;
GetLocalTime(&stTime);
//初期表示
\\ボタンの非表示、テキストブロックのテキスト更新のサンプルです。
//次画面であれば日時を表示
if(gPanelNo == 1) {
WCHAR wStrW[128];
memset(wStrW,0x00,sizeof(wStrW));
wsprintf(wStrW, L"%04d/%02d/%02d %02d:%02d:%02d", stTime.wYear, stTime.wMonth, stTime.wDay, stTime.wHour, stTime.wMinute, stTime.wSecond );
ControlSetText(1,wStrW);
}
//TOP画面のエンドボタンを奇数秒の場合に非表示
if(gPanelNo == 0 && (stTime.wSecond % 2)) {
BYTE visible = 0; //不可視
ObjectInvisible(1,0,visible);
}
HWND hwnd = NULL;
UINT exitcode;
g_UiMainvhost->GetContainerHWND(&hwnd);
\\常に背景XAMLよりも前に出す必要があるため、前面表示指定します。
//前面表示
ShowWindow(hwnd, SW_SHOW);
//画面表示
\\ここでようやく、XAMLが表示されます。close()が呼ばれるまで、ここに留まります。
if (FAILED(retcode=g_UiMainvhost->StartDialog(&exitcode))) {
break;
}
}
\\ここからは、アプリケーション終了処理。
//背景用スレッドの終了
g_UiBackvhost->EndDialog(0);
CloseHandle(ghBackThreadHandle);
//これを行うと異常終了する ->Release()でも同様
\\不可解な現象その2。お片づけしようとすると、例外で落ちます。ふしぎー。
// g_UiMainvhost.Release();
//生成しているのに、FALSEが返るのは何故?
\\不可解な現象その3。お片づけしようとすると、FALSEが返ります。他のスレッドを作成する直前までであれば、呼ぶとTRUEが返ります。スレッド作ったらあかんの?ふしぎー。
if(XamlRuntimeUninitialize() == FALSE) {
}
//スレッドの終了を待つ
//87(パラメータ異常)が返る理由が分からない
\\不可解な現象その4。タスクの終了を待つと、何故かエラーが返り、lastcodeは87(パラメータ異常)となります。引数は間違ってないはずなんですが、どんなに調べても分からんので無視。
DWORD res = WaitForMultipleObjects(1, (const HANDLE *)ghBackThreadHandle, TRUE, INFINITE);
if(res == (DWORD)-1) {
}
return 0;
}
次にイベント定義。
class EventHandler
{
public:
\\その名の通り、キーダウン、キーアップ、クリックイベント発生時にコールされる関数どもです。
HRESULT KeyDownEvent(IXRDependencyObject* source,XRMouseButtonEventArgs* args)
{
return S_OK;
}
HRESULT KeyUpEvent(IXRDependencyObject* source,XRMouseButtonEventArgs* args)
{
return S_OK;
}
HRESULT OnClickEvent(IXRDependencyObject* source,XRMouseButtonEventArgs* args)
{
BSTR pName;
//クリックボタン名取得
source->GetName(&pName);
//ボタン名との比較
\\ボタン名称との比較で、どのボタンのイベントであるか判断しています。
if (_tcscmp(L"BTN_NEXT", pName) == 0) {
MainPanelChange(1);
}
else if (_tcscmp(L"BTN_BACK", pName) == 0) {
MainPanelChange(0);
}
else if (_tcscmp(L"BTN_END", pName) == 0) {
//終了
gPanelNo = -1;
g_UiMainvhost->EndDialog(0);
}
return S_OK;
}
};
次にwindows messageのフック。
BOOL CALLBACK HookProc(VOID* pv, HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, LRESULT* pRetVal)
{
ULONG ulStyle;
switch(msg)
{
case WM_CREATE:
ulStyle = ::GetWindowLong(hwnd, GWL_STYLE);
ulStyle = ulStyle & ~ulStyle;
::SetWindowLong(hwnd, GWL_STYLE, ulStyle);
\\タイマイベント周期をmsで指定。この例では100ms周期。
g_nTimer = ::SetTimer(hwnd, XLOOPTIMER_ID, 100, NULL);
//ジェスチャの登録
\\これを行うと、ジェスチャのメッセージも飛んでくるようになります。逆に言えば、ジェスチャを受けたくないときはこれを行わなければ、飛んできません。
EnableGestures(hwnd, TGF_GID_ALL,TGF_SCOPE_PROCESS);
//下の処理も実行させるために、break無し
\\周期タイマイベントです。
case WM_TIMER:
if (wParam == XLOOPTIMER_ID) {
}
break;
\\特に用事が無くても、このイベントは拾っておく必要があります。画面遷移時に変更のある部分しか更新されなくなります。
case WM_PAINT: //DefWindowProcに渡すと、画面更新の仕方によっては、変更部分しか画面更新がされなくなる
break;
case WM_DESTROY: // ウィンドウ破棄
PostQuitMessage( 0 );
break;
\\ジェスチャイベントは、これでくくられています。下のコメントにもあるとおり、かなりクセの有るイベントとなっています。スマホのようなサクサク動作を想定すると、かなりガッカリします。
case WM_GESTURE:
GESTUREINFO gi;
gi.cbSize = sizeof(GESTUREINFO);
\\ここで、ジェスチャの種類とポイント(座標)が取得できます。
if (GetGestureInfo(reinterpret_cast<HGESTUREINFO>(lParam), &gi))
{
//動作検証結果
//フリックイベントは、かなり力強くフリックしないと来ない
//軽いフリックの場合、イベントが来たとしても、なぜか終了ポイントが開始と同じ
//フリックイベントを拾うよりも、まず最初に必ず来るパンイベントを拾って、
//そのポイントを追いかけたほうが、結果的に反応が良くなる。
//ただしこの場合、画面上をゆっくり時間をかけて指を動かしても、フリック扱いになってしまう。
//これを避けたいのなら、開始から終了までの時間を取得し、一定時間以上かかっていれば無視するなどの小細工が必要。ああめんどくさい。
switch(wParam){
case GID_DOUBLESELECT: //ダブルタップ
break;
case GID_HOLD: //ホールド
break;
case GID_PAN: //パン
break;
case GID_SELECT: //タップ
break;
case GID_BEGIN: //ジェスチャ開始
break;
case GID_SCROLL: //フリック
break;
case GID_END: //ジェスチャ終了
break;
}
}
//HGESTUREINFOの開放
CloseGestureInfoHandle((HGESTUREINFO)lParam);
break;
\\Expression Blend 2は綺麗な画面と引き換えに、メモリやCPUをバカ食いします。イベントのうち、アプリめがけて来ている情報は、不要でも受け止めておいたほうがいいみたいです。
//このアプリで止めてしまって良い情報は、負荷軽減のために止めておく
//この程度の負荷が、わりと馬鹿にならない。
case WM_SETCURSOR:
break;
case WM_LBUTTONUP:
break;
case WM_LBUTTONDOWN:
break;
case WM_MOUSEMOVE:
break;
default:
return DefWindowProc( hwnd, msg, wParam, lParam );
}
return 0;
}
次に背景画像用スレッド。
DWORD WINAPI ThreadBackGroundUI(LPVOID arg)
{
XRXamlSource xamlsrc;
HRESULT retcode;
XRWindowCreateParams wp;
ZeroMemory(&wp, sizeof(XRWindowCreateParams));
wp.Style = WS_BORDER;
wp.pTitle = L"BACKGROUND";
wp.Left = 0;
wp.Top = 0;
xamlsrc.SetFile(_T("\\TEST\\XAML\\BackGround.xaml"));
//Host XAMLの作成
if (FAILED(retcode=g_Mainapp->CreateHostFromXaml(&xamlsrc, &wp, &g_UiBackvhost))) {
return -1;
}
IXRFrameworkElementPtr root;
//ルートエレメントの作成
if (FAILED(retcode=g_UiBackvhost->GetRootElement(&root))) {
return -1;
}
HWND hwnd = NULL;
g_UiBackvhost->GetContainerHWND(&hwnd);
/* 前面表示しない */
ShowWindow(hwnd, SW_SHOWNA);
UINT exitcode;
if (FAILED(retcode=g_UiBackvhost->StartDialog(&exitcode))) {
}
return 0;
}
次に画面遷移時に必要な処理です。
void MainPanelChange(int no)
{
\\不可解な現象その5。画面遷移時に、元画面のコントロールを開放する必要がありますが、開放時に稀に例外が出ます。なのでtry-catchしています。よく分りませんが、いつの間にか勝手に開放されている事があるみたいです。まだガーベジコレクトなんて無いはずですしね。ふしぎー。
//画面を閉じた際に、勝手に開放されている事があり、その時は例外になるのでtry-catchで無視する
for( int i = 0; i < XBTNEVENTMAX; i++) {
if(m_btnControl[i] != 0x00) {
try { m_btnControl[i].Release(); } catch(...) { }
}
}
for( int i = 0; i < XTEXTBLOCKMAX; i++) {
if(m_txtblockControl[i] != 0x00) {
try { m_txtblockControl[i].Release(); } catch(...) { }
}
}
gPanelNo = no;
//今の画面から抜けさせる
\\これを実行する事で、メインのスレッドはStartDialog()から抜けてきます。
g_UiMainvhost->EndDialog(0);
}
最後にサブ関数。
渡されたオブジェクトの操作をします。
void ObjectInvisible(int idx, BYTE type, BYTE visible)
{
\\表示、非表示を切り替えます。
if(type == 0) {
if(m_btnControl[idx] != NULL) {
m_btnControl[idx]->SetVisibility((XRVisibility)!visible);
}
}
else if(type == 1) {
if(m_txtblockControl[idx] != NULL) {
m_txtblockControl[idx]->SetVisibility((XRVisibility)!visible);
}
}
}
void ControlSetText(int idx, const WCHAR *text)
{
\\テキストブロックのテキストを更新。
if(m_txtblockControl[idx] != NULL) {
m_txtblockControl[idx]->SetText(text);
}
}
最低限はこんな感じです。
迷えるIT戦士たちの魂に、救いのあらんことを。
サンプルアプリケーションの概要は、以下の通り。
・開発環境はVisual Studio2005 VC++ 6.0
・ダイアログ無しベース。画面はMicrosoft Expression Blend 2で作成した、XAML(ざむる)を呼び出す事で実現。
・2つの画面を用意。画面は交互に行き来が可能。
・画面遷移時にデスクトップが見えてしまう事を防ぐ為に、背景用のXAMLを常に表示。
・各画面にボタンを用意し、ボタンクリック、キーアップ、キーダウンを取れるようにしています。
・windows messageのHookにより、100msタイマ、ジェスチャ(フリック等)に対応。
開発中に見つかった不可解な問題と、回避策についても一部言及しています。
ソース全文のダウンロードはこちらから。
http://nagisa.okoshi-yasu.net/blog/Subproject1.zip
まずは定義から。
#define XLOOPTIMER_ID 1
#define XBTNEVENTMAX (10+1) //1画面の最大登録ボタン数
#define XTEXTBLOCKMAX (2+1) //1画面の最大登録テキストブロック数
#define EM_UI_MAX 2 //最大画面数
IXRVisualHostPtr g_UiMainvhost;
IXRVisualHostPtr g_UiBackvhost = NULL;
IXRApplicationPtr g_Mainapp;
int gPanelNo = 0;static UINT g_nTimer;
HANDLE ghBackThreadHandle;
IXRButtonBasePtr m_btnControl[XBTNEVENTMAX];
IXRDelegate<XRMouseButtonEventArgs>* g_ButtonEventTBL[XBTNEVENTMAX];
IXRDelegate<XRMouseButtonEventArgs>* g_ButtonDownEventTBL[XBTNEVENTMAX];
IXRDelegate<XRMouseButtonEventArgs>* g_ButtonUpEventTBL[XBTNEVENTMAX];
IXRTextBlockPtr m_txtblockControl[XTEXTBLOCKMAX];
\\画面XAMLの定義です。1つ目にTOPPAGE、2つ目にNEXTPAGEを定義しています。
\\画面XAMLの定義です。1つ目にTOPPAGE、2つ目にNEXTPAGEを定義しています。
const WCHAR gc_PanelFileTBL[][64] = {
L"\\TEST\\XAML\\TOPPAGE.xaml",
L"\\TEST\\XAML\\NEXTPAGE.xaml",
};
\\各画面のボタンオブジェクト名の定義です。Microsoft Expression Blend 2で作成する際のオブジェクトのName定義と全く同じ名称である必要があります。
const WCHAR gc_ButtonNameTBL[EM_UI_MAX][XBTNEVENTMAX][32] = {
const WCHAR gc_ButtonNameTBL[EM_UI_MAX][XBTNEVENTMAX][32] = {
{ L"BTN_NEXT", L"BTN_END", 0x00, },
{ L"BTN_BACK",0x00, },
};
\\各画面のテキストブロックオブジェクト名の定義です。ボタンと同じく、Name定義と全く同じ名称である必要があります。
const WCHAR gc_TextBoxNameTBL[EM_UI_MAX][XTEXTBLOCKMAX][32] = {
const WCHAR gc_TextBoxNameTBL[EM_UI_MAX][XTEXTBLOCKMAX][32] = {
{ L"TXT_TITLE", 0x00, },
{ L"TXT_TITLE", L"TXT_DATE",0x00, },
};
次にWinMainです。
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
HRESULT retcode;
if (!XamlRuntimeInitialize())
return -1;
if (FAILED(retcode=GetXRApplicationInstance(&g_Mainapp)))
return -1;
if (FAILED(retcode=g_Mainapp->AddResourceModule((HINSTANCE)hInstance)))
return -1;
XRWindowCreateParams wp;
ZeroMemory(&wp, sizeof(XRWindowCreateParams));
wp.Style = WS_BORDER;
wp.pTitle = L"TEST";
wp.Left = 0;
wp.Top = 0;
wp.pHookProc = HookProc;
XRXamlSource xamlsrc;
//背景用スレッド起動
ghBackThreadHandle = CreateThread(NULL, 0, ThreadBackGroundUI, NULL, 0, NULL);
\\不可解な現象その1。通常のwindowsなら以下の方法でマウスカーソルを非表示に出来ますが、CE+Expression Blendでは何故か、画面遷移の瞬間だけカーソルが出てしまいます。これを避けるために、画面サイズギリギリの場所にマウスカーソルを飛ばして、見えなくしています。ただし、画面サイズよりも大きい場所を指定すると、指定が無効になってしまうので、サイズギリギリの800,480にする必要があります。このためよく見ると、右下に1ドットだけカーソルが見えていて、画面遷移時に出たり消えたりしています。ダサい対処法。
while(ShowCursor(FALSE) >= 0); //マウスカーソル消去 これだけでは画面遷移時にカーソルが出てしまう
//マウスカーソルを画面外に飛ばしてしまうことで、非表示とする
RECT recta;
recta.top = recta.bottom = 800;
recta.right = recta.left = 480;
ClipCursor((const RECT *)&recta);
\\ここからが、繰り返し処理。1画面を表示-破棄を繰り返します。
for( ; gPanelNo != -1; ) {
\\XAMLをメモリにロード
xamlsrc.SetFile(gc_PanelFileTBL[gPanelNo]);
if (FAILED(retcode=g_Mainapp->CreateHostFromXaml(&xamlsrc, &wp, &g_UiMainvhost))) {
break;
}
IXRFrameworkElementPtr root;
//ルートエレメントの作成
if (FAILED(retcode=g_UiMainvhost->GetRootElement(&root))) {
break;
}
int i;
EventHandler handler;
//ボタン登録
\\各画面に応じたボタンイベントを作成
for( i = 0; gc_ButtonNameTBL[gPanelNo][i][0] != 0x00; i++) {
//名前からオブジェクト取得
if (FAILED(retcode=root->FindName(gc_ButtonNameTBL[gPanelNo][i], &m_btnControl[i])))
{
continue;
}
//クリックイベント登録
if (FAILED(retcode=CreateDelegate(&handler,&EventHandler::OnClickEvent, &g_ButtonEventTBL[i]))) {
continue;
}
if (FAILED(retcode=m_btnControl[i]->AddMouseLeftButtonDownEventHandler(g_ButtonEventTBL[i])))
{
continue;
}
//キーダウンイベント登録
if (FAILED(retcode=CreateDelegate(&handler,&EventHandler::KeyDownEvent, &g_ButtonDownEventTBL[i]))) {
continue;
}
if (FAILED(retcode=m_btnControl[i]->AddMouseLeftButtonDownEventHandler(g_ButtonDownEventTBL[i])))
{
continue;
}
//キーアップイベント登録
if (FAILED(retcode=CreateDelegate(&handler,&EventHandler::KeyUpEvent, &g_ButtonUpEventTBL[i]))) {
continue;
}
if (FAILED(retcode=m_btnControl[i]->AddMouseLeftButtonUpEventHandler(g_ButtonUpEventTBL[i])))
{
continue;
}
}
\\各画面に応じたテキストブロックイベントを作成。これらを応用すれば、どんなオブジェクトのイベントでも、作れるようになると思います。
//テキストブロック登録
for( i = 0; gc_TextBoxNameTBL[gPanelNo][i][0] != 0x00; i++) {
//名前からオブジェクト取得
if (FAILED(retcode=root->FindName(gc_TextBoxNameTBL[gPanelNo][i], &m_txtblockControl[i])))
{
continue;
}
}
SYSTEMTIME stTime;
GetLocalTime(&stTime);
//初期表示
\\ボタンの非表示、テキストブロックのテキスト更新のサンプルです。
//次画面であれば日時を表示
if(gPanelNo == 1) {
WCHAR wStrW[128];
memset(wStrW,0x00,sizeof(wStrW));
wsprintf(wStrW, L"%04d/%02d/%02d %02d:%02d:%02d", stTime.wYear, stTime.wMonth, stTime.wDay, stTime.wHour, stTime.wMinute, stTime.wSecond );
ControlSetText(1,wStrW);
}
//TOP画面のエンドボタンを奇数秒の場合に非表示
if(gPanelNo == 0 && (stTime.wSecond % 2)) {
BYTE visible = 0; //不可視
ObjectInvisible(1,0,visible);
}
HWND hwnd = NULL;
UINT exitcode;
g_UiMainvhost->GetContainerHWND(&hwnd);
\\常に背景XAMLよりも前に出す必要があるため、前面表示指定します。
//前面表示
ShowWindow(hwnd, SW_SHOW);
//画面表示
\\ここでようやく、XAMLが表示されます。close()が呼ばれるまで、ここに留まります。
if (FAILED(retcode=g_UiMainvhost->StartDialog(&exitcode))) {
break;
}
}
\\ここからは、アプリケーション終了処理。
//背景用スレッドの終了
g_UiBackvhost->EndDialog(0);
CloseHandle(ghBackThreadHandle);
//これを行うと異常終了する ->Release()でも同様
\\不可解な現象その2。お片づけしようとすると、例外で落ちます。ふしぎー。
// g_UiMainvhost.Release();
//生成しているのに、FALSEが返るのは何故?
\\不可解な現象その3。お片づけしようとすると、FALSEが返ります。他のスレッドを作成する直前までであれば、呼ぶとTRUEが返ります。スレッド作ったらあかんの?ふしぎー。
if(XamlRuntimeUninitialize() == FALSE) {
}
//スレッドの終了を待つ
//87(パラメータ異常)が返る理由が分からない
\\不可解な現象その4。タスクの終了を待つと、何故かエラーが返り、lastcodeは87(パラメータ異常)となります。引数は間違ってないはずなんですが、どんなに調べても分からんので無視。
DWORD res = WaitForMultipleObjects(1, (const HANDLE *)ghBackThreadHandle, TRUE, INFINITE);
if(res == (DWORD)-1) {
}
return 0;
}
次にイベント定義。
class EventHandler
{
public:
\\その名の通り、キーダウン、キーアップ、クリックイベント発生時にコールされる関数どもです。
HRESULT KeyDownEvent(IXRDependencyObject* source,XRMouseButtonEventArgs* args)
{
return S_OK;
}
HRESULT KeyUpEvent(IXRDependencyObject* source,XRMouseButtonEventArgs* args)
{
return S_OK;
}
HRESULT OnClickEvent(IXRDependencyObject* source,XRMouseButtonEventArgs* args)
{
BSTR pName;
//クリックボタン名取得
source->GetName(&pName);
//ボタン名との比較
\\ボタン名称との比較で、どのボタンのイベントであるか判断しています。
if (_tcscmp(L"BTN_NEXT", pName) == 0) {
MainPanelChange(1);
}
else if (_tcscmp(L"BTN_BACK", pName) == 0) {
MainPanelChange(0);
}
else if (_tcscmp(L"BTN_END", pName) == 0) {
//終了
gPanelNo = -1;
g_UiMainvhost->EndDialog(0);
}
return S_OK;
}
};
次にwindows messageのフック。
BOOL CALLBACK HookProc(VOID* pv, HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, LRESULT* pRetVal)
{
ULONG ulStyle;
switch(msg)
{
case WM_CREATE:
ulStyle = ::GetWindowLong(hwnd, GWL_STYLE);
ulStyle = ulStyle & ~ulStyle;
::SetWindowLong(hwnd, GWL_STYLE, ulStyle);
\\タイマイベント周期をmsで指定。この例では100ms周期。
g_nTimer = ::SetTimer(hwnd, XLOOPTIMER_ID, 100, NULL);
//ジェスチャの登録
\\これを行うと、ジェスチャのメッセージも飛んでくるようになります。逆に言えば、ジェスチャを受けたくないときはこれを行わなければ、飛んできません。
EnableGestures(hwnd, TGF_GID_ALL,TGF_SCOPE_PROCESS);
//下の処理も実行させるために、break無し
\\周期タイマイベントです。
case WM_TIMER:
if (wParam == XLOOPTIMER_ID) {
}
break;
\\特に用事が無くても、このイベントは拾っておく必要があります。画面遷移時に変更のある部分しか更新されなくなります。
case WM_PAINT: //DefWindowProcに渡すと、画面更新の仕方によっては、変更部分しか画面更新がされなくなる
break;
case WM_DESTROY: // ウィンドウ破棄
PostQuitMessage( 0 );
break;
\\ジェスチャイベントは、これでくくられています。下のコメントにもあるとおり、かなりクセの有るイベントとなっています。スマホのようなサクサク動作を想定すると、かなりガッカリします。
case WM_GESTURE:
GESTUREINFO gi;
gi.cbSize = sizeof(GESTUREINFO);
\\ここで、ジェスチャの種類とポイント(座標)が取得できます。
if (GetGestureInfo(reinterpret_cast<HGESTUREINFO>(lParam), &gi))
{
//動作検証結果
//フリックイベントは、かなり力強くフリックしないと来ない
//軽いフリックの場合、イベントが来たとしても、なぜか終了ポイントが開始と同じ
//フリックイベントを拾うよりも、まず最初に必ず来るパンイベントを拾って、
//そのポイントを追いかけたほうが、結果的に反応が良くなる。
//ただしこの場合、画面上をゆっくり時間をかけて指を動かしても、フリック扱いになってしまう。
//これを避けたいのなら、開始から終了までの時間を取得し、一定時間以上かかっていれば無視するなどの小細工が必要。ああめんどくさい。
switch(wParam){
case GID_DOUBLESELECT: //ダブルタップ
break;
case GID_HOLD: //ホールド
break;
case GID_PAN: //パン
break;
case GID_SELECT: //タップ
break;
case GID_BEGIN: //ジェスチャ開始
break;
case GID_SCROLL: //フリック
break;
case GID_END: //ジェスチャ終了
break;
}
}
//HGESTUREINFOの開放
CloseGestureInfoHandle((HGESTUREINFO)lParam);
break;
\\Expression Blend 2は綺麗な画面と引き換えに、メモリやCPUをバカ食いします。イベントのうち、アプリめがけて来ている情報は、不要でも受け止めておいたほうがいいみたいです。
//このアプリで止めてしまって良い情報は、負荷軽減のために止めておく
//この程度の負荷が、わりと馬鹿にならない。
case WM_SETCURSOR:
break;
case WM_LBUTTONUP:
break;
case WM_LBUTTONDOWN:
break;
case WM_MOUSEMOVE:
break;
default:
return DefWindowProc( hwnd, msg, wParam, lParam );
}
return 0;
}
次に背景画像用スレッド。
DWORD WINAPI ThreadBackGroundUI(LPVOID arg)
{
XRXamlSource xamlsrc;
HRESULT retcode;
XRWindowCreateParams wp;
ZeroMemory(&wp, sizeof(XRWindowCreateParams));
wp.Style = WS_BORDER;
wp.pTitle = L"BACKGROUND";
wp.Left = 0;
wp.Top = 0;
xamlsrc.SetFile(_T("\\TEST\\XAML\\BackGround.xaml"));
//Host XAMLの作成
if (FAILED(retcode=g_Mainapp->CreateHostFromXaml(&xamlsrc, &wp, &g_UiBackvhost))) {
return -1;
}
IXRFrameworkElementPtr root;
//ルートエレメントの作成
if (FAILED(retcode=g_UiBackvhost->GetRootElement(&root))) {
return -1;
}
HWND hwnd = NULL;
g_UiBackvhost->GetContainerHWND(&hwnd);
/* 前面表示しない */
ShowWindow(hwnd, SW_SHOWNA);
UINT exitcode;
if (FAILED(retcode=g_UiBackvhost->StartDialog(&exitcode))) {
}
return 0;
}
次に画面遷移時に必要な処理です。
void MainPanelChange(int no)
{
\\不可解な現象その5。画面遷移時に、元画面のコントロールを開放する必要がありますが、開放時に稀に例外が出ます。なのでtry-catchしています。よく分りませんが、いつの間にか勝手に開放されている事があるみたいです。まだガーベジコレクトなんて無いはずですしね。ふしぎー。
//画面を閉じた際に、勝手に開放されている事があり、その時は例外になるのでtry-catchで無視する
for( int i = 0; i < XBTNEVENTMAX; i++) {
if(m_btnControl[i] != 0x00) {
try { m_btnControl[i].Release(); } catch(...) { }
}
}
for( int i = 0; i < XTEXTBLOCKMAX; i++) {
if(m_txtblockControl[i] != 0x00) {
try { m_txtblockControl[i].Release(); } catch(...) { }
}
}
gPanelNo = no;
//今の画面から抜けさせる
\\これを実行する事で、メインのスレッドはStartDialog()から抜けてきます。
g_UiMainvhost->EndDialog(0);
}
最後にサブ関数。
渡されたオブジェクトの操作をします。
void ObjectInvisible(int idx, BYTE type, BYTE visible)
{
\\表示、非表示を切り替えます。
if(type == 0) {
if(m_btnControl[idx] != NULL) {
m_btnControl[idx]->SetVisibility((XRVisibility)!visible);
}
}
else if(type == 1) {
if(m_txtblockControl[idx] != NULL) {
m_txtblockControl[idx]->SetVisibility((XRVisibility)!visible);
}
}
}
void ControlSetText(int idx, const WCHAR *text)
{
\\テキストブロックのテキストを更新。
if(m_txtblockControl[idx] != NULL) {
m_txtblockControl[idx]->SetText(text);
}
}
最低限はこんな感じです。
迷えるIT戦士たちの魂に、救いのあらんことを。