VC++5.0入門10
マウスのキャプチャ 97/10/26

こんにちは。メールをくださったみなさん、最近、ちょっと返事が書けないのですが、お許しください。10月28日にひとつ締め切りを抱えているのですが、それが過ぎれば時間に余裕ができます。返事ができるのはその後になると思います。
前回、マウスの左ボタンクリックで猫という字を書くプログラムを作りました。プロジェクトの名前は確かNekoでしたね。今週から、これを少しづつ改造してみましょう。
それでは、VCを開いて、メニューの「ファイル」から「ワークスペースを開く」を選び、適当なディレクトリでNekoを見つけ、開いて(ファイルを開くダイアログボックスで、Neko.dswというファイルを開けば良いのです。このファイルがプロジェクトを管理しています)ください。(以後、こういうのをメニュー−>ファイル−>ワークスペース開く、なんて書くかも知れません。わかりますよね。)
さて、ちゃんとうまく行っているか、もう一度、メニューのビルドで実行を選択して、うまくいったらちょっと遊んで見ましょう。(当然そのはずですが)実行できたら、終了して、VCに戻りましょう。
前回の最後にClassWizardを使って、マウスの左ボタン押し下げに対するハンドラを作ったのでした。今日はちょっとその続きをやってみます。まず、クラスビューでCNekoViewを右クリックしてみてください。出てきたポップアップメニューからハンドラの追加を選んでください。新しく出てきたダイアログの右の窓には、処理できるウインドウズからのメッセージが並んでいます。前回は、WM_LBUTTONDOWNを選んだのでした。今回は、WM_MOUSEMOVEを選んでみましょう。まず、WM_MOUSEMOVEを選択し、追加と編集ボタンを押します。これで一瞬にして、必要なメンバ関数の宣言やメッセージマップへの書き込みは行われて、void CNekoView::OnMouseMove(UINT nFlags, CPoint point)の定義を書くように促されることになります。これはもちろん、NekoView.cppというファイルの中です。
ここで、この関数の定義を下のように変えて見てください。
void CNekoView::OnMouseMove(UINT nFlags, CPoint point) 
{
    CClientDC dc(this);
    dc.TextOut(point.x, point.y, "猫");
}
これはOnLButtonDownの中身と同じですね。実はこのハンドラ(メンバ関数)はマウスが動いたときに呼び出されます。そしてマウスが動いたときにマウスの座標がウインドウズによってpointとして与えられるのです。結果はマウスを動かすとその後に猫という文字を軌跡として残すというものです。では、実行してみてください。
どうでしたか?マウスを動かすとひたすらその後に猫という字が出てきます。(もちろん、猫が嫌なら別の語句を入れてください。)ちょっと面白いですが、マウスを動かすといつでも字が書けてしまうので困ります。例えば、マウスの左ボタンを押しながら動かすと字が書け、そうでないときには何も起こらない、という方が良いでしょう。そうするために「マウスをキャプチャする」という技法を使います。
まず、後で必要になるので、もうひとつハンドラを付け加えます。(クラスビューなどから)CNekoViewにWM_LButtonUpというメッセージに対応するハンドラを追加を行っておいてください。
ここで念のため、NekoView.hを開いて、CNekoViewの宣言部を見てください。クラスの宣言部には、
    afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
    afx_msg void OnMouseMove(UINT nFlags, CPoint point);
    afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
という三つの関数が宣言されています。これらの関数は順に、マウスの左ボタンが押されたとき、マウスが動いたとき、マウスの左ボタンが放されたときに、呼び出される関数です。(前に書きましたが、afx_msgは目印という以上の意味は無いようです。したがって、無視しておいてください。)さて、NekoView.cppにあるこれらの関数の定義を次のように直してみてください。
void CNekoView::OnLButtonDown(UINT nFlags, CPoint point) 
{
SetCapture();  //マウスをキャプチャ(捕獲)する
}

