Mesoscopic Programming

タコさんプログラミング専門

ヌメロン製作講座第11回:マウスクリックに対応する

本日はマウスクリックに対応します。

余談になりますが本当にはてなブログは書きやすいですね。本連載もはてなブログの練習がてら行っているので毎回文書スタイルが変化すると思いますがあしからずご了承願います。ついおとといまで、はてな記法でもほぼ問題なく HTML が使えるなんて知らなかったので使えると知ったいま楽しくてしょうがありません。文章を書く楽しさを再認識させてくれるブログ、それがはてなブログですね。最近 facebook をやり始めましたがシステムの分かりづらさったらありゃしません。それに較べてはてなシステムの分かりやすさは群を抜いています。
本当に天才の集まりなんでしょうね、はてなの中の人たちって。はてな萌え

それでは本題に入ります。

  • 修正ファイル
  1. main.h
  2. main.cpp

class Application

アプリケーションクラスの変更点。

  • 追加メソッド
  1. VOID OnLbuttonDown();
  2. VOID OnLbuttonUp();
  3. VOID OnLbuttonDblClk();

言わずと知れたマウスメッセージに対応するやつらです。

  • 修正メソッド
  1. BOOL HookMessage( LPMSG pmsg );
  2. VOID OnCommand();

コンボボックスを思い通りに操るための追加修正です。

VOID Application :: OnLbuttonDown()

マウス左ボタンダウンメッセージ処理です。

VOID Application :: OnLbuttonDown()
{
    int x = LOWORD( lparam );
    int y = HIWORD( lparam );

    setGrid.OnLbuttonDown( x, y );

    lresult = 0;
}

グリッドクラスにマウス座標付きで通知します。

VOID Application :: OnLbuttonUp()

マウス左ボタンアップメッセージ処理です。

VOID Application :: OnLbuttonUp()
{
    setGrid.OnLbuttonUp();

    lresult = 0;
}

これもグリッドに通知するだけ。

VOID Application :: OnLbuttonDblClk()

左ボタンダブルクリックメッセージ処理です。

VOID Application :: OnLbuttonDblClk()
{
    int x = LOWORD( lparam );
    int y = HIWORD( lparam );

    setGrid.OnLbuttonDblClk( x, y );

    lresult = 0;
}

これまた同様にグリッドへの通知のみでOK。

BOOL Application :: HookMessage()

さてここからがアプリケーションクラスの醤油です。いや味噌です。

BOOL Application :: HookMessage( LPMSG pmsg )
{
    NMKEY   nmk;
    LRESULT result;
    POINT   pt;
    ~中略~
    else if ( pmsg->hwnd == GetCapture() )
    {
        if ( pmsg->message == WM_LBUTTONDOWN )
        {
            GetCursorPos( & pt );

            result = SendMessage( pmsg->hwnd, WM_NCHITTEST, 0, MAKELPARAM( pt.x, pt.y ) );

            if ( result == HTNOWHERE )
            {
                ScreenToClient( hwnd, & pt );

                PostMessage( hwnd, pmsg->message, pmsg->wParam, MAKELPARAM( pt.x, pt.y ) );
            }
        }
    }

    return FALSE;
}

以前の投稿でも話しましたが、実はコンボボックス野郎はドロップダウンリスト表示中はマウスをキャプチャしやがっているため、マウスメッセージがメインウィンドウまで届きません。チキショー!
そこでコンボボックスがマウスメッセージを受け取る前に、こっちで勝手にフックしてやりました。左クリックしたときの座標がドロップダウンリストウィンドウ内でなければ、メインウィンドウ宛てにマウスメッセージをポストしています。これでコンボボックス以外の領域をクリックされたときにコンボボックスを破棄することができるようになりました。

VOID Application :: OnCommand()

こちらもコンボボックスにまつわる追加修正です。

    UINT    id   = LOWORD( wparam );
    UINT    code = HIWORD( wparam );

    if ( id == ID_COMBO )
    {
        if ( code == CBN_SELENDOK )
        {
            setGrid.OnEditEnd();
        }
        else
        {
            return;
        }
    }
    else
    ~中略~
}

これがないとドロップダウンリストの項目をクリックしたときにドロップダウンリストが閉じるだけでコンボボックスは残っています。これを追加することでリターンキーが押されたときのようにすぐさま決定するようにしました。


