VisualStudio MFC CListCtrl SetItemText fails - c++

I created a MFC Visual Studio Project with a CListCtrl. I added some items in the CDialog class like:
int l_iItem = m_listCtrl.InsertItem(LVIF_TEXT|LVIF_STATE, counter, someString, 0, LVIS_SELECTED, 0, 0);
m_listCtrl.SetItemText( l_iItem, 1, blockHexChar );
m_listCtrl.SetItemText( l_iItem, 2, description);
This works fine.
Afterwards i want to edit a subitem (over double click event). Works also fine.
If the editing is finished (this is in the CListCtrl class),
OnEndLabelEdit(NMHDR* pNMHDR, LRESULT* pResult)
will be called. It looks like this
LV_DISPINFO *plvDispInfo = (LV_DISPINFO *)pNMHDR;
LV_ITEM *plvItem = &plvDispInfo->item;
if (plvItem->pszText != NULL)
{
bool res = SetItemText(plvItem->iItem, plvItem->iSubItem, plvItem->pszText);
}
I always getting 0 back, so the SetItemText is failing.
Any idea what i'm doing wrong?
Cheers ehmkey

You have to post yourself a user-defined message (WM_USER+NNN) using PostMessage from within OnEndLabelEdit. Change label in response to that message.

LVN_ENDLABELEDIT passes a pointer to NMLVDISPINFO through lParam. I think you're looking at the wrong structure when processing the notification. Using the class wizard to generate the event handler in VS2013 gives
void CMFCApplication6Dlg::OnLvnEndlabeleditList1(NMHDR *pNMHDR, LRESULT *pResult)
{
NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
// TODO: Add your control notification handler code here
*pResult = 0;
}

Thanks for your input, but the problem was in the MESSAGE_MAP(....) in CDialog class.
I have a member here, which holds the list and I directly mapped to the CListCtrl Class.
BEGIN_MESSAGE_MAP(CPeriDialog, CDialog)
ON_NOTIFY(LVN_ENDLABELEDIT, IDC_LIST1, &CListCtrl::OnEndLabelEdit)
END_MESSAGE_MAP()
Now I made a wrapper function which forward the event to the right object.
BEGIN_MESSAGE_MAP(CPeriDialog, CDialog)
ON_NOTIFY(LVN_ENDLABELEDIT, IDC_LIST1, &CPeriDialog::EndEdit)
END_MESSAGE_MAP()
Function simple looks like this
void CPeriDialog::EndEdit(NMHDR* pNMHDR, LRESULT* pResult)
{
m_listCtrl.OnEndLabelEdit(pNMHDR, pResult);
}

Related

C++ MFC CListCtrl - How to sort item upon startup (without any UI interaction)?

I have a list of item to be loaded in a MFC window with CListCtrl in it.
Upon OnInitDialog(), I will perform
m_List.InsertItem(&item);
where this item contain data such as: filename, last date modified, comment.
The current output that I have now is that it will sort it with last date modified when I open this dialog, then I would need to manually click on the list header to trigger an event to sort the data according to filename with functions such as below:
ON_NOTIFY(LVN_COLUMNCLICK, IDC_FILELIST, OnColumnclickFilelist)
int CALLBACK CDlg::AlphaNumericSorting(LPARAM lParam1, LPARAM lParam2, LPARAM ParameterSort)
{
std::wstring wv1(((CLVData*)lParam1)->strFileName);
std::wstring wv2(((CLVData*)lParam2)->strFileName);
return StrCmpLogicalW(wv1.c_str(), wv2.c_str()) < 0;
}
void Dlg::OnColumnclickFilelist(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
m_List.SortItems(AlphaNumericSorting, pNMListView->iSubItem);
*pResult = 0;
}
Now I'm trying to make it sort automatically upon OnInitDialog(), is there a correct way to do so? From what I searched so far, all samples I got are those with event handling.

Using ON_WM_HSCROLL() while still having a functional default scrollbar?

I am writing an MFC application which uses the CSliderCtrl class to control a slider. I have the ON_WM_HSCROLL() message in my message map, but this arises with the problem that this disables the default window scrollbar that appears at the bottom of the view when the windows is too small. Manipulating it does nothing to the window. What do I have to do in order to preserve functionality in that scrollbar?
Currently, my OnHScroll() function simply looks like:
void myClass::OnHScroll(UINT nSHCode, UINT nPos, CScrollBar* pScrollBar)
{
if (*pScrollBar == mySlider)
{
// do stuff
}
}
You still need to call the default handler defined in base/parent class: CDialog::OnHScroll(nSBCode, nPos, pScrollBar); in case of dialog window or CFormView::OnHScroll(nSBCode, nPos, pScrollBar); in case of SDI/MDI view.
So your handler will look like this:
void myClass::OnHScroll(UINT nSHCode, UINT nPos, CScrollBar* pScrollBar)
{
if (*pScrollBar == mySlider)
{
// do stuff
}
CDialog::OnHScroll(nSBCode, nPos, pScrollBar)
}

