ソフトウェア開発 Win32プログラミング

複数選択ラバーバンドを実装する

戻る


[ラバーバンドのイメージ]

前回、単一のラバーバンドの実装を紹介した。 今回は、「理想エディタ」という自作のリソースエディタで活用するために、 複数選択可能、同時編集可能にしたラバーバンドを実装する。

まず、複数のラバーバンドを動的に生成しなければならないので、 WM_NCDESTROYの処理に「delete this;」を書く。

    virtual void PostNcDestroy()
    {
        m_hwnd = NULL;
        delete this;
    }

これでウィンドウを破棄したときに、自動的にMRubberBandが破棄される。

次に、MRadCtrlクラスを定義する。

class MRadCtrl : public MWindowBase
{
public:
    BOOL m_bTopCtrl;
    HWND m_hwndRubberBand;
    BOOL m_bMoving;
    BOOL m_bSizing;
    INT m_nIndex;
    POINT m_pt;

    MRadCtrl() : m_bTopCtrl(FALSE), m_hwndRubberBand(NULL),
                 m_bMoving(FALSE), m_bSizing(FALSE), m_nIndex(-1)
    {
        m_pt.x = m_pt.y = -1;
    }

    typedef std::set<HWND> set_type;
    static set_type& GetTargets()
    {
        static set_type s_set;
        return s_set;
    }

    static HWND& GetLastSel()
    {
        static HWND s_hwnd = NULL;
        return s_hwnd;
    }

    MRubberBand *GetRubberBand()
    {
        MWindowBase *base = GetUserData(m_hwndRubberBand);
        if (base)
        {
            MRubberBand *band = static_cast<MRubberBand *>(base);
            return band;
        }
        return NULL;
    }

    static MRadCtrl *GetRadCtrl(HWND hwnd)
    {
        MWindowBase *base = GetUserData(hwnd);
        if (base)
        {
            MRadCtrl *pCtrl;
            pCtrl = static_cast<MRadCtrl *>(base);
            return pCtrl;
        }
        return NULL;
    }

    static void DeselectSelection()
    {
        set_type::iterator it, end = GetTargets().end();
        for (it = GetTargets().begin(); it != end; ++it)
        {
            MRadCtrl *pCtrl = GetRadCtrl(*it);
            if (pCtrl)
            {
                ::DestroyWindow(pCtrl->m_hwndRubberBand);
                pCtrl->m_hwndRubberBand = NULL;
            }
        }
        GetTargets().clear();
        GetLastSel() = NULL;
    }

    static void DeleteSelection(HWND hwnd = NULL)
    {
        set_type::iterator it, end = GetTargets().end();
        for (it = GetTargets().begin(); it != end; ++it)
        {
            if (hwnd == *it)
                continue;

            MRadCtrl *pCtrl = GetRadCtrl(*it);
            if (pCtrl)
            {
                ::DestroyWindow(pCtrl->m_hwndRubberBand);
                pCtrl->m_hwndRubberBand = NULL;
                ::DestroyWindow(*pCtrl);
            }
        }
        GetTargets().clear();
        GetLastSel() = NULL;
    }

    void Deselect()
    {
        MRubberBand *band = GetRubberBand();
        if (band)
        {
            ::DestroyWindow(*band);
        }
        GetTargets().erase(m_hwnd);
        m_hwndRubberBand = NULL;
        GetLastSel() = NULL;
    }

    static void Select(HWND hwnd)
    {
        if (hwnd == NULL)
            return;

        MRadCtrl *pCtrl = GetRadCtrl(hwnd);
        if (pCtrl == NULL)
            return;

        MRubberBand *band = new MRubberBand;
        band->CreateDx(GetParent(hwnd), hwnd, TRUE);

        pCtrl->m_hwndRubberBand = *band;
        GetTargets().insert(hwnd);
        GetLastSel() = hwnd;
    }

    static void MoveSelection(HWND hwnd, INT dx, INT dy)
    {
        set_type::iterator it, end = GetTargets().end();
        for (it = GetTargets().begin(); it != end; ++it)
        {
            if (hwnd == *it)
                continue;

            MRadCtrl *pCtrl = GetRadCtrl(*it);
            if (pCtrl)
            {
                RECT rc;
                ::GetWindowRect(*pCtrl, &rc);
                ::MapWindowPoints(NULL, ::GetParent(*pCtrl), (LPPOINT)&rc, 2);
                OffsetRect(&rc, dx, dy);

                pCtrl->m_bMoving = TRUE;
                pCtrl->SetWindowPosDx((LPPOINT)&rc);
                pCtrl->m_bMoving = FALSE;
            }
        }
    }

