Unable to get data from list control - mfc

I am working with list control in MFC. I have written code to insert elements into list control present in a dialog box as follows:
int nIndex = 0;
for (int count = 0; count < arrResults.GetSize(); count++)
{
nIndex = m_cListCtrl.InsertItem(count, _T(arrResults[count].ElementAt(0)));
m_cListCtrl.SetItemText(nIndex, 1, _T(arrResults[count].ElementAt(1)));
}
However, when I try to retrieve data from m_cListCtrl, it always returns blank. Also, the GetItemCount() method also returns 0 items. Any suggestions are appreciated.
Following is the data retrieve code that I have written:
arrResults.SetSize(1);
arrResults[0].Add("Header1");
arrResults[0].Add("Header2");
TestDialog testDlg;
testDlg.FillControlList(arrResults); // This function has above code to add data to control list
EXPECT_EQ("Header1", queryDlg.m_cListCtrl.GetItemText(0, 0));
EXPECT_EQ("Header2", queryDlg.m_cListCtrl.GetItemText(0, 1));
The GetItemText function is returning blank string.

When you call FillControlList(), you are using testDlg object. But when you call GetItemText() you're using queryDlg object. You have inserted the items in one dialog and you're trying to get data from different object. Please check with that.

Related

Move all selected items below specific one with LVM_SORTITEMSEX

INTRODUCTION:
In order to keep this post as brief as possible, let me just say that I need to move all selected items in the listview below certain (unselected) item.
Browsing through listview documentation I came upon LVM_SORTITEMSEX message.
QUESTION:
How to use the above message to achieve my goal.
MY EFFORTS TO SOLVE THIS:
So far, by using this message, I was able to move all selected items to the bottom of the list -> listview is sorted in such a way that unselected items precede selected ones.
I just can not figure out how to implement moving the selected items below certain item.
Below are the images of what I get, and what I want to achieve:
The left image shows what I get when I use the code submitted below, while the right one shows the result I aim for.
Here are the relevant code snippets:
// compare function -> see the documentation
int CALLBACK CompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM hwnd)
{
LVITEM lvi1 = { 0 }, lvi2 = { 0 };
// get selected state of the first item
lvi1.iItem = (int)lParam1;
lvi1.iSubItem = 0;
lvi1.mask = LVIF_STATE;
lvi1.stateMask = LVIS_SELECTED;
ListView_GetItem((HWND)hwnd, &lvi1);
// get selected state of the second item
lvi2.iItem = (int)lParam2;
lvi2.iSubItem = 0;
lvi2.mask = LVIF_STATE;
lvi2.stateMask = LVIS_SELECTED;
ListView_GetItem((HWND)hwnd, &lvi2);
// if first is selected and second is not selected, swap them
if ((lvi1.state & LVIS_SELECTED) && (0 == (lvi2.state & LVIS_SELECTED)))
return 1;
return 0;
}
// somewhere in code, on button click for example
ListView_SortItemsEx(hwndListView, CompareFunc, hwndListView);
I have passed listview handle as third parameter to ListView_SortItemsEx so I can use ListView_GetItem in CompareFunc.
If I understand this correctly, you want to rearrange items with drag and drop, and you want the sort function to do it. This could get complicated if it is to be done inside the sort proc. Another solution is to find the arrangement first.
Use vector to store items before "redMark"
Add selected items which appear after "redMark"
Add unselected items which appear after "redMark"
Save this order to LVITEM::lParam
Call ListView_SortItems (not ListView_SortItemsEx)
The only problem is that maybe lParam was used for other reasons. We have to save lParam, and then restore it after sort is done.
Also it's better if ListView has LVS_SHOWSELALWAYS.
Note, this method moves items before the "redMark". In your example you should set redMark = 3 to move selection before "Item 60"
int CALLBACK CompareFunc(LPARAM lp1, LPARAM lp2, LPARAM)
{
return lp1 > lp2;
}
void sort()
{
int redMark = 3;
int count = ListView_GetItemCount(hwndListView);
std::vector<int> order;
std::vector<LPARAM> saveLParam(count);
//add everything before redMark
for (int i = 0; i < redMark; i++)
order.push_back(i);
//add highlighted items
for (int i = redMark; i < count; i++)
if (ListView_GetItemState(hwndListView, i, LVIS_SELECTED))
order.push_back(i);
//add the rest
for (int i = redMark; i < count; i++)
if (!ListView_GetItemState(hwndListView, i, LVIS_SELECTED))
order.push_back(i);
if (order.size() != count)
{
assert(0);
return;
}
//set lParam
for (int i = 0; i < count; i++)
{
LVITEM item = { 0 };
item.iItem = order[i];
item.mask = LVIF_PARAM;
//save old LParam value
ListView_GetItem(hwndListView, &item);
saveLParam[i] = item.lParam;
//set new lParam
item.lParam = i;
ListView_SetItem(hwndListView, &item);
}
ListView_SortItems(hwndListView, CompareFunc, 0);
//restore old lParam
for (int i = 0; i < count; i++)
{
LVITEM item = { 0 };
item.iItem = order[i];
item.mask = LVIF_PARAM;
item.lParam = saveLParam[order[i]];
ListView_SetItem(hwndListView, &item);
}
::SetFocus(hwndListView);
}
Introduction:
For LVM_SORTITEMSEX, all items must have unique lParam's
To pass multiple parameters to sorting callback, make a struct and pass pointer to it.
As soon as you start sorting, the original order of items is lost, and you can no longer refer to it if not saved somewhere.
If you can repeat the operation of moving sorted items, then carefully crafted lParam's are also not enough to know the original order of items.
You can mess with lParam's instead of preparing desired order of items separately, but this is rather dirty and prone to errors.
Generic solution
Ensure all items have unique lParam's
Before calling LVM_SORTITEMSEX, prepare a vector of lParam's in desired order:
Enumerate items in list from beginning up to the "red mark" item, add their lParam's into vector.
Enumerate items in list from "red mark" item to the list end, if item is selected, add its lParam into vector.
Enumerate items in list from "red mark" item to the list end, if item is NOT selected, add its lParam into vector.
Now you have the order of lParam's: start of the list, then selected items retaining their original order, then unselected items retaining their original order.
Pass that vector to sorting callback
In it, lookup positions of two lParam's in vector and make answer based on this positions. For example, if you find that first position is less then second, you return a negative number. The typical approach is return (firstPos - secondPos), that will handle all relative orders of firstPos and secondPos in one line of code.
That will have your sorting calllback apply prepared order of items to list.

