VC++5.0入門23
チュートリアル読書会5 98/4/26

こんにちは。このホームページを書くようになってから、ほとんど自分でプログラムをしなくなってしまったのですが、最近、仕事と趣味を兼ねて、プログラムを作っています。内容はとても簡単なデータ処理プログラムなんですが、クリックくらいしかしない人でも簡単に使えるインタフェースを作る(考える)というところが面倒で、時間不足もあって、ちょっと苦しんでいます。でも、やっぱり、自分でプログラムするのは楽しいですね。仕事でさえなければ。
前に私はバージョン4.0でもらったチュートリアルを読んでいると書きましたが、バージョン4.0と5.0ではそのチュートリアルが微妙に違うとの指摘を何度か受けました。(みなさんありがとうございます。)それで、以後、ヘルプ(InfoView)のチュートリアルを中心に読んでいくことにします。そのため、章建てではなく、ヘルプ上のタイトルを主に書くようにします。。突然の変更でごめんなさい。とは言え、本質的な違いはないはずです。製本されたチュートリアル(4.0用でも5.0用でも)には、必ず対応個所があるので、ああ、この辺の話だなと、思いながら製本版を読んでもらえば問題ないはずです。どっちにしろ、そんなに細かい(何ページの何行目、というように)コメントはしませんので。なお、コード中のコメントが英語になるようですね。まあ、気にしないでください。

というわけで、今回はヘルプで言えば「ビュー内での Windows メッセージの処理」からですが、その前にちょっとコメントです。
前回はOnDrawのコードとその関連を見ました。ちょっと今更なんですが、OnDrawが何か、、、ということはもうわかっていますよね。わからないという人はVC入門の11を見直しておいてください。

それと、ちょっと本題からはずれるんですが、CStroke::DrawStrokeという名前にはStrokeという言葉が2度もあってちょっと面倒な気がしますか?CStroke::Drawでよいんじゃないか、と思う人も多いのではないでしょうか。命名方法はなかなか難しいのですが、例えば、
    CStroke x;  
    //xなどという名前は、意味がわかり難いので、
    //寿命の長い変数には望ましい名前ではないと私は思うが、、、。

    //まあ、まあ、長いコード

    x.DrawStroke(p);  //pはデバイスコンテキストへのポインタ
などとあった場合、最後のx.DrawStrokeが、例えばx.Drawであるより意味がわかりやすいですね。そういう理由も考えて、チュートリアルの作者はこの名前にしたのだと思います。たぶん。

さて、(ヘルプでのタイトル)「ビュー内での Windows メッセージの処理」から読んでいきましょう。これも意味がわからない人はVC入門のはじめの方を読んで復習してください。簡単に言うと、「マウスボタンが押されたりすると、ウインドウズがメッセージを送信する。そのメッセージをどう処理するか」というお話で、ウインドウズプログラミングの本質的部分です。
ウインドウズ(OSの名前)が送信したメッセージをウインドウ(アプリケーションの窓)が受け取ったときに、線を引くとか何か特別なことをしたいと考えたらどうするか。そのときには、ハンドラ(「扱う者」という意味ですね)という特別な関数を書きなさい、、、ということです。ハンドラはメッセージに対応しています。例えば、 「ユーザがマウスの左ボタンを押す−>ウインドウズがWM_LBUTTONDOWNというメッセージを送信する−> OnLButtonDownという名の関数が呼ばれる」、、、という仕組みです。
こういうメッセージ名やハンドラ名を覚えるのは大変だなぁと思うかもしれません。また、逆に、たくさん覚えていることを自慢する人もいるようです。ただ、初心の内は、必要なものの数はそれほどでもないのと、どうせ、ウイザードバーやクラスウイザードを使うと一覧が出てくるので、正確に覚える必要もないのです。安心してください。
チュートリアルの「メッセージとコードの対応付け」には、ウイザードバーを使ってハンドラを記述する方法が書かれていますが、また、afx_msgとかON_WM_何々とか難しそうなことも書いてありますね。この辺のことは中級にいくころ問題になると思いますが、今は、読み飛ばしておきましょう。どうせ、自動で書かれてしまう部分です。

