Click here to Skip to main content
15,121,030 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.
AlwaysLearningNewStuff 17-Sep-14 15:29pm
   
Thanks, I highly appreciate it!

I have found some examples in MFC and C# but don't know how to convert them into C/C++ and raw WinAPI. If you need inspiration I could post the links somewhere so you can take a look...

I am continuing to try and find the solution on my own, hopefully my light bulb will fire up soon :)

Best regards mate :)
enhzflep 17-Sep-14 22:58pm
   
Always a please, and you're _always_ welcome.

Sure why not? Here seems as good a place as any for the links.
Cheers mate. :)
AlwaysLearningNewStuff 19-Sep-14 15:03pm
   
I have found many links, but ha chosen few of them in order not to waste your time. Here they are:

http://www.codeguru.com/cpp/controls/listview/reports/article.php/c4155/Customized-Report-List-Control-with-InPlace-Combo-Box--Edit-Control.htm

http://www.codeguru.com/cpp/controls/listview/columns/article.php/c4169/How-to-Easily-Navigate-and-Edit-a-List-View-Control.htm

http://support.microsoft.com/kb/816188

http://support.microsoft.com/kb/320344

http://www.codeproject.com/Articles/28478/How-to-edit-listview-subitems-in-Win

http://www.codeproject.com/Articles/3225/TreeListView

http://www.codeproject.com/Articles/9188/Embedding-Controls-in-a-ListView

I know you must be busy so take your time. I will continue to work on my own as well.

Good luck!
enhzflep 26-Sep-14 0:38am
   
Still looking at your links when I get a chance. Something I toyed with was using a dialog as the popup. Just using a single check-box or an edit control, or even a list/combo control is reasonably trivial.

When thinking of radio-buttons I realized that you need to use a number of controls. At this point I also considered just how messy all of the popups could make the application's code.

About then, I realized I could simply make a dialog in the resource-editor, ensuring that its styles prevented showing it with a dialog-frame.

Doing this, means that you can simply send a message from the popup dialog to it's parent to signify that a valid choice/edit was made (i.e, the popup wasn't cancelled with ESC).

By simply handling the WM_ACTIVATE message in the dialog, looking for fActive = WA_INACTIVE, we can tell if the dialog looses focus. When it does, send a message to the parent and then finally, end the popup dialog.

A testing, proof-of-concept proc for the popup dialog is as simple as:
BOOL CALLBACK popupWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
case WM_ACTIVATE:
{
if(LOWORD(wParam) == WA_INACTIVE)
EndDialog(hwndDlg, 0);
break;
}
}
return FALSE;
}


You'll note that I've still not bothered to implement a mechanism to send a message to it's parent when action is taken in the popup's child controls. You could just send a 32(64) bit variable in SendMessage that points to the data being returned - whether it be a true/false, float, int or string. Casting as required would do the trick here. I didn't bother, since I wanted to use a single proc to test three different dialogs.

As for the main dialog's code:
BOOL CALLBACK DlgMain(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
case WM_INITDIALOG:
{
}
return TRUE;

case WM_CLOSE:
{
EndDialog(hwndDlg, 0);
}
return TRUE;

case WM_COMMAND:
{
HWND popupWnd;
long resNum;
switch(LOWORD(wParam))
{
case IDC_TESTBTN:
resNum = IDD_POPUP_EDIT;
break;
case IDC_TESTBTN2:
resNum = IDD_POPUP_RADIO;
break;
case IDC_TESTBTN3:
resNum = IDD_POPUP_CHECK;
break;
}
GetSystemMetrics()
popupWnd = CreateDialog(hInst, MAKEINTRESOURCE(resNum), hwndDlg, popupWndProc);
//HWND btnWnd = GetDlgItem(hwndDlg, IDC_TESTBTN);
HWND btnWnd = (HWND) lParam;
POINT btnPos = {0,0};
MapWindowPoints(btnWnd, hwndDlg, &btnPos, 1);
RECT btnRect;
GetWindowRect(btnWnd, &btnRect);
int height = btnRect.bottom - btnRect.top + 1;

POINT origin = {0,0};
ClientToScreen(hwndDlg, &origin);
SetWindowPos(popupWnd,
hwndDlg,
origin.x + btnPos.x,
origin.y + btnPos.y+height,
0,0,
SWP_NOSIZE|SWP_SHOWWINDOW);
}
return TRUE;
}
return FALSE;
}


EDIT: Damn you CP formatting!!
AlwaysLearningNewStuff 26-Sep-14 17:35pm
   
