=[広告]======================================================================
 【無料メルマガ】週刊大前研一《ニュースの視点》
 マニュフェスト?郵政民営化?年金崩壊? どーなってるの大前さん!
 あなたは世の中の動きをどう読むか?コレを読まずに明日は語れない。
  http://px.a8.net/svt/ejp?a8mat=IIQKZ+633K62+7C6+60WN7 
=============================================================================


★☆=====================================================================☆★
      【wxWindowsでGUIプログラミング】
             第8号: 調子笛ソフト改造(2)    2004.02.07(Sun)
★☆=====================================================================☆★

 こんにちは,二条です。
 発行が大幅に遅れてしまってすみません。仕事でプログラミングに関わっていないときは,プログラミング全般に対するモチベーションが低下してしまうようです。
 最近,久々にプログラミングの仕事を請け負ったら,wxもいじってみようかという気になってきました。
 今後も気が向いたときに発行という形になると思いますので,発行周期を隔週刊から不定期刊に変更しました。気まぐれで申し訳ありませんが,今後ともお見捨てなくおつきあいくださいませ。
 調子笛ソフトの改造に着手してしまいましたので,次回はそれほどお待たせせずに発行できると思います。

*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
 [1] サンプルプログラム  調子笛ソフト改造(2)
*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-

 こちらは,今までの内容が簡単すぎるという方のために,少し複雑なサンプルを作ってみるコーナーです。(初心者の方でも,ソースをコピー&ペーストするだけで,とりあえず動くものは作れます。)
 今回は,前回までの内容を前提としていますので,初購読の方,間が空きすぎて忘れたという方はお手数ですが,バックナンバーを参考にしてください。

 前回書きましたように,行おうとしている改造は
(1) 雅楽専用から西洋音楽などでも使えるように変更
  具体的には実行画面から設定ダイアログを出し,各音名と周波数が変更できるようにすること
(2) ソース中のほとんど繰り返しに近い部分をマクロにするかループにするかして,もう少し美しいプログラムにすること
の2点です。

 今回は,(1)を念頭におきつつ,(2)のソースの美化を行います。

------------------------------------------------------------------------
(1)書き変えるファイル

 今回書き換えるファイルは以下の3つです。
 http://members10.tsukaeru.net/ariera/soft/wx.html に変更後のものを含めた全ソースファイルを置いてあります。なお,私のマシン環境が変わったため,Makefileやバッチファイルの内容が変わっているところがあります。前回のもので動いていたという方は,以下の3ファイルだけを入れ替えた方がいいかもしれません。

  fue_gui.h
  fue_freq.h
  fue_gui.cpp

------------------------------------------------------------------------
(2)ボタンに関連づける関数を減らす

 ソースコードを眺めていて,一番気になるのが fue_gui.h の32行目あたりからの
void OnSound**Button(wxCommandEvent &event);
という関数の羅列です。fue_gui.cpp の182行目あたりからこれらの関数の実装を見てみると,引数を変えて
pushSoundButton() という関数を呼んでいるだけですので,何だかとても無駄に見えます。

 これはどこで使っているかというと,fue_gui.cpp の上の方のイベントテーブルで
   EVT_BUTTON(ID_SOUND_D_BUTTON,  FueFrame::OnSoundDButton) 
のように,ボタンと動作を関連づけるために使っているのです。

 マヌケな素人考えでは,イベントテーブルで指定した関数にユーザー定義の引数を渡せれば,どのボタンを押したかがわかってよいかと思ったのですが,
void OnSound**Button(wxCommandEvent &event);
にあるように,ボタンイベント関数の引数は wxCommandEvent 型ひとつと決まっています。

 それでは,wxCommandEvent クラスの中に,どのボタンをおされたことによるイベントなのかを知る方法があるかもしれないと思い,探してみるとやはりありました。
 wxCommandEvent の親クラス wxEvent の GetId() というメソッドです。
 ヘルプによると Returns the identifier associated with this event, such as a button command id. (イベントに関連づけられた識別子を返す。たとえばボタンコマンドID。) と書いてあるのでドンピシャリ! イベントテーブル
   EVT_BUTTON(ID_SOUND_D_BUTTON,  FueFrame::OnSoundDButton) 
