MFC List Control
In MFC, I can edit the text of items in the list control but only for the first column by setting the Edit Labels to true. Now when I click the first column item to change its text, I'm able to change its text but when I hit Enter, its tex开发者_如何学JAVAt isn't updated, why and how do I edit text for other columns?
We create CEdit control on the List control's cell's position (when we double click on List Control) and when we press enter it updates the value. In this example we modify only 1 subitem (the 2nd), when we dbclick on List Control you can create many CEdit controls an do it with all the subitems. List Control SingleSelection = true
//.h
// Global variables in dialog
private:
//..
const static int ID_TXTCTRL_TOMODIFY = 1001;
bool m_IsEnterPressed;
CEdit * m_pTxtCtrlToModify;
//..
protected:
//..
BOOL PreTranslateMessage(MSG* pMsg);
virtual BOOL OnInitDialog();
//..
public:
//..
afx_msg void OnNMDblclkList(NMHDR *pNMHDR, LRESULT *pResult);
afx_msg void OnLvnItemchangedList(NMHDR *pNMHDR, LRESULT *pResult);
afx_msg void OnEnKillFocusCtrlToModify();
//..
//------------------------------------------------------------------
// .cpp
BEGIN_MESSAGE_MAP(DlgMFC, CDialogEx)
//..
ON_NOTIFY(NM_DBLCLK, IDC_LIST, &DlgMFC::OnNMDblclkList)
ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST, &DlgMFC::OnLvnItemchangedList)
ON_EN_KILLFOCUS(ID_TXTCTRL_TOMODIFY, &DlgMFC::OnEnKillFocusCtrlToModify)
//..
END_MESSAGE_MAP()
BOOL DlgMFC::OnInitDialog()
{
CDialogEx::OnInitDialog();
// Set the icon for this dialog. The framework does this automatically
// when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
// TODO: Add extra initialization here
m_pTxtCtrlToModify = NULL;
m_IsEnterPressed = false;
//
return TRUE; // return TRUE unless you set the focus to a control
}
// in this function we let to modify only 1 subitem (the 2nd),
// you can create many CEdit controls an do it with all the subitems
// We create CEdit Control
void DlgMFC::OnNMDblclkList(NMHDR *pNMHDR, LRESULT *pResult)
{
//LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
//*pResult = 0;
POSITION p = m_CtrlList.GetFirstSelectedItemPosition();
int index, id;
if(p)
{
index = m_CtrlList .GetNextSelectedItem(p);
CRect rect, rect_ListControl; // rect wiil be cell position in m_CtrlList, rect_ListControl will be m_CtrlList's position in dialog
if( m_CtrlList.GetSubItemRect(index, 2, LVIR_BOUNDS, rect)) // 2 is subitem number
{
if(m_pTxtCtrlToModify != NULL)
delete m_pTxtCtrlToModify;
m_pTxtCtrlToModify = new CEdit(); // Do not forget to delete it at the end of your program (you can use OnClose())
m_CtrlList.GetWindowRect(&rect_ListControl);
this->ScreenToClient(&rect);
rect.left += rect_ListControl.left + 2; // 2 is just a correction
rect.right += rect_ListControl.left + 2;
rect.top += rect_ListControl.top + 2;
rect.bottom += rect_ListControl.top + 2;
m_pTxtCtrlToModify->Create(ES_CENTER | WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL, rect, this, ID_TXTCTRL_TOMODIFY);
m_pTxtCtrlToModify->SetFocus();
m_pTxtCtrlToModify->SetWindowTextW(m_CtrlList.GetItemText(index, 2)); // 2 is subitem number
}
}
}
// If Selection changes, we delete that CEdit
void DlgMFC::OnLvnItemchangedList(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
*pResult = 0;
if(m_pTxtCtrlToModify != NULL)
{
delete m_pTxtCtrlToModify;
m_pTxtCtrlToModify = NULL;
}
}
// Do not let the dialog to be closed when we press enter
BOOL DlgMFC::PreTranslateMessage(MSG* pMsg)
{
if (pMsg->message == WM_KEYDOWN)
{
if ((pMsg->wParam == VK_RETURN) || (pMsg->wParam == VK_ESCAPE))
{
pMsg->wParam = VK_TAB;
m_IsEnterPressed = true;
}
}
return CDialog::PreTranslateMessage(pMsg);
}
//If we pressed enter, we update it and delete CEdit control
void DlgMFC::OnEnKillFocusCtrlToModify()
{
// we will update only when we press enter
if(m_IsEnterPressed == true)
{
m_IsEnterPressed = false;
// UPDATE here your Database or just ListControl or both ...
// Example: We update the same m_CtrlList's cell
POSITION p = m_CtrlList.GetFirstSelectedItemPosition();
int index;
if(p)
{
CString str;
m_pTxtCtrlToModify->GetWindowTextW(str);
index = m_CtrlList .GetNextSelectedItem(p);
m_CtrlList.SetItemText(index,2,str); // 2 is subitem number
}
// Delete CEdit control
if(m_pTxtCtrlToModify != NULL)
{
delete m_pTxtCtrlToModify;
m_pTxtCtrlToModify = NULL;
}
m_CtrlList.SetFocus();
}
}
I hope it will help you. Thanks
Unfortunately it isn't possible to utilize LVS_EDITLABELS
and LVN_ENDLABELEDIT
for editing other columns than the first.
For a workaround see the XListCtrl article on CodeProject for further information, it dynamically creates an edit control when needed.
For the first column:
- create the list with the LVS_EDITLABELS style
- set a dialog control on your list if it does not have one, eg SetDlgCtrlID( ID_EDITLABEL );
- you might need some code for tracking the current selection
- create the edit in a messagehandler reacting on a click/doubleclick or other user input (seems you have that covered already), best to put this in the parent class.
add a handler for the edit end in the parent class
ON_NOTIFY( LVN_ENDLABELEDIT, ID_EDITLABEL, OnEndEdit ) void MyParentClass::OnEndEdit( NMHDR* pNMHDR, LRESULT* pResult ) { NMLVDISPINFO* pLVDI = reinterpret_cast< NMLVDISPINFO* >( pNMHDR ); if( pLVDI->item.pszText ) m_List.SetItemText( m_iCurrentSelection, 0, pLVDI->item.pszText ); *pResult = 0; }
For other columns: I haven't tried it yet, but it should not be too hard, as you can lookup in the MFC source code how they do it. Note that the code above is tested with a CMFCListCtrl from the latest feature pack, though I assume a plain CListCtrl behaves the same.
精彩评论