CListCtrl do action based on selected row values - c++

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!!

Related

MFC CListCtrl - how to move item text to the left

I'm trying to implement single-column CListCtrl (or CMFCListCtrl, doesn't matter) in a way, that some rows might have checkboxes and some might not (I don't want to use neither CListBox, nor CCheckListBox, because in the future I'm planning to use multiple columns). I'm using LVS_EX_CHECKBOXES style, but that forces every item to have a checkbox. Then I manually delete the checkbox with a custom draw handler, but then I'm having trouble moving the item's text to the left side so that it takes place of the erased checkbox.
This is what my list control looks like:
But I need it to look like this (item2 is aligned to the left border, taking place of the erased checkbox):
I create my list control dynamically like this:
list->Create(WS_CHILD | WS_VISIBLE | WS_BORDER | LVS_REPORT | LVS_NOCOLUMNHEADER
rect, this, SOME_ID);
list->SetExtendedStyle(list->GetExtendedStyle() | LVS_EX_CHECKBOXES);
And my custom draw handler function looks like this:
I create my list control dynamically like this:
void MyCListCtrl::OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult)
{
*pResult = CDRF_DODEFAULT;
LPNMLVCUSTOMDRAW lpn = (LPNMLVCUSTOMDRAW)pNMHDR;
if (CDDS_PREPAINT == lpn->nmcd.dwDrawStage)
{
*pResult = CDRF_NOTIFYITEMDRAW; // get notification for every row (item)
}
else if (CDDS_ITEMPREPAINT == lpn->nmcd.dwDrawStage)
{
int row = lpn->nmcd.dwItemSpec;
if (row == 1) { // we are in the first row (item2)
lpn->nmcd.rc.left -= 20; // doesn't do anything
lpn->rcText.left -= 20; // doesn't do anything
// this erases checkbox from the current row
SetItemState(row, INDEXTOSTATEIMAGEMASK(0), LVIS_STATEIMAGEMASK);
}
}
}
Is there any way to achieve the desired result? Am I doing it the right way, or is it better to use CListCtrl without the LVS_EX_CHECKBOXES and draw the checkboxes myself where I want to? If so, how? Thanks in advance.
You could try indentation with minus value (I didn't tried though):
LV_ITEM lvItem;
lvItem.iItem = nYourItem;
lvItem.iSubItem = 0;
lvItem.mask = LVIF_INDENT;
lvItem.iIndent = -1;
VERIFY(SetItem(&lvItem));
If it would be an option for you, why don't you just leave it as it is?
The first version looks much more uncluttered, anyway.
The second version breaks guidance of the eyes.

issue with retrieving current list item text from CListCtrl

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
}
}

How to get multiline tooltip for a list control on a dialog box?

As text on my list box is very huge I am trying to get multiline a tooltip on the list control.
BOOL CTestDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
mylist.EnableToolTips(TRUE);
mylist.SetExtendedStyle(LVS_EX_INFOTIP | mylist.GetExtendedStyle());
mylist.InsertColumn(0, L"suri", LVCFMT_LEFT, 10000);
CString str1 = L"nonNegativeInteger GetVehicleOwnerHolderByRegNumAndDateResponse.GetVehicleOwnerHolderByRegNumAndDateResult[optional].GetVehicleOwnerHolderByRegNumAndDateResultType.VHOwnerHolderResponse.VHOwnerHolderResponseType.Body.VehicleCountries.VehicleCountriesType.VehicleCountry[1, unbound].VehicleCountryType.VehCountryReplies.VehCountryRepliesType.VehCountryReply[1, unbound].Messages[optional].Message[1, unbound].MessageType.MessageCode";
for (int i = 0; i < 20 ; i++) {
CString str2;
str2.Format(L"%d",i);
str2 = str2 + str1;
mylist.InsertItem(LVIF_TEXT | LVIF_PARAM, i, str2, 0, 0, 0, NULL);
}
return TRUE; // return TRUE unless you set the focus to a control
}
I am getting following output which is truncated text i.e complete text is missing.
How to get text on tooltip multiline?
EDIT: I used following also.
still same result.
CToolTipCtrl* pToolTip = AfxGetModuleThreadState()->m_pToolTip;
if (pToolTip)
pToolTip->SetMaxTipWidth(SHRT_MAX);
You can get multi-line tooltips using newlines-charecters with SetMaxTipWidth() set to a large value. And if calling SetMaxTipWidth() with a small value, then it will automatically break into multiple lines when meeting a space-character.
You need to subclass your tooltip/infotip in order to use it:
BEGIN_MESSAGE_MAP(CListCtrl_InfoTip, CListCtrl)
ON_NOTIFY_REFLECT_EX(LVN_GETINFOTIP, OnGetInfoTip)
ON_NOTIFY_EX(TTN_NEEDTEXTA, 0, OnToolNeedText)
ON_NOTIFY_EX(TTN_NEEDTEXTW, 0, OnToolNeedText)
END_MESSAGE_MAP()
void CListCtrl_InfoTip::PreSubclassWindow()
{
CListCtrl::PreSubclassWindow();
SetExtendedStyle(LVS_EX_INFOTIP | GetExtendedStyle());
}
BOOL CListCtrl_InfoTip::OnGetInfoTip(NMHDR* pNMHDR, LRESULT* pResult)
{
// Will only request tooltip for the label-column
NMLVGETINFOTIP* pInfoTip = (NMLVGETINFOTIP*)pNMHDR;
CString tooltip = GetToolTipText(pInfoTip->iItem, pInfoTip->iSubItem);
if (!tooltip.IsEmpty())
{
_tcsncpy(pInfoTip->pszText, static_cast<LPCTSTR>(tooltip), pInfoTip->cchTextMax);
}
return FALSE; // Let parent-dialog get chance
}
BOOL CListCtrl_InfoTip::OnToolNeedText(UINT id, NMHDR* pNMHDR, LRESULT* pResult)
{
...
// Break tooltip into multiple lines if it contains newlines (\r\n)
CToolTipCtrl* pToolTip = AfxGetModuleThreadState()->m_pToolTip;
if (pToolTip)
pToolTip->SetMaxTipWidth(SHRT_MAX);
...
}
There are two aspects to consider:
1. The size of the window
To activate multilines mode. This instruction is sufficient :
pToolTip->SetMaxTipWidth(SHRT_MAX);
2. Number of characters to display
For this second point it is necessary to be wary because the size of the field pszText is limited to 80 characters:
typedef struct tagNMTTDISPINFOA {
NMHDR hdr;
LPSTR lpszText;
char szText[80];
...
}
Therefore, even if you change SetMaxTipWidth you will not see any difference.
I suggest you to use the lpszText field which has no limit. Below is the code fragment that interests you:
pTTTW->lpszText = T2W (strTipText.GetBuffer (strTipText.GetLength ()));
Where strTipText is the CString that contains the message to show in the pop-up

