ホーム < ゲームつくろー! < IKD備忘録

wxWidgets
wxWidgetsを使ってウィンドウを表示する

(2010. 5. 3)


 DirectXでGUIが欲しくなったのですが、自前で全部作るのはとにかく大変。「何とかして〜」と泣きついたら、「wxWidgetsで何とかなるんでねけ?」と教えてもらいました。そこでwxWidgetsをお勉強です。実際試してみると、なるほど、はめが沢山あります(笑)。はまらないように備忘録を付けておきます。



@ サイトとダウンロード

 wxWidgetsのホームページはこちらです:
http://www.wxwidgets.org/(wxWidgets Cross-Platform GUI Library)

 Downloadタブを辿って最新版をDLしてきます。wxWidgetsはマルチプラットフォーム対応です。今回はWindows版の開発となりますので「wxMSW」をDLします。

 DLしたインストーラを起動してライブラリをインストールします。デフォルトではC:/wxWidgets-2.8.11(フォルダはバージョンによって変わります)に各種ファイルが置かれます。この辺りはお決まりなのでサクっと流しますね。



A 最初の一歩はここから

 で、ここからです。使い方はさっぱりわかりません。Google先生に訪ねてみると、なるほど、最初にVC用のライブラリを作る必要があるようです。

 まず、build\mswにあるwx/wx.dswワークスペースを起動します。これはVC6++用なので、VS2005やVS2008などで開くと変換を求められます。これは変換して下さい。ただし、ここでハメその1!Visual Studio 2008で変換するとVisual Studio 2008で使えるライブラリが出来るようです。VS 2008で作成したライブラリを使ってVS2005で開発すると、デバッグ版で「デバッグ情報が壊れています」というエラーが出てしまい、二進も三進もいかなくなってしまいます。どのバージョンのVSで開発するかを考えてライブラリを作成するようにして下さい。

 ライブラリの作成自体は極めて簡単で、変換後にそのままビルドするだけです(Debug/Release共に)。これで全プロジェクトからライブラリが作られ
lib/vc_libフォルダに格納されます。このフォルダを後々リンクフォルダとして指定することになります。

 さて、では早速簡単なプログラムを組んで環境テストです。私はVS2008EEでライブラリを作ったので、テストも同じ環境で作成します。wxWidgetsのHPにあったTutorials(右側のQuick Linksのすぐ下)の'Hello world' in wxWidgets: A Very Short Tutorialによると、新規のプロジェクトを立ち上げて「wx/wx.h」をインクルードするようです。ほうほう、こういう感じね:

#include "wx/wx.h"
#include <windows.h>
#include <tchar.h>


int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
   return 0;
}

これでコンパイルすると、あ、「include ファイルを開けません。'wx/setup.h': No such file or directory」。ん〜、Tutorialには書いてないぜよ。ん〜Google先生。…なるほど、先程できたlib/vc_lib/mswdをインクルード参照フォルダとして追加しないといけないようです。プロジェクトのプロパティのC/C++にある追加のインクルードディレクトリに追加します。さ、再度コンパイル…よし、通りました(^-^)。
 ちなみに、このエラーの解決方法はwxWidgetsのホームページのFAQにありました。みんなハマるよね、うん。



B wxWidgets版「Hello World」

 ではwxWidgetsのホームページにあったHello WorldのTutorialを続けます。
 「Practically every app should define a new class derived from wxApp.(すべてのアプリケーションではwxAppの派生クラスを作って下さい)」なるほど。さらに、「By overriding wxApp's OnInit() the program can be initialized, e.g. by creating a new main window.(wxApp::OnInitメソッドをオーバーライドすると初期化が出来るよ。例えばメインウィンドウの作成とか。)」なるほどね。と言うことで、テストのクラスを次のように作りました:

class MyApp : public wxApp {
public:
   virtual bool OnInit();
};

 次です。メインウィンドウはxwFrameというクラスの派生クラスで作成するようです。こんな感じです:

class MyFrame : public wxFrame {
public:
    MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size);
};

コンストラクタの引数にはメインウィンドウの名前、位置、ウィンドウサイズを与えます。この辺りはTutorialに従っています。

 メインウィンドウやその他のクラスはマウスの動きやクリックなど何らかの「イベント」を受け取ることができるようです。それを受け取るには「wxCommandEvent&を引数とするメソッド」を定義するようです。Tutorialでは例として終了させるQuitメソッド、そしてヘルプを表示するAboutメソッドを作成しています。これらのメソッドはプログラマが自由に作れるようです。それはすばらしい(^-^):

class MyFrame : public wxFrame {
public:
    MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size);

    void OnQuit(wxCommandEvent& event);
    void OnAbout(wxCommandEvent& event);

    DECLARE_EVENT_TABLE()
};

イベントを受け取るためには「DECLARE_EVENT_TABLE()」というマクロを入れる必要があります。

 イベントと上記のOnQuitメソッドのようなイベントハンドラを結びつける必要があります。そのための専用マクロがあります。まずイベント自体は列挙型やconst値で定義します:

enum {
    ID_Quit = 1,
    ID_About,
};

 イベントとハンドラを結びつけるマクロは次のように実装します:

BEGIN_EVENT_TABLE(MyFrame, wxFrame)
    EVT_MENU(ID_Quit, MyFrame::OnQuit)
    EVT_MENU(ID_About, MyFrame::OnAbout)
END_EVENT_TABLE()

BEGIN_EVENT_TABLE〜END_EVENT_TABLEで挟む方式です。BEGIN_EVENT_TABLEの引数は自分のクラスと親クラスです。EVT_MENUはメニュー項目が送出するイベントの事かなと思います。第1引数にイベントID、第2引数にそのハンドラを指定します。イベントIDと上のマクロは必ずmainが定義されている.cppに置かないといけないようです。

 そのmainですが、wxWidgetsではこれもマクロ定義となります:

