Window size of edit control/combobox is not properly adjusted when using MoveWindow or SetWindowPos
Asked Answered
S

1

7

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:

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:

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:

enter image description here

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.

Stannary answered 17/9, 2014 at 16:4 Comment(1)
Your three sibling controls should all have the WS_CLIPSIBLINGS style to stop them drawing over each other except in strict z-order.Richman
R
8

The issue you're facing is multi-faceted.

Firstly, the default font of the edit control is larger (higher) than that of the list-view. You can fix this one quite trivially, by first getting the font from the list-view and then setting it to the edit control. Doing this will then make the bottom border of the control visible.

The next issue is that the caret of the edit control needs a pixel above and below it, to ensure that the control doesn't have its borders interfered with. In addition to this 1 pixel of 'space' you then need another pixel for the border.

Added to this second point, the dimensions calculated by rc.right - rc.left and rc.bottom - rc.top are 1 pixel too small. Think of a rect that starts at 1,1 and extends to 2,2 - this is a rect of 4 pixels - 2 wide and 2 high. Simply subtracting the top/left from the bottom/right would give you a width/height of only 1 pixel each. To fix this, you need to add 1 to each of these subtractions.

Finally, since the caret is exactly the height of the 'client-area' of each item/sub-item, you need to make the edit control 2 pixels taller than the item/sub-item, and start 1 2 pixels higher than it does currently.

Here's the output I get when making the suggested changes: enter image description here

And here's the changes/additions I made.

1. Get/Set the font. (inserted after creating the list-view and before setting its extended style)

    HFONT lvFont = (HFONT)SendDlgItemMessage(hWnd, 2000, WM_GETFONT, 0, 0);
    SendDlgItemMessage(hWnd, 1500, WM_SETFONT, (WPARAM)lvFont, TRUE);

2. Set the window position/size

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

Finally, contrast this against the original output from your code: enter image description here

UPDATE: Here's a snapshot of the appearance when the built-in label editing functionality is used (LVS_EDITLABELS style)

enter image description here

Ramonaramonda answered 17/9, 2014 at 17:36 Comment(10)
I apologize for unaccepting the answer, but I have decided to tamper more with the size of the rectangle, so it can be the same size as the item / subitem rectangle. That is why I will start a bounty to try and fix this properly. I consider it visually more appealiong, hope you don't mind. Best regards.Stannary
No need to apologize, it's quite okay - I'd prefer that to having an 'unacceptable' (or not quite 'it') answer 'accepted'.. As for the minimum size of the box - I'm not sure if that would be the easiest method. The edit-control controls the hiding/display of the caret (I pressume it's creation too) The default size is 16px high. Trying to create a caret 14 pixels high doesn't seem to have any effect. To match the sizes, I'd consider handling the WM_MEASUREITEM message of the list-view (if you can, without custom drawing) to return a larger value, such that items match the edit's height.Ramonaramonda
:grins: I've just tried a sample, using the LVS_EDITLABELS style. I found a number of interesting things. (1) the cell height is increased by 3 pixels (was 15 pixels high interior, now 18 pixels high) (2) The top border of the edit is 1 pixel lower than the top-pixel of the cell, and the left of the edit is 3 pixels to the right of the left of the cell (you can see both top borders simultaneously, and a 2 px gap) and finally (3) the caret for the edit is only 15 pixels high. (4) you can only edit sub-item 0 - the first column. I'll add the screen-shot to my original solution.Ramonaramonda
But what about combobox? Furthermore, there was a way to subclass that edit control and move it to the clicked subitem. Still it doesn't fix combobox issue...Stannary
Yeah, I thought of that, and the check-box. Hmmmm. I guess my main points were (a) MS themselves, it seems, use taller rows when editing is required, as I suggested trying in an earlier comment, and (b) they're using a different sized caret, too. Perhaps the way would be to alter the row-height with the WM_MEASUREITEM message and subclass the edit control to handle creation of and display/hiding of the caret, if no other method is found for changing the caret on an otherwise untouched std edit control.. Sorry, not sure what else to try. - Perhaps setting a larger font to the LV?Ramonaramonda
I had no luck with the bounty, so I have awarded it to you. I think you deserved it, but will continue to try and fine-tune the dimensions of the common controls to fit cell dimensions... Best regards.Stannary
Hi again! While I continue to tackle this problem, the previous one related to printing and many others simultaneously, I have found some time to try and improve one of the UI I made. It was mostly designed by my boss, then I had to jump in and try to fix the issues but haven't done that in accepting way in my opinion. Can you help me with your suggestions about it? Or maybe you have a colleague who is willing to help? I apologize for disturbing but I really can't handle this on my own... :(Stannary
Gday mate! :grin: I feel your pain. Been there, done that, still got the t-shirt.. - I'll boil the kettle and have a look. Just give me half an hour or so. :)Ramonaramonda
Thank you so much for a fast response! I must warn you, things are grim. For further help just look this question on CP ( Mr.Kryukov already offered some assistance in view of "tough love" hehe ). Thanks again!Stannary
Always welcome my friend. Hahaha, haha, ha! "Tough love" indeed. Never one to mince his words, is he? He makes some really good suggestions, it's rather a tough act to follow, to be honest! It's rare for me to see such a comprehensive answer from him - count yourself lucky and well-regarded! Something you may find helpful is this layout guidelines document from MS: msdn.microsoft.com/en-us/library/windows/desktop/… I've found a lot of helpful info in it in the past. I'll keep thinking of a more formal answer - it may take a 'little' longer than I thought..Ramonaramonda

© 2022 - 2024 — McMap. All rights reserved.