MFC limit selected item in ClistCtrl

Hi
I use ClistCtrl that have 20 items and I want to limit selected item number.
for example only 10 item can be selected.
how i can do it?
thanks for your help herzl.
You would have to handle the LVN_ODSTATECHANGED notification message and count the number of selected item each time the LVIS_SELECTED state changes
Thanks
So I wrote this code. It should work. Just create an event handler for the list
void CDatenbankView::OnLvnItemchangedList1(NMHDR *pNMHDR, LRESULT *pResult)
{
int SelctedItems;
SelctedItems = 0;
int Index;
LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
for (Index = 0; Index < m_List.GetItemCount(); ) //Check every Item
{
if (m_List.GetItemState (Index, LVIS_SELECTED) == LVIS_SELECTED) //Checks if it is selected
{
if (SelctedItems > 10)
{
MessageBox (_T("Cant select more than 10 Items"));
for (Index = 0; Index < m_List.GetItemCount(); )
{
m_List.SetItemState (Index, ~LVIS_SELECTED, LVIS_SELECTED);
Index++;
}
break;
}
else
{
SelctedItems++;
}
}
Index++;
}
*pResult = 0;
}
m_List is my control variable for the CListCtrl
There is no built-in functionality for such a feature. You'd have to write your our code for that. Maybe you can find another way to do it, like having a source list and a "selection list". You copy/move items from the first to the second, but you do not allow the users to put more than 10 items into the destination list.

CTreeCtrl - getting an item position

Is there a way of getting the position (index) of an item in a CTreeCtrl?
I am interested in the index of a node at its particular level.
I was thinking to maintain the item positions within the item "data" field, but the problem is that my tree is sorted and I cannot predict the position an item will receive (well, only if I sort the items in advance which I would like to avoid).
I don't think you can. I assumed that maybe the control could be treated as an array (maybe it still can but I can't find a reference).
Anyways, there are no member functions (according to the MFC API) that give you access to that information
/// there is another way if you "Use Unicode Character Set" (visual studio)
/// Properties->General->Character Set
CPoint point;
GetCursorPos(&point);
m_Tree.ScreenToClient(&point);
UINT nHitFlags;
HTREEITEM hItem = m_Tree.HitTest(point, &nHitFlags);
int idx = m_Tree.MapItemToAccId(hItem);
Get the node handle and then iterate over the elem
Iterate over all the elements, while you count the elements, till you reach the right item?
int GetIndex(const CString & a_Cstr)
{
int idx = 0;
std::vector<CString>::const_iterator _begIt = m_RulesVec.begin();
std::vector<CString>::const_iterator _PosIt = find(m_RulesVec.begin(), m_RulesVec.end(), a_Cstr);
if (_PosIt == m_RulesVec.end()) {
return -1;
}
else {
while (_begIt != _PosIt) {
++idx;
++_begIt;
}
return idx;
}
}
/// it can(must) be done in this function
/// OnNMClickRulesTree(NMHDR *pNMHDR, LRESULT *pResult)
// Create vector like this
std::vector<CString> Vec{"first", "second", "third" };
// OnInit insert items to CtreeCtrl like this
m_Tree.InsertItem("first", hItem);
m_Tree.InsertItem("second", hItem);
m_Tree.InsertItem("third", hItem);
// then get cur selected item like this
CPoint point;
GetCursorPos(&point);
m_Tree.ScreenToClient(&point);
UINT nHitFlags;
HTREEITEM hItem = m_Tree.HitTest(point, &nHitFlags);
// get item text
CString Cstr = m_Tree.GetItemText(hKid);
int idx = GetIndex(Cstr);