★☆=====================================================================☆★ 【wxWindowsでGUIプログラミング】 第4号: 計算結果を画面表示する(1) 2003.06.15(Sun) ★☆=====================================================================☆★  こんにちは,二条です。  前回のアンケートには前々回より多くの方にご回答いただきました。また,暖かい励ましのお言葉もたくさんいただきました。ありがとうございました。  当たり前といえば当たり前ですが,C++を知っている方は簡単すぎ,C言語しか知らないあるいはどっちも知らないという方は少し難しいという回答が多かったです。  私は最初に覚えた言語がC++で,C言語はほとんど使ったことがないという妙な経歴のため,オブジェクト指向用語を深く考えずに多用してしまう癖があるようで,C言語専門のプログラマーの知人には「わけわからんぞっ!」とよく怒られています。このメルマガでも,クラスとかオブジェクトとかいう言葉がたくさん出てくると思いますが,意味がわからなければ説明を無視して,ソースをコピー&ペーストして適当に遊んでみてください。言葉の意味はともかく,プログラムの作り方のコツはそのうちわかってくると思います。  また,私よりプログラミングに詳しい読者の方も多数いらっしゃるようですので,中級以上の方にも役に立つ内容を考えていきたいと思います。  今回から,サンプルプログラムの解説とワンポイント解説の2本立てにしてみました。  サンプルプログラムの解説は今回が最初ですが,ワンポイント解説のほうは前回までの内容を前提として進めますので,初購読の方はお手数ですが,バックナンバーを参考にしてくださいませ。  バックナンバーは,まぐまぐのサイト http://www.mag2.com/m/0000108320.htm で見ることができます。  また, http://members10.tsukaeru.net/ariera/soft/wx.html こちらにダウンロード用のテキストファイルも用意してあります。 *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- [1] サンプルプログラム 計算結果を画面表示する(1) *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-  こちらは,今までの内容が簡単すぎるという方のために,少し複雑なサンプルを作ってみるコーナーです。(初心者の方でも,ソースをコピー&ペーストするだけで,とりあえず動くものは作れます。) ------------------------------------------------------------------------ (1)作ろうとしているもの  私がはじめてwxWindowsを使ったのは,計算結果をずらずらとコンソール画面に出力するプログラムを,GUI出力に変えたいと思ったときでした。LinuxとWindowsとSolarisと3種類のOSで動かしたいとか,GUIと計算部分をきれいに分けたいとか,無料で商用利用OKでないと手続きがめんどくさいとか(お客様へのデモだったのです),いろいろな理由があってwxWindowsを選びました。  今回から数回かけて,このときのプログラムを簡易化したものを作っていこうと思います。そんなもん誰が使うか!と言われそうですが,お絵かきの基本なども出てきますので,お許しください。(実はサンプルを考える時間がなくてかなり手抜き……)  今回は計算がメインではないので,計算部分は以下のような単純なものにします。 int x=0; int y=0; while(1) { x = (x+2)%100; y = (y+1)%100; sleep(1); }  これは説明しなくてもわかると思いますが,0から99の範囲でxを2ずつ,yを1ずつ進めて,99まで来たら0に戻るというものです。このxとyを座標だと思って 100 x 100 の画面に点を描きます。  といっても,このループの中にお絵かきの処理を入れてしまうと,計算部分とGUIを分けられなくなりますので,計算部分とGUIを別スレッドで走らせることにします。GUIを最初に起動する必要がありますので,GUIのほうがメインスレッドになります。  計算用のスレッドが1ループ回るたびにイベントをメインスレッドに送り,イベントを受け取ったらメインスレッドが画面の点の位置を描き替えます。また,xとyの座標に同時アクセスを禁止するために,クリティカルセッションを使います。(別にミューテックス等でもいいのですが,今回はクリティカルセッションにします。)  また,xとyをグローバル変数にしてしまうとあまりC++的でないので,以下のようなクラスを作ります。画面の大きさと,座標の最大値が一致しないとマヌケなことになりますので,書きかえが簡単なようにマクロにしておきます。 #define X_MAX = 100; #define Y_MAX = 100; class Point { private: int x; int y; public: int get_x() { return x; } int get_y() { return y; } void set_x(int i) { x=i; } void set_y(int i) { x=j; } void move() { x = (x+2) % X_MAX; y = (y+1) % Y_MAX; } }  この程度のものにわざわざクラスを作るのはアホくさいかもしれませんが,もともとのプログラムでは計算部分がもっと複雑で,画面を動く点もたくさんあったので,このような構造になっていました。  この class Point の定義部分を一つのファイルに貼り付けて,point.h とでも名前をつけておいてください。今回は,move関数などすべてのメンバー関数をインラインで実装してしまいましたので,計算部分の実装ファイルは不要になります。  move関数の中身を書きかえると,GUI部分はいじらずに点の動き方をかえることができます。 ------------------------------------------------------------------------ (2)GUI部分のヘッダ  次に,GUI部分のヘッダです。以下のソースを貼り付けて,wx_point.h というファイルを作ってください。 // ここから #include "wx/wxprec.h" #ifndef WX_PRECOMP #include "wx/wx.h" #endif class PointCalcThread; class PointCanvas; //------------------------------------------------------------------------- class PointApp : public wxApp { public: virtual bool OnInit(); int OnExit(); private: wxSingleInstanceChecker *checker; }; //------------------------------------------------------------------------- class PointFrame : public wxFrame { public: PointFrame(const wxString& title, const wxPoint& pos, const wxSize& size, long style = wxDEFAULT_FRAME_STYLE); void OnCalcThreadEvent(wxCommandEvent &event); void OnAboutMenu(wxCommandEvent &event); void OnCloseButton(wxCommandEvent &event); protected: DECLARE_EVENT_TABLE() private: PointCalcThread *pointThread; PointCanvas *canvas; wxTextCtrl *logWin; wxButton* closeButton; }; //------------------------------------------------------------------------- class PointCalcThread : public wxThread { public: PointCalcThread(PointFrame*); void* Entry(); private: PointFrame *parentFrame; }; //------------------------------------------------------------------------- class PointCanvas : public wxSplitterWindow { public: PointCanvas(wxWindow* parent, wxWindowID id = -1); void OnPaint(wxPaintEvent &event); protected: DECLARE_EVENT_TABLE() }; //------------------------------------------------------------------------- enum { ID_POINT_THREAD, ID_MENU_ABOUT, ID_CLOSE_BUTTON }; //------------------------------------------------------------------------- DECLARE_APP(PointApp) // ここまで  PointApp と PointFrame は,前回までのHelloWorldで作ったものとよく似ています。  PointApp の wxSingleInstanceChecker は,アプリケーションの二重起動を防止するためのものです。古いwxWindowsでは実装されていないものもあります。OnExit 関数は,アプリケーションを終了するときの後始末を行うための関数です。OnInit関数で動的に確保したメモリを開放するのに使います。  PointFrame の OnCalcThreadEvent 関数は,計算用のスレッドからイベントが送られたときに,画面を描き替えるための関数,OnAboutMenu 関数はメニューバーのAboutがクリックされたときの動作を設定するための関数です。OnCloseButton はhelloWorldの場合と同じように,閉じるボタンを押したときの処理です。それぞれの詳しい説明は実装のところでします。  次の PointCalcThread が,wxThread を継承したスレッドクラスです。コンストラクタでは親ウインドウのポインタが必要です。Entry 関数が,スレッド内で処理する内容です。  PointCanvas は,wxSplitterWindow を継承したクラスで,点を書くための領域になります。OnPaint 関数は,Paintイベントが送られたときの処理内容です。Paintイベントは,ウインドウを作ったときやOSによって自動再描画されたとき,Refresh関数が呼ばれたときなどに送られます。Refresh関数については,前回(第3回)を参照してください。  wxSplitterWindowのコンストラクタの引数は,親ウインドウのポインタとウインドウを識別するためのIDです。IDはMDIプログラムでよく使いますが,今回は使いませんので-1にしてあります。その他,位置,サイズ,ウインドウスタイル,名前を指定することができますが,今回はすべてデフォルトで構わないので,省略してあります。  enum とDECLARE_APP については,第2回と第3回で説明しましたので,ここでは説明しません。 ------------------------------------------------------------------------ (3)PointApp クラスの実装  ソースが長かったので,メールのサイズが大きくなってきましたが,これだけではナサケナイので,PointApp の二つの関数の実装部分だけ書いてしまいます。wx_point.cpp というファイルを作って,以下のソースを貼り付けてください。 // ここから #include "point.h" #include "point_wx.h" IMPLEMENT_APP(PointApp) bool PointApp::OnInit() { // 二重起動チェック checker = new wxSingleInstanceChecker(GetAppName()); if ( checker->IsAnotherRunning() ) { wxMessageBox(_T("既に起動されています"), _T("エラー")); return false; } // クリティカルセッションの作成 wxCriticalSection pointLocker; // 点のかわりに画像を使うときのため wxImage::AddHandler(new wxJPEGHandler); // フレームの作成 PointFrame *frame = new PointFrame(_T("点が動くサンプル"), wxPoint(50, 50), wxSize(X_MAX+20, Y_MAX+100)); frame->Show(TRUE); SetTopWindow(frame); return true; } int PointApp::OnExit() { // 動的に確保したメモリを開放 delete checker; return 0; } // ここまで  IMPLEMENT_APP は,以前説明したように,コマンドラインプログラムのmain関数に当たる部分です。  OnInit 関数の最初で,二重起動防止用のオブジェクトを作っています。GetAppName は,アプリケーションの名前を取得する関数で,wxApp クラスのメンバー関数ですので,wxAppを継承した PointApp クラスでも使うことができます。  既に起動されているかどうかは,wxSingleInstanceChecker::IsAnotherRunning 関数でチェックします。これがTRUEだった場合は,既に起動されています。既に起動されている場合は,wxSingleInstanceChecker インスタンスは自動的に開放されますが,そうでなかった場合は,アプリケーションを終了する前に,メモリ領域を開放する必要があります。これが,OnExit 関数の方の, delete checker; です。  アプリケーションのメイン(トップ)フレームとその子ウインドウは自動的に開放されるので,明示的にdelete を呼ぶ必要はありませんが,それ以外のものを動的にメモリ確保(newで作成)した場合には,最後にdelete が必要になります。  次のクリティカルセッションは見たまんまなので説明しません。これを使う部分は次回以降に説明します。  wxImage::AddHandler は,後ほど,画面に点ではなくJPEG画像を表示できるように拡張するために,ハンドラーを作成しています。  フレームの作成以降は前回までに説明したHelloWorldとほとんど同じなので,説明は省略します。  まだ肝心な部分に到達していませんが,今回はここまでにします。  次回は,スレッドの起動,スレッド内の処理について説明します。余裕があれば,お絵かき部分の説明もしたいと思います。次回は,wx_point.cpp ファイルの後ろに処理を追加していくことになります。  今回のソースは作りかけですので,このままではコンパイルできませんし,動きません。余裕のある方は,ヘッダと説明を見ながら,自分で関数の実装部分を考えてみてください。 ※今回のソースコードは,コンパイルチェックしていないので,とんでもない間違いがあるかもしれません。発見した方はお知らせいただけるとありがたいです。 *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- [2] ワンポイント解説 メニューバー *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-  こちらは,前回までのHelloWorldプログラムを少しずついじってみるコーナーです。基本的なことから少しずつ覚えたい方向けです。  今回は,第2回の一番最後にあるソースコード,あるいはそれに第3回の内容の手を加えたものにメニューバーをつけてみます。メニューバーの「メニュー(M)」をクリックすると,「バージョン情報(V)」と「終了(Q)」のメニューが出るようにします。 ------------------------------------------------------------------------ (1)メニュー用のIDを追加する。  まず,enum の中にメニュー用のIDを追加してください。 enum { ID_HELLOWORLD_BUTTON, ID_CLOSE_BUTTON }; の部分を以下のように書きかえます。 enum { ID_HELLOWORLD_BUTTON, ID_CLOSE_BUTTON, ID_ABOUT_MENU, ID_CLOSE_MENU }; ------------------------------------------------------------------------ (2)HelloWorldFrame のコンストラクタをいじる  次に,HelloWorldFrame のコンストラクタ (HelloWorldFrame::HelloWorldFrame) の最後に,以下の行を追加してください。 // ここから wxMenu *menu = new wxMenu; menu->Append(ID_ABOUT_MENU, _T("バージョン情報(&V)"), _T("バージョン情報を表示します")); // indows+MinGW またはcygwin の場合はこちらを使う // menu->Append(ID_ABOUT_MENU, _T("バージョン情報(&V)"), _T("バージョン情報を表\示します")); menu->Append(ID_CLOSE_MENU, _T("終了(&Q)"), _T("プログラムを終了します")); wxMenuBar *menuBar = new wxMenuBar(); menuBar->Append(menu, _T("メニュー(&M)")); SetMenuBar(menuBar); // ここまで  最初の wxMenu *menu = new wxMenu; で,wxMenu クラスのインスタンスを作成しています。ここでは,第3回で説明したボタンとは違って,プログラムの途中でメニューの属性(表示する文字列など)を変更することはありませんので,ポインタを HelloWorldFrame クラスのメンバー変数にする必要はありません。  ここで作ったメニューは,メニューバー上に表示される項目の一つ(たとえば,一般的なアプリケーションでは「メニュー」「編集」「表示」などの一項目分)にあたります。  次の menuFile->Append でメニューに項目を追加しています。ここでは,「バージョン情報(V)」と「終了(Q)」の二つを追加しています。Append 関数の最初の引数は,メニューが選択されたときの処理と関連づけるためのID,2つ目の引数はメニューの文字列,3つ目の引数はメニューの説明です。メニューの文字列の中で & がついている記号は,たとえば「バージョン情報(&V)」であれば,キーボードで v を押すと,メニューが選択されることを表しています。(アルファベットの大文字と小文字は区別しません。) また,メニューの説明は,デフォルトではステータスバーに表示されます。  Append 関数には,引数が異なるものがいくつかありますが,ここでは使わないので説明しません。  Windows+MinGW またはcygwinの方は,「表示」を「表\示」とする必要があります。SJISの文字コードでは,「示」のように,2バイトの先頭バイトがエスケープ文字と重なってしまう文字があります。MinGW や cygwin では,日本語環境をあまり考慮していないので,これをエスケープ文字と解釈して文字化けが起きてしまいます。(SJISで書かれたCGIなどで俗に「ダメ文字」と言われているものです。)  しかし,ソースコードをEUCにしてしまうと,WindowsのシステムがSJISなので,メニューなどに表示される文字が完全に化けてしまいます。  そのため,エスケープ文字と解釈されないために,最初に \ をつける必要があります。  これ以外の環境で \ をつけると,「表\示」のようにそのまま表示されます。  真ん中あたりの wxMenuBar *menuBar = new wxMenuBar(); ではメニューバーのインスタンスを作成しています。次の行で,メニューバーに先ほど作成したメニューを追加しています。メニューの名前「メニュー(M)」は,このときに設定されます。Mの前についている & は,キーボードで Alt + m を押すと,メニューが選択されることを表しています。  メニューバーに表示する項目を増やす場合は,wxMenu のインスタンスをもうひとつつくって,メニューバーに追加します。wxMenuBar のインスタンスは,通常は一つのウインドウに一つです。  最後に, SetMenuBar(menuBar); でフレームにメニューバーをセットします。 ------------------------------------------------------------------------ (3)バージョン情報用の関数を作る  メニューの「終了」は,「閉じる」ボタンと同じ処理をすればいいですが,バージョン情報を表示する関数はないので,新しく作成する必要があります。  まず,HelloWorldFrame の宣言部分(class HelloWorldFrame : public wxFrame のブロック) の, void OnCloseButton(wxCommandEvent &event); の下に, void OnAboutMenu(wxCommandEvent &event); の1行を追加してください。  それから,ソースファイルの一番下に,以下のコードを追加してください。メッセージの内容は適当に書き換えてください。\nは改行です。 // ここから void HelloWorldFrame::OnAboutMenu(wxCommandEvent &event) { wxMessageBox(_T("HelloWorldプログラム\n作成者:二条\n作成日:2003.06.15"), _T("バージョン情報")); } // ここまで  wxMessageBox については,第2回で説明しましたので省略します。 ------------------------------------------------------------------------ (4)イベントテーブルを設定する  最後に,メニューと関数を関連づけます。 BEGIN_EVENT_TABLE(HelloWorldFrame, wxFrame) EVT_BUTTON(ID_HELLOWORLD_BUTTON, HelloWorldFrame::OnHelloWorldButton) EVT_BUTTON(ID_CLOSE_BUTTON, HelloWorldFrame::OnCloseButton) END_EVENT_TABLE() の部分を,以下のように書き換えてください。 BEGIN_EVENT_TABLE(HelloWorldFrame, wxFrame) EVT_BUTTON(ID_HELLOWORLD_BUTTON, HelloWorldFrame::OnHelloWorldButton) EVT_BUTTON(ID_CLOSE_BUTTON, HelloWorldFrame::OnCloseButton) EVT_MENU(ID_ABOUT_MENU, HelloWorldFrame::OnAboutMenu) EVT_MENU(ID_CLOSE_MENU, HelloWorldFrame::OnCloseButton) END_EVENT_TABLE()  メニューと関数を関連づけるには,EVT_MENU を使います。引数はボタンの場合と同じなので説明は省略します。  メニューの「終了」には,「閉じる」ボタンを同じ OnCloseButton 関数を関連づけています。OnCloseButton という名前は適切ではなくなってしまうので,本当は変更した方がよいです。 ------------------------------------------------------------------------ (5)動かしてみよう  出来上がったプログラムをコンパイル・リンクして起動してみてください。(コンパイル方法は創刊号と第1号を参照してください。)  メニューバーがついていて,「メニュー」をクリックすると,「バージョン情報」と「終了」のメニューが出ます。「バージョン情報」にマウスをあわせると,ステータスバーに「バージョン情報を表示します」という文字が出ます。バージョン情報をクリックすると,メッセージが表示されます。  マウスを「バージョン情報」から離すと,ステータスバーの「バージョン情報を表示します」はきえますが,起動直後に表示されていた「Hello World」は再表示されません。  また,メニューを選んでいない状態で,キーボードで Alt + m を押すと「メニュー」が選択され,二つのメニューが表示されます。この状態で v を押すと,「バージョン情報」をクリックしたのと同じ状態になります。また,キーボートの上下カーソルを動かすと,各メニュー項目にマウスを合わせたときと同じ状態になります。  「メニュー」「終了」を選ぶと,プログラムが終了します。  余裕があれば,メニューを増やしたり,文字列を変えたりして遊んでみてください。 ------------------------------------------------------------------------ 編集後記:  梅雨入りしてじめじめと暑い日が続いていますが,我が家では今日やっとストーブを片付けました。1ヶ月以上使っていなかったのですが,出しっぱなしにしてあることを忘れていたのです。  ついでに,扇風機を引っ張り出しました。エアコンがないので夏は扇風機が必需品なのですが,久々にスイッチを入れたところ,部屋に散らばっている書類が飛んで大変。本格的に暑くなる前に,部屋を片付けなければいけないと思うのでした。  次回発行日は2003.6.29(日)の予定です。  前回のアンケートで,発行頻度を上げてほしいというご意見をいただきましたが,隔週以上の頻度で定期的に発行するのは辛いです。定期発行でないとうざい,という方がいなければ,発行予定日前であっても時間が取れた場合には発行しようかと考えているのですが,反対の方がいらっしゃいましたらご連絡くださいませ。  もちろん,発行間隔が2週間以上あくことは,できるだけないように努力します。  ご連絡は, http://members10.tsukaeru.net/ariera/other/form/form.cgi こちらのメールフォームか,ariera@members10.tsukaeru.net のアドレスにお願いします。 ★☆=====================================================================☆★ メールマガジン:wxWindowsでGUIプログラミング 第4号 2003.06.15(Sun) 発行人:二条 ご意見・ご感想はこちらへ: ariera@members10.tsukaeru.net 解除・バックナンバーはこちらからどうぞ http://members10.tsukaeru.net/ariera/soft/wx.html マイサイト「気まぐれ歴史散歩」もよろしくお願いします http://members10.tsukaeru.net/walk/ このメールマガジンは「まぐまぐ」を使って配信しています http://www.mag2.com/ ★☆=====================================================================☆★