忍者ブログ
[PR]
×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。


2024/11/22 23:40 |
Microsoft Expression Blend 2とVC++で作る、windows CE 6.0アプリケーション その2
では早速、サンプルアプリケーションのソースを公開します。
サンプルアプリケーションの概要は、以下の通り。

・開発環境は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を定義しています。
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] = {
    {    L"BTN_NEXT", L"BTN_END", 0x00,    },
    {    L"BTN_BACK",0x00,    },
};
\\各画面のテキストブロックオブジェクト名の定義です。ボタンと同じく、Name定義と全く同じ名称である必要があります。
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戦士たちの魂に、救いのあらんことを。

拍手


2015/02/11 12:15 | Comments(0) | 仕事

コメント

コメントを投稿する






Vodafone絵文字 i-mode絵文字 Ezweb絵文字 (絵文字)



<<まいにちコーヒー。進化したおすすめポット。 | HOME | Microsoft Expression Blend 2とVC++で作る、windows CE 6.0アプリケーション その1>>
忍者ブログ[PR]