    static void ResizeSelection(HWND hwnd, INT cx, INT cy)
    {
        set_type::iterator it, end = GetTargets().end();
        for (it = GetTargets().begin(); it != end; ++it)
        {
            if (hwnd == *it)
                continue;

            MRadCtrl *pCtrl = GetRadCtrl(*it);
            if (pCtrl && !pCtrl->m_bSizing)
            {
                pCtrl->m_bSizing = TRUE;
                SIZE siz = { cx , cy };
                pCtrl->SetWindowPosDx(NULL, &siz);
                pCtrl->m_bSizing = FALSE;

                MRubberBand *band = pCtrl->GetRubberBand();
                if (band)
                {
                    band->FitToTarget();
                }
            }
        }
    }

    virtual LRESULT CALLBACK
    WindowProcDx(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        switch (uMsg)
        {
            HANDLE_MSG(hwnd, WM_NCHITTEST, OnNCHitTest);
            HANDLE_MSG(hwnd, WM_KEYDOWN, OnKey);
            HANDLE_MSG(hwnd, WM_NCLBUTTONDOWN, OnNCLButtonDown);
            HANDLE_MSG(hwnd, WM_NCLBUTTONDBLCLK, OnNCLButtonDown);
            HANDLE_MSG(hwnd, WM_NCMOUSEMOVE, OnNCMouseMove);
            HANDLE_MSG(hwnd, WM_NCLBUTTONUP, OnNCLButtonUp);
            HANDLE_MSG(hwnd, WM_MOVE, OnMove);
            HANDLE_MSG(hwnd, WM_SIZE, OnSize);
            HANDLE_MSG(hwnd, WM_LBUTTONDBLCLK, OnLButtonDown);
            HANDLE_MSG(hwnd, WM_LBUTTONDOWN, OnLButtonDown);
            HANDLE_MSG(hwnd, WM_LBUTTONUP, OnLButtonUp);
            HANDLE_MSG(hwnd, WM_MOUSEMOVE, OnMouseMove);
            HANDLE_MSG(hwnd, WM_NCRBUTTONDOWN, OnNCRButtonDown);
            HANDLE_MSG(hwnd, WM_NCRBUTTONDBLCLK, OnNCRButtonDown);
            HANDLE_MSG(hwnd, WM_NCRBUTTONUP, OnNCRButtonUp);
            case WM_MOVING: case WM_SIZING:
                return 0;
        }
        return DefaultProcDx(hwnd, uMsg, wParam, lParam);
    }

