ぷるぷるの雑記

低レイヤーがんばるぞいなブログ. 記事のご利用は自己責任で.

C++でハンドラをシンプルに書きたい

C++でデスクトップアプリケーションを書いているとイベントハンドラ(メッセージハンドラ)を書くことになると思います. ボタンをクリックしたら何かを実行するくらいであれば問題ないですが、右クリック中にマウスを動かしたときはAという動き、マウスホイールをクリック中にマウスを動かしたときはBという処理をしたいときにどうすればシンプルに書けるかなーと少し思案していました. 以下フレームワークMFC(という古の技術)を使用していますが、フレームワークに依らない話になります.


最初は次のように実装しました.

/********************** Hoge.h **********************/
class Hoge
{
// Field
private:
    BOOL isRButtonDown=false;
    BOOL isMButtonDown=false;

// Method
private:
    void ShoriA(CPoint pt);
    void ShoriB(CPoint pt);
    DECLARE_MESSAGE_MAP()
    afx_msg void OnMouseMove(UINT nFlags, CPoint pt);
    afx_msg void OnRButtonDown(UINT nFlags, CPoint point);
    afx_msg void OnRButtonUp(UINT nFlags, CPoint point);
    afx_msg void OnMButtonDown( UINT nFlags, CPoint point);
    afx_msg void OnMButtonUp(UINT nFlags, CPoint point);
}

/********************** Hoge.cpp **********************/

BEGIN_MESSAGE_MAP(Hoge, CEdit)
    ON_WM_PAINT()
    ON_WM_MOUSEMOVE()
    ON_WM_RBUTTONDOWN()
    ON_WM_RBUTTONUP()
    ON_WM_MBUTTONDOWN()
    ON_WM_MBUTTONUP()
END_MESSAGE_MAP()


void Hoge::OnPaint() {
// 描画処理
}

void Hoge::OnMouseMove(UINT nFlags, CPoint pt)
{
    if(isRButtonDown){
         ShoriA(pt);
    }else if(isMButtonDown){
         ShoriB(pt);
    }
    
    // 再描画
    OnPaint();
}

void Hoge::ShoriA(CPoint pt)
{
// 右クリック中の処理
}

void Hoge::ShoriB(CPoint pt)
{
// マウスホイールクリック中の処理
}

void Hoge::OnRButtonDown(UINT nFlags, CPoint point)
{
    isRButtonDown=TRUE;
}

void Hoge::OnRButtonUp(UINT nFlags, CPoint point)
{
    isRButtonDown=FALSE;
}

void Hoge::OnMButtonDown(UINT nFlags, CPoint point)
{
    isMButtonDown=TRUE;
}

void Hoge::OnMButtonUp(UINT nFlags, CPoint point)
{
    isMButtonDown=FALSE;
}

これでも動くには動くんですが、OnMouseMove()内にif文が複数あり読みにくいです. さらに条件分岐が増えると、コードが読みにくくなったり条件の追加忘れが生じる恐れがあります.


処理をシンプルにするために、関数ポインタを使った実装方法に変更しました。

/********************** Hoge.h **********************/
class Hoge
{
// Field
private:
void (Hoge::*pFuncOnMouseMove)(CPoint pt) = NULL;

// Method
private:
    void ShoriA(CPoint pt);
    void ShoriB(CPoint pt);
    DECLARE_MESSAGE_MAP()
    afx_msg void OnMouseMove(UINT nFlags, CPoint pt);
    afx_msg void OnRButtonDown(UINT nFlags, CPoint point);
    afx_msg void OnRButtonUp(UINT nFlags, CPoint point);
    afx_msg void OnMButtonDown( UINT nFlags, CPoint point);
    afx_msg void OnMButtonUp(UINT nFlags, CPoint point);
}

/********************** Hoge.cpp **********************/

BEGIN_MESSAGE_MAP(Hoge, CEdit)
    ON_WM_PAINT()
    ON_WM_MOUSEMOVE()
    ON_WM_RBUTTONDOWN()
    ON_WM_RBUTTONUP()
    ON_WM_MBUTTONDOWN()
    ON_WM_MBUTTONUP()
END_MESSAGE_MAP()


void Hoge::OnPaint() {
// 描画処理
}


void Hoge::OnMouseMove(UINT nFlags, CPoint pt)
{
    // NULLチェック
    if (pFuncOnMouseMove) {
                // メンバの関数ポインタの呼び出しにはthisポインタを使う必要がある
        (this->*pFuncOnMouseMove)(pt);
    }

}

void Hoge::ShoriA(CPoint pt)
{
// 右クリック中の処理
}

void Hoge::ShoriB(CPoint pt)
{
// マウスホイールクリック中の処理
}

void Hoge::OnRButtonDown(UINT nFlags, CPoint point)
{
    pFuncOnMouseMove = &Hoge::ShoriA;
}

void Hoge::OnRButtonUp(UINT nFlags, CPoint point)
{
    pFuncOnMouseMove = NULL;
}

void Hoge::OnMButtonDown(UINT nFlags, CPoint point)
{
    pFuncOnMouseMove = &Hoge::ShoriB;
}

void Hoge::OnMButtonUp(UINT nFlags, CPoint point)
{
    pFuncOnMouseMove = NULL;
}

かなりシンプルな実装になったと思います. 関数ポインタのNULLチェックが必要になることとインターフェースを統一する必要があることを考慮してもこちらの実装の方が良い気がします.