Click here to Skip to main content
15,885,985 members
Please Sign up or sign in to vote.
4.00/5 (2 votes)
INTRODUCTION AND RELEVANT INFORMATION:

I am trying to implement listview control with editable items and subitems. Instead of regular listview look, items and subitems should have edit control, checkbox or combo box.

I am using raw WinAPI and C++. I am targeting Windows XP onwards.

MY EFFORTS TO SOLVE THE PROBLEM:

After researching here and on the Internet, I was able to only find examples in MFC. They all use LVN_BEGINLABELEDIT technique to implement this behavior.

Unfortunately I do not understand entirely this concept so I have decided to start from scratch ( I consider this also to be the best approach for improving ones programming skills ).

MY CONCEPT:


I have decided to catch NM_DBLCLK for listview and to get coordinates from there using ListView_GetItemRect or ListView_GetSubItemRect macro.

Then I would simply move the combobox/checkbox/edit control over corresponding item/subitem ( combobox/edit control/checkbox would be created as separate, hidden windows ).

After user finishes with input ( by pressing enter or changing focus ) I would simply hide the combobox/checkbox/edit control.

MY CURRENT RESULTS:

At the moment, I am stuck with the dimensions of combobox/edit control/checkbox not being the same as item/subitem dimensions, when moved above the item/subitem.

QUESTION:

Can my code example submitted below be improved to properly adjust combobox/edit control/checkbox window size to the size of the item/subitem? For now, I will only focus on this part of the problem, to keep this question as short as possible.

Here is the instruction for creating small application that illustrates the problem. Notice that I have tried to keep things as minimal as I could:

1.) Create default Win32 project in Visual Studio ( I use VS 2008 ).

2.) Add the following WM_CREATE handler to main window's procedure:
C++
case WM_CREATE:
    {
        HWND hEdit = CreateWindowEx( 0,WC_EDIT, L"",
            WS_CHILD | WS_VISIBLE | WS_BORDER | ES_CENTER | ES_AUTOHSCROLL,
            250, 10, 100, 20, hWnd, (HMENU)1500, hInst, 0 );

        HWND hComboBox = CreateWindowEx( 0,WC_COMBOBOX, L"",
            WS_CHILD | WS_VISIBLE | WS_BORDER | CBS_DROPDOWNLIST,
            100, 10, 100, 20, hWnd, (HMENU)1600, hInst, 0 );

        HWND hwndLV = CreateWindowEx( 0, WC_LISTVIEW, 
            L"Editable Subitems",
            WS_CHILD | WS_VISIBLE | WS_BORDER | 
            LVS_REPORT | LVS_SINGLESEL, 
            150, 100, 250, 150, hWnd, (HMENU)2000, hInst, 0 );

        // set extended listview styles
        ListView_SetExtendedListViewStyle( GetDlgItem( hWnd, 2000 ),
            LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_DOUBLEBUFFER );

        // add some columns
        LVCOLUMN lvc = {0};

        lvc.iSubItem = 0;
        lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
        lvc.fmt = LVCFMT_LEFT;

        for (long nIndex = 0; nIndex < 5; nIndex++ )
        {
            wchar_t txt[50];
            swprintf_s( txt, 50, L"Column %d", nIndex + 1 );

            lvc.iSubItem = nIndex;
            lvc.cx = 60;
            lvc.pszText = txt;

            ListView_InsertColumn( GetDlgItem( hWnd,2000 ), nIndex, &lvc );
        }   

        // add some items
        LVITEM lvi;

        lvi.mask = LVIF_TEXT;
        lvi.iItem = 0;

        for( lvi.iItem = 0; lvi.iItem < 10; lvi.iItem++ )
            for (long nIndex = 0; nIndex < 5; nIndex++ )
            {
                wchar_t txt[50];
                swprintf_s( txt, 50, L"Item %d%d", lvi.iItem + 1, nIndex + 1 );

                lvi.iSubItem = nIndex;
                lvi.pszText = txt;

                if( ! nIndex )  // item 
                    SendDlgItemMessage( hWnd, 2000, 
                        LVM_INSERTITEM, 0, 
                        reinterpret_cast<LPARAM>(&lvi) );
                else            // sub-item
                    SendDlgItemMessage( hWnd, 2000, 
                        LVM_SETITEM, 0, 
                        reinterpret_cast<LPARAM>(&lvi) );
            }

    }
    return 0L; 

