Behavior of selectionChanged() on removing first row - c++

Please run the following code (I am using Qt 5.9):
QTableWidget* tableWidget = new QTableWidget(2, 2, nullptr);
tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
tableWidget->setSelectionMode(QAbstractItemView::SingleSelection);
connect(tableWidget->selectionModel(), &QItemSelectionModel::selectionChanged,
[&](const QItemSelection& selected, const QItemSelection& deselected)
{ qDebug() << "selected =" << selected << endl << "deselected =" << deselected; });
tableWidget->show();
QTimer::singleShot(10000, [=](){ tableWidget->removeRow(0); });
Within 10 seconds, select the first of two rows. You will see debug ouput. It will show you that row 0 was selected by your click.
Then, after 10s, row 0 is removed automatically. Debug output now shows that row 1 is selected and row 0 is deselected.
The latter doesn't make any sense to me. When removing row 0 I would expect the "new" row 0 being selected afterwards. Also the visually selected row still is row 0 and row 1 simply doesn't exist anymore.
This also happens with a custom model and generic view and makes my application crash by pointing to a row that does not exist.
Is this desired behavior? Where is my misunderstanding?

It makes perfectly sense to change the selected row before deleting it. Doing the opposite may lead to reading dangling data, for example, if the UI is refreshed when the model has changed but the view holds outdated indices.
Think about removing row 0 twice: the second time is very obvious that the selection must be changed (deselected in this case) before removing the last row in the table to avoid having an invalid index as the selected row.
You can use the following modified example to see when the model is actually updated.
auto tableWidget = new QTableWidget(2, 2, nullptr);
tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
tableWidget->setSelectionMode(QAbstractItemView::SingleSelection);
connect(tableWidget->selectionModel(), &QItemSelectionModel::selectionChanged,
[&](const QItemSelection& selected, const QItemSelection& deselected)
{ qDebug() << "selected =" << selected << endl << "deselected =" << deselected; });
connect(tableWidget->model(), &QAbstractItemModel::rowsRemoved, [&](const QModelIndex &, int first, int last)
{ qDebug() << "first row removed =" << first << endl << "last row removed =" << last; });
tableWidget->show();
QTimer::singleShot(10000, [=](){ tableWidget->removeRow(0); });
QTimer::singleShot(15000, [=](){ tableWidget->removeRow(0); }); // remove twice
Workaround
The main problem is that, as you pointed out in the comments, you cannot rely on the signal information: those QModelIndex may or not be valid if a removal was performed. You can keep track of all changes but that would be exhausting.
Instead, you can try deferring the selection signal, so when it is handled the model has been updated and you can trust the information from the selection model. The trick is to use a timer: the function handling the timeout event will be executed in the next iteration of the events loop (even if the timeout time is 0), while the model and the widget are updated in the current iteration:
connect(tableWidget->selectionModel(), &QItemSelectionModel::selectionChanged,
[&](const QItemSelection&, const QItemSelection&) {
QTimer::singleShot(0, [&]() {
qDebug() << "selected =" << tableWidget->selectionModel()->selectedIndexes() << endl;
});
});

Related

Qt program crashes after deleting last object from a vector

I have an application I'm making for my work. It basically takes in employees and their availability and makes a schedule. With that aside, I have a page called "edit employee" in which I can edit information from any employee. I have a "delete" button in which when you press it, you get the current row from the qlistwidget and it deletes that element. It all works, until I only have one element left in the vector.
I have tried the different forms of deleting from a vector. I tried .clear(), .erase(vec.begin(), vec.end()), .erase(vec.begin() + position), etc.
I also had a for loop displaying the contents of the vector, it showed only 1 element in the vector, which was correct. The vector did not have empty spaces or anything that you could consider "corrupted".
void MainWindow::deleteEmployee(int position)
{
// employeeList.erase(employeeList.begin() + position);
if(employeeList.size() == 1)
{
// fails only when trying to delete from last index
employeeList.clear();
}
else
{
employeeList.erase(employeeList.begin() + position);
}
}
void MainWindow::on_editEmployeeDelete_pushButton_clicked()
{
QMessageBox::StandardButton reply;
reply =
QMessageBox::question(this, "Delete Employee",
"Are you sure you want to delete " +
employeeList.at(ui->editEmployee_listWidget->currentRow()).getFirstName() +
" " +
employeeList.at(ui->editEmployee_listWidget->currentRow()).getLastName() +
"?",
QMessageBox::Yes|QMessageBox::No);
if (reply == QMessageBox::Yes)
{
QMessageBox::information(nullptr, "Success",
employeeList.at(ui->editEmployee_listWidget->currentRow()).getFirstName() +
" " +
employeeList.at(ui->editEmployee_listWidget->currentRow()).getLastName() +
" has been deleted successfully.");
deleteEmployee(ui->editEmployee_listWidget->currentRow());
// clears the qlistwidget once again to add all employees left in the vector
ui->editEmployee_listWidget->blockSignals(true);
ui->editEmployee_listWidget->clear();
ui->editEmployee_listWidget->blockSignals(false);
// adds all the employees names to the qlistwidget
for(unsigned long long i = 0; i < employeeList.size(); i++)
{
ui->editEmployee_listWidget->addItem(employeeList.at(i).getLastName() +
", " +
employeeList.at(i).getFirstName());
}
}
}
employeeList is a vector of type employee, which is a class I created. It holds their name, ranking, etc (all QStrings and ints).
When you click on the qlistwidget, the delete button becomes enabled. When you click on the delete button, the selected employee will be deleted. This works perfectly fine until you have 1 person left, in which it crashes, stating "Program has unexpectedly finished". I have tried many different forms of deleting. None worked.
Any help is greatly appreciated, thank you!

