★☆=====================================================================☆★ 【wxWindowsでGUIプログラミング】 第2号:Hello World をいじろう 2003.05.18(Sun) ★☆=====================================================================☆★ こんにちは,二条です。メルマガの発行が遅れてしまい,申し訳ありませんでした。 今回は,前回の「Hello World」に少し手を加えてみます。 今回の内容は,前回の内容を前提として進めますので,初購読の方はお手数ですが,バックナンバーを参考にしてくださいませ。 バックナンバーは,まぐまぐのサイト http://www.mag2.com/m/0000108320.htm で見ることができます。 また, http://members10.tsukaeru.net/ariera/soft/wx.html こちらにダウンロード用のテキストファイルも用意してあります。 今回はサンプルソースがとても長くなってしまいました。 サンプルソースはWebからダウンロードしていただき,メルマガでは解説だけにするとい う方法も考えたのですが,今までどおりメルマガにソースもすべて載せるのとどちらがよ ろしいでしょうか? アンケートフォームを用意しましたので,ぜひご回答ください。 また,ご意見,ご感想,ご要望などもお気軽にコメントボードに書き込んでください。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ◆サンプルソースコードはWeb上に置いたほうがいい ┗< http://clickanketo.com/cgi-bin/a.cgi?q00015005a01 > ◆サンプルソースコードはメルマガに載せたほうがいい ┗< http://clickanketo.com/cgi-bin/a.cgi?q00015005a72 > ■コメントボード(自由にご意見をお書きください) ┗< http://clickanketo.com/cgi-bin/cb.cgi?q0001500593 > ☆締切:2003年05月26日23時00分 ★協力:メールマガジンをおもしろくする《クリックアンケート》          →→  [ http://clickanketo.com/ ] ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ------------------------------------------------------------------------ (0)前回の訂正 前回のプログラムに間違いがありました。お詫びと訂正をさせていただきます。 ソースコードの17行目 /*17*/ bool HelloWorldApp*/*/OnInit() の,「OnInit()」 の前は,「*/*/」ではなく「::」としてください。 ソースコードの17行目 /*19*/ wxFrame *frame = new wxFrame((wxFrame*) NULL, -1, "Hello World"); の「NULL」と「-1」の後のカンマ(,) が全角になっていましたので,半角に修正してください。 このままでは絶対コンパイルできないはずなのですが,今回はどなたからもご指摘がありませんでした。もしかして,誰も試してくれていないのかと,少々不安になっています。間違いがアホくさすぎて,なおし方も簡単なので,ご自分で訂正して試していただいているのならよいのですが…… ------------------------------------------------------------------------ (1)フレームとボタン まず,前回の HelloWorld.cpp を少し編集した,以下の内容を, HelloWorld2.cpp とか適当に名前をつけて,適当な場所に保存してください。 そして,前回と同じように,サンプルのMakefile等を利用して手抜きコンパイルし,実行してみてください。 前回よりソースがだいぶん長くなってしまいましたが,まずは深く考えずに動かしてみてください。 // ここから #include "wx/wxprec.h" #ifndef WX_PRECOMP #include "wx/wx.h" #endif class HelloWorldApp : public wxApp { public: virtual bool OnInit(); }; /* ここを追加 1*/ class HelloWorldFrame : public wxFrame { public: HelloWorldFrame(const wxString& title, const wxPoint& pos, const wxSize& size, long style = wxDEFAULT_FRAME_STYLE); }; /* 追加1 ここまで*/ DECLARE_APP(HelloWorldApp) IMPLEMENT_APP(HelloWorldApp) bool HelloWorldApp::OnInit() { /* ここを変更 1*/ HelloWorldFrame *frame = new HelloWorldFrame( _T("はろ〜わーるど"), wxPoint(50, 50), wxSize(500, 150)); /* 変更1 ここまで*/ frame->CreateStatusBar(); frame->SetStatusText("Hello World"); frame->Show(TRUE); SetTopWindow(frame); return true; } /* ここを追加 2*/ HelloWorldFrame::HelloWorldFrame(const wxString& title, const wxPoint& pos, const wxSize& size, long style) : wxFrame(NULL, -1, title, pos, size, style) { wxButton* helloWorldButton = new wxButton(this, 1, _T("こんにちは"), wxPoint(20,20), wxSize(150,40)); wxButton* closeButton = new wxButton(this, 2, _T("さようなら"), wxPoint(220,20), wxSize(150,40)); } /* 追加2 ここまで*/ // ここまで 実行すると「こんにちは」「さようなら」という二つのボタンのあるウインドウが出たと思います。 前回からの追加・変更は3箇所です。 まず,wxFrame を継承した HelloWorldFrame というクラスを定義します。これはボタンなどの部品を乗せる「枠」になります。今回は,コンストラクタのみを定義します。 そして,実行時に最初に呼ばれる関数 HelloWorldApp::OnInit() の中で,HelloWorldFrame のインスタンスを作成します。最初の引数はウインドウのタイトル,2つめは表示位置,3つめはサイズです。単位はピクセルです。 前回の HelloWorld では,ここで wxFrame のインスタンスを作っていましたが,今回はそれを継承したクラス HelloWorldFrame の方を使います。 最後に, HelloWorldFrame クラスのコンストラクタを HelloWorldFrame::HelloWorldFrame 実装します。ここでは,親クラス wxFrame のコンストラクタを継承します。そして,「こんにちは」と「さようなら」の二つのボタンを追加しています。ボタンを使うには wxButton クラスを使います。wxButton のコンストラクタの最初の引数は親ウインドウのポインタなので,HelloWorldFrame のインスタンスのポインタ(this) を入れています。二つ目は部品のIDで,後で部品とイベントを関連づけるときなどに使います。IDは,重複しないように設定する必要があります。3つめの引数は位置,4つめの引数はサイズです。 さて,このプログラムを起動して,ボタンを押しても何も起こりません。ボタンを押したときの動作を書いていないのですから,あたりまえですね。次に,ボタンを押したら何かの動作をするように,変更してみます。 ※このプログラムを終了するには,Windowsですと,左上の×ボタンを押してください。 ------------------------------------------------------------------------ (2)イベントとダイアログボックス 上のソースコードに,以下のような変更を加えてみてください。 (A) HelloWorldFrame の定義を以下のように変えます。 // ここから class HelloWorldFrame : public wxFrame { public: HelloWorldFrame(const wxString& title, const wxPoint& pos, const wxSize& size, long style = wxDEFAULT_FRAME_STYLE); void OnHelloWorldButton(wxCommandEvent &event); void OnCloseButton(wxCommandEvent &event); protected: DECLARE_EVENT_TABLE() }; // ここまで (B) IMPLEMENT_APP(HelloWorldApp) の下に,以下の内容を追加します。 // ここから BEGIN_EVENT_TABLE(HelloWorldFrame, wxFrame) EVT_BUTTON(1, HelloWorldFrame::OnHelloWorldButton) EVT_BUTTON(2, HelloWorldFrame::OnCloseButton) END_EVENT_TABLE() // ここまで (C) ソースの一番最後に,以下の内容を追加します。 // ここから void HelloWorldFrame::OnHelloWorldButton(wxCommandEvent &event) { wxMessageBox(_T("こんにちは,世界!"), _T("こんにちは")); } void HelloWorldFrame::OnCloseButton(wxCommandEvent &event) { Close(TRUE); } // ここまで これをコンパイルして実行すると,今度は「こんにちは」を押すと「こんにちは,世界!」というダイアログボックスが出ます。「さようなら」を押すとプログラムを終了します。 余談ですが,「こんにちは,世界!」はなんだかとんでもない日本語ですね。「Hello World」を直訳してみただけなのですが…… (A) の HelloWorldFrame クラスで追加した OnHelloWorldButton() と OnCloseButton() の2つのメソッド(メンバー関数) は,ボタンを押したときの処理内容を書くための関数です。その中身(実装)は,(C) のところに書いてあります。もちろん,これを追加したからといって,勝手にボタンと関数が関連づけられるわけではありません。wxWindowsでは,変数や関数の名前で関連づけを行うことはないので,関数名は適当につけて構いません。(ボタンに関連のある名前にしておいたほうがわかりやすいとは思いますが。) 次の,DECLARE_EVENT_TABLE() は,イベント(関数)と部品(ボタンなど)を関連づけるイベントテーブルを使うときには必ず書かなくてはいけないマクロです。中で何をしているのかはまだ調べていないので,とりあえずオマジナイだと思ってください。 (B) がそのイベントテーブルの本体です。イベントテーブルはフレームなどのクラスごとに定義します。今回関連づけを行いたいボタンはどちらも HelloWorldFrame の上に載っていますので (ボタンのコンストラクタで指定する親ウインドウが HelloWorldFrameなので ) ,イベントテーブルの最初 (BEGIN_EVENT_TABLE) に HelloWorldFrame とその親クラスの wxFrame を指定します。 次の2行が,ボタンと関数を関連づけている部分です。EVT_BUTTON というのは,「ボタンを押したとき」の関連付けを行う関数で,最初の引数が部品のID,次の引数が関連づける関数です。部品のIDは,部品を作るときのコンストラクタで指定したものです。HelloWorldFrame のコンストラクタの中で, wxButton* helloWorldButton = new wxButton(this, 1, _T("こんにちは"), wxPoint(20,20), wxSize(150,40)); と書いていますが,この一つ目の引数が親ウインドウ,2つ目の引数が部品IDです。「こんにちは」ボタンの部品IDは 1 ですので, EVT_BUTTON(1, HelloWorldFrame::OnHelloWorldButton) ,こんにちは」ボタンを押したときに,関数 HelloWorldFrame::OnHelloWorldButton を実行するように設定しています。 テーブルの最後は,必ず END_EVENT_TABLE() で閉じてください。 (C) が,ボタンと関連づけられた関数です。 HelloWorldFrame::OnHelloWorldButton() が,「こんにちは」ボタンを押したときの処理で,wxMessageBox は,ダイアログボックスを表示する関数です。最初の引数が本文,2つめの引数がメニューになります。 wxWindowsでは,文字列は _T("") で囲むようにしてください。日本語が使えるOSでしたら,日本語は普通にソースコード中に書くことができます。 HelloWorldFrame::OnCloseButton() が,「さようなら」ボタンを押したときの処理です。Close は,フレームを閉じる関数で,HelloWorldFrame はトップウインドウなので,これを閉じるとプログラムは終了します。Close の引数に TRUE を指定すると,エラーがあっても強制終了します。FALSE だと強制終了しません。 ------------------------------------------------------------------------ (3)細かい修正 上のプログラムでは,部品IDに数字を直に書き込んできましたが,部品の数が増えてくると,それでは何がなんだかわからなくなってしまいます。このような場合は, enum を使って ID を定義すると楽になります。 ※その他,リソースファイル(.rcファイル) を使って指定する方法もありますが,今回は使いません。 また,ボタンのポインタを HelloWorldFrame のコンストラクタのなかで宣言していましたが,これではコンストラクタを抜けるとポインタは消えてしまいます。(ボタンの実体は残っていますが。) ボタンの色を変えたり文字を変えたりといった処理をするにはポインタが必要です。部品IDではこれらの処理はできません。なので,ポインタが消えてしまうと,他の関数からボタンをいじることができなくなってしまいます。たとえば,ボタンを押したらボタンの色が変わる,というような設定ができなくなってしまいます。それを防ぐためには,HelloWorldFrame クラスの中に,メンバー変数としてポインタを用意しておき,コンストラクタでは初期化するだけにしておきます。 この2点を修正したソースコードを載せておきます。 // ここから #include "wx/wxprec.h" #ifndef WX_PRECOMP #include "wx/wx.h" #endif class HelloWorldApp : public wxApp { public: virtual bool OnInit(); }; class HelloWorldFrame : public wxFrame { public: HelloWorldFrame(const wxString& title, const wxPoint& pos, const wxSize& size, long style = wxDEFAULT_FRAME_STYLE); void OnHelloWorldButton(wxCommandEvent &event); void OnCloseButton(wxCommandEvent &event); protected: DECLARE_EVENT_TABLE() private: wxButton* helloWorldButton; wxButton* closeButton; }; enum { ID_HELLOWORLD_BUTTON, ID_CLOSE_BUTTON }; DECLARE_APP(HelloWorldApp) IMPLEMENT_APP(HelloWorldApp) BEGIN_EVENT_TABLE(HelloWorldFrame, wxFrame) EVT_BUTTON(ID_HELLOWORLD_BUTTON, HelloWorldFrame::OnHelloWorldButton) EVT_BUTTON(ID_CLOSE_BUTTON, HelloWorldFrame::OnCloseButton) END_EVENT_TABLE() bool HelloWorldApp::OnInit() { HelloWorldFrame *frame = new HelloWorldFrame( _T("はろ〜わーるど"), wxPoint(50, 50), wxSize(500, 150)); frame->CreateStatusBar(); frame->SetStatusText("Hello World"); frame->Show(TRUE); SetTopWindow(frame); return TRUE; } HelloWorldFrame::HelloWorldFrame(const wxString& title, const wxPoint& pos, const wxSize& size, long style) : wxFrame(NULL, -1, title, pos, size, style) { helloWorldButton = new wxButton(this, ID_HELLOWORLD_BUTTON, _T("こんにちは"), wxPoint(20,20), wxSize(150,40)); closeButton = new wxButton(this, ID_CLOSE_BUTTON, _T("さようなら"), wxPoint(220,20), wxSize(150,40)); } void HelloWorldFrame::OnHelloWorldButton(wxCommandEvent &event) { wxMessageBox(_T("こんにちは,世界!"), _T("こんにちは")); } void HelloWorldFrame::OnCloseButton(wxCommandEvent &event) { Close(TRUE); } // ここまで C++に詳しい方は new したものを delete していないので不安に思われるかもしれませんが,,途中で new した wxButton のインスタンスは,親ウインドウが終了するときに自動的に消されますので, delete を呼ぶ必要はありません。delete を呼ぶ必要があるのは,プログラムの途中でボタンを作り直すときなど,既に new で実体を作ったポインタに対して,再び new を呼ぼうとするときだけです。 ※ここは,意味がわからない方は無視していただいて構いません。 ------------------------------------------------------------------------ 今回はこれで終わりです。次回はボタン,フレーム,ダイアログボックスの飾り方と,メニューバーの作り方を解説する予定です。 編集後記: 龍笛という日本の笛を習っているのですが,息がとんでもなく短いのが悩みの種です。音をのばせる長さが師匠と比べて5分の1程度,一緒に習っている普通の人たちと比べても半分以下という情けなさ。今までは音がまともに鳴らせなかったのであまり気にならなかったのです。確かに心肺機能が貧弱だとは昔から思っていたのですが,これほどまでとは思いませんでした。別に虚弱体質なわけではないのですが……どなたか,よい対策をご存知でしたら教えてくださいませ。 次回発行日は2003.6.1(日)の予定です。 ★☆=====================================================================☆★ メールマガジン:wxWindowsでGUIプログラミング 第2号 2003.05.18(Sun) 発行人:二条 ご意見・ご感想はこちらへ: ariera@members10.tsukaeru.net 解除・バックナンバーはこちらからどうぞ http://members10.tsukaeru.net/ariera/soft/wx.html マイサイト「気まぐれ歴史散歩」もよろしくお願いします http://members10.tsukaeru.net/walk/ このメールマガジンは「まぐまぐ」を使って配信しています http://www.mag2.com/ ★☆=====================================================================☆★