(初心者は読み飛ばしてもよい注:私は、はじめ、だいたいこれC++なの?って思いましたが、みな、C++のルールと仕掛け(マクロなど)をうまく使ってあたかも別の言語のような見栄えを作っているだけ(のよう)です。MFCを使う以上、これに反発してもしかたありません。これらは存在さえ知っていれば、だんだん使い方もわかってきます。しかし、はじめはどうせ自動で書かれてしまうので気にしなくて良いでしょう!)

ウイザードバーの使い方は、何度か使っている内に覚えるでしょう。今回の目玉は3つのハンドラ関数 OnLButtonDown、 OnLButtonUp、 OnMouseMoveのコードを書くことです。(インプリメントする、、、なんて書いてあるところもありますね。インプリメントとは、実際に動くようにコードを書くという意味です。)
マウスの左ボタンが押されたときに呼び出されるOnLButtonDown
    m_pStrokeCur = GetDocument( )->NewStroke( );
    // Add first point to the new stroke
    m_pStrokeCur->m_pointArray.Add(point); 

    SetCapture( );  // Capture the mouse until button up
    m_ptPrev = point;  // Serves as the MoveTo( ) anchor point 
                    // for the LineTo() the next point, as 
                    // the user drags the mouse
    return;

ですね。意味はわかりますか?もうわかる人もいるかもしれません。わかったら素直に喜んでください。わからなくても落ち込む必要はありません。チュートリアルはなかなか難攻不落で有名なようです。
最初の1行目は、まず、GetDocument()が実行されます。その結果、ドキュメントが戻ります。「ドキュメントが戻ります」?なんか意味不明だな、と思った人は、関数の戻り値についてちょっとだけ勉強して下さい。結論を言うと、コードのGetDocument()の部分が実行中にCScribbleDocへのポインタに変わるのと同じです。(GetDocument()はそういう特別な関数なのです。こいつの名前は覚えておく必要がありますぜ。だんな。(あねご。))すると、GetDocument( )->NewStroke( )は、CScribbleDocというクラスのNewStroke()というメンバ関数を実行しろという意味です。この関数は、CStroke(一筆)のはじまりを作り、m_strokeList(というドキュメントにあるリスト)にそれを登録する関数で、定義はすでに書いてあります。ちょっと面倒ですが、宣言と定義の部分を探し出し、もう一度読み直すことをお勧めします。(教師の立場からいうと、こう言われてちゃんと見直す人と見直さずに次に進む人で、後々、、、。なんて、いや〜なことを言ってすみません。)
この関数は戻り値として、今書いているCStrokeへのポインタを戻すので、それを作業用の変数m_pStrokeCurに代入しています。これでようやく1行目が終わったんですね。
なお、このm_pStrokeCurは作業用の変数ですが、ちゃんと一筆を表すCStrokeを指しているので、この変数を使って何かすれば、それは、本当に何かしているのです。実際、コメントをはさんで次の行では、マウスが押された点の座標(この関数が呼ばれた点の座標)を、m_pointArrayに格納していますね。m_pointArrayがなんだか忘れた人は、もう一度、CStrokeの宣言を見直しておいてください。これが、一筆中のたくさんの点を格納する場所だったんです。
SetCapture()はマウスを「捕まえておく(キャプチャする)」関数ですが、具体的な例はVC入門10に書きました。そして、ビューのなぞのメンバ変数m_ptPrevにこの点を代入して(つまり記憶させて)終わりです。m_ptPrevはまた後でみましょう。
さて、チュートリアルでは、次にOnLButtonUpの定義に行きます。これは、マウスのボタンを離すときの処理ですが、どうしてこんな順なんでしょうね。私なら、ボタンを押して、マウスを動かして、ボタンを離す(一筆のできあがり)という順番にしたがって、OnMouseMoveの定義を考えるのですが、、、。まあ、ここでは、この順番で見てみましょう。
OnMouseMoveはマウスが動かされたときに呼び出されるハンドラです。その定義は
if( GetCapture( ) != this )
    return;        // If this window (view) didn't capture the 
                // mouse, the user isn't drawing in this window.

CClientDC dc( this );

m_pStrokeCur->m_pointArray.Add(point);

// Draw a line from the previous detected point in the mouse
// drag to the current point.
CPen* pOldPen = 
        dc.SelectObject( GetDocument( )->GetCurrentPen( ) );
   dc.MoveTo( m_ptPrev );
   dc.LineTo( point );
   dc.SelectObject( pOldPen );
   m_ptPrev = point;
   return;