の ID_SOUND_D_BUTTON の部分が 
void OnSound**Button(wxCommandEvent &event);
の中から event.GetId() で取得できるわけです。

 前置きが長くなりましたが,改造に入ります。
 まず,fue_gui.h の32行目 void OnSoundDButton(wxCommandEvent &event); から43行目 void OnSoundCpButton(wxCommandEvent &event); までをばっさり削って void OnSoundButton(wxCommandEvent &event); を追加します。
 そして,fue_gui.cpp の182行目から262行目の実装部分も削除して,新しく追加した OnSoundButton() の実装を書きます。
 削った実装は
void FueFrame::OnSoundDButton(wxCommandEvent &event)
{
    pushSoundButton(ICHIKOTSU);
}
のようになっていましたので,OnSoundButton() の実装では,event.GetId() で取得したIDに対応する音名を引数に与えて,pushSoundButton() を呼べばよいことになります。

 ここで引数に与えるべき音名の型は,pushSoundButton() を見ると SoundName 型となっていて,これは fue_freq.h の下の方で enum として定義されています。この enum は,いくつかの関数に引数として渡して音を指定するためと,音ごとの周波数やラベルを入れる配列の添え字のために使われています。
 しかし,SoundName 型と,fue_gui.h の一番下にある,オブジェクトIDのための enum を比べてみると,よく似ています。しかも,脱雅楽専用のためには,「ICHIKOTSU」「DANKIN」などの雅楽用語はできるだけ排除したいものです。
 そこで,ちょっと邪道ですが,SoundName 型をなくして,オブジェクトIDの enum ですべてをまかなうことにしました。配列の添え字にも使うので,数字が同じになるよう,オブジェクトIDの enum の最初に
  ID_INVALID = 0,
を追加し,
    ID_FREQ_PLAY_BUTTON, // 周波数を指定して発音
をの直後に移動し,SoundName の enum を削除します。
 ID_INVALIDはオブジェクトIDとしては使わないのでちょっと妙ですが,気にしないことにします。

 そうすると,OnSoundButton() の実装は
void FueFrame::OnSoundButton(wxCommandEvent &event)
{
    pushSoundButton(event.GetId());
}
と簡単に書くことができます。

 そして,SoundName の enum を削除した副作用を修正します。fue_gui.h のオブジェクトIDの enum をクラス定義の先に持ってきて,fue_gui.h と fue_gui.cpp の中の SoundName という型名をすべて int にし,音名は次のように変えます。
  INVALID    =>  ID_INVALID
  ICHIKOTSU  =>  ID_SOUND_D_BUTTON
  DANKIN     =>  ID_SOUND_DP_BUTTON
  HYOUJOU    =>  ID_SOUND_E_BUTTON
  SYOUZETSU  =>  ID_SOUND_F_BUTTON
  SHIMOMU    =>  ID_SOUND_F_BUTTON
  SOUJOU     =>  ID_SOUND_G_BUTTON
  HUSYOU     =>  ID_SOUND_GP_BUTTON
  OUSHIKI    =>  ID_SOUND_GP_BUTTON
  RANKEI     =>  ID_SOUND_AP_BUTTON
  BANJIKI    =>  ID_SOUND_B_BUTTON
  SHINSEN    =>  ID_SOUND_C_BUTTON
  KAMIMU     =>  ID_SOUND_C_BUTTON
  FREQ_SET   =>  ID_FREQ_SET
 実は fue_gui.cpp の方で,この変更が必要な場所は,後でもっときれいにする必要がある場所でもあるのです。

 最後に,fue_gui.cpp の最初のイベントテーブルの上から12行を
   EVT_BUTTON(ID_SOUND_D_BUTTON,  FueFrame::OnSoundButton) 
   EVT_BUTTON(ID_SOUND_DP_BUTTON, FueFrame::OnSoundButton)
   EVT_BUTTON(ID_SOUND_E_BUTTON,  FueFrame::OnSoundButton) 
   EVT_BUTTON(ID_SOUND_F_BUTTON,  FueFrame::OnSoundButton) 
   EVT_BUTTON(ID_SOUND_FP_BUTTON, FueFrame::OnSoundButton)
   EVT_BUTTON(ID_SOUND_G_BUTTON,  FueFrame::OnSoundButton) 
   EVT_BUTTON(ID_SOUND_GP_BUTTON, FueFrame::OnSoundButton)
   EVT_BUTTON(ID_SOUND_A_BUTTON,  FueFrame::OnSoundButton) 
   EVT_BUTTON(ID_SOUND_AP_BUTTON, FueFrame::OnSoundButton)
   EVT_BUTTON(ID_SOUND_B_BUTTON,  FueFrame::OnSoundButton) 
   EVT_BUTTON(ID_SOUND_C_BUTTON,  FueFrame::OnSoundButton) 
   EVT_BUTTON(ID_SOUND_CP_BUTTON, FueFrame::OnSoundButton)