void CNekoView::OnMouseMove(UINT nFlags, CPoint point) 
{
    //マウスをキャプチャしていなければ、何もしない
    if(GetCapture()!=this)
        return;
    //マウスをキャプチャしていれば猫と書く
    CClientDC dc(this);
    dc.TextOut(point.x, point.y, "猫");
}

void CNekoView::OnLButtonUp(UINT nFlags, CPoint point) 
{
    //マウスをキャプチャしていなければ、何もしない
    if(GetCapture()!=this)
        return;
    ReleaseCapture();  //マウスを解放する
}
です。まず、OnLButtonDown関数(左ボタンの押し下げのとき)は、猫と書かないようにしました。これは、マウスが動いているときに書いてくれるので、省略したのですが、深い意味はありません。そして新しい関数SetCaptureを呼び出すことにしました。これは、マウスをこのウインドウが捕獲(キャプチャ)する、という関数です。この関数を呼ぶことで、CNekoViewというビューウインドウがマウスからの信号を常に受け取れるように設定されるのです。
また、この関数を呼び出すと、マウスはキャプチャされた状態になり、キャプチャされていない状態と区別が付けられるようになります。(上のコードでは、つまり、マウスの左ボタンを押すと、そのマウスはキャプチャされた状態になるのです。)
さて、OnMouseMove関数を考えましょう。この関数はマウスが動いたときに、左ボタンが押されていれば、猫という字を書く関数です。ところで、はじめに左ボタンを押していれば、マウスはキャプチャされています。左ボタンが押されていなければ、マウスはキャプチャされていません。つまり、猫という字を書くか書かないか(左ボタンが押されているかどうか)は、マウスがキャプチャされているかどうかで判断できるのです。
実は、この判断をするのにGetCaptureという関数あります。この関数の戻り値はマウスをキャプチャしているウインドウへのハンドル(ウインドウを表すもののことです)です。GetCapture()==thisなら、このビューウインドウが、マウスをキャプチャしていることになり、逆に、GetCapture()!=thisなら、このビューウインドウはマウスをキャプチャしていないということです。その場合、何もしたくないので、ただ、return;としました。(returnとは、その関数をすぐに終了させるという意味でしたね。)これが、
    //マウスをキャプチャしていなければ、何もしない
    if(GetCapture()!=this)
        return;
です。もし、マウスをキャプチャしていれば、if文の条件を満たしていないので、rfeturn;は実行されず、次の「猫という字を書くコード」
    CClientDC dc(this);
    dc.TextOut(point.x, point.y, "猫");
が実行されるのです。
最後に、左ボタンを放すときに呼ばれる関数OnLButtonUp関数を考えましょう。猫という字は左ボタンを押しているときだけ書かれ、左ボタンを放したら、もう字は書かれないようにしたいのでした。そのためには、OnLButtonUpでRelaseCaptureという関数を実行すれば良いのです。こうすると、マウスはキャプチャ状態から解放されるのです。ただ、マウスがキャプチャされていないときには、何もしたくないので、if(GetCapture()!=this) return; というコードが最初にあるのです。マウスの解放はとても重要なので忘れないでください。
以上で、マウスのキャプチャの説明を終わります。もう一度、Nekoを実行してみてください。ただ、マウスを動かすだけでは、何も起きません。一方、左ボタンを押しながらマウスを動かすと猫という字がかけるのです。
前より少しだけよくなったプログラムですが、ちょっと問題もあります。まず、マウスを(左ボタンを押しながら)動かして、猫という字を書いてみてください。次にこのプログラム(Nekoという名でしたね)をアイコン化してもう一度ウインドウ化してください。すると先ほど書いた猫という字はきれいさっぱり無くなっているはずです。これは、このプログラムは、マウスを動かしながら字を書いているだけで、そのデータ(文字の位置など)を保存はしていません。このため、このプログラムのウインドウの一部が見えなくなり、次に現れると、前の字が消えているのです。どこに字を書いたかというデータが残っていないので、ウインドウズがそのウインドウを再現できないからです。
この問題を解決するには、字を書いた場所のデータをどこかに保存すれば良いのですが、それは次回ということにしましょう。
目次のページ
前のページ
後のページ