Thank you, I assumed you were busy. Take your time, I have thrown in a bounty on SO but so far no success :(

I can understand some of your idea, but can't create a demo app. Still, I will give this a look carefully over next few days.

In some of the examples I have noticed that edit control / combobox / etc were bigger than the cell.

I wanted to create a reusable code for the future, but maybe I should just set cell size to the size of combobox /edit / etc, and make those controls ( combo + edit + etc ) the same height... Perhaps that is the least painful solution, as I really doubt I will get anything better than I already have :(

Anyway, thank you so much! Best regards :)
enhzflep 28-Sep-14 20:21pm
   
No problem, you're welcome. Unfortunately, more so than I'd care to be..

I can throw something onto pastebin or a zip file somewhere else if you'd like. The only thing I canthink of that I didn't include was the resource definition for the dialog boxes. Perhaps editing them into the above comment would be more useful?

I didn't try (m/any?) other sizes, but I did notice that after stealing the font from the control and applying it to the popup edit control, that the edit-control's text is still larger than that of the list-view.Iguess this effect would be more noticeable as the font-size was increased. Some other things I considered over the weekend were: to get the font of the listview, before measuring the size of text draw with it and verifying that this is the same size as text contained within its cells. If not, I'd consider trying the code we played with that time for correctly sizing text to print with. I don't think there's a way (or cant remember it) to get the logical size of an HFONT, so it leaves you to try to fiddle around. Also, perhaps Spy++ can intercept the popup edit-control that the listview provides by default. It may be possible to glean information regarding the relationship between the size of the font that the list-view reports using and the size of the font it's actually using - remembering that when using the font returned in response to a WM_GETFONT message, we still draw the text larger than the listview does.

You're always welcome, thanks for the SO bounty!!
Best,
S.

EDIT: Just had another thought - have you tried looking at the Wine sources for the default behaviour when the LVS_EDITLABELS style is used? All the hard work may already be done..

EDIT #2: You can find the routine for in-place label editing here: Wine sources - listview.c, line 6064.
AlwaysLearningNewStuff 16-Oct-14 11:55am
   
I have experimented a little, it seems that edit's font size must be less than listview's font size by 3 ( sorry for bad English, to be clear editfont = listfont - 3 ). It seems that this gives the best results...

Off-topic, can you help me with following problem : http://dba.stackexchange.com/questions/80360/cant-finish-my-er-diagram

I know I am a pain, but this issue puzzles me for 2 days...

Thank you.

Best regards 'till next time ( I will keep working on this font problem ).
enhzflep 17-Oct-14 4:08am
   
Ah-ha. Thanks, nice to know the figure is 3, thank you very much.
Sorry, I looked at your linked question, but haven't learned to use ER's.
Best of luck though. :)
Sergey Alexandrovich Kryukov 17-Sep-14 16:06pm
   
Right. As I say, if other inquirers just tried to approach problems like that, it would be really good.
As to the motivation for help: there is no stronger motivation then the hope that the efforts in providing some help are put in the fertile soil. No one likes wasting time, but if at least one or few percent of the answer posts changed things to better, I would call my time spent in a good way.
—SA
AlwaysLearningNewStuff 17-Sep-14 15:25pm
   
Thank you for the kind words. I appreciate highly people who spare their free time and provide high quality answer for free, so I try to repay them by atleast asking useful question that is properly phrased. I consider this the least I can do.

Again, thank you for taking time to try helping me. Best regards Mr. Kryukov.
Sergey Alexandrovich Kryukov 17-Sep-14 16:09pm
   
My pleasure, from all the heart. I really hope you will be able to repay the help in exact same way, by helping others. In a way, you already started doing that, because your questions and the responses to those question can eventually be useful for other readers, not just you. Another kind of help could be: your questions can be served as a model of productive ways of getting help. I wish other inquirers could read and understand them, before asking their own questions...

Thank you.
—SA
[no name] 17-Sep-14 15:05pm
   
I have some code at home that does this. I'll post a solution later - unless someone beats me to it. IIRC, the edit control likes to be bigger that the selected font height.
AlwaysLearningNewStuff 17-Sep-14 15:21pm
   
IIRC, the edit control likes to be bigger that the selected font height. Yes indeed. I have asked here at StackOverflow for help and got a workaround by setting the same font for edit control / combobox / checkbox and listview: http://stackoverflow.com/questions/25895351/window-size-of-edit-control-combobox-is-not-properly-adjusted-when-using-movewin/25896947?noredirect=1#comment40534680_25896947

Still, I would like to have combobox / edit control / checkbox the same size as listview cell...

Thank you for your time and for trying to help.

Best regards.

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