C++ CListCtrl - GetItemData() returning wrong value?

I have a C++ application with an SQL backend, and have been storing the row id of any retrieved columns (an integer, primary key bigint in the database) with SetItemData() on list control rows as required. This is then retrieved with GetItemData() if that ID needs to be queried.
I am now getting a weird problem in that, in this one scenario, GetItemData() is returning a random 7-digit number instead of the stored ID. When I add the row I use the following code:
CListCtrl& lc = GetListCtrl();
for (int i = 0; i < vInsertItems.size(); i++) {
int j = lc.InsertItem(i,i.strName);
DWORD dwdRowID = (DWORD)cammms,nRowID;
lc.SetItemData(j,dwdRowID);
}
To retrieve and check the value I can do the following (where I have determined that nCurrentlySelectedIndex is correct):
CListCtrl& lc = GetListCtrl();
int msgID = lc.GetItemData(nCurrentlySelectedIndex);
CString debugInt; debugInt.Format(_T("debugInt = %d"),msgID);
AfxMessageBox(debugInt);
What is bizarre, is that if I run the second batch of code directly after the first, it is all fine. But if I run it in a separate function, msgID becomes set to a set of random 7 digits, different every time.
Does anyone have any idea what could be causing this?

How to delete all rows from QTableWidget

I am trying to delete all rows from a QTableWidget . Here is what I tried.
for ( int i = 0; i < mTestTable->rowCount(); ++i )
{
mTestTable->removeRow(i);
}
I had two rows in my table. But this just deleted a single row. A reason could be that I did not create the the table with a fixed table size. The Qt Documentation for rowCount() says,
This property holds the number of rows in the table.
By default, for a table constructed without row and column counts,
this property contains a value of 0.
So if that is the case, what is the best way to remove all rows from table?
Just set the row count to 0 with:
mTestTable->setRowCount(0);
it will delete the QTableWidgetItems automatically, by calling removeRows as you can see in QTableWidget internal model code:
void QTableModel::setRowCount(int rows)
{
int rc = verticalHeaderItems.count();
if (rows < 0 || rc == rows)
return;
if (rc < rows)
insertRows(qMax(rc, 0), rows - rc);
else
removeRows(qMax(rows, 0), rc - rows);
}
I don't know QTableWidget but your code seems to have a logic flaw. You are forgetting that as you go round the loop you are decreasing the value of mTestTable->rowCount(). After you have removed one row, i will be one and mTestTable->rowCount() will also be one, so your loop stops.
I would do it like this
while (mTestTable->rowCount() > 0)
{
mTestTable->removeRow(0);
}
AFAIK setRowCount(0) removes nothing. Objects are still there, but no more visible.
yourtable->model()->removeRows(0, yourtable->rowCount());
QTableWidget test;
test.clear();
test.setRowCount( 0);
The simple way to delete rows is to set the row count to zero. This uses removeRows() internally.
table->setRowCount(0);
You could also clear the content and then remove all rows.
table->clearContents();
table->model()->removeRows(0, table->rowCount());
Both snippets leave the headers untouched!
If you need to get rid of headers, too, you could switch from clearContents() to clear().
In order to prevent an app crash, disconnect all signals from the QTableView.
// Deselects all selected items
ui->tableWidget->clearSelection();
// Disconnect all signals from table widget ! important !
ui->tableWidget->disconnect();
// Remove all items
ui->tableWidget->clearContents();
// Set row count to 0 (remove rows)
ui->tableWidget->setRowCount(0);
Look this post : http://forum.qt.io/topic/1715/qtablewidget-how-to-delete-a-row
QList<QTableWidgetItem*> items = table.findItems(.....);
QMap<int, int> rowsMap;
for(int i = 0; i < items.count(); i++{
rowsMap[items.at(i).row()] = -1; //garbage value
}
QList<int> rowsList = rowsMap.uniqueKeys();
qSort(rowsList);
//Now go through your table and delete rows in descending order as content would shift up and hence cannot do it in ascending order with ease.
for(int i = rowList.count() - 1; i >= 0; i--){
table.removeRow(rowList.at(i));
}
You can just add empty item model (QStandardItemModel) to your QTableView (myTableView):
itemModel = new QStandardItemModel;
ui->myTableView->setModel(itemModel);
In python you can just set the rowCount to zero and that'll work!
tableWidget.setRowCount(0)
This code is tested with PySide6. Hope it will work for PyQt5 and PyQt6 too.
Your code does not delete last row.
Try this one.
int totalRow = mTestTable->rowCount();
for ( int i = 0; i < totalRow ; ++i )
{
mTestTable->removeRow(i);
}
In your code, on the first time, rowCount() have value 2 and value of the i is 0, so its delete 1st row,
But on the second time value of i incremented with 1, but rowCount() return the updated row count which is now 1, so, it does not delete the last row.
Hope now you ll be clear.
Removes all items not in the headers from the view. This will also remove all selections. The table dimensions stay the same.
void QTableWidget::clearContents()
Removes all items in the view. This will also remove all selections and headers.
void QTableWidget::clear()
This works for me:
for i in reversed(range(self.tableWidget.rowCount())):
self.tableWidget.removeRow(i)

How to move an item up and down in a wxListCtrl (wxwidgets)

This should be pretty easy but I'm having a heck of a time doing it. Basically I want to move a row in my wxListCtrl up or down. I posted this to wxwidgets forum and got the following code.
m_list->Freeze();
wxListItem item;
item.SetId(item_id); // the one which is selected
m_list->GetItem(item); // Retrieve the item
m_list->DeleteItem(item_id); // Remove it
item.SetId(item_id - 1); // Move it up
m_list->SetItem(item); // Apply it's new pos in the list
m_list->Thaw();
which doesn't work. The element is deleted but not moved up (I guess the setitem line is not working). Then I thought to just switch the text and the image but I can't even get the text from the row reliably. I have
int index = m_right->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
wxString label = m_right->GetItemText(index);
if(index == 0)
return;
wxListItem item;
item.SetId(index);
bool success = m_right->GetItem(item);
wxString text = item.GetText();
but text is blank even though there is text and the index is correct. So, I'm stuck not even being able to do the most basic task. Anybody know how to do this? The code runs in a button callback (the user presses a little up arrow and my code executes to try to move it). I'm using 2.9.1 on windows.
I made it work like this with wxWidgets 2.9.3 :
void FileSelectionPanel::OnMoveUp( wxCommandEvent& WXUNUSED(evt) )
{
int idx = _listCtrl->GetNextItem( -1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED );
if( idx == 0) idx = _listCtrl->GetNextItem( 0, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED );
_listCtrl->Freeze();
while( idx > -1 ) {
wxListItem item;
item.SetId(idx); _listCtrl->GetItem(item);
item.SetId(idx-1); _listCtrl->InsertItem(item);
_listCtrl->SetItemData( idx-1, _listCtrl->GetItemData( idx+1 ));
for( int i = 0; i < _listCtrl->GetColumnCount(); i++ ) {
_listCtrl->SetItem( idx-1, i, _listCtrl->GetItemText( idx+1, i ));
}
_listCtrl->DeleteItem( idx + 1 );
idx = _listCtrl->GetNextItem( idx-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED );
}
_listCtrl->Thaw();
}
The thing I noticed it that wxListItem is more of a convenience struct, for storing state of the view and help pass values into the wxListCtrl "nicely". It is in no way bound to what is actually inside of the wxListCtrl.
Hope this still helps anyone !
Even there is already an answer that is checked. I have the same problem here, but my list is unordered. By looking into wxWidgets' code I found out there is another important information inside the wxListItem object - the mask. I got my reordering to work correctly by setting the mask value to -1, which means that all data shall be copied. This includes the item text as well as other information, like the item data (which was important in my case).
wxListItem item;
item.SetId(item_id); // set needed id
item.SetMask(-1); // set needed data
m_list->GetItem(item); // actually retrieve the item
m_list->DeleteItem(item_id); // remove old copy
item.SetId(item_id - 1); // move item up
m_list->InsertItem(item); // insert copy of item
I also had to use "InsertItem" instead of "SetItem". Otherwise, there was no new item inserted, but an existing one overwritten (see also tomcat31's answer).
Is the list ordered? if it is auto ordering it may be ignoring the order you are trying to apply.
From recollection the internal order was not necessarily sequential, you might have to get the index of the previous item and go one before it.

CStringList in MFC

I was coding with 2 CStringList objects. Each has its own data, for eg one has name and other the phoneno, and both are in sync, i.e, if there is a phoneno there is a name and viceversa.
Now, i have 2 combobox in which i show the names and the respective phonenos. The name combobox is sorted, hence the sync between the two goes for a toss. hence for sorting i did the following:
int aComboElementNo = myNameComboBox.GetCount();
if( aComboElementNo >= 1 )
{
for( int aIndex = 0; aIndex < aComboElementNo; aIndex++ )
{
CString aTempStr;
// Getting the string in the combobox
myNameComboBox.GetLBText( aIndex, aTempStr );
// Getting the position where the name is present in the list
POSITION aPos = aNameList.Find( aTempStr );
// setting the appropriate phoneno in the 2nd combobox
myPhoneComboBox.AddString( aPhoneList.GetAt( aPos ) );
}
}
When i executed this i got the names in the myPhoneComboBox rather than the phonenos.
Now i have 2 qns:
how come i get the name present in namelist when i am accessing the phonelist? isn't it a breach, as i am able to access some other variables data using some other variable.
how to sort the 2nd list.
I Hope U are using CStringArray and not CStringList.
You need to use FindIndex rather than Find since Find will return OBJECT Pos rather than the Index count....
and to get the element with array use simply [] the operator.
If You still want to use CStringList then through Iterator Find the Index Count of the first match of string in one List and Use FindIndex of that IndexCount to get the postition object for the secondlist to use GetAt to the second list.
Why do you have 2 separate lists? Why not one CTypedPtrArray of structures holding both the name and the phone nb?
That is a crazzy, forgive me, stupid way to find names. It assumes the names are unique. God help me, I've had to deal with these things, name fields should never be viewed as unique, its bloody dangerious. Just ask my dad Baash05 Sr.
I'd assume there's an ID or some data set as the app adds to the combo box. Please use that in your map. My guess is the programmer set the data to either the ID of the name, or a pointer to the object that contained the name. (person object/business object/student object...).
If the code that adds the names didn't add a way to tell the difference between George Forman and any of his kids, then make an argument to the boss, that it's implementation should be changed, because by god it should be!
int aComboElementNo = myNameComboBox.GetCount();
for( int aIndex = 0; aIndex < aComboElementNo; aIndex++ )
{
int nameLocal = myNameComboBox.GetItemData( aIndex);
myPhoneComboBox.InsertString(aIndex, aPhoneList[namelocal] );
}