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
- 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.
- Now Add a List-Control and in properties change the
style to Report, this style is necessary if we want multiple columns
- Add two buttons to the Dialog and name them as OK and Exit
- Add one Edit box and in the properties remove the Border style
- Using the Class Wizard add the message handlers for the OK and Exit Buttons.
Add the following code to those functions
void CMultipleColumnsDlg::OK()
{
CDialog::EndDialog (0);
}
void CMultipleColumnsDlg::OnExit()
{
CDialog::EndDialog (0);
}
- Add a function called
InsertItems() to the CMulipleColumnsDlg class.
void InsertItems();
In the function handler add the following code
void CMultipleColumnsDlg::InsertItems()
{
HWND hWnd = ::GetDlgItem(m_hWnd, IDC_LIST1);
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;
::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);
SetCell(hWnd,"1",0,0);
SetCell(hWnd,"Prabhakar",0,1);
SetCell(hWnd,"Hyderabad",0,2);
SetCell(hWnd,"India",0,3);
SetCell(hWnd,"2",1,0);
SetCell(hWnd,"Uday",1,1);
SetCell(hWnd,"Chennai",1,2);
SetCell(hWnd,"India",1,3);
SetCell(hWnd,"3",2,0);
SetCell(hWnd,"Saradhi",2,1);
SetCell(hWnd,"Bangolore",2,2);
SetCell(hWnd,"India",2,3);
SetCell(hWnd,"4",3,0);
SetCell(hWnd,"Surya",3,1);
SetCell(hWnd,"Calcutta",3,2);
SetCell(hWnd,"India",3,3);
}
- Add another function called
SetCell( ) to the CMultipleColumnsDlg class
void SetCell(HWND hWnd1, CString value, int nRow, int nCol);
In the function handler add the following code
void CMultipleColumnsDlg::SetCell(HWND hWnd1,
CString value, int nRow, int nCol)
{
TCHAR szString [256];
wsprintf(szString,value ,0);
LVITEM lvItem;
lvItem.mask = LVIF_TEXT;
lvItem.iItem = nRow;
lvItem.pszText = szString;
lvItem.iSubItem = nCol;
if(nCol >0)
::SendMessage(hWnd1,LVM_SETITEM,
(WPARAM)0,(WPARAM)&lvItem);
else
ListView_InsertItem(hWnd1,&lvItem);
}
- Add one more function called
GetItemText() to the same Class
CString GetItemText(HWND hWnd, int nItem, int nSubItem) const;
Inside the function add the following code
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;
}
- Also add two member variables to the
CMultipleColumnsDlg
class which are of type int
int nItem, nSubItem;
- From the Class wizard add
NM_CLICK notification to the List control.
Inside the function handler write the following code
void CMultipleColumnsDlg::OnClickList(
NMHDR* pNMHDR, LRESULT* pResult)
{
Invalidate();
HWND hWnd1 = ::GetDlgItem (m_hWnd,IDC_LIST1);
LPNMITEMACTIVATE temp = (LPNMITEMACTIVATE) pNMHDR;
RECT rect;
nItem = temp->iItem;
nSubItem = temp->iSubItem;
if(nSubItem == 0 || nSubItem == -1 || nItem == -1)
return ;
CString str = GetItemText(hWnd1,nItem ,
nSubItem);
RECT rect1,rect2;
ListView_GetSubItemRect(hWnd1,temp->iItem,
temp->iSubItem,LVIR_BOUNDS,&rect);
::GetWindowRect(temp->hdr.hwndFrom,&rect1);
::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));
::Rectangle(::GetDC(temp->hdr.hwndFrom),
rect.left,rect.top-1,rect.right,rect.bottom);
::SetWindowText(::GetDlgItem(m_hWnd,IDC_EDIT1),str);
*pResult = 0;
}
- To handle the ENTER key we need to write the virtual function
OnOk in the
MultipleColumnsDlg.h, so add the following as protected member
afx_msg void OnOK();
In MultipleColumnsDlg.cpp write the following code.
void CMultipleColumnsDlg::OnOK()
{
CWnd* pwndCtrl = GetFocus();
int ctrl_ID = pwndCtrl->GetDlgCtrlID();
CString str;
switch (ctrl_ID)
{
case IDC_EDIT1:
GetDlgItemText(IDC_EDIT1,str);
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;
}
}
- The last step in the implementation is add the following code in side the
OnInitDialog function
ListView_SetExtendedListViewStyle(::GetDlgItem
(m_hWnd,IDC_LIST1),LVS_EX_FULLROWSELECT |
LVS_EX_GRIDLINES);
InsertItems();
::ShowWindow(::GetDlgItem(m_hWnd,IDC_EDIT1),SW_HIDE);
With this I will hope , it will give an idea to edit any sub items in a List control.
|
|
 |
 | How 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.
|
|
|
|
 |
 | How 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?
|
|
|
|
 |
 | doubts 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
|
|
|
|
 |
 | Modifying 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? What do I need for this???
Cristiano - Brazil
|
|
|
|
 |
|
 |
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 )
Vivian De Smedt (Belgium).
|
|
|
|
 |
|
 |
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.
|
|
|
|
 |
 | Auto 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
|
|
|
|
 |
 | Fix 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
|
|
|
|
 |
 | Modifying 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.
|
|
|
|
 |
 | Can'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
|
|
|
|
 |
|
 |
I figured it out.
I wasn't setting the LVS_EX_FULLROWSELECT extended style.
|
|
|
|
 |
 | CEdit 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
|
|
|
|
 |
|
 |
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);
|
|
|
|
 |
|
 |
//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 );
|
|
|
|
 |
 | Handling Events Anonymous | 10:35 24 Oct '02 |
|
 |
Not only should this be subclassed, but handling of Focus, Scrollbars, Mousewheels, ect.. should be handled.
Kudos, KC
|
|
|
|
 |
|
|
 |
 | Question... Puiu | 13:23 24 Sep '02 |
|
 |
How can I attach a combo box to the list?
|
|
|
|
 |
 | How 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!
JW
|
|
|
|
 |
|
 |
//Set the listItem text in the EditBox ::SetWindowText(::GetDlgItem(m_hWnd,IDC_EDIT1),str);
::SendMessage(::GetDlgItem(m_hWnd,IDC_EDIT1), EM_SETSEL, 0, -1);
|
|
|
|
 |
 | Missing 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
|
|
|
|
 |
|
 |
Or just turn the border option back on for the edit box and you don't need to draw a rectangle...
|
|
|
|
 |
 | It 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.
|
|
|
|
 |
|
 |
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. )
|
|
|
|
 |
|
 |
Here is the code how to place properly the edit box:
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)
|
|
|
|
 |
|
 |
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!
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!
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
|
|
|
|
 |
|
|