issue with retrieving current list item text from CListCtrl - c++

I am trying to retrieve the selected list item from CListCtrl. The first item text is retrieved correct. Later on when I go select next, only the previous list item text is retrieved. Below is my event method that is triggered when I select an item from CListCtrl.
example scenario
List(m_RListCtrl) -> Item1, Item2, Item3
First time I click/select Item2. Item2 text displayed in m_EditBox.
Next I click Item3. Item2 is still displayed
Then I click Item1. Item3 is displayed in the editbox
then I click Item2. Item1 is displayed.
...
...
...
event code :
void CRTConfigDlg::OnLvnItemchangedRepoConfigList(NMHDR *pNMHDR, LRESULT *pResult)
{
CString itemText = L"";
itemText = m_RListCtrl.GetItemText(m_RListCtrl.GetSelectionMark(), 0);
m_EditBox.SetWindowText(itemText);
//UpdateWindow();
}
I have even tried following solution from Get Index of Item Text in MFC CListCtrl. But still the issue was same.
Can you help me to know , where I am going wrong?

You can also use the Itemchanged Notification but you have to keep in mind, that this event is triggered when an item is selected and deselected.
So you need to examin the items state.
void CAnyDialogClass::OnLvnItemchanged(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
// check if the items state changed to selected.
if ((pNMLV->uChanged & LVIF_STATE)!=0 &&
(pNMLV->uOldState & LVIS_SELECTED)==0 &&
(pNMLV->uNewState & LVIS_SELECTED)!=0)
{
// This item is selected now
...
Even more precise is to use LVIS_FOCUSED. The user may change the focus of an item by just holding the Ctrl key and using the cursor movement keys.

You need to iterate through selected items like this:
int nColumns = m_RListCtrl.GetHeaderCtrl()->GetItemCount();
POSITION pos = m_RListCtrl.GetFirstSelectedItemPosition();
while (pos)
{
int nItem = m_RListCtrl.GetNextSelectedItem(pos);
for(int i=0; i<nColumns; i++)
{
CString sItem = m_RListCtrl.GetItemText(nItem, i);
// TO DO: do stuff with item text here
}
}

Related

How to catch the click event of checkbox which is in a listctrl cell?

Insert checkbox column on a listctrl
I made a list with a checkbox column consulting the answer above.
Now my superior asks me to disable the OK button at first, enable it when at least there is one line is checked.
I looked up seems there is easy way to catch the click event when a checkbox is in a listctrl.
Add LVN_ITEMCHANGED to the message map. This will notify the dialog when changes are made to the list item:
BEGIN_MESSAGE_MAP(CMyDialog, CDialogEx)
ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST1, OnItemChanged)
...
END_MESSAGE_MAP()
Next, handle the message and respond each time a list item is checked or unchecked. Then you have to go through all the items in the list box and use CListCtrl::GetCheck. Example:
void CMyDialog::OnItemChanged(NMHDR* pNMHDR, LRESULT*)
{
NMLISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
if(pNMListView->uChanged & LVIF_STATE)
{
if(pNMListView->uNewState & LVIS_STATEIMAGEMASK && pNMListView->iItem >= 0)
{
BOOL checked_once = FALSE;
for(int i = 0; i < m_list.GetItemCount(); i++)
if(m_list.GetCheck(i))
checked_once = TRUE;
GetDlgItem(IDOK)->EnableWindow(checked_once);
}
}
}
You can add GetDlgItem(IDOK)->EnableWindow(FALSE); in OnInitDialog so that the OK button is initially disabled.
Side note, your dialog is using the old style look. See this link on using the modern style UI:
Upgraded MFC application still looks old

CListCtrl do action based on selected row values

I have to enable/disable buttons on a dialog based on values in a CListViewCtrl. Based on the selected row. I got this far:
NOTIFY_HANDLER(IDC_LIST, LVN_ITEMCHANGED, OnMyListChange)
// ....
LRESULT OnMyListChange(int, LPNMHDR pNMHDR, BOOL&)
{
NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
if ((pNMListView->uChanged & LVIF_STATE)
&& (pNMListView->uNewState & LVIS_SELECTED))
{
// enable/disable buttons based on row field value
}
return 0;
}
Lets say I have column1 column2 column3. I need to write a condition based on column2 value in the selected row. Multiple row selection is not a case. Thank you.
There is a method GetItemText. Notice it refers to:
nItem
The index of the item whose text is to be retrieved.
nSubItem
Specifies the subitem whose text is to be retrieved.
Think of them as row and column. Now, look at the NM_LISTVIEW structure in your handler:
typedef struct tagNMLISTVIEW {
NMHDR  hdr;
int    iItem;
int    iSubItem;
UINT   uNewState;
UINT   uOldState;
UINT   uChanged;
POINT  ptAction;
LPARAM lParam;
} NMLISTVIEW, *LPNMLISTVIEW;
It too has those properties:
int    iItem;
int    iSubItem;
So you should be able to get at the item text and perform what you wanted to do. Example:
// Get text in column 2 (it might 1 - can't remember if it is zero based indexing)
CString strValue = m_myList.GetItemText(pNMListView->iItem, 2);
if(strValue == "DoThis")
{
// ...
}
The above code is not tested!!

How to react to LVN_ITEMCHANGED notification only when user finishes selecting action on multipe selection CListCtrl?

I have a dialog with two list controls and one custom control with some graphic preview.
The first has a list of one kind of entities (1a, 1b, 1c,...) and the second has a list of another kind of entites (2a, 2b, 2c,...), both of them are multi-select.
I want to allow user to select a set of entities that will be highlighted on preview, but only those from a list where the last selection has been made.
For example:
select 1a,1b,1c -> highlight them on preview
select 2a,2b,2c -> unhighlight 1a,1b,1c and highlight 2a,2b,2c
If I process each LVN_ITEMCHANGED notification, the preview will be flickering, so I want to paint the preview when user finishes selection with a function like this:
void CPreviewPage::PaintSelection(HWND hWnd)
{
m_preview.DeselectAll();
SelectArray select;
if(hWnd == m_lstFirst.GetSafeHwnd())
{
for(int i = 0; i < m_lstFirst.GetItemCount(); i++)
{
if( m_lstFirst.GetItemState(i, LVNI_SELECTED) & LVNI_SELECTED)
{
Entity *pEnt = (Entity *) m_lstFirst.GetItemData(i);
select.append(pEnt);
}
}
}
else
if(hWnd == m_lstSecond.GetSafeHwnd())
{
for( int i = 0; i < m_lstSecond.GetItemCount(); i++ )
{
if( m_lstSecond.GetItemState(i, LVNI_SELECTED) & LVNI_SELECTED)
{
Entity *pEnt = (Entity *) m_lstSecond.GetItemData(i);
select.append(pEnt);
}
}
}
m_preview.PaintSelect(&select);
}
The problem is; when I have 2a selected, then hold shift and click 2c (to select 2a-2c), I get multiple LVN_ITEMCHANGED and can't detect which of them is last. If I could, then it would be possible to repaint the preview at the right moment, which is when the user finishes his/her selecting action.
I tried to call the repaint function when I get LVNI_FOCUSED:
void CPreviewPage::OnLstSecondSelChanged(NMHDR *pNMHDR, LRESULT *pResult)
{
NM_LISTVIEW* pNMListView = (NM_LISTVIEW*) pNMHDR;
if((pNMListView->uChanged & LVIF_STATE) && (pNMListView->uNewState & LVNI_FOCUSED) )
PaintSelection(pNMHDR->hwndFrom);
}
However, the LVNI_FOCUSED isn't guaranteed to be the last, and I don't want to add a button to call PaintSelection function.
So the question is: when is the right moment when I will have the state of all items set, according to user selection so I can call PaintSelection?

Hide the checkbox from a QListView item

I wave a QListView that is backed by a QStandardItemModel. Under certain circonstances, the QStandardItem are made checkable. A checkbox gets displayed besides the item's display. At some point, I want to remove hide the QStandardItem checkbox. I set its checkable state to false but it doesn't hide the checkbox (though it cannot be checked anymore).
The only way I have found of hiding the checkbox is to replace the item with a new one. This doesn't seem the proper way to preceed.
This is the code:
MyModel::MyModel(QObject *parent):QStandardItemModel(parent){}
void MyModel::createItem(int row, const QString &text)
{
setItem(row, new QStandardItem(text));
}
void MyModel::setCheckable(int row)
{
item(row)->setCheckState(Qt::Unchecked);
item(row)->setCheckable(true); // A checkbox appears besides the text
}
void MyModel::hideCheckBox(int row)
{
item(row)->setCheckState(Qt::Unchecked);
item(row)->setCheckable(false); // does not work
// I need to completely replace the item for the checkbox to disapear.
// This doesn't seem the proper way to proceed
setItem(row, new QStandardItem(item(row)->data(Qt::DisplayRole).toString()));
}
Is there better way to proceed?
When you call setCheckState or setCheckable, the qt will update the data of list item by adding or setting a Qt::CheckStateRole data. If the Qt::CheckStateRole data is existed, the check icon will be shown. So you need remove it from the data map of the list item.
Finally, the code of hideCheckBox should be:
void MyModel::hideCheckBox(int row)
{
// check the item pointer
QStandardItem* pitem = item(row);
if (pitem == NULL) return;
// find and delete the Qt::CheckStateRole data
QMap<int, QVariant> mdata = itemData(pitem->index());
if (mdata.remove(Qt::CheckStateRole))
{
setItemData(pitem->index(), mdata);
}
}
Hope it useful. :)
I think the presence of the check boxes in items defined by item flags, so that I would write the function in the following way:
void MyModel::hideCheckBox(int row)
{
// Does not set the Qt::ItemIsUserCheckable flag.
item(row)->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
}

