VC++5.0入門25
チュートリアル読書会 98/5/10

こんにちは。前回は「メニュー」の見てくれ(カッコよく言うとインターフェイス部分)を作りました。しかし、これだけでは、メニュー項目があるだけで、実際に選択しても何も起こらないでしょう。今回(と次回)の(ヘルプのタイトルでは)「ウィザード バーによるビジュアル オブジェクトとコードの対応付け」で、ちゃんと動くようになるはずです。

前に言いましたが、印刷されたチュートリアルはバージョンによって微妙に違うらしいので、前々回からヘルプ(InfoView)でのタイトルを使って説明しています。印刷されたチュートリアルを読んでいる人(大部分の人はそうでしょう)には、タイトル(題名)が少し違うこともあると思いますが、そのまま、対応していそうな個所を読めば問題ないはずです。
さて、今回は、ウイザードバーの使い方を覚えながらコードを書き加えるということになります。とにかく考え込まず、手を動かすことが重要です。手を動かせばコードはできるが、何をやっているのかわからない、ということはよくあります。もちろん、いつまでもわからないままでは、気が滅入りますが、考えてから手を動かすより、手を動かしてから考えた方がずっと得策だと思います。

いつもの注意でしたが、ついでにもうひとついつもの注意を。いつもそうですが、チュートリアルは順番に読めば理解できる、、、という構造になっていません。(初心者の場合です。)今回も、知らない関数やメンバがいきなり出てきて、なんだろうと思っていると説明は後になっていたりします。そういうもんだと思っておいてください。

さあ、もうはじめましょうか。今回の目玉は
1.描画での線の太さを切り替えるコマンドを生成するメニュー項目
2.ドキュメントからすべてのストロークを削除するメニュー項目  
のインプリメント(動くようにコードを書くという意味、実装とも言う)です。チュートリアル途中の項目「ハンドラを記述するコマンド ターゲットクラスの決定」は実はとても重要な情報を含んでいる(はず)のですが、初心者の内はななめ読みにとどめた方が良いかもしれません。
「Scribble プログラムの [すべてを削除] コマンドとハンドラのコードとの対応付け」は初心者にはとても重要です。ただ、最初に「COMMANDとUPDATE_COMMAND_UI」ってなんだろうと思うかもしれません。UPDATE_COMMAND_UIの方は次回考えましょう。COMMANDは、WM_COMMANDを意味しています。このWM_COMMANDはScribbleのユーザがメニューのどれかを選択したときに、ウインドウズが送信するメッセージです。なお、ウインドウズはメッセージと一緒にどのメニューが選ばれたのかそのIDも通知してくれるので、実際にどの項目が選ばれたのかちゃんとわかります。(前回もそう書きましたよね。^^)
今回ウイザードバーでやることは、このメッセージが来た時にどう処理するか決める関数(ハンドラというのでした。)の元を自動で書くということです。もちろん、その後に、本当に何をするのか手で(テキストエディタで)書き込まなければなりません。

「すべてを削除」のハンドラOnEditClearAllのコードは単純ですね。
    DeleteContents( );
    SetModifiedFlag();
    UpdateAllViews( NULL );
最初の関数は前に定義したデータを削除する関数です。忘れた人は、どこにあったか見つけだし、内容をもう一度考え直すとよい勉強になるでしょう。
次の関数は、CDocumentクラスに定義されている(つまりMFCの作者が作った)関数で、この関数を(この形で)呼び出すと、「データに変更が加えられた」という記録が残され、アプリケーション(今はScribble)を終了する時に、「保存しますか?」というダイアログが自動ででるようになるのです。便利ですね。
次の「UpdateAllViews( NULL );」の解説はちょっとわかりにくいかもしれませんが、要するに、ビュー(データを見せる窓)に「内容を更新しなさい」と命令しているのです。ドキュメントのデータを削除してもビューを更新しなければ、元のデータが表示されたままです。この更新命令で、きちんと画面上の絵も消去されるわけです。

「太線」の方はかなりわかりにくいかもしれません。(どうでしょう?)私がはじめて読んだ時には、何をしているのかわかりにくかったような記憶があります。
どういうことかというと、完成後にはメニューに「太線」という項目ができるのですが、Scribbleのユーザがこの項目を選択する度にチェックが付いたり外れたり、、、となるのです。(こういうのをトグルとか言いますよね。)このチェックが入っているときは、「一筆」が太線になり、チェックが消えている時は一筆が細線になるのです。今回はチェックを入れるところまで行けませんが、とにかく、いずれそうなると思っておいてください。
チェックは次回にまわすとしても、コード内容はどうすれば良いのでしょうか?普通なら、現在の「一筆」が太線なのか細線なのか記録する場所が欲しいですね。それから細線が選択されているときのペン幅と太線が選択されているときのペン幅も記録しておく必要があります。これらの変数は実は、ちょっと後に出てくるのです。詳しくは、後で、チュートリアルをじっくり読んでもらうことにして、この「読書会」では変数の方を先に眺めておきましょう。チュートリアルの作者は、それらの変数を
    BOOL    m_bThickPen;    // Thick currently selected or not 
    UINT    m_nThinWidth;    // Current definition of thin
    UINT    m_nThickWidth;    // Current definition of thick 
