スポンサーリンク

wxWidgetsでイベントに反応できるGUIアプリケーションを作る ~Bind関数の使い方~

記事内に広告が含まれています。

さて、第1回記事で、wxWidgetsを利用する上で押さえておくべき項目として、次の独断と偏見に満ちた7項目を挙げていました。

  1. main関数ではなく、wxApp::OnInit関数がエントリポイント(main関数を書かない)
  2. wxAppは抽象クラスとなっている
  3. wxIMPLEMENT_APP()に、wxAppの派生クラスを指定する
  4. wxDECLARE_APP()で、wxIMPLEMENT_APP()で使われている関数を宣言する
  5. wxFrameでウィンドウを作成する
  6. 途中でnewしたwxWidgetsのコントロール達は、途中でdeleteしなくてもいい
  7. wxWidgetsはイベント駆動型の設計になっている

このうちの7つ目が今回のメインテーマになります。

イベント駆動型の設計になっているとはどういうことかを説明してから、具体的な利用方法と解説していきます。

スポンサーリンク

イベント駆動型とそうでないもの

まずは、wxWidgetsとイベント駆動型プログラムの関係から説明します。

ですが、それを説明するためには「そもそもイベント駆動型とは何か?」を理解できている必要があるので、先にイベント駆動型プログラムについて説明します。

イベント駆動型のプログラムというものは、何かしらのイベントが起こったときに、そのイベントに応じた処理が起動されるようなタイプのプログラムを言います。

イベントは、プログラムが認識できる変化のことだ

「イベント」というのは、ざっくりと言えば、そのプログラムが認識できるコンピュータ上の変化一般のことです。もっと短く表現するなら、「出来事」ということになります。

そのイベントには色々な種類のものがあります。例えば、アプリケーションのユーザーが「インターネットからのファイルダウンロードが終了した」とか、「プリンタのインクが少なくなっている」といったように、システム外部で発生するものもあれば、「有限要素法解析が完了した」とか、「ビットコインのマイニングに成功した」とかみたいに、システム内部で発生するものもあります。

そのように、処理が終わった時に発生するようなものだけでなく、例えば「ユーザがキーボードのaキーを押した」とか「プリンタを接続した」といったように、人間が介在したことで発生するようなイベントもあります。

このように、どんな変化であれ、プログラムが「これはイベントだ」と認識するようになっているものは、すべてイベントだということになります。※ですから、プログラムがそもそも認識できないようなこと(例えば、「PCケースが傷ついた」とか、「PCに水がかかった」とか)はイベントにはなりえません。

では、wxWidgetsはどのような変化をイベントとして認識するのかという話ですが、例えば「ボタンがクリックされた」とか「メニューバーが選択された」といったようなものがwxWidgetsが認識できるイベントとして定義されています。

wxWidgetsはGUIを作るためのライブラリですから、ユーザが起こした何かしらのアクションを認識できるように、イベントとして定義されています。別な表現をすれば、色々な種類のユーザ入力がイベントとして定義されていると。

wxWidgetsには色々なイベントが定義されているわけですが、それらのイベントすべてを利用する必要はありません(すべてのイベントを使っても構いません。骨は折れるでしょうが)。利用したいと思うイベントだけを利用できます。利用したいイベントを選んで、その選んだイベントそれぞれに対して、こんな処理を起動してくれといった指示を出すことになります。

C++言語では、何かしらの処理は”関数”という形で定義して実装することになりますから、あるイベントに対して、そのイベントが発生したときに呼び出してほしい関数を設定することになります。

つまり、イベントと関数を結びつけるわけですな。

※イベントが発生したときに呼び出される関数のことを「イベントハンドラ」と呼びます。

以上をまとめると、wxWidgetsには、何かしらのイベントが発生したときに、そのイベントに紐づけられた関数を呼び出すような仕組みが組み込まれていると。そして、その関数をwxWidgetsユーザ(ここでの「ユーザ」はプログラマのことです。wxWidgetsを利用して作られたアプリケーションのユーザではないのでご注意ください)が独自に決めることができると。

「僕たちwxWidgetsユーザがイベントを利用する上で必要なこと」という視点で考えると、次の3つが必要だということになります。

  1. 監視する必要があるイベントを決める
  2. そのイベントが起こったときに呼び出される関数(イベントハンドラ)を定義する
  3. イベントそれぞれに対して、定義した関数を結びつける

という3つです。まぁ、2番については言わずもがなな気もしますが。

そして、この3つが正しく済んでいると、次のような流れで関数が呼び出されると。

  1. イベント発生をwxWidgetsが認識する
  2. wxWidgetsがそのイベントに紐づけられた関数を呼び出す
  3. ユーザが定義した関数の中に制御が移る