Why my delete button didn't delete all widgets on QHBoxLayout

I have problem when deleting my widgets on QHBoxLayout.
i use QList for listing my layout, because i add layout at runtime.
this is my QList
QList<QHBoxLayout*> hBoxLayoutParent;
this my code when i add my widgets
hBoxLayoutParent.push_back(createNewHBoxParent());
hBoxLayoutParent.last()->addWidget(label);
hBoxLayoutParent.last()->addWidget(cmbBox);
hBoxLayoutParent.last()->addWidget(cmbJurusan);
hBoxLayoutParent.last()->addWidget(listButton.last());
ui->formLayout_2->addLayout(hBoxLayoutParent.last());
and this how i delete them
for(int i = 0; i < hBoxLayoutParent[index]->count(); i++)
{
delete hBoxLayoutParent[index]->takeAt(0)->widget();
qDebug() << "Widget Number: " << hBoxLayoutParent[index]->count();
}
hBoxLayoutParent.removeAt(index);
when i click on delete button, not all been deleted.
cmbJurusan still exists.
The problem is that your for loop isn't counting in quite the way you think it is. You have...
for (int i = 0; i < hBoxLayoutParent[index]->count(); i++)
So you're incrementing i on each iteration. But...
delete hBoxLayoutParent[index]->takeAt(0)->widget();
will remove an item from hBoxLayoutParent[index]. So you're modifying the QHBoxLayout over whose items you're iterating -- each iteration increases i by one but also decreases the number of items in the layout by one.
Instead, try...
while (!hBoxLayoutParent[index]->isEmpty()) {
delete hBoxLayoutParent[index]->takeAt(0)->widget();
qDebug() << "Widget Number: " << hBoxLayoutParent[index]->count();
}
Note also that if this code is run within the context of the event loop then you might want to use QObject::deleteLater rather than delete.

Pointer and QVector issue

I want to define functions which will delete self-defined type object and the index from the QVector.
Originally the source was as follows:
Point PointCollection::RemovePoint(int index)
{
Point removedPoint = new Point(this[index].Id, this[index].X, this[index].Y);
this->remove(index);
updateCentroid();
return (removedPoint);
}
Point PointCollection::RemovePoint(Point p)
{
Point removedPoint = new Point(p.GetId(), p.GetX(), p.GetY());
this.remove(p);
updateCentroid();
return (removedPoint);
}
which was not working as I think because of new. Then I modified the source to the following:
Point PointCollection::deletePoint(int Index)
{
Point deleted = Point(this[Index].Id, this[Index].X, this[Index].Y);
this->remove(Index);
updateCentroid();
return(deleted);
}
Point PointCollection::deletePoint(Point point)
{
Point deleted = Point(point.GetId(), point.GetX(), point.GetY());
this->remove(point);
updateCentroid();
return(deleted);
}
Now Point PointCollection::deletePoint(int Index) compiles without any error, but this->remove(point); in Point PointCollection::deletePoint(Point point) functioned is compiled with following error:
error: no matching function for call to 'PointCollection::remove(Point&)'
Q1: Did I do correct that removed new?
Q2: How to fix the error I am having.
Your approach seems to be overall wrong. First of all focus on what you need:
performance and memory efficiency or...
fast inserts and deletes
QVector is of the former kind. If you do deletes and inserts that are not in the back you will likely get poor performance. Because the entire vector has to be reallocated every time you do a change.
If you need to insert and delete often go for a linked list, for example QLinkedList.
Qt already provides containers, implementing your own doesn't offer much benefit, it is not likely that you will produce a better container than a bunch of professionals working on this framework for 20 years.
Here is a simple snippet how to insert and delete points in vector and linked list. You can use this methods to implement your own wrapper class if you want:
QVector<QPoint> myPointVector;
QLinkedList<QPoint> myPointList;
// push back some data
myPointVector << QPoint(1, 1) << QPoint(2, 2) << QPoint(3, 3) << QPoint(4, 4);
myPointList << QPoint(1, 1) << QPoint(2, 2) << QPoint(3, 3) << QPoint(4, 4);
foreach (QPoint p, myPointVector) qDebug() << p;
foreach (QPoint p, myPointList) qDebug() << p;
qDebug() << endl;
auto i1 = myPointVector.indexOf(QPoint(2, 2));
auto i2 = qFind(myPointList.begin(), myPointList.end(), QPoint(2,2));
myPointVector.insert(i1, QPoint(5,5)); // or existing point object / reference
auto i3 = myPointList.insert(i2, QPoint(5,5));
foreach (QPoint p, myPointVector) qDebug() << p;
foreach (QPoint p, myPointList) qDebug() << p;
qDebug() << endl;
QPoint deletedFromVector = myPointVector[i1]; // use those to return before deleting
QPoint deletedFromList = *i3; // note you don't need to construct just assign
myPointVector.remove(i1);
myPointList.erase(i3);
foreach (QPoint p, myPointVector) qDebug() << p;
foreach (QPoint p, myPointList) qDebug() << p;
qDebug() << endl;
As you see, initially both containers contain points 1 2 3 4, then point 5 is inserted in the place of 2, then removed again. The vector uses an integer index for the operations, the list uses an iterator. That is why when 5 is inserted, I get its "index" because unlike the vector it won't push back the rest, so if i2 is removed it will not remove point 5 which was inserted in place of point 2, but point 2 to which it still refers to.
Also, if you want to insert in the list in a given index, you can just use the begin iterator + index to "forward" the iterator the appropriate number of positions.
Hopefully that made sense. Naturally you can use you point class in the place of QPoint.