IMPLEMENT_APP(MyApp)

このマクロ一つで様々な実装を行ってくれるのですから便利ですね。さて、ここでコンパイルするとコードは通るのですが、リンカーエラーが山ほど出ます。そりゃそうです、先に定義したMyAppやMyFrameを実装していないのですから。

 MyApp::OnInitメソッドでは各種初期化を行います。このTutorialではメインウィンドウの作成が主のようなので、以下のようにしてみました:

bool MyApp::OnInit() {
    MyFrame *frame = new MyFrame(_("First wxWidgets Program of Marupeke !"), wxPoint(100, 100), wxSize(640, 480));
    frame->Show(true);
    this->SetTopWindow(frame);
    return true;
}

MyFrameオブジェクトを作り、メインウィンドウを表示状態にしてからアプリケーションに登録しているという状態ですね。

 MyFrameのコンストラクタは親クラスへウィンドウサイズ等の情報を渡し、さらにメニューを作っています:

MyFrame::MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size) : wxFrame(NULL, -1, title, pos, size) {
    wxMenu *menu = new wxMenu;
    menu->Append(ID_About, _("&About..."));
    menu->Append(ID_Quit, _("E&xit"));

    wxMenuBar *menuBar = new wxMenuBar;
    menuBar->Append(menu, _("&File"));
    SetMenuBar(menuBar);

    this->CreateStatusBar();
    this->SetStatusText(_("This is Status Text."));
}

メニューの作り方はなるほどなというシンプルさです。wxMenuオブジェクトを作ってそこにイベントIDとメニュー項目の表示名をAppendメソッドで登録しています。さらにメニューバーオブジェクト(wxMenuBar)を作成してメニューを登録しています。この辺りも一度やり方を学んでしまえば問題ないですね。

 続いてMyFrame::OnQuitメソッドの実装です。このメソッドはメニューのExitを選んだ時に呼ばれるハンドラです:

MyFrame::OnQuit(wxCommandEvent& WXUNUSED(event)) {
    Close(true);
}

まず引数がポイントですね。ハンドラが呼ばれる時にイベントオブジェクトも渡されるのですが、それを実装内で使用しない時は「WXUNUSEDマクロ」を使うルールのようです。実装内ではCloseメソッドをtrueにして終了状態へ移行させます。

 さぁもう少し。MyFrame::OnAboutメソッドはヘルプ表示を行います:

void MyFrame::OnAbout(wxCommandEvent& WXUNUSED(event)) {
    wxMessageBox( _("This is Hello World Sample in Marupeke."),
        _("About Hello World"),
        wxOK | wxICON_INFORMATION, this );
}

ヘルプ表示はwxWidgetsが用意しているメッセージボックスを使います。引数の意味はおおよそ検討が付きますね。

 これで実装が終わりました。さぁ、ビルド!…うぉ!スゴイ量のリンカエラー!…あ、そりゃそうです。冒頭で折角作成したライブラリへ何もリンクしていませんものね(^-^;。

 とは言え実装はこれで全部なので、全コードをこちらに公開します



C ライブラリへのリンク

 リンクすべきライブラリは冒頭で作りました。で、何をリンクするかなのですが…えらい沢山あります。Google先生に尋ねてみると、次のライブラリをリンクすると良いようです:

Debug版 wxmsw28d_core.lib wxbase28d.lib wxtiffd.lib wxjpegd.lib wxpngd.lib wxzlibd.lib wxregexd.lib wxexpatd.lib winmm.lib comctl32.lib rpcrt4.lib wsock32.lib odbc32.lib
Release版 wxmsw28_core.lib wxbase28.lib wxtiff.lib wxjpeg.lib wxpng.lib wxzlib.lib wxregex.lib wxexpat.lib winmm.lib comctl32.lib rpcrt4.lib wsock32.lib odbc32.lib

Debug版はRelease版のライブラリ名に「d」がついています。上のライブラリの種類については今の所よくわかりません。まぁ、そういうもんだと思っておきましょう。

 このライブラリはlib/vc_libフォルダ内にありますので、プロジェクトのプロパティのリンカ設定で追加フォルダして、上のライブラリをちまちまと追加していきましょう。



D ビルドするとリンカーエラー、ハメその2

 これでビルドすると、次のようなリンカーエラーがでました:

リンカエラー!
1>main.obj : error LNK2001: 外部シンボル ""public: virtual bool __thiscall wxApp::Initialize(int &,wchar_t * *)" (?Initialize@wxApp@@UAE_NAAHPAPA_W@Z)" は未解決です。
1>main.obj : error LNK2001: 外部シンボル ""protected: void __thiscall wxStringBase::InitWith(wchar_t const *,unsigned int,unsigned int)" (?InitWith@wxStringBase@@IAEXPB_WII@Z)" は未解決です。
1>main.obj : error LNK2001: 外部シンボル ""wchar_t const * const wxEmptyString" (?wxEmptyString@@3PB_WB)" は未解決です。

ん〜、何だろうと色々調べてみると、どうやらUnicode対応に関する事のようでした。解決するにはプロジェクトのプロパティで文字セットを「設定なし」にします。これで再度コンパイルすると、通りました〜〜。



E 実行画面はこうなります

 Tutorialを打ち込んでコンパイラもうまく通すと、次のような実行画面が出現しました:

お〜、メニュー付きのウィンドウです。もちろんメニューをクリックしたら反応してハンドラが呼ばれます。簡単な実装でこれらがでるのが嬉しいですねぇ。


 wxWidgetsはこれらの他にも様々なウィジットが表示できるようです。これは覚えると特にツール制作などに光明が見える気がします!よし、ちょっと頑張ろう〜