少し抽象的でわかりづらかったかもしれませんが、次の節からはより具体的な話になるのでご安心ください。

ソースコード

さて、以上の概念的な話を理解してもらったところで、実際にwxWidgetsを利用して、イベントと処理を結び付けてみます(まぁ、僕のミスで、前回時点のソースコードの中ですでにイベントを利用してしまっていましたが)。

今回は、ボタンを1つ作って、そのボタンが押されたらアプリケーションが終了するようにしています。つまり、「ボタンがクリックされる」というイベントと、「アプリケーションが終了する」というイベントを結び付けているわけですな。

ソースコードは以下の通りです。ただし、変更したのはMainFrame.hppとMainFrame.cppだけで、他のファイルは変更せずに済むため、また、この記事の話の本筋に関連するのはその2ファイルのみであるためという2つの理由から、ここではMainFrame.hppとMainFrame.cppのコードしか掲載しません。

次はMainFrame.hppのコードになります。

/********************************
* Frame.hpp
* メインウィンドウを表すフレームを定義する
*********************************/

#pragma once

#include <wx/menu.h>
#include <wx/frame.h>

// 使いたいIDは、すべてここで定義しておく
namespace ID {

enum class FileMenu : unsigned int
{
  QUIT = wxID_HIGHEST + 1,
  NUM
};

enum class Button : unsigned int
{
  BUTTON1 = (unsigned int)FileMenu::NUM + 1,
  NUM
};

}; // ID

class MainFrame : public wxFrame
{
private:

public:
  MainFrame( const wxString & title, const wxPoint & pos, const wxSize & size );
  virtual ~MainFrame();

  void onSelectedQuit( wxCommandEvent & evt );    // quit を呼び出す
  void quit( wxCloseEvent & event );
};

次がMainFrame.cppのコードになります。

#include "MainFrame.hpp"
#include <wx/frame.h>
#include <wx/menu.h>
#include <wx/statusbr.h>
#include <wx/button.h>

// 委譲コンストラクタで、親クラスである wxFrame に値を渡す
MainFrame::MainFrame( const wxString & title, const wxPoint & pos, const wxSize & size )
: wxFrame( nullptr, wxID_ANY, title, pos, size )
{
  /* メインウィンドウに必要な要素を作る */
  // メニューバーのファイル項目に配置する選択肢を作る。
  wxMenu* file_items = new wxMenu();   // 作った選択肢を管理するためのインスタンスを作成
  file_items -> Append( static_cast<int>(ID::FileMenu::QUIT), wxT("&Quit"), wxT("Quit this application") ); // Quit という選択肢を作成

  // メニューバーを作る
  wxMenuBar* menu_bar = new wxMenuBar();  // メニューバーを管理するためのインスタンスを作成
  menu_bar -> Append( file_items, wxT("&File") ); // File というメニュー項目を作成

  SetMenuBar( menu_bar );   // このウィンドウ上のメニューバーとして、 menu_bar を表示するように伝える

  // ボタンを作る
  wxButton* button_plus = new wxButton( this, static_cast<int>(ID::Button::BUTTON1), wxT("quit") );

  // メニューバーの Quit が押されたというイベントと onSelectedQuit 関数を結びつける。
  // Quit が押されると onSelectedQuit が呼び出されるようにする。
  Bind( wxEVT_MENU, &MainFrame::onSelectedQuit, this, static_cast<int>(ID::FileMenu::QUIT) );

  // ウィンドウの破棄と、このクラスの quit 関数を紐づける
  // ウィンドウの破棄というイベントが発生したときに、このクラスのインスタンスの quit 関数が呼び出されるようになる
  Bind( wxEVT_CLOSE_WINDOW, &MainFrame::quit, this );

  // ボタンが押されたというイベントとonSelectedQuit関数を結びつける
  Bind( wxEVT_BUTTON, &MainFrame::onSelectedQuit, this, static_cast<int>(ID::Button::BUTTON1) );
}

MainFrame::~MainFrame(){}

void MainFrame::onSelectedQuit( wxCommandEvent & evt )
{
  Close( true );    // ウィンドウを閉じる
}

void MainFrame::quit( wxCloseEvent & event )
{
  Destroy();  // ウィンドウを破棄する
}

これら2つのコードを理解できたら、この記事の目標はクリアということになります。

次の節と次の次の節という2つの節で、このソースコードを解説していきます。

ですが、解説したいこととして、イベント関連の話題と、イベントには特に関係ないものの、ソースコードの理解には役立つ話題の2つがあるので、その内の前者を次の節で、後者を次の次の節で解説しています。