のように書き換えてください。

------------------------------------------------------------------------
(3)マクロでループ

 これで少しはすっきりしましたが,今書き換えたイベントテーブルの方は全く行数が減っていないのが気になります。これがマクロでなければ
for(int i=ID_SOUND_D_BUTTON; i<=ID_SOUND_CP_BUTTON; i++) {
   EVT_BUTTON(i,  FueFrame::OnSoundButton)
}
とすっきり書くこともできるのですが,マクロではそうもいきません。

#define SET_EVT_BUTTON(i) EVT_BUTTON(i,  FueFrame::OnSoundButton) 
として
SET_EVT_BUTTON(ID_SOUND_D_BUTTON)
SET_EVT_BUTTON(ID_SOUND_DP_BUTTON)
SET_EVT_BUTTON(ID_SOUND_E_BUTTON)
 :
とすれば少しはきれいになるものの,行数は変わりません。

 そこで,マクロでループ構造がつくれないかと,横着なことを考えました。ネットで調べてみると, http://boost.cppll.jp/ ここにある boost ライブラリの中に,マクロでループをまわすライブラリがありました。
 早速,ライブラリをダウンロードして使ってみることにします。マクロの他にもいろいろなものが入っていて面白そうなライブラリですが,とりあえず目的のマクロループ部分だけを使ってみます。
 ライブラリは,一部を除いてヘッダだけでできているようです。今回使おうとしているプリプロセッサのライブラリは,コンパイルする必要はないようですので,適当な場所にダウンロードしたファイルを展開します。
 そして,ライブラリを置いた場所をインクルードパスに含めます。私の環境(Win + MinGW)では,D:\share\boost-1.3 に置いたので,Makefile(makefile.g95) に以下の1行を追加しました。
EXTRACPPFLAGS  = -ID:/share/boost

 boostの日本語マニュアルとにらめっこしていると,どうやら目的のものは BOOST_PP_REPEAT というマクロらしいです。マニュアルを見ながら,ループ部分を書いてみます。

 まず,インクルード文
#include <boost/preprocessor/repetition/repeat.hpp>

 それから,置換用マクロ
#define DECL(z, n, data)  EVT_BUTTON(ID_SOUND_D_BUTTON+n,data)

 最後に,12行の SET_EVT_BUTTON(ID_SOUND_D_BUTTON) を以下のものと置き換えます。
BOOST_PP_REPEAT(11, DECL, FueFrame::OnSoundButton)

 実は私もよく理解しないまま適当に書いているのですが,BOOST_PP_REPEATの第1引数が繰り返す数,第2引数が繰り返すマクロ,第3引数がマクロに渡すデータのようです。
 そして,マクロDECLの第1引数は繰り返し次元(これ,意味がよくわかってません),第2引数が0からはじまる繰り返し回数,第3引数がBOOST_PP_REPEATの第3引数で渡されるデータです。

 これで12行が3行に減りました。しかし,これだけのために新しくライブラリを導入するのもどうかと思います(笑) ループ回数11が直書きだったりするのもちょっと気持ち悪いですが,オブジェクトIDはマクロではなくenumなので,ID_SOUND_CP_BUTTON-ID_SOUND_D_BUTTON とか書けませんしねぇ……

------------------------------------------------------------------------
(4)ラベルの初期化

 先ほど,
  ICHIKOTSU  =>  ID_SOUND_D_BUTTON
のような書き換えをした,FueFrameのコンストラクタ内のラベルやボタンの初期化部分も似たようなコードが並んでいてとても汚いです。
 まず,雅楽以外への対応も念頭において,"壱\n越"などの音名と"〒"などのラベルを配列化してしまいましょう。余談ですが「壱越」は雅楽の音名で「〒」(実際は郵便マークではありません)は龍笛の楽譜の記法です。

 配列の置き場所は,fue_freq.h が一番落ち着くと思うので,fue_freq.h に以下のものを追加してください。長いので一部省略します。全文は上の方に書いたサイトからソースをダウンロードして確認してください。

const wxString soundnames[] = {
  _T(""),
  _T("壱\n越"),
  _T("断\n金"),
  :
};

