c++user-interfacemfcmouseeventclistctrl

MFC CListCtrl eating mouse up events?


It seems that CListCtrl doesn't send mouse up events unless there is a double click.

I've attempted sending the missing message from the mouse down handlers to compensate but that causes other undesirable behavior. Then I thought I could send the message within the mouse move handler to be a bit more accurate by checking state. However these are horrible hacks and apart from being ugly they may not work properly for every possible implementation of derived controls.

If anyone knows why mouse up events are not received I'd be curious. More importantly how do I get the CListCtrl using the LVS_OWNERDATA style to send mouse up messages like every other control?

EDIT: I am aware of LVN_BEGINDRAG, LVN_BEGINRDRAG, etc however in order to use these I would need to prevent WM_LBUTTONDOWN, WM_RBUTTONDOWN, and WM_MOUSEMOVE from going to the parent window or DragDropManager hooked into CWinAppEx/CMDIFrameWndEx so I could make a special one-off case for this control to work with the existing system.

This is because I have a central drag and drop manager which can notify various types of controls when to begin a drag operation, when to end, cancel, change animation, pass display objects for source and target in custom messages, etc. It needs to be flexible enough to have differing way of initiating as well as differing actions depending on the control, the input, the type of items selected or targeted, differing control types including 3D, or even different applications, etc.


Solution

  • For reference here's what I have that works but it's a shameful hack. If no one can come up with anything better than this that's really sad.

    Header:

    #pragma once
    
    // CListCtrlEx
    class CListCtrlEx : public CListCtrl
    {
        DECLARE_DYNAMIC(CListCtrlEx)
    
    public:
        CListCtrlEx();
        virtual ~CListCtrlEx();
    
        bool IsSelected(int index);
        BOOL SelectDropTarget(int item);
    
    protected:
        DECLARE_MESSAGE_MAP()
    
        afx_msg void OnStateChanged(NMHDR* pNMHDR, LRESULT* pResult);
    
        afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
        afx_msg void OnRButtonDown(UINT nFlags, CPoint point);
        afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
        afx_msg void OnRButtonUp(UINT nFlags, CPoint point);
        afx_msg void OnMouseMove(UINT nFlags, CPoint point);
    
    private:
        bool m_lbDown;
        bool m_rbDown;
    };
    

    Implementation:

    #include "stdafx.h"
    #include "ListCtrlEx.h"
    
    // CListCtrlEx
    IMPLEMENT_DYNAMIC(CListCtrlEx, CListCtrl)
    
    CListCtrlEx::CListCtrlEx() : m_lbDown(false), m_rbDown(false)
    {
    }
    
    CListCtrlEx::~CListCtrlEx()
    {
    }
    
    BEGIN_MESSAGE_MAP(CListCtrlEx, CListCtrl)
        ON_WM_LBUTTONDOWN()
        ON_WM_RBUTTONDOWN()
        ON_WM_LBUTTONUP()
        ON_WM_RBUTTONUP()
        ON_WM_MOUSEMOVE()
    
        ON_NOTIFY_REFLECT(LVN_ODSTATECHANGED, &CListCtrlEx::OnStateChanged)
    END_MESSAGE_MAP()
    
    // CListCtrlEx message handlers
    void CListCtrlEx::OnLButtonDown(UINT nFlags, CPoint point)
    {
        m_lbDown = true;
        CListCtrl::OnLButtonDown(nFlags, point);
    }
    
    void CListCtrlEx::OnRButtonDown(UINT nFlags, CPoint point)
    {
        m_rbDown = true;
        CListCtrl::OnRButtonDown(nFlags, point);
    }
    
    void CListCtrlEx::OnLButtonUp(UINT nFlags, CPoint point)
    {
        m_lbDown = false;
        CListCtrl::OnLButtonUp(nFlags, point);
    }
    
    void CListCtrlEx::OnRButtonUp(UINT nFlags, CPoint point)
    {
        m_rbDown = false;
        CListCtrl::OnRButtonUp(nFlags, point);
    }
    
    void CListCtrlEx::OnMouseMove(UINT nFlags, CPoint point)
    {
        if (m_lbDown && ((nFlags & MK_LBUTTON) == 0))
        {
            PostMessage(WM_LBUTTONUP,
                MAKEWPARAM(LOWORD(nFlags), HIWORD(nFlags)),
                MAKELPARAM(point.x, point.y));
        }
    
        if (m_rbDown && ((nFlags & MK_RBUTTON) == 0))
        {
            PostMessage(WM_RBUTTONUP,
                MAKEWPARAM(LOWORD(nFlags), HIWORD(nFlags)),
                MAKELPARAM(point.x, point.y));
        }
    
        CListCtrl::OnMouseMove(nFlags, point);
    }
    
    bool CListCtrlEx::IsSelected(int index)
    {
        return (GetItemState(index, LVIS_SELECTED) & LVIS_SELECTED) != 0;
    }
    
    // highlight drop targets sort of like CTreeCtrl
    BOOL CListCtrlEx::SelectDropTarget(int item)
    {
        static int prevHighlight(-1);
        if (item >= 0 && item < GetItemCount())
        {
            if (item != prevHighlight)
            {
                if (prevHighlight >= 0)
                {
                    SetItemState(prevHighlight, 0, LVIS_DROPHILITED); // remove highlight from previous target
                    RedrawItems(prevHighlight, prevHighlight);
                }
    
                prevHighlight = item;
                SetItemState(item, LVIS_DROPHILITED, LVIS_DROPHILITED); // highlight target
                RedrawItems(item, item);
    
                UpdateWindow();
                return TRUE;
            }
        }
        else
        {
            for (int i(0); i < GetItemCount(); ++i)
                SetItemState(i, 0, LVIS_DROPHILITED); // un-highlight all
            prevHighlight = -1;
        }
    
        return FALSE;
    }
    
    void CListCtrlEx::OnStateChanged(NMHDR* pNMHDR, LRESULT* pResult)
    {
    // MSDN:
    // If a list-view control has the LVS_OWNERDATA style,
    // and the user selects a range of items by holding down the SHIFT key and clicking the mouse,
    // LVN_ITEMCHANGED notification codes are not sent for each selected or deselected item.
    // Instead, you will receive a single LVN_ODSTATECHANGED notification code,
    // indicating that a range of items has changed state.
    
        NMLVODSTATECHANGE* pStateChanged = (NMLVODSTATECHANGE*)pNMHDR;
    
        // redraw newly selected items
        if (pStateChanged->uNewState == LVIS_SELECTED)
            RedrawItems(pStateChanged->iFrom, pStateChanged->iTo);
    }