トップ 一覧 検索 ヘルプ RSS ログイン

【C/C++】POLでハードボタン1〜4をフックする方法の変更点

  • 追加された行はこのように表示されます。
  • 削除された行はこのように表示されます。
{{category 開発情報}}
{{category 陰郎の書いた記事,nolink}}


!!!概要
 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( ) を呼び出す方式を試してみたところ、何の問題もなく動作しました。ということで、時間を見つけて記事本体を修正することにします。

{{comment multi}}