Click here to Skip to main content
Email Password   helpLost your password?

Introduction

Almost every one of us who are programming in VC++ , will come across the List control. There are many cases where there is a need to represent data in List Control in multiple columns. By default it is not possible to modify the data in the List control itself. In this small article I am putting a simple way to edit any value in any column in a Report style List control. The logic here is simple, whenever user clicks on an sub-item which he wants to modify at that place I am displaying a edit box and allowing to modify the value. Once modified and by clicking the ENTER key, the updated value is set in the List control. Here I am assuming the user is familiar with VC++ and using Class Wizard

Implementation steps:

  1. Using MFC AppWizard, create a Dialog Based application. Give the application name as MultipleColumns. By default the wizard adds OK and Cancel buttons to the Dialog, Remove these two buttons.
  2. Now Add a List-Control and in properties change the style to Report, this style is necessary if we want multiple columns
  3. Add two buttons to the Dialog and name them as OK and Exit
  4. Add one Edit box and in the properties remove the Border style
  5. Using the Class Wizard add the message handlers for the OK and Exit Buttons. Add the following code to those functions
  6. void CMultipleColumnsDlg::OK() 
    {
        CDialog::EndDialog (0); // Add this line
    
    }
    
    
    void CMultipleColumnsDlg::OnExit() 
    {
        CDialog::EndDialog (0); // Add this line
    
    }
    
    
  7. Add a function called InsertItems() to the CMulipleColumnsDlg class.
  8. void InsertItems();
    

    In the function handler add the following code

    // This function inserts the default values 
    
    // into the listControl
    
    void CMultipleColumnsDlg::InsertItems()
    {
        HWND hWnd = ::GetDlgItem(m_hWnd, IDC_LIST1);
    
        // Set the LVCOLUMN structure with the required 
    
        // column information
    
        LVCOLUMN list;
        list.mask =  LVCF_TEXT |LVCF_WIDTH| 
            LVCF_FMT |LVCF_SUBITEM;
        list.fmt = LVCFMT_LEFT;
        list.cx = 50;
        list.pszText   = "S.No";
        list.iSubItem = 0;
        //Inserts the column
    
        ::SendMessage(hWnd,LVM_INSERTCOLUMN, 
            (WPARAM)0,(WPARAM)&list);
    
        list.cx = 100;
        list.pszText   = "Name";
        list.iSubItem = 1;
        ::SendMessage(hWnd  ,LVM_INSERTCOLUMN, 
            (WPARAM)1,(WPARAM)&list);
    
        list.cx = 100;
        list.pszText   = "Address";
        list.iSubItem = 2;
        ::SendMessage(hWnd  ,LVM_INSERTCOLUMN, 
            (WPARAM)1,(WPARAM)&list);
    
        list.cx = 100;
        list.pszText   = "Country";
        list.iSubItem = 2;
        ::SendMessage(hWnd  ,LVM_INSERTCOLUMN, 
            (WPARAM)1,(WPARAM)&list);
    
        // Inserts first Row with four columns .
    
        SetCell(hWnd,"1",0,0);
        SetCell(hWnd,"Prabhakar",0,1);
        SetCell(hWnd,"Hyderabad",0,2);
        SetCell(hWnd,"India",0,3);
    
        // Inserts second Row with four columns .
    
        SetCell(hWnd,"2",1,0);
        SetCell(hWnd,"Uday",1,1); 
        SetCell(hWnd,"Chennai",1,2);
        SetCell(hWnd,"India",1,3);
    
        // Inserts third Row with four columns .
    
        SetCell(hWnd,"3",2,0);
        SetCell(hWnd,"Saradhi",2,1); 
        SetCell(hWnd,"Bangolore",2,2);
        SetCell(hWnd,"India",2,3);
    
        // Inserts fourth Row with four columns .
    
        SetCell(hWnd,"4",3,0);
        SetCell(hWnd,"Surya",3,1); 
        SetCell(hWnd,"Calcutta",3,2);
        SetCell(hWnd,"India",3,3);
    }
    
  9. Add another function called SetCell( ) to the CMultipleColumnsDlg class
  10. void SetCell(HWND hWnd1, CString value, int nRow, int nCol);
    

    In the function handler add the following code

    // This function set the text in the specified 
    
    // SubItem depending on the Row and Column values
    
    void CMultipleColumnsDlg::SetCell(HWND hWnd1, 
            CString value, int nRow, int nCol)
    {
        TCHAR     szString [256];
        wsprintf(szString,value ,0);
    
        //Fill the LVITEM structure with the 
    
        //values given as parameters.
    
        LVITEM lvItem;
        lvItem.mask = LVIF_TEXT;
        lvItem.iItem = nRow;
        lvItem.pszText = szString;
        lvItem.iSubItem = nCol;
        if(nCol >0)
            //set the value of listItem
    
            ::SendMessage(hWnd1,LVM_SETITEM, 
                (WPARAM)0,(WPARAM)&lvItem);
        else
            //Insert the value into List
    
            ListView_InsertItem(hWnd1,&lvItem);
    
    }
    
  11. Add one more function called GetItemText() to the same Class
  12. CString GetItemText(HWND hWnd, int nItem, int nSubItem) const;
    

    Inside the function add the following code

    //this function will returns the item 
    
    //text depending on the item and SubItem Index
    
    CString CMultipleColumnsDlg::GetItemText(
        HWND hWnd, int nItem, int nSubItem) const
    {
        LVITEM lvi;
        memset(&lvi, 0, sizeof(LVITEM));
        lvi.iSubItem = nSubItem;
        CString str;
        int nLen = 128;
        int nRes;
        do
        {
            nLen *= 2;
            lvi.cchTextMax = nLen;
            lvi.pszText = str.GetBufferSetLength(nLen);
            nRes  = (int)::SendMessage(hWnd, 
                LVM_GETITEMTEXT, (WPARAM)nItem,
                (LPARAM)&lvi);
        str.ReleaseBuffer();
        return str;
    }
    
  13. Also add two member variables to the CMultipleColumnsDlg class which are of type int
  14. int nItem, nSubItem;
    
  15. From the Class wizard add NM_CLICK notification to the List control. Inside the function handler write the following code
  16. //This function Displays an EditBox at the position 
    
    //where user clicks on a particular SubItem with 
    
    //Rectangle are equal to the SubItem, thus allows to 
    
    //modify the value
    
    void CMultipleColumnsDlg::OnClickList(
            NMHDR* pNMHDR, LRESULT* pResult) 
    {
        Invalidate();
        HWND hWnd1 =  ::GetDlgItem (m_hWnd,IDC_LIST1);
        LPNMITEMACTIVATE temp = (LPNMITEMACTIVATE) pNMHDR;
        RECT rect;
        //get the row number
    
        nItem = temp->iItem;
        //get the column number
    
        nSubItem = temp->iSubItem;
        if(nSubItem == 0 || nSubItem == -1 || nItem == -1)
            return ;
        //Retrieve the text of the selected subItem 
    
        //from the list
    
        CString str = GetItemText(hWnd1,nItem ,
            nSubItem);
    
        RECT rect1,rect2;
        // this macro is used to retrieve the Rectanle 
    
        // of the selected SubItem
    
        ListView_GetSubItemRect(hWnd1,temp->iItem,
            temp->iSubItem,LVIR_BOUNDS,&rect);
        //Get the Rectange of the listControl
    
        ::GetWindowRect(temp->hdr.hwndFrom,&rect1);
        //Get the Rectange of the Dialog
    
        ::GetWindowRect(m_hWnd,&rect2);
    
        int x=rect1.left-rect2.left;
        int y=rect1.top-rect2.top;
        
        if(nItem != -1) 
        ::SetWindowPos(::GetDlgItem(m_hWnd,IDC_EDIT1),
            HWND_TOP,rect.left+x,rect.top+4, 
            rect.right-rect.left - 3,
            rect.bottom-rect.top -1,NULL);
        ::ShowWindow(::GetDlgItem(m_hWnd,IDC_EDIT1),SW_SHOW);
        ::SetFocus(::GetDlgItem(m_hWnd,IDC_EDIT1));
        //Draw a Rectangle around the SubItem
    
        ::Rectangle(::GetDC(temp->hdr.hwndFrom),
            rect.left,rect.top-1,rect.right,rect.bottom);
        //Set the listItem text in the EditBox
    
        ::SetWindowText(::GetDlgItem(m_hWnd,IDC_EDIT1),str);
        *pResult = 0;
    }
    
    
  17. To handle the ENTER key we need to write the virtual function OnOk in the MultipleColumnsDlg.h, so add the following as protected member
  18. afx_msg void OnOK();
    

    In MultipleColumnsDlg.cpp write the following code.

    // This function handles the ENTER key 
    
    void CMultipleColumnsDlg::OnOK() 
    {   
        CWnd* pwndCtrl = GetFocus();
        // get the control ID which is 
    
        // presently having the focus
    
        int ctrl_ID = pwndCtrl->GetDlgCtrlID();
        CString str;
        switch (ctrl_ID)
        {   //if the control is the EditBox 
    
            case IDC_EDIT1:
            //get the text from the EditBox
    
            GetDlgItemText(IDC_EDIT1,str);
    
            //set the value in the listContorl with the
    
            //specified Item & SubItem values
    
            SetCell(::GetDlgItem (m_hWnd,IDC_LIST1),
                str,nItem,nSubItem);
    
            ::SendDlgItemMessage(m_hWnd,IDC_EDIT1,
                WM_KILLFOCUS,0,0);
            ::ShowWindow(::GetDlgItem(m_hWnd,IDC_EDIT1),
                SW_HIDE);
                break;     
            default:
                break;
        }
    }
    
    
  19. The last step in the implementation is add the following code in side the OnInitDialog function
  20. 	
    //Set the style to listControl
    
    ListView_SetExtendedListViewStyle(::GetDlgItem 
            (m_hWnd,IDC_LIST1),LVS_EX_FULLROWSELECT | 
            LVS_EX_GRIDLINES); 
    
    InsertItems();
    ::ShowWindow(::GetDlgItem(m_hWnd,IDC_EDIT1),SW_HIDE);
    

Conclusion

With this I will hope , it will give an idea to edit any sub items in a List control.

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
QuestionHow to retrieve data from the columns
PradeepChandra
2:06 22 Dec '08  
Can any one please help me?

I have made a list control in which I don't want it to be populated by duplicate rows.

So to avoid this, I thought of retrieving values from the rows/columns.

But how do I do that?

Thanks.
QuestionHow to prevent editbox visibility on dialog box after changing width of right most column?
login0001
21:05 24 Oct '07  
When I change column widh for the above application such that my right most column is not fully visible on the screen (will be fully visible only if we scroll horizontal scroll bar to right),

and I double click on such subitem on the rightmost column, my edit box goes on screen i.e. on dialog box.
I don't want to show my edit box on doialig box.
What should I do for this?
Questiondoubts on code int x=rect1.left-rect2.left; How it works?
login0001
20:10 24 Oct '07  
In Code,

//Get the Rectange of the listControl
::GetWindowRect(temp->hdr.hwndFrom,&rect1); //1

//Get the Rectange of the Dialog
::GetWindowRect(m_hWnd,&rect2); //2


What will be x and y in this case? How does it work?

int x=rect1.left-rect2.left;
int y=rect1.top-rect2.top;



-- modified at 1:26 Thursday 25th October, 2007
GeneralModifying without clicking the ENTER key
crisoc
4:08 24 Jan '06  
Is it possible to modify whithout clicking the ENTER key, like clicking again in other sub-item? Confused What do I need for this???

Cristiano - Brazil
GeneralRe: Modifying without clicking the ENTER key
Vivian De Smedt
20:13 18 Feb '06  
According to me you can handle the kill focus message of the IDC_EDIT1
With the wizard add an handler for IDC_EDIT1, message EN_KILLFOCUS.

MultipleColumnsDlg.h:
    afx_msg void OnKillFocusValueEdit();

MultipleColumnsDlg.cpp:
    ON_EN_KILLFOCUS(IDC_EDIT1, OnKillFocusValueEdit)

Do the same kind of job you did in OnOK in OnKillFocusValueEdit.

Hopes it helps (at least it works for me Wink )

Vivian De Smedt (Belgium).

AnswerRe: Modifying without clicking the ENTER key
mones
1:10 28 Jul '08  
hello,
you can try below,
make a "Events" to List Control, select LVN_ITEMCHANGED to create a notification.
then, copy the code from OnOK to the notification(default, OnItemchangedList1).
after that, you can try to complie.
good luck.
we can talk.
mones84@126.com

The important thing is not what you've ever done, but what you'll do next.

GeneralAuto forward to next column?
dpsauter
6:34 30 Dec '04  
I'm using the EN_UPDATE message from the edit box in the list control to move the edit box to the next column/row when the user enters the maximum number of chars for the edit box. This appears to be working but neither the edit box nor the cursor is visible in the new column. If I do not make the call to SetCell within the EN_UPDATE code (I am bypassing the SetCell call on an ENTER key), the blinking cursor shows up in the next column, but obviously the previous column has no text displayed. Any solutions?


Dave
GeneralFix a bug!!!
SniperLee
16:32 9 Feb '04  
int function void CMultipleColumnsDlg::OnClickList( NMHDR* pNMHDR, LRESULT* pResult)
{
...
int x=rect1.left-rect2.left;
int y=rect1.top-rect2.top;

if(nItem != -1)
::SetWindowPos(::GetDlgItem(m_hWnd,IDC_EDIT1),
HWND_TOP,rect.left+x,rect.top+4,
rect.right-rect.left - 3,
rect.bottom-rect.top -1,NULL);
//why rect.top + 4 ?
//I think rect.top + y,but you may subtract window title-bar's high.
...
}

so I fix it such as:
void CMultipleColumnsDlg::OnClickList( NMHDR* pNMHDR, LRESULT* pResult)
{
...
TITLEBARINFO oTitleBarInfo;
oTitleBarInfo.cbSize = sizeof(TITLEBARINFO);
int x=rect1.left-rect2.left;
int y=rect1.top-rect2.top;
y -= (oTitleBarInfo.rcTitleBar.bottom - oTitleBarInfo.rcTitleBar.top);
if(nItem != -1)
::SetWindowPos(::GetDlgItem(m_hWnd,IDC_EDIT1),
HWND_TOP,rect.left+x,rect.top + y,
rect.right-rect.left - 3,
rect.bottom-rect.top - 2,NULL);
...
}
// you need installed windows SDK
GeneralModifying the content automatically
mervick
6:09 19 Dec '03  
I wonder if it's possible for me to edit the value automatically instead of manually by user. Meaning, the computer will do the changes without user have to type in anything. For example, a digital clock that shows every second ticking. Thanks for helping me out.
GeneralCan't get the row
Rooster242
11:12 11 Sep '03  
The line

int nItem = temp->iItem;

returns the coorect item number if i click on the item in column 0 but always returns -1 when I click on a sub item. Am I doing somthing wrong?

- Jim
GeneralRe: Can't get the row
Rooster242
11:22 11 Sep '03  
I figured it out.

I wasn't setting the LVS_EX_FULLROWSELECT extended style.
GeneralCEdit placement error
pelloq1
5:01 11 Apr '03  
Hello

I have implement this code in VS 7

But the CEdit becom allway one line upper...

The correction is in the OnNMClick method :

ListView_GetSubItemRect(hWnd1,temp->iItem, temp->iSubItem,LVIR_BOUNDS,&rect);
CRect myCrect(rect) ; // newLine

if(m_iItem != -1)
::SetWindowPos(::GetDlgItem(m_hWnd,IDC_EDT_BLANK), HWND_TOP,rect.left+x,(rect.top+4)+myCrect.Height()/*<- correction*/, rect.right-rect.left - 3, rect.bottom-rect.top -1,NULL);



pelloquin cedric
www.bacad.ch
GeneralRe: CEdit placement error
Carwarlock
10:09 16 Jun '03  
Hello

I changed this line for this:

::SetWindowPos(::GetDlgItem (m_hWnd,IDC_EDIT1),HWND_TOP,rect.left+x,y+((rect.bottom-rect.top)*nItem),rect.right-rect.left - 3,rect.bottom-rect.top -2,NULL);


GeneralRe: CEdit placement error
Dima Polyakov
17:01 29 Aug '03  
//Get the Rectange of the Dialog
GetClientRect(&rect2);
ClientToScreen(&rect2);

int x = rect1.left - rect2.left;
int y = rect1.top - rect2.top;

if(m_nItem != -1)
::SetWindowPos(::GetDlgItem(m_hWnd,IDC_EDIT1), HWND_TOP,
x + rect.left + 6, y + rect.top + 2,
rect.right - rect.left - 1, rect.bottom - rect.top - 1,
NULL
);
GeneralHandling Events
Anonymous
10:35 24 Oct '02  
Not only should this be subclassed, but handling of Focus, Scrollbars, Mousewheels, ect.. should be handled. Dead

Kudos,
KC
GeneralRe: Handling Events
quox
20:12 5 Aug '03  
THAT'S RIGHT!!!Mad Dead
GeneralQuestion...
Puiu
13:23 24 Sep '02  
How can I attach a combo box to the list?
GeneralHow to make the text selected
John Wong
10:55 13 Sep '02  
Thank for distributing the great and yet simple code.
I utilized the code and want to improve it a little bit.

How can I make the text selected in the edit box as I click the cell. I think this is more convenient to the users as they can go left or right side of the text or delete the old data.

Thank in advance!
Cool
JW
GeneralRe: How to make the text selected
Dima Polyakov
16:58 29 Aug '03  
//Set the listItem text in the EditBox
::SetWindowText(::GetDlgItem(m_hWnd,IDC_EDIT1),str);

::SendMessage(::GetDlgItem(m_hWnd,IDC_EDIT1), EM_SETSEL, 0, -1);

GeneralMissing ReleaseDC
Antony Kancidrowski
0:40 16 Nov '01  
Within:

void CMultipleColumnsDlg::OnClickList(NMHDR* pNMHDR, LRESULT* pResult)

we had

::Rectangle(::GetDC(temp->hdr.hwndFrom),rect.left,rect.top-1,rect.right,rect.bottom);

you could change to

//Draw a Rectangle around the SubItem
HDC hDC = ::GetDC(temp->hdr.hwndFrom);
::Rectangle(hDC,rect.left,rect.top-1,rect.right,rect.bottom);
::ReleaseDC(temp->hdr.hwndFrom, hDC);



-------------------------------------------
Antony Kancidrowski
Software Engineer
Chevin Ltd
GeneralRe: Missing ReleaseDC
Jamie.
7:46 20 Nov '02  
Or just turn the border option back on for the edit box and you don't need to draw a rectangle...
GeneralIt needs more flexibility
a_dyhrberg
7:40 12 Sep '01  
I like the ideer, this is an easy way to make editable ListBoxes

But you code kan only manage the placement of the edit box if them ListBox is placed at the TOP of the dialog. Why not make it calculate the placement of the editbox according to the listbox´s placement instead of the dialog borders.

This would make the code much more usefull, since people could then use this method on their project witout making changes to their dialog layout.

Hope to get a responce on this.

thanks for this great and easy ideer.
GeneralRe: It needs more flexibility
Anders Dyhrberg
8:12 12 Sep '01  
I Looked throug the code one more time, and I could see that it was already ready for this.

I just changed this line in OnClickList()

::SetWindowPos(::GetDlgItem(m_hWnd,IDC_EDIT1),HWND_TOP,rect.left+x,rect.top +4,rect.right-rect.left - 3,rect.bottom-rect.top -1,NULL);

To this.

::SetWindowPos(::GetDlgItem(m_hWnd,IDC_EDIT1),HWND_TOP,rect.left+x,rect.top+y-20,rect.right-rect.left - 1,rect.bottom-rect.top -1,NULL);

Hope this is of any use. Blush )
GeneralRe: It needs more flexibility
Dima Polyakov
16:57 29 Aug '03  
Here is the code how to place properly the edit box:
//Get the Rectange of the Dialog
GetClientRect(&rect2);
ClientToScreen(&rect2);