Why do i get a repeated QStringList?

I am writing a Qt application that deals with scheduling employees. The header data for the main QTableView is a pointer to a QStringList. The headerData() function works correctly, but when i add a string to the list elsewhere, it appends the entire list including the new string to the end of the list.
For example, if i have the list 1,2,3 and i append 4 to it, then iterating through the list based on the pointer gives the result 1,2,3,1,2,3,4. I don't know a better way than using pointers to have multiple classes access the same data. Does anyone know how to fix the repeating list?
Example Code
//function to save a new employee in memory
bool EmployeeViewDialog::saveEmployee(Employee *e)
{
employees->insert(e->name,e);
*employeeNames << e->name;
for (int i = 0; i < employeeNames->length(); i++) {
qDebug() << employeeNames->at(i);
}
QList<QStandardItem*> items;
items << new QStandardItem(e->name);
items << new QStandardItem(e->id);
items << new QStandardItem(e->phone);
items << new QStandardItem(e->email);
model->appendRow(items);
return true;
}
The append was just changed to the << method. It is the employeeNames << e->name; line.
The for loop iterates through the list and does the same thing as what happens in the external class.

Qt 4.6 - C++ - QTreeWidgetItem Iterator

I have a QTreeWidget with items in it. The first column contains a unique number. This is set via item->setData(0, 0, unique_number);. The second column contains a checkbox, set via item->setCheckState(1, Qt::Unchecked);. The user can select the items (s)he would like to work with and presses a button. The slot for this button will run a loop on the checked items. In the Qt documentation an example is given. You use the QTreeWidgetItemIterator.
QTreeWidgetItemIterator it(treeWidget);
while (*it) {
if ((*it)->text(0) == itemText)
(*it)->setSelected(true);
++it;
}
It also says that you can specify an argument in the constructor to only iterate over the checked items. The flag is : QTreeWidgetItemIterator::Checked. My slightly customized loop is as follows:
QTreeWidgetItemIterator it(treeWidget, QTreeWidgetItemIterator::Checked);
while (*it)
{
QVariant w;
w = (*it)->data(0, 0);
std::cout << "Selected item # " << w.toInt() << "\n";
it++;
}
This code will compile fine but won't work when you actually run the program. It doesn't print any values.
Any tips? Thanks!
The one caveat missing from the Qt documentation is that the QTreeWidgetItemIterator::Checked flag only tests the check state for column 0 in each of your QTreeWidgetItems. Using column 0 for your checkbox and column 1 for your unique number should cause the loop to print values.
Thanks Roneel!
Another way to do this is like this (I just figured it out).
QTreeWidgetItemIterator it(ui->treeWidget);
while (*it)
{
if ((*it)->checkState(1) == 2)
{
QVariant w;
w = (*it)->data(4, 0);
std::cout << "Selected item # " << w.toString().toStdString() << "\n";
}
++it;
}
checkState takes a int column argument and the == 2 only allows checked items to be processed.
Just one thing... I think it's not a good habit to compare enum to int, as enum "int value" may change... Compare to Qt::Checked instead... It's just "for sure" and much more clean for reading