class Grid

続きましてグリッドクラスの修正点についてご説明いたします。

  • 追加変数
  1. BOOL pushMouse;

pushMouse はマウスボタンが押されているときに立つフラグです。pushKey のマウス版ですね。

  • 追加メソッド
  1. virtual BOOL OnLbuttonDown( int x, int y );
    • 左ボタンが押された。
  2. virtual VOID OnLbuttonUp();
    • 左ボタンが離された。
  3. virtual BOOL OnLbuttonDblClk( int x, int y );
    • 左ボタンがダブルクリックされた。
  4. virtual int HitTest( int x, int y );
    • セルを叩いているか調べる。
  5. virtual BOOL IsCapture();
    • マウスかキーボードが押されていれば真を返す。

まあ大体わかると思います。

BOOL Grid :: OnLbuttonDown()

マウスボタンが押されたとき呼ばれます。

BOOL Grid :: OnLbuttonDown( int x, int y )
{
    Cell    * cells = GetCells();
    int     n;

    if ( hctrl != NULL )
    {
        OnEditBreak();
    }

    if ( ! IsCapture() && cursor )
    {
        n = HitTest( x, y );

        if ( n >= 0 )
        {
            Select( n );

            selectLR = n;

            if ( cells[ n ].cid == CELL_BUTTON )
            {
                pushMouse = TRUE;

                Push( TRUE );
            }

            return TRUE;
        }
    }

    return FALSE;
}

まずここに来るということは、編集作業中のコントロール以外の場所がクリックされたことを意味するので編集作業は中止です。コンボボックス以外のコントロールは、自分以外の場所がクリックされたときにちゃんとメインウィンドウまでマウスメッセージが届くので特別なことはいりません。編集領域以外の場所がクリックされたということは、ユーザが編集作業を止めたがっているに違いないのですからそれに対応してあげましょう。
次に、現在マウスもキーボードも押されていない、そして押された場所が編集可能なセルだったらそれを選択します。
もしそのセルがボタンだったら、キーが押されたときと同じように押された状態を表示して押されてる状態だというフラグを立てます。

VOID Grid :: OnLbuttonUp()

ボタンが離されました。

VOID Grid :: OnLbuttonUp()
{
    Cell & cell = GetCells()[ select ];

    if ( pushMouse )
    {
        Push( FALSE);

        pushMouse = FALSE;

        cell.OnButton();
    }
}

もしボタンがマウスによって押された状態ならボタンをもとに戻してボタン機能処理を実行します。

BOOL Grid :: OnLbuttonDblClk()

ダブルクリックされました。

BOOL Grid :: OnLbuttonDblClk( int x, int y )
{
    Cell    * cells = GetCells();
    int     n;

    if ( hctrl != NULL )
    {
        OnEditBreak();
    }

    if ( ! IsCapture() && cursor )
    {
        n = HitTest( x, y );

        if ( n >= 0 )
        {
            Select( n );

            selectLR = n;

            if ( cells[ select ].cid >= CELL_EDIT )
            {
                OnEditBegin();
            }

            return TRUE;
        }
    }

    return FALSE;
}

選択されたセルを即編集開始する以外は押されたときといっしょです。

int Grid :: HitTest()

クリックされた場所がセルを指しているか調べます。

int Grid :: HitTest( int x, int y )
{
    Cell    * cells = GetCells();
    RECT    rc;

    for ( int n = 0; n < maxCell; n++ )
    {
        Cell & cell = cells[ n ];

        if ( CanSelectCell( cell ) )
        {
            cell.GetRect( rc, offset );

            if ( x >= rc.left && x <= rc.right )
            {
                if ( y >= cell.top && y <= rc.bottom )
                {
                    return n;
                }
            }
        }
    }

    return -1;
}

すべてのセルに対して範囲チェックします。セル番号はゼロから始まるので失敗した場合はゼロではなく -1 を返します。

BOOL Grid :: IsCapture()

マウスかキーが押された状態なら真を返します。

BOOL Grid :: IsCapture()
{
    return ( pushKey != 0 || pushMouse );
}

この検査は今後何度も使うのでメソッドにしました。

今回は以上です。
次回は何にしましょう、考えときます。