int x = rect1.left - rect2.left;
int y = rect1.top - rect2.top;

if(m_nItem != -1)
::SetWindowPos(::GetDlgItem(m_hWnd,IDC_EDIT1), HWND_TOP,
x + rect.left + 6, y + rect.top + 2,
rect.right - rect.left - 1, rect.bottom - rect.top - 1,
NULL
);
Where +6 and +2 are just shifts to look a little bit better (aprox. match the text in box and in the greed)
GeneralFor most flexibility
HoangLan
1:55 12 Apr '05  
Hi, I try all code of you but there are some problem:

1. Code of author doesn't display properly if replace List Control's position in the Dialog Box

2. Code of Anders Dyhrberg is best off all but it doesn't display all 4 border lines of edit rectangle (diplays 2 lines, not 4 lines, in my machine). And does he know if he switchs Display Properties of Windows between Windows Classic Style and Windows XP Style, affect of code changes.

3. Code of Dima Polyakov is not always properly! And he didn't explane appropriatly that "where +6 and +2 are just shifts to look a little bit better". He didn't know that +6 is total width of two boder lines of App Window.

So, for most flexibility, code of Dyhrberg needs to be modified a little! Smile

I temporily replace variables' names:

x with x1
y with y1

(All that for more clearly!)

And declare additional variable for retrieve App Window client rect:

