クラス: wxEvtHandler, wxWindow, wxEvent
イントロダクション
イベント処理の仕組み
ユーザの生成したイベントとプログラム的に生成したイベント
動的イベントハンドラ
ウィンドウ識別子
イベントマクロの概要
カスタムイベントの概要
wxWidgets 2.0 より前のバージョンでは、イベント処理はコールバック関数か、OnSize といった仮想メンバ関数のオーバーライドで実現されていました。
wxWidgets 2.0 からは一部の例外を除き、代わりに イベントテーブル が使用されています。
イベントテーブルはソースファイルに配置され、wxWidgets にイベントとメンバ関数のマッピング方法を知らせます。これらのメンバ関数は非仮想関数ですが、すべて同じ形式をとります: wxEvent を継承した引数をひとつ取り、戻り値の型は void です。
イベントテーブルの例を以下に示します。
BEGIN_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) END_EVENT_TABLE()最初の 2 つのエントリはメニューコマンドを異なる 2 つのメンバ関数にマッピングしています。EVT_SIZE マクロはウィンドウ識別子を必要としません。なぜなら、通常は現在のウィンドウのリサイズイベントにのみ関心があるはずだからです。
EVT_BUTTON マクロはイベントの発生元がウィンドウクラスである必要がないことを表しています。ウィンドウ階層をたどりながらイベントテーブルを検索するため、イベントの発生元がフレーム内のパネルにあるボタンである場合でも動作します。この場合、まずボタンのイベントテーブルが検索され、続いて、その親パネル、フレームの順に検索されます。
前に述べたように、イベントを処理するメンバ関数は仮想関数である必要はありません。実際に、イベントテーブルはメンバ関数が仮想関数であることを無視する、つまり、仮想メンバ関数を継承クラスでオーバーライドしてもなにも影響を及ぼしません。そのため、メンバ関数を仮想関数にするべきではありません。これらのメンバ関数はイベント引数を取りますが、その引数のクラスはイベントの型と発生元ウィンドウのクラスに応じて異なります。リサイズイベントでは wxSizeEvent が使用されます。メニューコマンドと (ボタン押下といった) 大半のコマンドコントロールでは wxCommandEvent が使用されます。より複雑なコントロールのときには特定のイベントクラスが使用されます。例えば、wxTreeCtrl から発生するイベントには wxTreeEvent が使用されます。
イベントテーブルをソースファイルに配置するように、クラス宣言のどこかに DECLARE_EVENT_TABLE マクロを配置しなければなりません。以下に例を示します:
class MyFrame : public wxFrame
{
public:
...
void OnExit(wxCommandEvent& event);
void OnSize(wxSizeEvent& event);
protected:
int m_count;
...
DECLARE_EVENT_TABLE()
};
このマクロはクラスの任意の場所 (public、protected、private) に配置することができますが、おそらく例示のように最後に挿入する方が良いでしょう。なぜなら、このマクロは暗黙的にアクセス制御を protected に変更してしまうためです。
最後に、マクロを使ってイベントテーブルを静的に初期化したくない場合、wxEvtHandler::Connect を使用して動的に (実行時に) イベントとイベントハンドラを接続することもできます。これを行なっているサンプルとして イベントサンプル を参照してください。
ウィンドウからイベントを受信したとき、wxWidgets はイベントを生成したウィンドウに属する最初のイベントハンドラオブジェクトの wxEvtHandler::ProcessEvent を呼び出します。
wxWidgets の行うイベント処理の仕組みは通常の C++ の仮想関数にとても近いことに着目してください。つまり、イベント処理関数をオーバーライドすることでクラスの振る舞いを変更できるということです。ネイティブコントロールの振る舞いを変更するような場合にも、これは動作します。例えば、ネイティブテキストコントロールに送られるキーイベントをフィルタリングする場合、wxTextCtrl をオーバーライドし、EVT_KEY_DOWN を使用してキーイベントハンドラを定義することで実現できます。実際には、これはネイティブコントロールに送られるキーイベントをすべて横取りしますが、これはおそらく期待する動作ではないと思います。この場合、イベントハンドラの検索を続行させるためにイベントハンドラ関数で Skip() を呼ぶ必要があります。
要約すると、C++ の仮想関数で行うように基底クラスの関数を直接呼ぶ (つまり、wxTextCtrl::OnChar() を呼ぶ) 代わりに、 Skip を呼ぶようにしてください。
実際に、テキストコントロールで 'a' から 'z' と 'A' から 'Z' までのみを受け付ける場合、以下のようになります。
void MyTextCtrl::OnChar(wxKeyEvent& event)
{
if ( isalpha( event.KeyCode() ) )
{
// キーコードが正しい範囲に入っている。wxWidgets の規定クラスや
// ネイティブコントロールでもイベントを処理するため、
// event.Skip() を呼び出す。
event.Skip();
}
else
{
// 対象外のキーが押下された。このイベントを他の場所で処理することはないため、
// event.Skip() は呼び出さない。
wxBell();
}
}
ProcessEvent によるイベントテーブルの検索は通常、以下の順番で行われます:
ステップ 5 に注意してください。 wxWidgets のイベント処理システムの持つこの強力な機能はしばしば見落とされたり、人を混乱させたりします。別の言い方をすると、伝播するように設定されたイベント (参照: wxEvent::ShouldPropagate) (よくあるのは wxCommandEvent を直接、または間接的に継承したイベントです) は最大回数、伝播するか event.Skip() を呼んでいないイベントハンドラが見つかるまで、子から親へコントロール階層をさかのぼっていきます。
最後に、他にも複雑な仕組みがあります (実際には、これは wxWidgets プログラマの生活を非常にシンプルにしてくれるものです): コマンドイベントが親ウィンドウへ伝播するとき、親ダイアログまで到達した時点で伝播が終了します。これはつまり、モーダルダイアログを表示しているときにダイアログコントロールから意図しないイベントを受け取ることがないことを意味します。 (それらのイベントは処理されないままになるでしょう) しかし、通常のイベントはフレームを超えて伝播します。このようにした理由は、普通のアプリケーションでは数個のフレームしか存在せず、プログラマはそれらの親子関係のことをよく理解している一方で、複雑なプログラムで表示されるすべてのダイアログを追跡することは、不可能でないにしても非常に難しいためです。 (いくつかのダイアログは wxWidgets によって自動的に生成されることを思い出してください) なんらかの理由で別の振る舞いをさせる必要がある場合、明示的に SetExtraStyle(wxWS_EX_BLOCK_EVENTS) を使用することでイベントが指定されたウィンドウ外へ伝播するのを止めたり、ダイアログ (デフォルトでこのフラグが設定されています) のフラグを解除したりできます。
(リサイズ、移動、描画、マウスイベント、キーボードイベントといった) ウィンドウに関係する一般的なイベントはウィンドウに対してのみ送られます。(ボタンクリック、メニュー選択、ツリーの展開といった) 高水準で、ウィンドウ自身が生成するイベントはコマンドイベントと呼ばれ、そのイベントを処理対象とする場合に親コントロールに対して送られます。
ProcessEvent をオーバーライドすることでイベント処理をリダイレクトしたいと思うかもしれません。これは例えば、ドキュメント/ビューフレームワークがドキュメントやビューでイベントハンドラを定義するために行います。コマンドイベントかどうか (おそらくリダイレクト対象のイベントかどうかだと思いますが) を調べるため、遅い実行時型情報の代わりに wxEvent::IsCommandEvent を使用することができます。
上で述べたように、コマンドイベントだけが親コントロールのイベントハンドラで再帰的に処理されます。このことについて混乱する人が多いため、親コントロールのイベントハンドラへ送信 "されない" システムイベントの一覧を以下に示します。
wxEvent | イベントの基底クラス |
wxActivateEvent | ウィンドウまたはアプリケーションのアクティブ化イベント |
wxCloseEvent | ウィンドウの終了、またはセッションの終了イベント |
wxEraseEvent | 背景の消去イベント |
wxFocusEvent | ウィンドウフォーカスイベント |
wxKeyEvent | キー押下イベント |
wxIdleEvent | アイドルイベント |
wxInitDialogEvent | ダイアログの初期化イベント |
wxJoystickEvent | ジョイスティックイベント |
wxMenuEvent | メニューイベント |
wxMouseEvent | マウスイベント |
wxMoveEvent | 移動イベント |
wxPaintEvent | 描画イベント |
wxQueryLayoutInfoEvent | レイアウト情報の問い合わせに使用される |
wxSetCursorEvent | 現在のマウス位置に基づいた特殊なカーソル処理に使用される |
wxSizeEvent | リサイズイベント |
wxScrollWinEvent | (スクロールバーではなく) スクロール可能なウィンドウから送信されるスクロールイベント |
wxSysColourChangedEvent | システムカラーの変更イベント |
例えばダイアログ内のネイティブコントロールへ送信される (そのコントロール自身では使用しない) キーイベントなど、特定のシステムイベントを親ウィンドウで取得したいと思うことがあるかもしれません。この場合、すべてのイベント (もしくはその中の特定のイベント) を親ウィンドウへ渡すようにイベントハンドラの ProcessEvent() をオーバーライドする必要があります。
一般的に wxEvents はユーザ操作 (wxWindow のリサイズなど) と関数呼び出し (wxWindow::SetSize など) のどちらからでも発生しますが、wxCommandEvent を継承したイベントはユーザが操作した場合のみ、wxWidgets コントロールから送信されます。このルールの 例外 は以下のとおりです:
wxNotebook::AddPage | イベントを送信しない代替方法を持たない |
wxNotebook::AdvanceSelection | イベントを送信しない代替方法を持たない |
wxNotebook::DeletePage | イベントを送信しない代替方法を持たない |
wxNotebook::SetSelection | wxNotebook::SetSelection は非推奨のため、代わりに wxNotebook::ChangeSelection を使用してください。 |
wxTreeCtrl::Delete | イベントを送信しない代替方法を持たない |
wxTreeCtrl::DeleteAllItems | イベントを送信しない代替方法を持たない |
wxTreeCtrl::EditLabel | イベントを送信しない代替方法を持たない |
wxTextCtrl のすべての関数 | wxTextCtrl::SetValue の代わりに wxTextCtrl::ChangeValue を使用できますが、Replace や WriteText といった他の関数にはイベントを送信せずに同じことを行う関数はありません。 |
実際のところ、ウィンドウクラスを継承して新しいクラスを作成したくない場合は、必ずしもそうする必要はありません。代わりに wxEvtHandler を継承した新しいクラスを作成して適切なイベントテーブルを定義し、wxWindow::SetEventHandler (か、より望ましいのは wxWindow::PushEventHandler) を呼び出してこのイベントテーブルを使用するようにできます。この方法を使うことで、たくさんの継承クラスを作成しなくてすむとともに、同じイベントハンドラクラスのインスタンスを異なるウィジェットクラスのインスタンスから利用することができます。 (ただし、異なるオブジェクトを同じイベントハンドラオブジェクトとして複数回使用するべきではありません) もし手動でウィンドウのイベントハンドラを呼ぶ必要がある場合、GetEventHandler 関数でウィンドウのイベントハンドラを取得し、そのメンバ関数を呼び出してください。SetEventHandler や PushEventHandler を使用してイベント処理をリダイレクトしていない場合、デフォルトでは GetEventHandler はウィンドウ自身を返却します。
PushEventHandler の使用法のひとつは、一時的または永続的に GUI の振る舞いを変更することです。例えば、ダイアログの外見を変更するためのダイアログエディタをアプリケーション内で起動したいと思うかもしれません。その場合、既存のダイアログの振る舞いを元に戻す前にすべての入力を取得し、"その場で" 編集することができます。そのため、アプリケーションの振る舞いをカスタマイズするために新しいクラスを継承していたとしても、あなたのユーティリティから自由に振る舞いを変更することができます。これはオンラインチュートリアルでも有用なテクニックです。レッスンから外れることなく、ユーザに一連のステップを実行させることができます。ここでは、ボタンやウィンドウから送られてくるイベントを検証し、それが適用可能なものである場合に元のイベントハンドラへ渡す、ということを行えます。他のイベントハンドラとは異なる範囲のイベントを独立して処理するようなイベントハンドラを作成する場合に PushEventHandler/PopEventHandler を使用してください。
ウィンドウ識別子とは、イベントシステムにおいてウィンドウを一意に識別するための整数値です。 (とはいえ、他の用途に使用することもできます) 実際には、特定のコンテキスト (フレームやその子コントロール) 内で一意であれば、アプリケーション全体で一意である必要はありません。例えば、同じダイアログ内で複数回使うのでなければ、いくつものダイアログで wxID_OK 識別子を使用しても構いません。
ウィンドウのコンストラクタに wxID_ANY を渡した場合、wxWidgets が自動的に識別子を生成します。コントロールの生成するイベントを処理しない場合や、イベントを一箇所で処理する場合 (このときはイベントテーブルに wxID_ANY を指定するか、それと同様に wxEvtHandler::Connect を呼ぶ必要があります) など、コントロールの正確な識別子が必要でないときにこれは役立ちます。自動生成される識別子は常に負の値のため、必ず正の値でなければならないユーザ定義の識別子と衝突することはありません。
標準識別子には以下のものが用意されています。新たな識別子を定義する場合、wxID_HIGHEST 以上の値を使用することで安全に値を決定することができます。もしくは wxID_LOWEST 以下の識別子を使用することもできます。
#define wxID_ANY -1 #define wxID_LOWEST 4999 #define wxID_OPEN 5000 #define wxID_CLOSE 5001 #define wxID_NEW 5002 #define wxID_SAVE 5003 #define wxID_SAVEAS 5004 #define wxID_REVERT 5005 #define wxID_EXIT 5006 #define wxID_UNDO 5007 #define wxID_REDO 5008 #define wxID_HELP 5009 #define wxID_PRINT 5010 #define wxID_PRINT_SETUP 5011 #define wxID_PREVIEW 5012 #define wxID_ABOUT 5013 #define wxID_HELP_CONTENTS 5014 #define wxID_HELP_COMMANDS 5015 #define wxID_HELP_PROCEDURES 5016 #define wxID_HELP_CONTEXT 5017 #define wxID_CUT 5030 #define wxID_COPY 5031 #define wxID_PASTE 5032 #define wxID_CLEAR 5033 #define wxID_FIND 5034 #define wxID_DUPLICATE 5035 #define wxID_SELECTALL 5036 #define wxID_DELETE 5037 #define wxID_REPLACE 5038 #define wxID_REPLACE_ALL 5039 #define wxID_PROPERTIES 5040 #define wxID_VIEW_DETAILS 5041 #define wxID_VIEW_LARGEICONS 5042 #define wxID_VIEW_SMALLICONS 5043 #define wxID_VIEW_LIST 5044 #define wxID_VIEW_SORTDATE 5045 #define wxID_VIEW_SORTNAME 5046 #define wxID_VIEW_SORTSIZE 5047 #define wxID_VIEW_SORTTYPE 5048 #define wxID_FILE1 5050 #define wxID_FILE2 5051 #define wxID_FILE3 5052 #define wxID_FILE4 5053 #define wxID_FILE5 5054 #define wxID_FILE6 5055 #define wxID_FILE7 5056 #define wxID_FILE8 5057 #define wxID_FILE9 5058 #define wxID_OK 5100 #define wxID_CANCEL 5101 #define wxID_APPLY 5102 #define wxID_YES 5103 #define wxID_NO 5104 #define wxID_STATIC 5105 #define wxID_HIGHEST 5999
イベントクラスごとのマクロ
このドキュメントではイベントマクロをイベントクラスごとに分類しています。詳細は各章を参照してください。
wxActivateEvent | EVT_ACTIVATE、EVT_ACTIVATE_APP マクロはアクティブ化、非アクティブ化イベントを捉えます。 |
wxCommandEvent | よく使われる一連のコントロールイベント。 |
wxCloseEvent | EVT_CLOSE マクロは wxWindow::Close によるウィンドウの終了を処理します。 |
wxDropFilesEvent | EVT_DROP_FILES マクロはファイルドロップイベントを処理します。 |
wxEraseEvent | EVT_ERASE_BACKGROUND マクロはウィンドウの消去要求を処理するために使用されます。 |
wxFocusEvent | EVT_SET_FOCUS、EVT_KILL_FOCUS マクロはキーボードフォーカスイベントを処理するために使用されます。 |
wxKeyEvent | EVT_CHAR、EVT_KEY_DOWN、EVT_KEY_UP マクロは任意のウィンドウのキーボード入力を処理します。 |
wxIdleEvent | EVT_IDLE マクロはアプリケーションのアイドルイベントを処理します。 (例えば、バックグラウンド処理などに使用します) |
wxInitDialogEvent | EVT_INIT_DIALOG マクロはダイアログの初期化イベントを処理するために使用されます。 |
wxListEvent | これらのマクロは wxListCtrl のイベントを処理します。 |
wxMenuEvent | これらのマクロは特殊なメニューイベント (メニューコマンドではありません) を処理します。 |
wxMouseEvent | マウスイベントマクロは個別のマウスやすべてのマウスのイベントを処理することができます。 |
wxMoveEvent | EVT_MOVE マクロはウィンドウの移動を処理するために使用されます。 |
wxPaintEvent | EVT_PAINT マクロはウィンドウの描画要求を処理するために使用されます。 |
wxScrollEvent | これらのマクロは wxScrollBar、wxSlider、wxSpinButton から送信されるスクロールイベントを処理するために使用されます。 |
wxSetCursorEvent | EVT_SET_CURSOR マクロは特殊なカーソル処理のために使用されます。 |
wxSizeEvent | EVT_SIZE マクロはウィンドウのりサイズを処理するために使用されます。 |
wxSplitterEvent | EVT_SPLITTER_SASH_POS_CHANGED、EVT_SPLITTER_UNSPLIT、EVT_SPLITTER_DCLICK マクロはさまざまなウィンドウ分割イベントを処理するために使用されます。 |
wxSysColourChangedEvent | EVT_SYS_COLOUR_CHANGED マクロはユーザによるシステムカラーの変更イベントを処理するために使用されます。 (Windows のみ) |
wxTreeEvent | これらのマクロは wxTreeCtrl のイベントを処理します。 |
wxUpdateUIEvent | EVT_UPDATE_UI マクロはアプリケーションがメニュー、ツールバー、コントロールの見た目の状態を変更するための擬似イベントを処理するために使用されます。 |
一般的なアプローチ
wxWidgets 2.2.x から 実行時に 決定される ID を使用してイベントの型を識別します。これにより、ID の衝突を起こす (異なるふたつのイベント型が同じイベント ID を取得してしまう) ことなく、新しいイベント型をライブラリやアプリケーションに追加することができます。このイベント ID は const wxEventType 型の構造体に格納されます。
新しいイベント型を定義する方法として、主に 2 種類の選択肢があります。ひとつは完全に新しいイベントクラスを定義することです。 (通常は wxEvent か wxCommandEvent を継承します) もう一方の方法は、既存のイベントクラスを使用し、それらに新しいイベント型を与える方法です。どちらの方法を使うにせよ、新しいイベント型の定義と宣言が必要となりますが、それには以下のマクロを使用します:
// ヘッダファイルで使用する BEGIN_DECLARE_EVENT_TYPES() DECLARE_EVENT_TYPE(name, value) END_DECLARE_EVENT_TYPES() // ソースファイルで使用する DEFINE_EVENT_TYPE(name)DECLARE_EVENT_TYPE の value 引数はイベント ID を明示的に指定していた wxWidgets 2.0.x との後方互換性のために使用されているだけなので、無視することができます。
コードの書き方とカスタムイベント型の動作例として イベントサンプル も参照してください。
既存のイベントクラスの使用
新しいイベント型で wxCommandEvent を使用したいだけの場合、自分自身で新しいマクロを定義するのではなく、以下の汎用的なイベントテーブルマクロを使用することができます。この方法には、スレッド間でイベントを送信するための新しい wxEvent::Clone() 関数を定義する必要がないといった利点もあります。以下にコード例を示します。
DECLARE_EVENT_TYPE(wxEVT_MY_EVENT, -1)
DEFINE_EVENT_TYPE(wxEVT_MY_EVENT)
// イベントを捕まえるユーザコード
BEGIN_EVENT_TABLE(MyFrame, wxFrame)
EVT_MENU (wxID_EXIT, MyFrame::OnExit)
// ....
EVT_COMMAND (ID_MY_WINDOW, wxEVT_MY_EVENT, MyFrame::OnMyEvent)
END_EVENT_TABLE()
void MyFrame::OnMyEvent( wxCommandEvent &event )
{
//処理を行う
wxString text = event.GetText();
}
// イベントを送信するユーザコード
void MyWindow::SendEvent()
{
wxCommandEvent event( wxEVT_MY_EVENT, GetId() );
event.SetEventObject( this );
// 何か値を設定する
event.SetText( wxT("Hallo") );
// 送信する
GetEventHandler()->ProcessEvent( event );
}
汎用的なイベントテーブルマクロ
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 を引数に取るメンバ関数を指定します。 |
独自のイベントクラスの定義
(より複雑なデータをある場所から別の場所へ送るといった) 特定の状況下では独自のイベントクラスの定義が必要になるでしょう。イベントクラスの定義とは別に、独自のイベントテーブルマクロも定義する必要があります。 (そしてこれは非常に長くなります) 継承したイベント関数へのキャストが必要なことも忘れないでください。wxPlot ライブラリから抜粋した例を以下に示します。このコードは wxWidgets のソースコードの contrib セクションにあります。
// イベントを定義するコード
class wxPlotEvent: public wxNotifyEvent
{
public:
wxPlotEvent( wxEventType commandType = wxEVT_NULL, int id = 0 );
// アクセサ
wxPlotCurve *GetCurve()
{ return m_curve; }
// wxPostEvent() で必要になる
wxEvent* Clone();
private:
wxPlotCurve *m_curve;
};
DECLARE_EVENT_TYPE( wxEVT_PLOT_ACTION, -1 )
typedef void (wxEvtHandler::*wxPlotEventFunction)(wxPlotEvent&);
#define EVT_PLOT(id, fn) \
DECLARE_EVENT_TABLE_ENTRY( wxEVT_PLOT_ACTION, id, -1, \
(wxObjectEventFunction) (wxEventFunction) (wxCommandEventFunction) (wxNotifyEventFunction) \
wxStaticCastEvent( wxPlotEventFunction, & fn ), (wxObject *) NULL ),
// イベント型とイベントクラスを定義するコード
DEFINE_EVENT_TYPE( wxEVT_PLOT_ACTION )
wxPlotEvent::wxPlotEvent( ...
// イベントを捕まえるユーザコード
BEGIN_EVENT_TABLE(MyFrame, wxFrame)
EVT_PLOT (ID_MY_WINDOW, MyFrame::OnPlot)
END_EVENT_TABLE()
void MyFrame::OnPlot( wxPlotEvent &event )
{
wxPlotCurve *curve = event.GetCurve();
}
// イベントを送信するユーザコード
void MyWindow::SendEvent()
{
wxPlotEvent event( wxEVT_PLOT_ACTION, GetId() );
event.SetEventObject( this );
event.SetCurve( m_curve );
GetEventHandler()->ProcessEvent( event );
}