// ラベル設定
const wxString soundlabels[] = {
  _T(""),
  _T("口"),
  _T(""),
  _T("〒"),
  :
};

 配列の添え字は,同じ音の周波数やオブジェクトIDと一致していないといけません。STLのマップを使うか,
const wxString soundnames[ID_SOUND_CP_BUTTON+1];
soundnames[ID_INVALID] = _T("");
soundnames[ID_SOUND_D_BUTTON] = _T("壱\n越");
みたいな書き方をした方が可読性は増すのかもしれませんが,面倒なのでこのままにしておきます。

 そして,初期化部分をループ化します。たぶん,さきほどからいじっているうちに行番号が変わってしまったと思いますが,もとのソースの69~85行目,92~108行目,113~139行目は,次のようにすっきりと書けます。
    soundButton[ID_INVALID] = NULL;
    soundHoleText[ID_INVALID] = NULL;
    soundFreqText[ID_INVALID] = NULL;
    int point_x = 20;
    for(int i=ID_SOUND_D_BUTTON; i<=ID_SOUND_CP_BUTTON; i++) {
        // 音名ボタン設定
        soundButton[i] =  new wxButton(this, i, soundnames[i],
                                       wxPoint(point_x,20), wxSize(30,80));
        soundButton[i]->SetFont(button_font);

        // 音名ラベルの設定
        soundHoleText[i] =  new wxStaticText(this, ID_SOUND_HOLE_D_TEXT+i,
                                            soundlabels[i],
                                            wxPoint(point_x,110), wxSize(30,20),
                                            wxALIGN_CENTRE);
        soundHoleText[i]->SetFont(hole_font);

        // 周波数ラベルの設定
        char buf[8];
        sprintf(buf, "%4.2f", freqs[i]);
        soundFreqText[i] =  new wxStaticText(this, ID_SOUND_FREQ_D_TEXT+i,_T(buf),
                                             wxPoint(point_x,130), wxSize(33,20),
                                             wxALIGN_CENTRE);
        point_x += 40;
    }

 最後に,fue_gui.cpp の OnOctUpButton() のラベル書き換え部分を
    // OnOctUpButtonの周波数・音名ラベルの書き換え
    char buf[8];
    for(int i=ID_SOUND_D_BUTTON; i<=ID_SOUND_CP_BUTTON; i++) {
        sprintf(buf, "%4.2f", freqs[i+12]);
        soundFreqText[i]->SetLabel(_T(buf));
        soundHoleText[i]->SetLabel(soundlabels[i+12]);
    }
のようにします。OnOctDownButton() の方は, i+12 のかわりに iとすればOKです。何を言っているか分かりにくいと思いますので,ソースをダウンロードして確認してみてください。

------------------------------------------------------------------------
 なんだか自分のマヌケぶりをさらしている上に,wxと関係ない話ばかりになってしまいましたが,ソースがだいぶん短くなりました。(自己満足) 次回はもう少しまじめな機能追加のお話をしようと思います。


*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
 [2] ワンポイント解説  ダイアログエディタを使う
*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-

 今回はお休みさせていただきます。
 次回は「閉じる」「終了」ボタンなどをつけてみます。

------------------------------------------------------------------------

編集後記:
 私のまわりのPC初心者さんの間では,「エラーメッセージが出た」を「デバッグが出た」と言うのが流行っています。元ネタ(?) はWindowsの「エラーが発生しました。デバッグしますか?」のメッセージらしいですが,開発者としてはちょっとドッキリだったりします。しかも,大半の人が「デバッグ」の意味を知らないらしい……


=[広告]======================================================================
 ウザイけどちょっと気になる!? オークションで売られている情報を無料公開中
        http://www6.ocn.ne.jp/~prize/free.html
=============================================================================

★☆=====================================================================☆★
     メールマガジン:wxWindowsでGUIプログラミング 第8号 2004.02.08(Sun)
     発行人:二条
     ご意見・ご感想はこちらへ: ariera@members10.tsukaeru.net
     こちらからもメールが送れます
         http://members10.tsukaeru.net/ariera/other/form/form.cgi
     解除・バックナンバーはこちらからどうぞ
         http://members10.tsukaeru.net/ariera/soft/wx.html
     マイサイト「気まぐれ歴史散歩」もよろしくお願いします
         http://members10.tsukaeru.net/
     このメールマガジンは「まぐまぐ」を使って配信しています
         http://www.mag2.com/
★☆=====================================================================☆★