    void OnNCRButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT codeHitTest)
    {
        if (fDoubleClick)
            return;

        SendMessage(GetParent(hwnd), WM_NCRBUTTONDOWN, (WPARAM)hwnd, MAKELPARAM(x, y));
    }

    void OnNCRButtonUp(HWND hwnd, int x, int y, UINT codeHitTest)
    {
    }

    void OnLButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags)
    {
    }

    void OnLButtonUp(HWND hwnd, int x, int y, UINT keyFlags)
    {
    }

    void OnMouseMove(HWND hwnd, int x, int y, UINT keyFlags)
    {
    }

    void OnMove(HWND hwnd, int x, int y)
    {
        if (!m_bTopCtrl)
        {
            DefaultProcDx(hwnd, WM_MOVE, 0, MAKELPARAM(x, y));
            return;
        }

        if (!m_bMoving)
        {
            POINT pt;
            ::GetCursorPos(&pt);
            MoveSelection(hwnd, pt.x - m_pt.x, pt.y - m_pt.y);
            m_pt = pt;
        }

        MRubberBand *band = GetRubberBand();
        if (band)
        {
            band->FitToTarget();
        }

        RECT rc;
        ::GetClientRect(hwnd, &rc);
        ::InvalidateRect(hwnd, &rc, TRUE);
    }

    void OnSize(HWND hwnd, UINT state, int cx, int cy)
    {
        DefaultProcDx(hwnd, WM_SIZE, 0, MAKELPARAM(cx, cy));
        if (!m_bTopCtrl)
        {
            return;
        }

        if (!m_bSizing)
            ResizeSelection(hwnd, cx, cy);
    }

    void OnKey(HWND hwnd, UINT vk, BOOL fDown, int cRepeat, UINT flags)
    {
        if (fDown)
        {
            FORWARD_WM_KEYDOWN(GetParent(hwnd), vk, cRepeat, flags, SendMessage);
        }
    }

    void OnNCLButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT codeHitTest)
    {
        ::GetCursorPos(&m_pt);

        if (fDoubleClick)
            return;

        if (codeHitTest != HTCAPTION)
            return;

        if (GetKeyState(VK_CONTROL) < 0)
        {
            if (m_hwndRubberBand)
            {
                Deselect();
                return;
            }
        }
        else if (GetKeyState(VK_SHIFT) < 0)
        {
            if (m_hwndRubberBand)
            {
                return;
            }
        }
        else
        {
            if (m_hwndRubberBand)
            {
                ;
            }
            else
            {
                DeselectSelection();
            }
        }

        if (m_hwndRubberBand == NULL)
        {
            Select(hwnd);
        }

        HWND hwndPrev = GetWindow(hwnd, GW_HWNDPREV);
        ::DefWindowProc(hwnd, WM_NCLBUTTONDOWN, HTCAPTION, MAKELPARAM(x, y));
        SetWindowPosDx(NULL, NULL, hwndPrev);
    }

    void OnNCMouseMove(HWND hwnd, int x, int y, UINT codeHitTest)
    {
        ::DefWindowProc(hwnd, WM_NCMOUSEMOVE, codeHitTest, MAKELPARAM(x, y));
    }

    void OnNCLButtonUp(HWND hwnd, int x, int y, UINT codeHitTest)
    {
        m_bMoving = FALSE;
        m_pt.x = -1;
        m_pt.y = -1;
        ::DefWindowProc(hwnd, WM_NCLBUTTONUP, codeHitTest, MAKELPARAM(x, y));
    }

    UINT OnNCHitTest(HWND hwnd, int x, int y)
    {
        if (m_bTopCtrl)
        {
            return HTCAPTION;
        }
        return HTTRANSPARENT;
    }

    virtual void PostNcDestroy()
    {
        m_hwnd = NULL;
        delete this;
    }
};

MRadCtrlクラスは、理想エディタで編集するダイアログコントロールに対するクラスである。 これは、ダイアログコントロールをサブクラス化する。 GetTargetsとGetLastSelは、選択中のターゲットを保持する。 Select、Deselect、DeselectSelection、MoveSelection、ResizeSelection、DeleteSelectionは、 ラバーバンドによる選択に対する操作を行う関数である。 OnMoveが移動したときのコードであり、OnSizeがサイズ変更されたときのコードである。 これらの処理により、ラバーバンドで複数選択可能であり、同時編集が可能になる。

OnNCHitTestは、HTCAPTIONを返して、ドラッグ可能にしている。 m_bTopCtrlは、トップのコントロールであるかどうかを表す。 トップのコントロールでなければ、親のコントロールにクリックを転送する必要がある。

OnNCLButtonDownを見てみよう。

void OnNCLButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT codeHitTest)
    {
        ::GetCursorPos(&m_pt);

        if (fDoubleClick)
            return;

        if (codeHitTest != HTCAPTION)
            return;

        if (GetKeyState(VK_CONTROL) < 0)
        {
            if (m_hwndRubberBand)
            {
                Deselect();
                return;
            }
        }
        else if (GetKeyState(VK_SHIFT) < 0)
        {
            if (m_hwndRubberBand)
            {
                return;
            }
        }
        else
        {
            if (m_hwndRubberBand)
            {
                ;
            }
            else
            {
                DeselectSelection();
            }
        }

        if (m_hwndRubberBand == NULL)
        {
            Select(hwnd);
        }

        HWND hwndPrev = GetWindow(hwnd, GW_HWNDPREV);
        ::DefWindowProc(hwnd, WM_NCLBUTTONDOWN, HTCAPTION, MAKELPARAM(x, y));
        SetWindowPosDx(NULL, NULL, hwndPrev);
    }