ですね。まず、最初にマウスがキャプチャされているかどうか見て、もし、キャプチャされていなければ、returnでこの関数を終わらせてしまいます。(つまり、この場合は何もしないのです。)OLButtonDownで見たように、マウスはボタンを押したときにキャプチャされるので、ここでキャプチャされていないということは、ボタンが押されていないということです。このハンドラはマウスが押されていようがいまいが呼び出されるのでこの部分が必要なのです。
次にデバイスコンテキストが生成されています。そして、また、この点の座標が現在のCStrokeのm_pointArrayに格納されていますね。次の
CPen* pOldPen = 
        dc.SelectObject( GetDocument( )->GetCurrentPen( ) );
は少々複雑ですね。まず、GetDocument( )->GetCurrentPen( ) が実行されます。つまり、CScribbleDocのメンバ関数GetCurrentPen( )が実行されるということですね。この関数の定義をもう一度見ておいてください。 現在のペンへのポインタを戻すだけの単純な関数です。現在のペンm_penCurもドキュメントのメンバで、 InitDocument()の中で初期化されていましたね。
この辺のになると初心者は目が回るばかりかもしれませんが、辛抱強くコードを追いかけて下さい。とても大事なことです。
さて、このペン(へのポインタ)をSelectObjectで選択し、以下で使うのです。この際、古いペンへのポインタが戻されるので、pOldPenに保存し、後でdc.SelectObject(pOldPen)で元に戻しています。これはこのデバイスコンテキストがもともと持っているペンを戻す必要があるからです。(実は、戻さなくてもよい事は多々あるようですが、何かあるといけないので、ルールは守っておきましょう。)
その間の
   dc.MoveTo( m_ptPrev );
   dc.LineTo( point );
を見てください。ここでm_ptPrevが使われているのです。このOnMouseMoveがOnLButtonの直後に呼び出された時、m_ptPrevには、マウスボタンが押された点が記録されいます。そして、OnMouseMove内で、次の点(つまり、OnMouseMoveが呼び出された点)まで線を引いているのです。このOnMouseMoveが呼び出された点の座標はその後のm_ptPrev = point;で、また、m_ptPrevに記録されます。OnMouseMoveはマウスが動かされている限り呼び出されるので、次々に、m_ptPrevから新しい点まで線画引かれ、順繰りにm_ptPrevが更新されていくのです。
どうです。あっ、わかった!と思ってもらえれば本望なのですが。もし、わかりにくかったら、もう一度、がまん強く考えてみてください。
ところで、線を引くのはOnDrawでじゃなかったのかな、と思った人は、イイセン行っていると思います。実は、このプログラムでは、マウスのボタンを押して動かしたときには、すぐに線が引けるように、ハンドラで線を引いてしまうのです。ただ、その後、ウインドウの大きさがかわったりなんだりで、再描画の必要がでてくるかもしれません。そのときには、OnDrawが呼び出されるのです。OnDrawとはもともとそういうものです。
最後は、マウスの左ボタンが離された時に呼び出されるハンドラOnLButtonUpです。コードはチュートリアルを見て下さい。OnMouseMoveとほとんど同じですね。ちょっと違うのは、マウスを解放( ReleaseCapture( );)しているところと、ドキュメントへのポインタをわざわざCScribbleDoc* pDoc = GetDocument();などとして、変数pDocに記録している点でしょうか。なぜ、pDocなどという変数をわざわざ使うのか、今はわかりませんね。OnMouseMoveのように変数を作らなくても、ここまでなら、良いはずです。ただ、まあ、ここはチュートリアルにしたがっておきましょうか。(これは将来の拡張のためだと推論されます。)
もし、ONLButtonUpのコードが理解できれば、ここは卒業です。

そして、とうとうScribbleプログラムをビルドできるのです!!!やってみてください。うまくいきましたか?だめでもあきらめないで、なんとかエラーを直して、実行してみてください。
実行できたら、しばらく悦にいってから、友人か恋人か奥さんかだんなさんか、とにかく身近な人を呼んで見せてあげましょう。これがVC/MFCプログラムの有名なScribbleなんだよ。私はそれに成功したんだよ、と言って。

疲れましたね。ご苦労様。次回は「ユーザインターフェイスの作成」に入りましょう。
目次のページ
前のページ
後のページ