個人的にはイベント関連の話題が先にあった方が、枝葉末節に惑わされることなく、「wxWidgetsでのイベントの使い方を理解する」という話の本筋を理解しやすいかなと思ったのでそのような順序にしましたが、内容として順序関係は無い(次の節と次の次の節の内容に依存関係は無い)ので、どちらでもお好きな方からお読みくださいませ~。

スポンサーリンク

イベントと関数の紐づけ

まず、MainFrameクラス上でnewすることで、ボタンを作っています。コントロールは、配置したい画面上でnewすれば作ることができるというのは前回説明しました。

そして、そのボタンに関数(イベントハンドラ)を結びつけている部分が34行目のBind(…)という行になります。

Bind関数にはBind関数自体のリファレンスだけでなく、公式による解説もあるので、そちらも併せてご覧くださいませ~。

イベントと関数を紐づける方法

リファレンスを見ていただければ分かる通り、Bind関数はwxEveHandlerというかなり上位のクラスに定義された関数になります。なので、wxWidgets中のよく利用するクラスにはほとんどBind関数が定義されていると考えてもいいと思います。

34行目の前にも2つほどBind関数が使われていますが、どちらも同じようにイベントと関数を結びつけています。ですが、ここではボタンに対するBind(つまり34行目)を説明していきます。ここからの話は特に断りがない限りは、34行目のコードについて、そしてボタンに関数を結びつけることについての話だと考えてください。

このBind関数ですが、第一引数には対象としたいイベントを指定します。そのため、「ボタンがクリックされた」というイベントを指定することになります。そのイベントにはwxEVT_BUTTONという名前が付けられているので、第一引数にはwxEVT_BUTTONを指定しています。

第二引数にはイベントハンドラを指定します。ここで、アプリケーションを終了する関数を指定することになります。メンバ関数を指定したいので、&MainFrame::onSelectedQuitという指定をしています。

この辺りの事情がよく分からない方は、「c++ 関数ポインタ」とか「c++ メンバ関数 関数ポインタ」みたいなキーワードでネット検索してください。

続く第三引数では、自分自身のアドレス(つまり、MainFrameのインスタンスの先頭のアドレス)を指定しています。イベントハンドラとしてメンバ関数を指定する場合、メンバ関数を持っているインスタンスのアドレスも必要になりますから、この第三引数で指定するわけです。

続く第四引数で、特にどのコントロールが発生させたイベント化を指定しています。例えば、ボタンが2つあったとしましょう。第四引数を指定しない状態では、wxWidgetsにしてみれば、どちらのボタンに対するイベントを処理すればいいのかが分かりません。

それを解決するために、ボタンにあらかじめIDを割り当てておき、そのIDを指定することで、「”ボタンクリック”というイベントでも、特にこのIDを持っているボタンが発生させたイベント」というような指定をしてやる必要があります。

この第四引数は必ずしも必要ではありませんが、最終的に作りたいアプリケーションではボタンを複数個作る予定なので、第四引数も指定しています。

なお、ここでは解説しませんが、27行目と31行目のBind関数の行についても、同様の意味になります。つまり、27行目と31行目はそれぞれwxEVT_MENU、wxEVT_CLOSE_WINDOWというイベントと、それ以降の引数に指定されているメンバ関数とを結びつけています。

ここまでがイベントハンドラをイベントに紐付ける方法でした。次は、イベントハンドラを作るときに気を付けなければいけないことになります。

イベントハンドラの作り方

何度も確認していますが、イベントハンドラとは、「イベントが起きたときに呼び出される関数」のことです。

wxWidgetsでこのイベントハンドラを定義するには、まず引数にwxCommandEvent&を引数に取る関数を定義します。そして、その関数の中に具体的な処理を定義します。

その関数を先ほどのBind()関数に渡して、イベントと紐づければイベントハンドラの完成です。

ポイントは「wxCommandEvent&を引数に取る関数を定義する」という部分ですね。

スポンサーリンク

周辺的な部分の解説

wxWidgetsでのイベントハンドラの作り方には関係ない変更ではあるものの、前回記事のコードから変更した部分ではあるので、一応解説しておこうといったような節です。wxWidgetsの使い方を理解するために必要不可欠な内容ではありません。

なので、この節は読まなくてもあまり問題はありません。

前回記事のソースコードでは、今回記事のソースコードでのnamespace IDのブロックに当たる部分は次のようにしていました。

enum class EventID : unsigned int
{
  FILE_MENU_QUIT = wxID_HIGHEST + 1
};

これを、次のように変更しました。