GetKeyStateにより、キーボードの[Ctrl]キーや[Shift]キーが押されているか確認している。 押されている場合、複数選択を可能にしないといけない。次のコードに着目:


        HWND hwndPrev = GetWindow(hwnd, GW_HWNDPREV);
        ::DefWindowProc(hwnd, WM_NCLBUTTONDOWN, HTCAPTION, MAKELPARAM(x, y));
        SetWindowPosDx(NULL, NULL, hwndPrev);

ここでは、規定のWM_NCLBUTTONDOWNの処理で、ドラッグ可能にしつつも、順序を変えないように SetWindowPosで元の位置に挿入している。次はOnMove:


    void OnMove(HWND hwnd, int x, int y)
    {
        if (!m_bTopCtrl)
        {
            DefaultProcDx(hwnd, WM_MOVE, 0, MAKELPARAM(x, y));
            return;
        }

        if (!m_bMoving)
        {
            POINT pt;
            ::GetCursorPos(&pt);
            MoveSelection(hwnd, pt.x - m_pt.x, pt.y - m_pt.y);
            m_pt = pt;
        }

        MRubberBand *band = GetRubberBand();
        if (band)
        {
            band->FitToTarget();
        }

        RECT rc;
        ::GetClientRect(hwnd, &rc);
        ::InvalidateRect(hwnd, &rc, TRUE);
    }

移動中でなければ、MoveSelection関数で、他のすべての選択を移動させている。 m_ptは、位置を記録している。ラバーバンドがあれば、FitToTargetにより、 ターゲットに位置を合わせる。最後の::InvalidateRect(hwnd, &rc, TRUE);では、 クライアント領域の&rcを指定しているが、この代わりにNULLを渡すと、 テキストボックスでうまく描画されないことがある。

次は、OnSize:

    void OnSize(HWND hwnd, UINT state, int cx, int cy)
    {
        DefaultProcDx(hwnd, WM_SIZE, 0, MAKELPARAM(cx, cy));
        if (!m_bTopCtrl)
        {
            return;
        }

        if (!m_bSizing)
            ResizeSelection(hwnd, cx, cy);
    }

OnSizeでは、最初にDefaultProcDx(CallWindowProc)により、規定の処理を行う。 これを行わないと、コンボボックスがうまく動作しない。 トップのコントロール以外を無視する。そしてResizeSelectionで 他のコントロールのサイズを変更させる。これで複数の選択中のコントロールを同時に サイズ変更可能である。

次は、編集するダイアログに対するクラス、MRadDialogである。

class MRadDialog : public MDialogBase
{
public:
    HWND GetNextCtrl(HWND hwndCtrl) const
    {
        HWND hwndNext = GetNextWindow(hwndCtrl, GW_HWNDNEXT);
        if (hwndNext == NULL)
        {
            hwndNext = GetNextWindow(hwndCtrl, GW_HWNDFIRST);
        }

        TCHAR szClass[64];
        while (hwndNext)
        {
            ::GetClassName(hwndNext, szClass, _countof(szClass));
            if (lstrcmpi(szClass, MRubberBand().GetWndClassNameDx()) != 0)
                break;

            hwndNext = GetNextWindow(hwndNext, GW_HWNDNEXT);
        }
        if (hwndNext == NULL)
        {
            hwndNext = GetNextWindow(hwndCtrl, GW_HWNDFIRST);
        }

        return hwndNext;
    }

    HWND GetPrevCtrl(HWND hwndCtrl) const
    {
        HWND hwndPrev = GetNextWindow(hwndCtrl, GW_HWNDPREV);
        if (hwndPrev == NULL)
        {
            hwndPrev = GetNextWindow(hwndCtrl, GW_HWNDLAST);
        }

        TCHAR szClass[64];
        while (hwndPrev)
        {
            ::GetClassName(hwndPrev, szClass, _countof(szClass));
            if (lstrcmpi(szClass, MRubberBand().GetWndClassNameDx()) != 0)
                break;

            hwndPrev = GetNextWindow(hwndPrev, GW_HWNDPREV);
        }
        if (hwndPrev == NULL)
        {
            hwndPrev = GetNextWindow(hwndCtrl, GW_HWNDLAST);
        }

        return hwndPrev;
    }