When deleting item in Combobox the secong one pop-up MFC

I have combo box and delete button. I want to make next combo box item pop-up when delete button pressed and when last item deleted clean combo box selected item.
I tried several methods with indexes but even one wont help me.
there is my code:
if(IDYES == MessageBox(L"Delete save?",L"Delete", MB_YESNO|MB_ICONQUESTION)){
CString pFileName = L"Save\\"+str+".dat";
CFile::Remove(pFileName);
CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_SAVE);
pComboBox->ResetContent();
}
How I can to make next combo box item pop-up when delete button pressed and when last item deleted clean combo box selected item?
I found a solution:
void CL2HamsterDlg::OnBnClickedButtonDelete(){
if(Validate()){
if(IDYES == MessageBox(L"Delete save?",L"Delete", MB_YESNO|MB_ICONQUESTION)){
CString pFileName = L"Save\\"+str+".dat";
CFile::Remove(pFileName);
CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_SAVE);
lookforfile();
int nIndex = pComboBox->GetCurSel();
if (nIndex == CB_ERR)
pComboBox->SetCurSel(0);
else{
pComboBox->SetEditSel(0, -1);
pComboBox->Clear();
}
}
LoadSave(false);
}else
AfxMessageBox(L"Please select or write correct name!");
}
the function look for file refreshes index
void CL2HamsterDlg::lookforfile()
{
Value.GetWindowText(str);
CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_SAVE);
pComboBox->ResetContent();
GetCurrentDirectory(MAX_PATH,curWorkingDir);
_tcscat_s(curWorkingDir, MAX_PATH, _T("\\Save\\*.dat"));
BOOL bWorking = finder.FindFile(curWorkingDir);
while (bWorking){
bWorking = finder.FindNextFile();
if (!finder.IsDots())
pComboBox->AddString(finder.GetFileTitle());
}
GetDlgItem(IDC_COMBO_SAVE)->SetWindowText(str);
}
so, in this case you do not need to use ResetContent(). Provided you already know the currently selected Item in the combobox (I think somewhere along the track you would have used the line int iSel = pComboBox->GetCurSel();) you could use this code IN PLACE OF YOUR pComboBox->ResetContent();:
pComboBox->DeleteString(iSel);
if(iSel < pComboBox->GetCount())
pComboBox->SetCurSel(iSel);
else if(iSel > 0)
pComboBox->SetCurSel(iSel-1);
However, I think this will not be necessary. I think the item will move by itself. So, forget about the code above, just use this:
pComboBox->DeleteString(pComboBox->GetCurSel())