3.) Add the following handler for WM_NOTIFY in main window's procedure:
C++
case WM_NOTIFY:
    {
        if( ((LPNMHDR)lParam)->code == NM_DBLCLK )
        {
            switch( ((LPNMHDR)lParam)->idFrom )
            {
            case 2000: // remember, this was our listview's ID
                {
                    LPNMITEMACTIVATE lpnmia = (LPNMITEMACTIVATE)lParam;

                    // SHIFT/ALT/CTRL/their combination, must not be pressed
                    if( ( lpnmia->uKeyFlags || 0 ) == 0 )
                    {
                        // this is where we store item/subitem rectangle
                        RECT rc = { 0, 0, 0, 0 };

                        if( (lpnmia->iSubItem) <= 0 ) // this is item so we must call ListView_GetItemRect
                        {
                            // this rectangle holds proper left coordinate
                            // since ListView_GetItemRect with LVIR_LABEL flag
                            // messes up rectangle's left cordinate
                            RECT rcHelp = { 0, 0, 0, 0 };

                            // this call gets the length of entire row
                            // but holds proper left coordinate
                            ListView_GetItemRect( lpnmia->hdr.hwndFrom,
                                lpnmia->iItem, &rcHelp, LVIR_BOUNDS );

                            // this call gets proper rectangle except for the left side
                            ListView_GetItemRect( lpnmia->hdr.hwndFrom,
                                lpnmia->iItem, &rc, LVIR_LABEL );

                            // now we can correct the left coordinate
                            rc.left = rcHelp.left;
                        }
                        else // it is subitem, so we must call ListView_GetSubItemRect
                        {
                            ListView_GetSubItemRect( lpnmia->hdr.hwndFrom,
                                lpnmia->iItem, lpnmia->iSubItem,
                                LVIR_BOUNDS, &rc );
                        }

                        // convert listview client coordinates to parent coordinates
                        // so edit control can be properly moved 
                        POINT p;
                        p.x = rc.left;
                        p.y = rc.top;

                        ClientToScreen( lpnmia->hdr.hwndFrom, &p );
                        ScreenToClient( hWnd, &p );

                        MoveWindow( GetDlgItem( hWnd, 1500 ),
                            p.x, p.y, 
                            rc.right - rc.left,
                            rc.bottom - rc.top, TRUE );

                        // set focus to our edit control
                        HWND previousWnd = SetFocus( GetDlgItem( hWnd, 1500 ) );
                    }
                }
                break;
            default:
                break;
            }
        }
    }
    break;


And this[^] is the result I get.

You can clearly see that top and bottom border of the edit control are not drawn properly. As for combobox, the width is properly adjusted, but height remains the same.

I have tried substituting MoveWindow call with SetWindowPos but the result was the same.

After further tampering, I have found out that NMITEMACTIVATE bugs when returning the rectangle of a subitem, if listview doesn't have LVS_EX_FULLROWSELECT style set. You can see this by simply commenting out the part in my WM_CREATE handler where I set this style. Maybe I am doing something wrong and this "bug" may be caused by my code, but I don't see the problem.

EDITED on September, 17th 2014:

After testing the values for iItem and iSubItem members of NMITEMACTIVATE structure when listview doesn't have LVS_EX_FULLROWSELECT I can verify that the bug is not in my code. It always returns iItem to be 0, no matter which subitem I click. This explains the faulty behavior I got when removing this style.

If any further info is required please leave a comment and I will act as soon as possible.

Thank you for your time and efforts to help.
Posted
Comments
Sergey Alexandrovich Kryukov 17-Sep-14 12:24pm    
From reading just the title of the question: if you just move the window, it does not change its size. Change the size properly, if you need to.
—SA
AlwaysLearningNewStuff 17-Sep-14 12:30pm    
The docs for MoveWindow and SetWindowPos claim otherwise. I have tested your claim with MoveWindow( GetDlgItem( hWnd, 1500 ), 10, 50, 50, 80, TRUE ); and window was properly resized, so I state that your observation is false. Can you suggest another solution?

Thank you for sparing some time to help. Best regards Mr.Kryukov!
CPallini 17-Sep-14 12:50pm    
The OP is right. Despite its name, MoveWindow can be used to change both the position and the size of a Window.
Sergey Alexandrovich Kryukov 17-Sep-14 12:59pm    
Agree. It was Microsoft who acted in a confusing way, not OP: http://msdn.microsoft.com/en-us/library/windows/desktop/ms633534%28v=vs.85%29.aspx. :-)

Thank you very much, Carlo.

—SA

P.S.:

Speaking of the OP's correctness: did you pay attention that he always prepares all his questions thoroughly, putting adequate effort and providing enough of sufficient detail? I with other inquirers at least tried to do the same.

Probably this is because he is always learning new stuff. :-)
enhzflep 17-Sep-14 15:25pm    
Re: your P.S to Carlo, I sure have, that in itself seems to make the Qs among the most polite and considerate I see, plus, I always find them interesting. I can't think of a better motivating factor to try to help, as I always do when seeing one of ALNS's posts.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900