Detecting Up / Down arrows in CSpinButtonCtrl MFC C++

Is there any way to differentiate when the Up or Down arrow of a CSpinButtonCtrl is pressed?
I am trying to use the OnPointerdown event, but I don't know how to do it...
afx_msg LRESULT CMySpinButtonCtrl::OnPointerdown(WPARAM wParam, LPARAM lParam)
{
if(IS_POINTER_PRIMARY_WPARAM(wParam))
{
//TODO
}
return 0;
}
I will appreciate any kind of help.
Is there any way to differentiate when the Up or Down arrow of a CSpinButtonCtrl is pressed?
You should use UDN_DELTAPOS to do this.
Right-click the control in the Resource Editor and select Add Event Handler:
Select the UDN_DELTAPOS message and click Add and Edit:
You will be provided with skeleton code:
void CMFCApplication1Dlg::OnDeltaposSpin1(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMUPDOWN pNMUpDown = reinterpret_cast<LPNMUPDOWN>(pNMHDR);
// TODO: Add your control notification handler code here
*pResult = 0;
}
The NMUPDOWN article explains about the structure that you use. What you need to do is test the iDelta value. Example:
void CColumnOrderDlg::OnDeltaposSpinColumns(NMHDR* pNMHDR, LRESULT* pResult)
{
LPNMUPDOWN pNMUpDown = reinterpret_cast<LPNMUPDOWN>(pNMHDR);
if (pNMUpDown != nullptr)
{
if( pNMUpDown->iDelta > 0)
// Up - Do stuff;
else if(pNMUpDown->iDelta < 0)
// Down - Do stuff;
}
*pResult = 0;
}
There is also a useful article here where it states:
If you use a spin control for some other purpose, for example, to page through a sequence of windows or dialog boxes, then add a handler for the UDN_DELTAPOS message and perform your custom action there.

List Control Delete Row on Delete Key press

I have a List Control holding rows with data. Now i am trying to delete row on delete key press. I am trying with : LVN_DELETEITEM as below :
Afx Message :
afx_msg void OnLvnDeleteitemList(NMHDR *pNMHDR, LRESULT *pResult);
Message Map:
ON_NOTIFY(LVN_DELETEITEM, IDC_LIST_ACQUISITION_SETTINGS, &MeasureDialog::OnLvnDeleteitemList)
Implementation of OnLvnDeleteitemList :
void MeasureDialog::OnLvnDeleteitemList(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
// TODO: Add your control notification handler code here
ReportMessage(L"Deleted");
*pResult = 0;
}
But i am never coming into the OnLvnDeleteitemList method. Whats wrong about it?
The message LVN_DELETEITEM clearly states that:
Notifies a list-view control's parent window that an item is about to be deleted.
Which means, when item is deleted, the notification message will be sent. Pressing the delete key won't invoke this method. You need to handle Delete key message itself (WM_KEYDOWN), and call CListCtrl::DeleteItem

How to avoid EN_CHANGE notifications when sending WM_SETTEXT?

