Palm Programmer's Laboratory
【C/C++】POLでハードボタン1〜4をフックする方法
[開発情報]
概要
CForm 派生クラスのイベントマップマクロでは、vchrHard1 などのハードボタンイベントを処理することができません。このページでは、この問題について説明し、これらのボタンイベントを処理する方法を解説します。
説明
問題点
以下のコードで示される CMyForm クラスでは、EVENT_MAP マクロでハードボタン1〜4に対して OnHardButton( ) メソッドを割り当てています。
class CMyForm : public CForm { public: CMyForm( ); public: // Form event handlers Boolean OnOpen(EventType* pEvent, Boolean& bHandled); Boolean OnClose(EventType* pEvent, Boolean& bHandled); Boolean OnHardButton(EventType* pEvent, Boolean& bHandled); private: // Event map BEGIN_EVENT_MAP(CForm) EVENT_MAP_ENTRY(frmOpenEvent, OnOpen) EVENT_MAP_ENTRY(frmCloseEvent, OnClose) EVENT_MAP_KEY_DOWN_ENTRY(vchrHard1, 0, OnHardButton) EVENT_MAP_KEY_DOWN_ENTRY(vchrHard2, 0, OnHardButton) EVENT_MAP_KEY_DOWN_ENTRY(vchrHard3, 0, OnHardButton) EVENT_MAP_KEY_DOWN_ENTRY(vchrHard4, 0, OnHardButton) END_EVENT_MAP() };
しかし、この OnHardButton( ) は決して呼び出されることがありません。なぜなら、POL のイベントハンドリングでは、ハードボタンの押下は CForm 派生クラスまで到達しないからです。ハードボタンの押下イベントは、ユーザー定義アプリケーションクラスの基底クラスである CPalmApp のレベルで処理されてしまいます。そのため、ハードボタン押下をフックしたければアプリケーションクラスで対処する必要があります。
CPalmApp::EventLoop( )の実装
アプリケーションクラスで対処するといっても、POL ではイベント処理のほとんどは CPalmApp クラスの内部で行われています。ユーザー定義のメソッドでオーバーライドできる仮想関数は AppHandleEvent( ) と PreSystemEventHook( ) しかありません。これらの仮想関数を呼び出している CPalmApp::EventLoop( ) の実装は以下のようになっています(元のソースから括弧のスタイルなどを整形してあります)。
void CPalmApp::EventLoop( ) { if( m_wCommand == sysAppLaunchCmdNormalLaunch ) { UInt16 error; EventType event; do { EvtGetEvent( &event, m_dwEventWaitDuration ); if( !PreSystemEventHook( &event ) ) if( !SysHandleEvent( &event ) ) if( !MenuHandleEvent( 0, &event, &error ) ) if( !AppHandleEvent( &event ) ) FrmDispatchEvent( &event ); } while( event.eType != appStopEvent ); } }
ハードボタンの処理は SysHandleEvent( ) で行われてしまうため、AppHandleEvent( ) をオーバーライドしてもハードボタンの処理をフックすることはできません。そのため、PreSystemEventHook( ) をオーバーライドする必要があります。
PreSystemEventHook( ) をオーバーライドしても、実際に押下されたハードボタンに対してどのような動作をするのは CForm 派生クラス側になります。そのため、アプリケーションクラスがフックしたハードボタンイベントを CForm 派生クラスに伝達する方法が必要になります。
PreSystemEventHook( ) から CForm 派生クラスへの伝達
フックしたイベントを他のオブジェクト( 画面クラス )へ伝達するための仕組みについて考えます。通常の(たとえば画面上のボタンなどのタップ)イベントについては POL の正規のルートで処理するという前提で、PreSystemEventHook( ) でフックする必要のあるイベントに限定した仕組みとして、以下の EventReceiver インタフェイスを考えます。
class EventReceiver { public: virtual Boolean HandleEvent( EventType* pEvent ) = 0; };
このインタフェイス(を実装するクラスのオブジェクト)をアプリケーションクラスに登録し、CPalmApp 派生クラスでオーバーライドする PreSystemEventHook( ) から呼び出すようにします。
class EventReceiver; class CMyApp : public CPalmApp { public: CMyApp( ); public: // : // : virtual Boolean PreSystemEventHook( EventType* pEvent ); // : // : // Form map BEGIN_FORM_MAP() FORM_MAP_ENTRY(MYFORM, CMyForm) END_FORM_MAP() private: EventReceiver* m_pEventReceiver; public: static void RegisterEventReceiver( EventReceiver* pReceiver ); };
大雑把ですが、CMyApp::PreSystemEventHook( ) の実装は以下のような感じになるでしょう。
Boolean CMyApp::PreSystemEventHook( EventType* pEvent ) { if( pEvent->eType != keyDownEvent || !m_pEventHandler ) return false; switch( pEvent->data.keyDown.chr ) { case vchrHard1: case vchrHard2: case vchrHard3: case vchrHard4: return m_pEventReceiver->HandleEvent( pEvent ); } return false; }
画面クラス側では、CForm( あるいは CModalForm )と同時に EventReceiver も継承します。
class CMyForm : public CForm, public EventReceiver { // : // : public: // <<EventReceiver>> virtual Boolean HandleEvent( EventType* pEvent ); // : // : };
この CMyForm クラスの frmOpenEvent ハンドラで CMyApp::RegisterEventReceiver( ) を呼び出して自身を登録することで、PreSystemEventHook( ) から CMyForm オブジェクトへハードボタン押下イベントを伝達できるようになります。また、frmCloseEvent ハンドラで登録を解除するために NULL を指定して再び CMyApp::RegisterEventReceiver( ) を呼び出す必要があることに注意してください。
注意事項
このページで紹介した方法は、筆者がハードボタン押下イベントを処理するために試行錯誤した結果であり、もっと良い(あるいはより正しい)方法が別に存在するかもしれません。より良い情報をお持ちの方はコメントいただけると幸いです。
コメント
- Re: - 陰郎 (2007年05月07日 08時54分02秒)
- 思いついたので自分でコメントしてしまいますが、PreSystemEventHook( ) 内から FrmDispatchEvent( ) を呼び出してしまえばそれで済むような気が...(無論条件を満たした場合だけ)。今職場なので確認できませんが、後ほど試してみます。
- Re: - 陰郎 (2007年05月08日 00時43分53秒)
- PreSystemEventHook( ) 内から FrmDispatchEvent( ) を呼び出す方式を試してみたところ、何の問題もなく動作しました。ということで、時間を見つけて記事本体を修正することにします。