    virtual INT_PTR CALLBACK
    DialogProcDx(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        switch (uMsg)
        {
            HANDLE_MSG(hwnd, WM_INITDIALOG, OnInitDialog);
            HANDLE_MSG(hwnd, WM_LBUTTONDOWN, OnLButtonDown);
            HANDLE_MSG(hwnd, WM_LBUTTONDBLCLK, OnLButtonDown);
        }
        return DefaultProcDx(hwnd, uMsg, wParam, lParam);
    }

    void OnLButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags)
    {
        if (::GetKeyState(VK_SHIFT) < 0 || ::GetKeyState(VK_CONTROL) < 0)
            return;

        MRadCtrl::DeselectSelection();
    }

    virtual LRESULT CALLBACK
    WindowProcDx(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        switch (uMsg)
        {
            HANDLE_MSG(hwnd, WM_NCLBUTTONDBLCLK, OnNCLButtonDown);
            HANDLE_MSG(hwnd, WM_NCLBUTTONDOWN, OnNCLButtonDown);
            HANDLE_MSG(hwnd, WM_NCLBUTTONUP, OnNCLButtonUp);
            HANDLE_MSG(hwnd, WM_NCRBUTTONDBLCLK, OnNCRButtonDown);
            HANDLE_MSG(hwnd, WM_NCRBUTTONDOWN, OnNCRButtonDown);
            HANDLE_MSG(hwnd, WM_NCRBUTTONUP, OnNCRButtonUp);
            HANDLE_MSG(hwnd, WM_NCMOUSEMOVE, OnNCMouseMove);
            HANDLE_MSG(hwnd, WM_KEYDOWN, OnKey);
        }
        return CallWindowProcDx(hwnd, uMsg, wParam, lParam);
    }

    void OnNCLButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT codeHitTest)
    {
    }

    void OnNCLButtonUp(HWND hwnd, int x, int y, UINT codeHitTest)
    {
    }

    void OnNCRButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT codeHitTest)
    {
        if (fDoubleClick)
            return;

        FORWARD_WM_CONTEXTMENU(GetParent(hwnd), hwnd, x, y, SendMessage);
    }

    void OnNCRButtonUp(HWND hwnd, int x, int y, UINT codeHitTest)
    {
    }

    void OnNCMouseMove(HWND hwnd, int x, int y, UINT codeHitTest)
    {
    }

    void OnKey(HWND hwnd, UINT vk, BOOL fDown, int cRepeat, UINT flags)
    {
        if (fDown)
        {
            FORWARD_WM_KEYDOWN(GetParent(hwnd), vk, cRepeat, flags, SendMessage);
        }
    }

    void DoSubclass(HWND hCtrl, BOOL bTop)
    {
        MRadCtrl *pCtrl = new MRadCtrl;
        pCtrl->SubclassDx(hCtrl);
        pCtrl->m_bTopCtrl = bTop;

        DoSubclassChildren(hCtrl);
    }

    void DoSubclassChildren(HWND hwnd, BOOL bTop = FALSE)
    {
        HWND hCtrl = GetTopWindow(hwnd);
        if (bTop)
        {
            INT nIndex = 0;
            while (hCtrl)
            {
                DoSubclass(hCtrl, bTop);

                MRadCtrl *pCtrl = MRadCtrl::GetRadCtrl(hCtrl);
                pCtrl->m_nIndex = nIndex;

                hCtrl = GetWindow(hCtrl, GW_HWNDNEXT);
                ++nIndex;
            }
        }
        else
        {
            while (hCtrl)
            {
                DoSubclass(hCtrl, bTop);
                hCtrl = GetWindow(hCtrl, GW_HWNDNEXT);
            }
        }
    }

    BOOL OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam)
    {
        POINT pt = { 0, 0 };
        SetWindowPosDx(&pt);
        SubclassDx(hwnd);

        DoSubclassChildren(hwnd, TRUE);

        return FALSE;
    }
};

GetNextCtrlとGetPrevCtrlは、ラバーバンド以外の直後または直前のコントロールを取得するためのメソッドである。 OnInitDialogでは、MRadCtrlにより、すべてのダイアログコントロールをサブクラス化している。 DoSubclassとDoSubclassChildrenがそれを行うメソッドである。 ダイアログコントロールは子ウィンドウを持つことがあるので、トップのコントロールを区別している。

ソース: https://github.com/katahiromz/RadWindowApp


戻る

©片山博文MZ
katayama.hirofumi.mz@gmail.com

inserted by FC2 system