関連クラス: wxEvtHandler, wxWindow, wxEvent
他のすべての GUI フレームワークのように、wxWidgets アプリケーションはイベントベースで処理の制御を行います: 一般的に、プログラムの処理の大半はユーザの生成するイベントに応じて行われます。これらのイベントは (キーボード、マウス、ジョイスティックなどの) 入力装置を利用して直接発生させるか、より一般的には、それらの入力イベントをより高レベルのイベントへ合成する標準コントロールを利用して発生させることができます: 例えば、wxButton はコントロール上でユーザが左マウスボタンを押下し、 (Esc
を押下することなく) 離したときにクリックイベントを生成します。wxTimerEvent や wxSocketEvent など、ユーザの操作に直接対応しないイベントも存在します。
しかし、すべての場合において、wxWidgets ではそれらのイベントを同じ形式で表現しており、イベントの派生元によらず、同一の方法で処理することができます。また、通常、イベントは wxWidgets 自身によって生成されますが、あなたがこれを行うことも可能です。これはカスタムイベントを使用する場合に特に有用です。 (カスタムイベントの概要 参照)
より正確には、イベントは以下の内容で表現されます:
wxWIdgets でイベントを処理する方法として、主に 2 種類の方法があります。ひとつは イベントテーブル マクロを使用する方法で、静的 (コンパイル時) にのみ、イベントとそのハンドラをバインドすることができます。もう一方は wxEvtHandler::Bind<>() を使用する方法で、動的に (実行時に条件に基づいて) ハンドラをバインドしたり解除したりできます。また、以下のものをイベントへ直接バインドすることができます:
静的なイベントテーブルではそれが定義されたオブジェクトのイベントしか処理できないため、柔軟性は Bind<>() の方がイベントテーブルよりも上です。一方で、イベントテーブルの方が簡潔であり、すべてのイベントハンドラを一箇所に集約することができます。より適切と思ういずれかの方法を選択することもできますし、(おそらく混乱を招く、悪い考えでしょうが) 異なるクラスや同じクラス内で自由に両方の方法を組み合わせることもできます。
既存の wxWidgets のチュートリアルや議論の大半ではイベントテーブルを使用していることにも注意してください。これは歴史的に、wxWidgets で動的イベント処理が可能になる前から、それらの文書が存在するためです。ただし、これはイベントテーブルが適切な方法であることを意味しているわけではまったくありません: ある側面においては動的なイベント処理の方が優れており、wxWidgets を使い始める際には動的にイベント処理を行なうことを強く検討するべきです。一方で、単に多くのサンプルでイベントテーブルを見かけるからという理由だけであっても、イベントテーブルについて学ぶ必要性は依然として残っています。
そのため、イベントテーブルと動的イベントハンドラのいずれかを選択する前に、それぞれの方法についてより詳しく議論しておきましょう。次の章ではイベントテーブルを用いたイベント処理の導入部分を説明します。Bind<>() についての議論は 動的イベント処理 を参照してください。
イベントテーブル を使うためには、まずイベントを処理するクラスを決定する必要があります。wxWidgets が唯一要求することとして、イベントを処理するクラスは wxEvtHandler を継承していなければなりません。したがって、wxWindow はこのクラスを継承していることを考慮すると、ウィンドウを表すクラスはどれもイベントを処理することができるということです。メニューコマンドといった単純なイベントは普通、メニューを保持するトップレベルウィンドウで処理されます。そのため、いくつかのイベントは wxFrame を継承した MyFrame
で処理する必要があるはずです。
最初に、ひとつ以上の イベントハンドラ を定義してください。これらのイベントハンドラは単純なメンバ関数であり、wxEvent を継承したクラスのオブジェクトへの参照を引数に取り、戻り値はありません。(情報を返却する場合は引数のオブジェクトを経由して行います。これが引数を const にしていない理由です) また、以下のマクロを
クラス宣言内のどこかに挿入する必要があります。どこに挿入しても構いませんが、慣習的にクラス宣言の最後に配置します。これはこのマクロがアクセス制御を内部的に変更してしまうからで、宣言の後ろに何もなければ一番安全だからです。完全なクラス宣言は以下のようになります:
class MyFrame : public wxFrame { public: MyFrame(...) : wxFrame(...) { } ... protected: int m_whatever; private: // 通常、イベントハンドラはクラス外から呼ばれることはないため、 // 普通は private にします。特に、これらを public に // する必要はありません。 void OnExit(wxCommandEvent& event); void OnButton1(wxCommandEvent& event); void OnSize(wxSizeEvent& event); // イベントハンドラは OnSomething() という名前にするのが一般的ですが、 // そうしなければならないわけではありません; これもイベントハンドラです: void DoTest(wxCommandEvent& event); wxDECLARE_EVENT_TABLE() };
次に、イベントテーブルを定義する必要があります。これは必ずソースファイル内に配置しなければなりません。イベントテーブルはイベントとメンバ関数のマッピングを wxWidgets へ知らせるものであり、以下のようになります:
wxBEGIN_EVENT_TABLE(MyFrame, wxFrame) EVT_MENU(wxID_EXIT, MyFrame::OnExit) EVT_MENU(DO_TEST, MyFrame::DoTest) EVT_SIZE(MyFrame::OnSize) EVT_BUTTON(BUTTON1, MyFrame::OnButton1) wxEND_EVENT_TABLE()
イベント処理に使用したいメンバ関数はイベントテーブル内に記載する必要があることに注意してください; MyFrame クラス内で定義するだけでは 不十分 です。
この定義を詳しく見ていきましょう: 最初の行は MyFrame クラスのイベントテーブルを定義しようとしており、基底クラスが wxFrame であることを意味しています。そのため、デフォルトでは MyFrame で処理されなかったイベントは wxFrame で処理されます。次の 4 行は個々のイベントにおけるイベントハンドラへのバインディングを定義しています: 最初のふたつはマクロの第 1 引数で指定された識別子を持つ項目のメニューコマンドを、ふたつの異なるメンバ関数にマッピングしています。次の行の EVT_SIZE
はフレームのサイズを変更すると OnSize() を呼び出すことを意味しています。このマクロはウィンドウ識別子を必要としません。なぜなら、通常は現在のウィンドウのリサイズイベントにのみ関心があるはずだからです。
EVT_BUTTON
マクロはイベントの発生元がウィンドウクラスである必要がないことを表しています。イベントテーブルの検索はウィンドウ階層をたどりながら行われるため、イベントの発生元がフレーム内のパネルにあるボタンである場合でも動作します。 (しかし、これが可能なのはコマンドイベントのみです。そのため、同じ方法で子コントロールでのマウス移動イベントを親ウィンドウで捕まえることはできません。なぜなら wxMouseEvent は wxCommandEvent を継承していないためです。マウスイベントで同じことを実現する方法は以下を参照してください) この場合、まずボタンのイベントテーブルが検索され、続いて、その親パネル、フレームの順に検索されます。
最後に、イベントハンドラを実装する必要があります。以前述べたように、すべてのイベントハンドラは wxEvent を継承した引数を取りますが、その引数のクラスはイベントの型と発生元ウィンドウのクラスに応じて異なります。リサイズイベントでは wxSizeEvent が使用されます。メニューコマンドと (ボタン押下といった) 大半のコマンドコントロールでは wxCommandEvent が使用されます。より複雑なコントロールのときには、コントロール独自の追加情報を提供する、wxCommandEvent を継承したより具体的なイベントクラスを使用できます。例えば、wxTreeCtrl ウィンドウから送られるイベントでは wxTreeEvent が使用できます。
ありえそうな一番単純な例では、イベントハンドラは event
引数をまったく使用しないかもしれません。以下に例を示します。
void MyFrame::OnExit(wxCommandEvent& WXUNUSED(event)) { // ユーザがメニューから "Exit" を選択したときには終了する必要がある。 Close(true); }
他の場合では、以下のように event
引数の持つ情報が必要になるかもしれません:
void MyFrame::OnSize(wxSizeEvent& event) { wxSize size = event.GetSize(); ... 新しいサイズを使用してフレームを更新する ... }
イベントテーブルマクロと、それに付随する wxEvent の継承クラスについては、イベントを生成する各コントロールの説明で詳しく説明しています。
この方法ではイベント処理のやり方が大きく異なります。構文を見ることから始めましょう: 最初の明らかな違いは wxDECLARE_EVENT_TABLE() や wxBEGIN_EVENT_TABLE()、およびそれらに関連するマクロを使用する必要がない点です。代わりに、コードの任意の場所で以下のように Bind<>() を呼び出します。 (通常はイベントを処理するクラスの定義内に配置し、イベントテーブルの場合のようにグローバルスコープ内には配置しません)
MyFrame::MyFrame(...) { Bind(wxEVT_COMMAND_MENU_SELECTED, &MyFrame::OnExit, this, wxID_EXIT); }
ここでは this
ポインタを指定する必要があることに注意してください。
さて、意味論的な違いについて説明しましょう:
イベントハンドラは任意のタイミングでバインドできます。例えば、先に何かの初期化処理を行い、それが成功したときにのみ、イベントハンドラをバインドすることも可能です。こうすることで、イベントハンドラ内でオブジェクトが正常に初期化されているか確認する必要がなくなります。Bind<>() を使用することで、初期化が正常に行われなかった場合は単純にイベントハンドラが呼び出されないだけになります。
この延長として、Unbind<>() を使ってイベントハンドラのバインディングを任意のタイミングで解除することもできます。 (また、あとから再バインドするかもしれません) もちろん、イベントハンドラの有効無効を表す内部フラグを用いることで、古典的な静的イベントハンドラ (つまり、イベントテーブル) でもこの振る舞いを模倣することは可能です。しかし、動的にバインドされるイベントハンドラを使用する方がコードも少なく、処理内容も明確になります。
大事なことを一つ言い残しましたが、バインド対象についても柔軟性が向上しており、イベントを以下のものにバインドできます:
これはイベントテーブルでは実現不可能です。なぜなら、イベントのディスパッチ先としてこれらのハンドラを指定することができないためです。そのため、イベントはそれを生成したオブジェクトに対して送信される必要があります。Bind<>() ではこれらのイベントハンドラを指定できるため、そのようなことはありません。一例として、よく質問される子フレーム内にマウスがあるときのマウス移動イベントの受信方法を挙げましょう。これは単純な方法では実現できません:
フレームのイベントテーブル内に EVT_LEAVE_WINDOW(MyFrame::OnMouseLeave)
を書いても効果がありません。なぜなら、マウス移動 (フレームへの出入りも含みます) イベントは (少なくともデフォルトでは) 親ウィンドウへ伝播しないためです。
しかし、次のように書くと
MyFrame::MyFrame(...) { m_child->Bind(wxEVT_LEAVE_WINDOW, &MyFrame::OnMouseLeave, this); }
期待した通りに動作します。イベントハンドラへ渡された event
引数の wxEvent::GetEventObject() 関数によって、イベントを生成したオブジェクトを取得できます。 (このオブジェクトはフレームではありません)
Bind() のふたつのオーバーロードを用いて、異なるイベントハンドラを使用する例を見てみましょう: 最初のオーバーロードはメンバ関数用で、もう一方は任意のファンクタ (単純な関数を含む、呼び出し可能なオブジェクト) 用です。
イベントを生成したオブジェクトに加えて、完全に別のオブジェクトのメンバ関数をイベントハンドラとして使用できます。
void MyFrameHandler::OnFrameExit( wxCommandEvent & ) { // 何か有用なことを行う。 } MyFrameHandler myFrameHandler; MyFrame::MyFrame() { Bind( wxEVT_COMMAND_MENU_SELECTED, &MyFrameHandler::OnFrameExit, &myFrameHandler, wxID_EXIT ); }
MyFrameHandler
は wxEvtHandler を継承している必要がないことに注意してください。ただし、myFrameHandler
の寿命を MyFrame
よりも長くなるようにするか、もしくは少なくとも破棄される前にバインディングを解除する必要があることを覚えておいてください。
イベントハンドラとして普通の関数や静的メンバ関数を使用する場合、以下のように書いてください:
void HandleExit( wxCommandEvent & ) { // 何か有用なことを行う } MyFrame::MyFrame() { Bind( wxEVT_COMMAND_MENU_SELECTED, &HandleExit, wxID_EXIT ); }
そして、最後に任意のファンクタをバインドし、イベントハンドラとして使用することができます:
struct MyFunctor { void operator()( wxCommandEvent & ) { // 何か有用なことを行う } }; MyFunctor myFunctor; MyFrame::MyFrame() { Bind( wxEVT_COMMAND_MENU_SELECTED, &myFunctor, wxID_EXIT ); }
ファンクタの一般的な例は boost::function<> です:
using namespace boost; void MyHandler::OnExit( wxCommandEvent & ) { // 何か有用なことを行う } MyHandler myHandler; MyFrame::MyFrame() { function< void ( wxCommandEvent & ) > exitHandler( bind( &MyHandler::OnExit, &myHandler, _1 )); Bind( wxEVT_COMMAND_MENU_SELECTED, exitHandler, wxID_EXIT ); }
boost::bind<>() のおかげで、シグネチャが完全に一致しないメンバ関数でも使用することができます:
void MyHandler::OnExit( int exitCode, wxCommandEvent &, wxString goodByeMessage ) { // 何か有用なことを行う } MyHandler myHandler; MyFrame::MyFrame() { function< void ( wxCommandEvent & ) > exitHandler( bind( &MyHandler::OnExit, &myHandler, EXIT_FAILURE, _1, "Bye" )); Bind( wxEVT_COMMAND_MENU_SELECTED, exitHandler, wxID_EXIT ); }
まとめると、Bind<>() を使用すると若干コードが増えますが、静的イベントテーブルよりも柔軟性があります。そのため、この追加の効果が必要なときはためらわずに使用してください。一方で、追加の柔軟性が必要とされないような単純な状況では、イベントテーブルもまだ充分に役立ちます。
前の章ではイベントハンドラの定義方法について説明しましたが、wxWidgets がどのようにイベントハンドラを探すのかについてはまだ述べていません。この章ではそのアルゴリズムについて詳しく説明します。おそらく、この章を読みながら イベントサンプル を実行し、コードや (イベント処理順序を確認するために) ボタンをクリックした時の出力を見たくなると思います。
ウィンドウからイベントを受信したとき、wxWidgets はイベントを生成したウィンドウに属する最初のイベントハンドラオブジェクトの wxEvtHandler::ProcessEvent を呼び出します。通常、ProcessEvent() によるイベントテーブルの検索順は以下のように行われ、イベントハンドラが見つかると処理を終了します。 (ただし、イベントハンドラで wxEvent::Skip() を呼ぶとイベントが処理されていないものとして扱われ、引き続き検索が行われます):
他の処理を行う前に wxApp::FilterEvent() を呼び出します。-1 (デフォルト) 以外の値を返却した場合、ただちにイベント処理を終了します。
イベントハンドラが wxEvtHandler::SetEvtHandlerEnabled() によって無効化されている場合、次の 3 ステップはスキップし、(5) から処理を継続します。
オブジェクトが wxWindow で、かつ、関連するバリデータを持っている場合、wxValidator でイベントを処理します。
動的イベントハンドラ (つまり、Bind<>() でバインドしたイベントハンドラ) の一覧を調べます。これは静的イベントハンドラをチェックする前に行われることに注意してください。そのため、動的イベントハンドラと静的イベントハンドラの両方がイベントに合致した場合、動的イベントハンドラで wxEvent::Skip() を呼ばない限りは静的イベントハンドラが呼ばれることはありません。
このクラスと基底クラスで (イベントテーブルマクロによって) 定義されたすべてのイベントハンドラを含むイベントテーブルを調べます。これは基底クラスで定義されたイベントハンドラがこのステップで実行されうることを意味します。
イベントハンドラの連鎖内において、次のイベントハンドラが存在する場合、次のイベントハンドラへイベントを引き渡します。つまり、そのイベントハンドラに対してステップ (1) から (4) を行います。普通は次のイベントハンドラが存在しないため、次のステップを行いますが、次のイベントハンドラを定義する方法は イベントハンドラの連鎖 を参照してください。
オブジェクトが wxWindow で、かつ、イベントが伝播するように設定されている (デフォルトでは wxCommandEvent を継承したイベントのみ、伝播するよう設定されています) 場合、親ウィンドウに対してステップ (1) から (ステップ (7) を除いて) 再度処理を行います。このオブジェクトがウィンドウでないが次のイベントハンドラが存在する場合、親コントロールがウィンドウであれば親コントロールにイベントを引き渡します。これにより、ウィンドウ上に (おそらく複数の) 非ウィンドウイベントハンドラが配置されているような一般的な場合において、イベントが最終的に親ウィンドウまで確実に到達するようになります。
ステップ 6 に注意してください。 wxWidgets のイベント処理システムの持つこの強力な機能はしばしば見落とされたり、人を混乱させたりします。ウィンドウ階層内におけるイベント伝播についての詳細は次の章で述べます。
また、wxWidgets ドキュメント/フレームワークのウィンドウ部分、つまり、wxDocParentFrame、wxDocChildFrame と、その MDI 版である wxDocMDIParentFrame、wxDocMDIChildFrame でのイベント処理には追加のステップが存在することにも注意してください。親フレームクラスでは上記のステップ (2) において、受信したイベントをまず wxDocManager オブジェクトに送信するように変わります。そして、このオブジェクトが現在のビューに対してイベントを送信し、ビュー自身が関連するドキュメントにまずイベントを処理させます。子フレームクラスはイベントを関連するビューへ直接送信し、このビューがさらに関連するドキュメントへイベントを転送します。ドキュメント/ビューフレームワークでの正確なイベント処理順を覚えなくてもいいようにする、もっとも単純で推奨する解決方法は、ビュークラスでのみイベントを処理し、ドキュメントやドキュメントマネージャでは処理しないことです。
以前述べたように、wxCommandEvent を継承したクラスのイベントは、ウィンドウ自身で処理されなかった場合にデフォルトで親ウィンドウまで伝播します。しかし、コマンドイベントがデフォルトで伝播するとはいえ、他のイベントも伝播させることは可能です。なぜなら、イベント処理コードではwxEvent::ShouldPropagate() を用いてイベントを伝播させるかどうかを確認するためです。イベントの伝播回数を制限することも可能ですし、処理されるまで (もしくはトップレベルの親ウィンドウに到達するまで) 伝播させることも可能です。
最後に、他にも複雑な仕組みがあります (実際には、これは wxWidgets プログラマの生活を非常にシンプルにしてくれるものです): コマンドイベントが親ウィンドウへ伝播するとき、親ダイアログまで到達した時点で伝播が終了します。これはつまり、モーダルダイアログを表示しているときにダイアログコントロールから意図しないイベントを受け取ることがないことを意味します。 (それらのイベントは処理されないままになるでしょう) しかし、通常のイベントはフレームを超えて伝播します。このようにした理由は、普通のアプリケーションでは数個のフレームしか存在せず、プログラマはそれらの親子関係のことをよく理解している一方で、複雑なプログラムで表示されるすべてのダイアログを追跡することは、不可能でないにしても非常に難しいためです。 (いくつかのダイアログは wxWidgets によって自動的に生成されることを思い出してください) なんらかの理由で別の振る舞いをさせる必要がある場合、明示的に SetExtraStyle(wxWS_EX_BLOCK_EVENTS)
を使用することでイベントが指定されたウィンドウ外へ伝播するのを止めたり、ダイアログ (デフォルトでこのフラグが設定されています) のフラグを解除したりできます。
(リサイズ、移動、描画、マウスイベント、キーボードイベントといった) ウィンドウに関係する一般的なイベントはウィンドウに対してのみ送られます。(ボタンクリック、メニュー選択、ツリーの展開といった) 高水準で、ウィンドウ自身が生成するイベントはコマンドイベントと呼ばれ、そのイベントを処理対象とする場合に親コントロールに対して送られます。より正確に言うと、前に述べたように wxCommandEvent を継承して いない イベントクラス (wxEvent の継承マップ参照) はどれも伝播 しません。
例えばダイアログ内のネイティブコントロールへ送信される (そのコントロール自身では使用しない) キーイベントなど、特定のシステムイベントを親ウィンドウで取得したいと思うことがあるかもしれません。この場合、すべてのイベント (もしくはその中の特定のイベント) を親ウィンドウへ渡すようにイベントハンドラの ProcessEvent() をオーバーライドする必要があります。
イベント伝播アルゴリズムのステップ 4 では、イベントハンドラの連鎖内における次のイベントハンドラを確認します。この連鎖は wxEvtHandler::SetNextHandler() を使用して形づくられます:
(図を参照すると分かるように、A->ProcessEvent
でイベントが処理されないと B->ProcessEvent
が呼び出され、これが繰り返されます。)
加えて wxWindow の場合、wxWindow::PushEventHandler() を使用してスタック (wxEvtHandler の双方向リンクリストで実装されます) を構築することができます:
(図を見ると分かるように、W->ProcessEvent
が呼ばれるとすぐに A->ProcessEvent
を呼び出します; もし A
でも B
でもイベントを処理しなかった場合、wxWindow 自身が使用されます。つまり、登録されたすべてのイベントハンドラを調べた後にwxWindow の動的イベントハンドラと静的イベントテーブルを調べます。
デフォルトでは連鎖は空、つまり、次のイベントハンドラは存在しません。
各イベントはイベント型によって一意に定義されるため、カスタムインベントは新しいイベント型を定義するところから始まります。これは wxDEFINE_EVENT() マクロによって行います。イベント型は変数なので、必要であれば wxDECLARE_EVENT() を使用して宣言することもできます。
次に行なうことは、このイベント型用のカスタムイベントクラスを定義する必要があるかどうか、もしくは既存のクラス、通常は (追加の情報を持たない) wxEvent か (いくつかの追加フィールドを持ち、デフォルトで伝播する) wxCommandEvent のどちらかを再利用できるかどうかを決定することです。両方の戦略について、以下で詳細に述べています。コードの書き方とカスタムイベント型の動作例として イベントサンプル も参照してください。
最後に、カスタムイベントを生成し、送信する必要があります。生成はカスタムイベントのインスタンス化と内部変数の初期化と同じくらい単純です。特定のイベントハンドラへイベントを送信する方法は 2 種類あります: wxEvtHandler::AddPendingEvent を使用する方法と wxEvtHandler::QueueEvent を使用する方法です。基本的に、スレッド間通信を行なう場合は後者を選択する必要があります; メインスレッドのみを使用する場合は前者も安全に使用することができます。最後に、前述の wxEvtHandler の関数に関連する、ふたつのグローバルラッパー関数があります: それは wxPostEvent() と wxQueueEvent() です。
新しいイベント型で wxCommandEvent を使用したいだけの場合、新しいイベントクラスを自分自身で定義する必要はなく、以下の汎用イベントテーブルマクロのひとつを使用してください。
例:
// このマクロは一般的にヘッダファイル内で使用します: これは MY_EVENT イベント型を定義するだけです wxDECLARE_EVENT(MY_EVENT, wxCommandEvent); // これは定義なのでヘッダ内で使用することはできません wxDEFINE_EVENT(MY_EVENT, wxCommandEvent); // イベントテーブルによるイベント処理のコード例 wxBEGIN_EVENT_TABLE(MyFrame, wxFrame) EVT_MENU (wxID_EXIT, MyFrame::OnExit) ... EVT_COMMAND (ID_MY_WINDOW, MY_EVENT, MyFrame::OnMyEvent) wxEND_EVENT_TABLE() void MyFrame::OnMyEvent(wxCommandEvent& event) { // 処理を行なう wxString text = event.GetString(); } // Bind<>() によるイベント処理のコード例: MyFrame::MyFrame() { Bind(MY_EVENT, &MyFrame::OnMyEvent, this, ID_MY_WINDOW); } // イベントを生成するコードの例 void MyWindow::SendEvent() { wxCommandEvent event(MY_EVENT, GetId()); event.SetEventObject(this); // 何か値を設定する event.SetString("Hello"); // 送信する ProcessWindowEvent(event); }
(より複雑なデータをある場所から別の場所へ送るといった) 特定の状況下では独自のイベントクラスを定義する必要があります。イベントクラスの定義とは別に、独自のイベントテーブルマクロも定義する必要があります。
例を以下に示します:
// 新しいイベントクラスを定義する class MyPlotEvent: public wxEvent { public: MyPlotEvent(wxEventType eventType, int winid, const wxPoint& pos) : wxEvent(winid, eventType), m_pos(pos) { } // アクセサ wxPoint GetPoint() const { return m_pos; } // 基底クラスの純粋仮想関数を実装する virtual wxEvent *Clone() const { return new MyPlotEvent(*this); } private: const wxPoint m_pos; }; // 上記のクラスに関連する MY_PLOT_CLICKED イベント型をひとつだけ定義しますが、 // 通常は複数のイベント型を持つようになるでしょう。例えば、 // MY_PLOT_ZOOMED や MY_PLOT_PANNED などです。 // そのような場合はこれと似たような行をここに追加するだけです。 wxDEFINE_EVENT(MY_PLOT_CLICKED, MyPlotEvent); // 古いコンパイラをサポートしたい場合、いくつかの醜いマクロを使用する必要があります: typedef void (wxEvtHandler::*MyPlotEventFunction)(MyPlotEvent&); #define MyPlotEventHandler(func) wxEVENT_HANDLER_CAST(MyPlotEventFunction, func) // ほどほどにモダンなコンパイラのみでビルドする場合、 // 代わりに以下のようにするだけです: #define MyPlotEventHandler(func) (&func) // 最後に、新しいイベント型のイベントテーブルエントリを作成するマクロを // 定義します // // Bind<>() のみを使用する場合はこうする必要がまったくないことと、 // 本当に古いコンパイラを使うのでなければ MyPlotEventHandler(func) の代わりに &func とすることができることを // 覚えておいてください #define MY_EVT_PLOT_CLICK(id, func) \ wx__DECLARE_EVT1(MY_PLOT_CLICKED, id, MyPlotEventHandler(func)) // イベント処理のサンプルコード (これらの関数のひとつを使用することになるでしょう。 // もちろん、両方使うことはありません): wxBEGIN_EVENT_TABLE(MyFrame, wxFrame) EVT_PLOT(ID_MY_WINDOW, MyFrame::OnPlot) wxEND_EVENT_TABLE() MyFrame::MyFrame() { Bind(MY_PLOT_CLICKED, &MyFrame::OnPlot, this, ID_MY_WINDOW); } void MyFrame::OnPlot(MyPlotEvent& event) { ...event.GetPoint() を使用して処理を行なう... } // イベントを生成するコードの例: void MyWindow::SendEvent() { MyPlotEvent event(MY_PLOT_CLICKED, GetId(), wxPoint(...)); event.SetEventObject(this); ProcessWindowEvent(event); }
wxWidgets の行うイベント処理の仕組みは通常の C++ の仮想関数にとても近いことに着目してください: 両方とも、継承クラスでイベント処理関数を定義することで基底クラスの振る舞いを置き換えることを可能にします。
しかし、継承クラスから基底クラスで実装されたデフォルトの振る舞いを呼び出すときに、このふたつの仕組みの間には重要な違いがあります。仮想関数では基底クラスの関数を直接呼び出す必要があり、継承クラスのイベントハンドラの最初 (イベントの事後処理を行なう場合) か最後 (イベントの事前処理を行なう場合) で呼ぶことができます。イベントハンドラでは選択肢は事前処理だけで、デフォルトの振る舞いを呼び出すためには wxEvent::Skip() を呼ぶ必要があり、基底クラスのイベントハンドラを直接呼び出しては いけません 。実際には、デフォルトの振る舞いは使用しているツールキットや OS によってプラットフォーム依存のコード内に実装されることが多いため、おそらく基底クラスにはイベントハンドラが存在しないでしょう。たとえ wxWidgets の階層に存在したとしても、イベントハンドラは wxWidgets API の一部ではないため、直接呼ぶべきではありません。
一般的に wxEvents はユーザ操作 (wxWindow のリサイズなど) と関数呼び出し (wxWindow::SetSize など) のどちらからでも発生しますが、wxCommandEvent を継承したイベントはユーザが操作した場合のみ、wxWidgets コントロールから送信されます。このルールの 例外 は以下のとおりです:
wxTextCtrl::SetValue の代わりに wxTextCtrl::ChangeValue を使用できますが、wxTextCtrl::Replace や wxTextCtrl::WriteText といった他の関数にはイベントを送信せずに同じことを行う関数はありません。
TODO: おそらく非推奨であり、Bind() がより良い方法を提供しています。
実際のところ、ウィンドウクラスを継承して新しいクラスを作成したくない場合は、必ずしもそうする必要はありません。代わりに wxEvtHandler を継承した新しいクラスを作成して適切なイベントテーブルを定義し、wxWindow::SetEventHandler (か、より望ましくは wxWindow::PushEventHandler) を呼び出してこのイベントテーブルを使用するようにできます。この方法を使うことで、たくさんの継承クラスを作成しなくてすむとともに、同じイベントハンドラクラスのインスタンスを異なるウィジェットクラスのインスタンスから利用することができます。 (ただし、異なるオブジェクトを同じイベントハンドラオブジェクトとして複数回使用するべきではありません)
もし手動でウィンドウのイベントハンドラを呼ぶ必要がある場合、GetEventHandler 関数でウィンドウのイベントハンドラを取得し、そのメンバ関数を呼び出してください。SetEventHandler や PushEventHandler を使用してイベント処理をリダイレクトしていない場合、デフォルトでは GetEventHandler はウィンドウ自身を返却します。
PushEventHandler の使用法のひとつは、一時的または永続的に GUI の振る舞いを変更することです。例えば、ダイアログの外見を変更するためのダイアログエディタをアプリケーション内で起動したいと思うかもしれません。その場合、既存のダイアログの振る舞いを元に戻す前にすべての入力を取得し、"その場で" 編集することができます。そのため、アプリケーションの振る舞いをカスタマイズするために新しいクラスを継承していたとしても、あなたのユーティリティから自由に振る舞いを変更することができます。これはオンラインチュートリアルでも有用なテクニックです。レッスンから外れることなく、ユーザに一連のステップを実行させることができます。ここでは、ボタンやウィンドウから送られてくるイベントを検証し、それが適用可能なものである場合に元のイベントハンドラへ渡す、ということを行えます。他のイベントハンドラとは異なる範囲のイベントを独立して処理するようなイベントハンドラを作成する場合に PushEventHandler/PopEventHandler を使用してください。
ウィンドウ識別子とは、イベントシステムにおいてウィンドウを一意に識別するための整数値です。 (とはいえ、他の用途に使用することもできます) 実際には、特定のコンテキスト (フレームやその子コントロール) 内で一意であれば、アプリケーション全体で一意である必要はありません。例えば、同じダイアログ内で複数回使うのでなければ、いくつものダイアログで wxID_OK
識別子を使用しても構いません。
ウィンドウのコンストラクタに wxID_ANY
を渡した場合、wxWidgets が自動的に識別子を生成します。コントロールの生成するイベントを処理しない場合や、イベントを一箇所で処理する場合 (このときはイベントテーブルに wxID_ANY
を指定するか、同じように wxEvtHandler::Bind を呼ぶ必要があります) など、コントロールの正確な識別子が必要でないときにこれは役立ちます。自動生成される識別子は常に負の値のため、必ず正の値でなければならないユーザ定義の識別子と衝突することはありません。
使用可能な標準識別子のリストは 標準イベント識別子 を参照してください。新たな識別子を定義する場合、wxID_HIGHEST 以上の値を使用することで安全に値を決定することができます。もしくは wxID_LOWEST 以下の識別子を使用することもできます。最後に、wxNewId() を使用して動的に識別子を割り当てることもできます。もしアプリケーション内で一貫して wxNewId() を使用するようにすれば、偶発的な識別子の衝突を避けることが可能です。
EVT_CUSTOM(event, id, func) | (wxEVT_SIZE といった) イベント識別子、ウィンドウ識別子、呼び出すメンバ関数を指定してカスタムイベントのエントリをイベントテーブルへ追加することができます。 |
EVT_CUSTOM_RANGE(event, id1, id2, func) | EVT_CUSTOM と同じですが、ウィンドウ識別子を範囲で指定できます。 |
EVT_COMMAND(id, event, func) | EVT_CUSTOM と同じですが、wxCommandEvent を引数に取るメンバ関数を指定します。 |
EVT_COMMAND_RANGE(id1, id2, event, func) | EVT_CUSTOM_RANGE と同じですが、wxCommandEvent を引数に取るメンバ関数を指定します。 |
EVT_NOTIFY(event, id, func) | EVT_CUSTOM と同じですが、wxNotifyEvent を引数に取るメンバ関数を指定します。 |
EVT_NOTIFY_RANGE(event, id1, id2, func) | EVT_CUSTOM_RANGE と同じですが、wxNotifyEvent を引数に取るメンバ関数を指定します。 |
イベントクラスの完全な一覧は イベントクラスグループ を参照してください。