// 使いたいIDは、すべてここで定義しておく
namespace ID {

enum class FileMenu : unsigned int
{
  QUIT = wxID_HIGHEST + 1,
  NUM
};

enum class Button : unsigned int
{
  BUTTON1 = (unsigned int)FileMenu::NUM + 1,
  NUM
};

}; // ID

なぜこのように変更したのかを説明していくのがこの節の内容になります。

このように変更した理由を、その思考過程を辿るというようなストーリー形式で説明していきます。

EventIDの構造を変更した

EventIDの構造を変更した主な理由は、「EventIDという名前があまり適切ではないと思ったから」というのが、EventIDの構造変更の理由になります。

そもそも、この列挙型を定義している理由は「Bind()関数で利用するため」になります。つまり、同じイベントでも、どのコントロールが発生させたイベントなのかを識別するために定義しています。

そのため、ある意味ではイベントを割り当てるためのIDという意味でEventIDとしていました。

しかし、仮にこの列挙型で定義される数値がBind関数の引数に指定するためにしか使われなかったとしても、この数値の本来の目的はコントロールを識別するためだよなと思いました。

つまり、Bind関数の引数に指定して、イベントをより細かく識別するための数値というよりも、各コントロールに通し番号を割り当てるための数値という意味合いの方が強いような気がしてきました。

そのため、コントロールに対する通し番号なのであれば、EventIDというよりもどちらかと言えばControlIDとかの方が名前としては適切だと思いました。

つまり、次のようにするのが妥当だと思いました。

enum class ControlID : unsigned int
{
  FILE_MENU_QUIT = wxID_HIGHEST + 1
};

ただ、ControlIDとすると範囲が広すぎて管理が難しくなりそうだったので、「コントロールの中でも、特に”メニューバーのファイル項目”というコントロール」や「コントロールの中でも、特に”1つ目のボタン”というコントロール」というような構造にしたいと思いました。

そこで、連続した整数値をいくつかに分割して、FileMenuIDや、ButtonIDといったような名前で管理したいと思いました。

つまり、次のようにしたいと思いました。

enum class FileMenuID : unsigned int {
  FILE_MENU_QUIT = wxID_HIGHEST + 1,
  NUM
};

enum class ButtonID : unsigned int {
  BUTTON1 = (unsigned int)FileMenuID::NUM + 1,
  NUM
}

これで、コントロールのIDという意味を残しつつ、細かな単位に分けることができました。

そして、各列挙型の中にあるNUMは利用する予定がないものの、間違って指定すると変なバグが置きそうだったので、ButtonID::BUTTON1はFileMenuID::NUMに1を足した数としました。

ここでは、BUTTON1はQUITという名前にした方が良かったかもしれませんが、最終的にはPLUSとかMINUSみたいに変更する予定なので、この名前でもいいかなと(とは言え、結局変更するのであれば、この記事ではQUITにしておいても特に問題は無かった、というか、むしろQUITの方が良かったなと少し後悔)。

基本的な構造変更は以上です。ですが、これだけではまだ構造が少しわかりづらいなと思ったので、namespaceを追加しました(ついでに名前空間の汚染も防げますし)。

同じ領域を扱っているという意図を記述したかった

上のような変更だけでも良かったのですが、それらの列挙型はどちらも「通し番号」という意味を持っています。「通し番号を割り当てるための整数値を定義したい」という問題を解決するという意味では、同じ領域の問題を扱っています。

そのため、同じ領域の問題を扱っているという意味でnamespaceを追加しました。そうすれば、一目見れば「ああ、これは同じ問題領域を扱っているんだな」と理解できるからです。

そして最終的に、上にも書いたような形に(ひとまず)落ち着きました。

※namespaceを利用すれば名前空間が汚染されずに済むという話は、すでにいろいろなサイトで説明されていますので割愛します。

 

以上が、イベントハンドラの作り方を理解するという話の本筋にはあまり関係ないような話題でした。要するに、無駄なだけの自己満足でした。

まとめ

さて、イベントハンドラの作り方を解説してきましたが、その方法は次の通り。

  1. wxCommandEvent&を引数に取る関数を定義する
  2. その関数とイベントをBind関数で紐づける

という2つでした。そして、Bind関数には起動したい関数の名前とイベントの2つだけでなく、その関数を持っているインスタンスのポインタ、イベントを発生させたコントロールのIDも併せて指定する必要がありました。

ということで、これでwxWidgetsを利用したアプリケーションは一応作れるようになったことになります。

ここまでの話だけでも、結構色々と作れると思うので、好きなコントロールを配置したり、好きな処理を追加したりと、遊んでみてはいかがでしょうか。

ということで、今回はこの辺で。ではでは~

タイトルとURLをコピーしました