CRect rect3;
GetClientRect(&rect3);
ClientToScreen(rect3);

UINT nDeltaWidth = rect2.Width() - rect3.Width(); // Total width of two boders of App Window. It's is 6 in my test
UINT nDeltaHeight = rect2.Height() - rect3.Height(); // Total Height of two boders and Title Bar of App Window. It's changes if you change Display Properties of Windows
UINT nTitleHeight = nDeltaHeight - nDeltaWidth; // App Window's Title Bar height

x1 = rect1.left - rect2.left; // rect1 and rect2 has absolute position with screen
y1 = rect1.top - (rect2.top + nTitleHeight);
x2 = rect.left; // rect has relative postition with rect1
y2 = rect.top;
x = x1 + x2; // x is relative horizontal distance from rect to rect2
y = y1 + y2; // y is relative vertical distance from rect to rect2

cx = rect.Width();
cy = rect.Height();

Replace this (author's code):
::Rectangle(::GetDC(temp->hdr.hwndFrom),rect.left,rect.top-1,rect.right,rect.bottom); With this:
::Rectangle(::GetDC(temp->hdr.hwndFrom), rect.left, rect.top, rect.right, rect.bottom); // It's seem more beautiful! Big Grin

And replace this (Dyhrberg's code):
::SetWindowPos(::GetDlgItem(m_hWnd,IDC_EDIT1),HWND_TOP,rect.left+x,rect.top+y-20,rect.right-rect.left - 1,rect.bottom-rect.top -1,NULL);
With this:
::SetWindowPos(::GetDlgItem(m_hWnd, IDC_EDIT1), HWND_TOP, x, y, cx - 2, cy - 2, NULL);
Now, edit text always display properly!

Let's try!


HoangLan



Last Updated 19 May 2001 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010