mfcwindows-10visual-studio-2019clistctrl

getting doubleclick position from CListCtrl in dialog's OnNotify


I have a dialog window (with CDialogEx based class) containing a single list control (report view, with CListCtrl attached as a control variable). I want to check which line of the displayed list was double clicked.

I thought it will be just a matter of adding OnNotify and converting the double click coordinates to the line number:

BOOL MyDialog::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT *pResult)
{
    if (((NMMOUSE *)lParam)->hdr.idFrom == IDC_MYLIST &&
        ((NMMOUSE *)lParam)->hdr.code == NM_DBLCLK)
    {
        CPoint cp(((LPNMMOUSE)lParam)->pt);
        int iLine = myListCtrl.HitText(cp);

It partially works: I am correctly catching doubleclick notifications coming from the control. What doesn't work though is I don't get correct double click coordinates. cp contains some numbers as coordinates, but they are wrong. x is always 0, and y is what x should be.

Any ideas where I am going wrong and what should I do? With some digging it looks like lParam points to some structure containing the coordinates, but it is not NMMOUSE as I expected, and I can't find any information about what other structure it could be.


Solution

  • This is deep into undefined behavior from the get-go: The only guarantee you get from the system when receiving a WM_NOTIFY message is that lParam points to an NMHDR structure. That's a piece of generic information provided to direct subsequent processing.

    Consequently, the only safe unconditional cast in an OnNotify override is from LPARAM into NMHDR const*, so let's just do that to keep our sanity:

    BOOL MyDialog::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT *pResult)
    {
        NMHDR const& hdr = *reinterpret_cast<NMHDR const*>(lParam);
        if (hdr.idFrom != IDC_MYLIST || hdr.code != NM_DBLCLK)
            return false;
        // ...
    

    Once the preconditions have been established, it's safe to move on. At this point we know that the origin passed our filter (IDC_MYLIST), and that the notification is an NM_DBLCLK, which passes additional information by way of a pointer to an NMITEMACTIVATE structure.

    The following takes advantage of that additional knowledge, and produces a CPoint holding the double-click position:

        // ...
        NMITEMACTIVATE const& info = *reinterpret_cast<NMITEMACTIVATE const*>(lParam);
        CPoint const cp = CPoint(info.ptAction);
        // ...