I have a CEdit derived control that displays the string "N/A" when the undelying data is null. I recently added code to empty the control(SetWindowText("");) when it gains focus and set if back to "N/A"(SetWindowText("N/A")) when the focus is lost if the user left the control empty.
The only problem is that setting the window text to "" or "N/A" triggers EN_CHANGE, so my dialog thinks that the data has changed.
How can I avoid EN_CHANGE from being fired when calling SetWindowText (WM_SETTEXT)?
NOTES
-I know I can set the edit control to Multiline=TRUE but that's not accpectable for me.
-My application is MBCS so I can't use SetCueBanner
-I want an elegant solution. Setting the parent window to NULL temporarily is not an elegant solution.
EDIT:
-I want the solution to be in my custom control, not in each dialog
Thanks
The way I've done it before (last time, like 20 minutes ago; in fact I was thinking about asking the same question), is by setting a flag. When I'm about to set the text programatically, I set the flag, and I check it in the EN_CHANGE handler:
void CMyDialog::MyFunction()
{
setEditTextProgramatically = true;
c_Edit.SetWindowText(_T("Whatever"));
setEditTextProgramatically = false;
}
void CMyDialog::OnEnChangeEdit()
{
if (!setEditTextProgramatically)
{
// Do whatever you need to do
}
}
I know it's not the most elegant solution, but it works, at least for me.
I've always wondered why MFC doesn't provide a way to distinguish user input from changes from code, but that's the way it is.
I finally found a suitable solution to my problem.
First, I added a flag to my derived control's header file and I initialized it to false in the constructor
bool m_bNoEnChange;
I overrode the OnChildNotify in my derived control's header file and in the implementation, I checked for the WM_COMMAND message with the EN_CHANGE parameter. I then returned TRUE to prevent the message from being sent to the parent(dialog/page)
virtual BOOL OnChildNotify(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pLResult);
BOOL CADEdit::OnChildNotify(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pLResult)
{
if(message == WM_COMMAND && HIWORD(wParam) == EN_CHANGE)
{
//If the flag is set, don't send the message to the parent window
if(m_bNoEnChange)
return TRUE;
}
return CEdit::OnChildNotify(message, wParam, lParam, pLResult);
}
Finally, when the control gains and loses focus, I wrapped the problematic SetWindowText with my flag
m_bNoEnChange = true;
SetWindowText(_T(""));
m_bNoEnChange = false;
This solution is the best in my case because I don't have to modify each dialog.
You could disable (EnableWindow(FALSE) or send WM_ENABLE with param FALSE) the control prior to sending WM_SETTEXT then enabling it afterwards. That should prevent the EN_CHANGE
There is probably some more elegant method :p
The below code uses a C++ 11 feature, but that can easily be changed.
HEADER
// CEditOptionalNotify.h
//
// CEdit derived class allowing the control's text value to be
// set without (optionally) causing EN_CHANGE processing.
//
#pragma once
class CEditOptionalNotify : public CEdit
{
//DECLARE_DYNAMIC(CEditOptionalNotify)
// Enable use of RUNTIME_CLASS macro and CObject::IsKindOf()
public:
CEditOptionalNotify();
virtual ~CEditOptionalNotify();
enum class PerformOnChangeProcessing { No, Yes };
void vSetText(const TCHAR* pText, PerformOnChangeProcessing e);
protected:
afx_msg BOOL bConsiderEnChangeAsHandled();
bool m_bChangeNotificationsEnabled;
DECLARE_MESSAGE_MAP()
};
IMPLEMENTATION
// EditOptionalNotify.cpp : implementation file
//
#include "stdafx.h"
#include <EditOptionalNotify.h>
//IMPLEMENT_DYNAMIC(CEditOptionalNotify, CEdit)
CEditOptionalNotify::CEditOptionalNotify() :
m_bChangeNotificationsEnabled(true)
{
}
CEditOptionalNotify::~CEditOptionalNotify()
{
}
BEGIN_MESSAGE_MAP(CEditOptionalNotify, CEdit)
ON_CONTROL_REFLECT_EX(EN_CHANGE, bConsiderEnChangeAsHandled)
END_MESSAGE_MAP()
BOOL CEditOptionalNotify::bConsiderEnChangeAsHandled()
{
return (m_bChangeNotificationsEnabled ? FALSE : TRUE);
}
void CEditOptionalNotify::vSetText(const TCHAR* pText, PerformOnChangeProcessing e)
{
bool bChangeNotificationsDesired = (PerformOnChangeProcessing::No == e ? false : true);
if (bChangeNotificationsDesired != m_bChangeNotificationsEnabled)
{
m_bChangeNotificationsEnabled = bChangeNotificationsDesired;
CEdit::SetWindowText(pText);
m_bChangeNotificationsEnabled = (bChangeNotificationsDesired ? false : true);
}
else
CEdit::SetWindowText(pText);
}
LRESULT CMainDlg::OnEnUpdateEditID(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
//using static variable
static bool isCodeChangeText = false;
if(isCodeChangeText)
return 0;
……//Deal Window Text
if(old == new)
return 0;
int nSel = m_editPID.GetSel();//record cursor pos
isCodeChangeText = true;
m_editID.SetWindowText(new);
m_editID.SetSel(nSel);
isCodeChangeText = false;
return 0;
}
In case somebody else finds this discussion...
As Steven wrote UpdateData does not cause an EN_CHANGE being sent.
Under the hood MFC calls AfxSetWindowText with which one can specify one hwnd.