としています。(ヘルプなら「Scribble への新しいメンバ変数の追加」という項にあります。)
最初の変数はBOOLというTRUEかFALSEというふたつの値のみをとる変数です。このm_bThickPenがTRUEのときは太線をFALSEのときは細線を使うという意味です。
次のふたつの変数は細線と太線の幅を記録する変数です。(thinとかthickとかちょっとなじみの薄い英単語ですよね。^^)
これらの変数はCScribbleDocのメンバとして定義され、InitDocumentの中で
m_bThickPen = FALSE;
m_nThinWidth = 2;     // Default thin pen is 2 pixels wide
m_nThickWidth = 5;    // Default thick pen is 5 pixels wide
と初期化されると書いてありますね。つまりはじめは細線が使われ、細線の幅は2ピクセル、太線の幅は5ピクセルということです。(ピクセルは描画のときの単位で、画面上の点を表しているのです。)(実は「CScribbleDocのメンバとして定義され、InitDocumentの中で初期化されていますね。」なんて言われてすんなりわかるなら、もうだいぶよく理解していると言えます。もし、意味がわかりにくかったら、InitiDocumentの定義位置を見つけだし、これが何をする関数か、どこで使われているか少しだけ考えてみてください。)
さあ、仕組みはわかってきました。つまり、メニューの「太線」のハンドラはこれらの変数をいじったり使ったりするわけです。
では、元に戻って、「太線」のハンドラOnPenThickOrThinの定義を見てみましょう。(ヘルプなら「Scribble プログラムの [太線] コマンドとハンドラの対応付け」のところ。)
    // Toggle the state of the pen between thin and thick.
    m_bThickPen = !m_bThickPen;

    // Change the current pen to reflect the new width.
    ReplacePen( );
最初のm_bThickPen = !m_bThickPen;はTRUEとFALSEを入れ替えよ、という意味です。(右辺は元の値の否定を意味しています。ちょっと詳しく言うと、「元の値の否定」を新たな値として代入せよ、、、ということです。)
次のReplacePen( );はどこでこんな関数定義したっけとびっくり(そうでもないですか)ですが、すぐ後で
    m_nPenWidth = m_bThickPen ? m_nThickWidth : m_nThinWidth;
    // Change the current pen to reflect the new width.
    m_penCur.DeleteObject( );
    m_penCur.CreatePen( PS_SOLID, m_nPenWidth, RGB(0,0,0) );
と定義されています。
最初の一行目の意味はわかりますか?Cでは普通の構文なんですが、例えば私のC++入門しか読んでいない人には意味不明でしょう。ちょっと、説明しましょうか。例えば
W = X ? A : B;
とあった場合、XがTRUEならWにはAが代入され、XがFALSEならWにはBが代入されるのです。初心者には嫌な感じでしょうが、慣れだけの問題です。
そういうわけで、1行目の意味は、「m_bThickPenがTRUEならm_nPenWidthにm_nThickWidthを代入、FALSEならm_nThinWidthを代入せよ」という意味になっているのです。
その後、m_penCur.DeleteObject( );で現在の「ペン」を削除し、m_penCur.CreatePen( PS_SOLID, m_nPenWidth, RGB(0,0,0) );で新しい「ペン」を作っています。(紛らわしいですが、ここで削除したり作ったりしているペンはCPenのオブジェクトそのものではないですね。CPenのオブジェクトm_penCurがまずあって、そのメンバ関数のCreatePenという関数を呼び出してはじめて使える「ペン」になる。また、DeleteObjectを呼び出すとその「ペン」(CPenオブジェクトそのものではない)は破棄されるという意味です。MFCのCPenはこのように使うよう決められているのです。ですから、この形を覚えて、こう使いましょうね。)
次は、最初に見た変数の追加の話です。ここはもう説明は要りませんね。

今回はここまでにしましょう。次回は(ヘルプでいう)「ユーザー インターフェイス オブジェクトの更新」です。ここを終えると、Scribbleはかなり本格的なプログラム(に見えるよう)になってきます。
目次のページ
前のページ
後のページ