Need some help finding why my QListView will not refresh.
I'm using QListView with a QSqlTableModel. I implement the model in the following function. I call this function from the class' constructor.
void myclass::refresh()
{
model_path = new QSqlTableModel(this);
model_path->setTable("mytable");
model_path->setEditStrategy(QSqlTableModel::OnManualSubmit);
model_path->select();
ui->listView_path->setModel(model_path);
ui->listView_path->setModelColumn(1);
}
The following function will add a row and the qlistView refreshes without any issue.
void myclass::on_pushButton_add_clicked()
{
QSqlRecord rec (model_path->record());
rec.setValue(1,ui->lineEdit->text());
rec.setValue(2,2);
model_path->insertRecord(-1, rec);
emit model_path->layoutChanged();
}
The following function will remove a row based on which line is highlighted in the QListView. The remove works as the row is deleted from the database once the .submitAll is done. However the QListView does not update consistently.
void myclass::on_pushButton_remove_clicked()
{
model_path->removeRow(ui->listView_path->currentIndex().row());
emit model_path->dataChanged(ui->listView_path->currentIndex(),ui->listView_path->currentIndex());
emit model_path->layoutChanged();
}
If I delete a row, the list won't refresh. If I add one or more new rows, and then delete one or all of them, they will get refreshed. As you can see, I use both dataChanged and layoutChanged but they don't seem to do much here.
I don't understand why the refresh is not consistent. Can anyone help?
You don't need to call both layoutChanged or dataChanged when you add or delete some rows. They are not designed for such kind of updates. Check documentation
Possible, your problem is in understanding edit strategy QSqlTableModel::OnManualSubmit. Try to change it to QSqlTableModel::OnFieldChange
Related
Mind that this question isn't a duplicate of question Making only one column of a QTreeWidgetItem editable, as it's proposed solution doesn't work.
Hello, so I just want to make only ONE column of my treeWidget editable.
propertyItems.push_back(new QTreeWidgetItem); //gets filled by the while-loop
propertyItems[propertyItems.size()-1]->setText(0, prop.name); //sets the text of the item
propertyItems[propertyItems.size()-1]->setText(1, prop.value);//set the text of the other item
propertyItems[propertyItems.size()-1]->setFlags(Qt::ItemIsEditable);
ui->treeWidget_3->insertTopLevelItem(ui->treeWidget_3->topLevelItemCount(), propertyItems[propertyItems.size()-1]); //appends the items
counter ++;
and
void MainWindow::onTreeWidget3ItemDoubleClicked()
{
if (ui->treeWidget_3->currentColumn() == 2) {
ui->treeWidget_3->editItem(ui->treeWidget_3->currentItem(), ui->treeWidget_3->currentColumn());
}
}
is my approach. ontreeWidget3ItemDoubleClicked is connected with treeWidget::doubleClicked, treeWidget_3 has NO edit-triggers
BUT: when I execute the programm, the QTreeView is just grayed out.
That said, I also tried
propertyItems[propertyItems.size()-1]->setFlags(propertyItems[propertyItem.size()].flags | Qt::ItemIsEditable);
The treeWidget_3 isn't grayed off anymore, but it is still uneditable...
How can I fix this?
BTW: I am a newb to Qt so I might have forgotten something crucial. Sorry in this case.
As mentioned in the documentation:
The QTreeWidgetItem class provides an item for use with the QTreeWidget convenience class.
It means that it won't work for all use cases. The solution is to create your own model and overload the flags(const QModelIndex& index) method returning the appropriate values (basically Qt:: ItemIsEnabled for read-only columns and Qt:: ItemIsEnabled | Qt::ItemIsEditable for the editable one). You can get the column from index.column().
Qt provides an example to start with trees and models.
The source code relating to this question is available on my public Git repository on BitBucket.
I'm trying to dynamically add some items to a QTreeView model using the following code in mainwindow.cpp:
if(dlg->exec() == QDialog::Accepted) {
QList<QVariant> qList;
qList << item.name << "1111 0000" << "0x00";
HidDescriptorTreeItem *item1 = new HidDescriptorTreeItem(qList, hidDescriptorTreeModel->root());
hidDescriptorTreeModel->root()->appendChild(item1);
}
This works when run from within my MainWindow constructor, just after ui->setupUi(this), but I need this to run from within an event filter, but the same code doesn't get the QTreeView updating. When I set a breakpoint at mainwindow.cpp:70 and step through the next few lines, I can see the data is being added to the Model, but I need the QTreeView to refresh.
I understand this is done by emitting dataChanged(), but not really sure how to do this. The signal signature for the dataChanged signal looks as follows:
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles = QVector<int>());
so I need to come up with topLeft and bottomRight QModelIndex instances. How do I build/obtain these from item1 in the above snippet?
Also, where does beginInsertRows() and endInsertRows() come into view with this, should I be calling these functions?
From QAbstractItemModel documentation:
void QAbstractItemModel::beginInsertRows ( const QModelIndex & parent, int first, int last ) [protected]
Begins a row insertion operation.
When reimplementing insertRows() in a subclass, you must call this function before inserting data into the model's underlying data store.
The parent index corresponds to the parent into which the new rows are inserted; first and last are the row numbers that the new rows will have after they have been inserted.
The other protected functions say similar things.
And insertRows() says:
If you implement your own model, you can reimplement this function if
you want to support insertions. Alternatively, you can provide your
own API for altering the data. In either case, you will need to call
beginInsertRows() and endInsertRows() to notify other components that
the model has changed.
Take a look to QAbstractItemModel protected functions and signals
Views connect to those signals to know when model data changes and rearrange data inside. The functions emit the signals internally to make it easy for you to warn the view when it has happenned. But signals can only be emitted by abstract class.
Components connected to this signal use it to adapt to changes in the
model's dimensions. It can only be emitted by the QAbstractItemModel
implementation, and cannot be explicitly emitted in subclass code.
So you will have to stick to the methods.
Edit in answer to your comment:
Indeed, Items should have a reference to model and tell it about changes, check theses lines from QStandardItem:
void QStandardItem::emitDataChanged()
void QStandardItem::removeRows(int row, int count)
( Note, how, in second, it calls model's rowsAboutToBeRemoved() and rowsRemoved() )
Maybe you should try to use QStandardItem and QStandardItemModel.
Either direct or subclassing. It will hide a lot of ugly stuff.
There's also a less proper but much easier way to achieve this - emit layoutChanged() instead of dataChanged(). More info - https://stackoverflow.com/a/41536459/635693
I have a QDialog, created with QT Designer, that looks like so:
The list of servers on the left is a QListView with a QStringListModel. Mouse clicking an item in the list view updates the form with the information for the selected item by connecting the view’s activated(QModelIndex) signal to a slot function in the dialog.
However, pressing up or down on the keyboard also changes the selected item, but no signal is emitted, so the form isn't updated to match the selected item. How can this be fixed?
The activated(QModelIndex) signal actually refers to something more than just the act of selecting. The concept is rather vague, but it's more like an act of explicit choosing. If you're just looking for notification that the current selection has changed, you can grab the selection model and connect to its updates.
MyView::MyView() {
QListView* view = new QListView(this);
connect(view->selectionModel(),
SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
this, SLOT(handleSelectionChanged(QItemSelection)));
}
...
MyView::handleSelectionChanged(const QItemSelection& selection){
if(selection.indexes().isEmpty()) {
clearMyView();
} else {
displayModelIndexInMyView(selection.indexes().first());
}
}
In the code above, displayModelIndexInMyView(QModelIndex) should be replaced with your current handler slot for activated(QModelIndex), and clearMyView() replaced with whatever it is that you want to do when there's nothing selected.
There's a lot of ways to do this, and honestly I'm not sure what is the canonical one, but I think this will work for you.
The other way is to implement QListView::currentChanged(...) virtual function.
I struggled also on a similar case with Qt6. In addition to the accepted answer, I propose this, with the new syntax it becomes (example non tested):
MyView::MyView() {
QListView* view = new QListView(this);
connect(view->selectionModel(),
&QItemSelectionModel::currentChanged,
this, &MyView::handleSelectionChanged);
}
...
MyView::handleSelectionChanged(
const QItemSelection& selection,
const QItemSelection& before){
if(selection.indexes().isEmpty()) {
clearMyView();
} else {
displayModelIndexInMyView(selection.indexes().first());
}
}
class genericTaskList : public QListWidget
{
Q_OBJECT
public:
QListWidgetItem *defaultText;
genericTaskList (QWidget *parentWidget)
{
setParent (parentWidget);
setFixedSize (445, 445);
defaultText = new QListWidgetItem ("Double click here to compose the task");
defaultText->setFlags (defaultText->flags () | Qt :: ItemIsEditable);
insertItem (0, defaultText);
QObject :: connect (this, SIGNAL (currentRowChanged (int)), this, SLOT (addDefaultText (int)));
}
public slots:
void addDefaultText (int rr)
{
std::cout << "\ndsklfjsdklfhsdklhfkjsdf\n";
insertItem (++rr, defaultText);
}
};
This code is supposed to issue a signal each time the row gets edited.
After I call "insertItem" in the constructor, the signal is issued.
But, that's it. It never gets issued after that - no matter how many times I edit the row.
What am I missing?
At first it seems like QListWidget::itemChanged is the way to go, but soon you run into a problem: the signal is sent for everything - inserts, removes, changing colors, checking boxes, etc! So then you end up trying to put in flags and filter everywhere by intercepting various signals to find out if editing was the actual event. It gets very messy.
There is also QAbstractItemModel::dataChanged , which would seem like a good solution. It even has a parameter "const QVector& lstRoles" so you could scan for Qt::EditRole and see if it was really edited. Alas, there's a catch - it gets called for everything just like QListWidget::itemChanged and unfortunately, for QListWidget anyway, the roles parameter is always empty when it's called (I tried it). So much for that idea...
Fortunately, there's still hope... This solution does the trick! :
http://falsinsoft.blogspot.com/2013/11/qlistwidget-and-item-edit-event.html
He uses QAbstractItemDelegate::closeEditor, but I prefer using QAbstractItemDelegate::commitData.
So make a connect like so...
connect(ui.pLstItems->itemDelegate(), &QAbstractItemDelegate::commitData, this, &MyWidget::OnLstItemsCommitData);
Then implement the slot like this...
void MyWidget::OnLstItemsCommitData(QWidget* pLineEdit)
{
QString strNewText = reinterpret_cast<QLineEdit*>(pLineEdit)->text();
int nRow = ui.pLstItems->currentRow();
// do whatever you need here....
}
Now you have a slot that gets called only when the list item's text has been edited!
currentRowChanged indicates the row selection has changed, not the content of the row. Perhaps you want to use currentTextChanged or itemChanged instead.
The reuse of the word current and changed in the QT docs is quite confusing.
Warning: A QListWidgetItem can only be added to a QListWidget once. Adding the same QListWidgetItem multiple times to a QListWidget will result in undefined behavior.
So even if it will emit the signal I think you should better to add newly created Item.
And when do you want the new row to be inserted ? -
as soon as item is double clicked or finishing edit - they differ.
I am a student programmer using Qt to build a GUI for work. I have ran into an issue; more or less and inconvience of sorts wiith multiple selections in the QTreeWidget. My GUI has a main interface with a QTreeWidget as the central item in this window. Below the QTreeWidget I have several buttons; copy, edit, and delete. As you might've already guessed each one of these buttons correlates to a function that executes the command. My tree widget has the ability to select multiple items; however, when multiple items are selected the only item that is passed through is the last item that was selected. I was hoping that somebody with some more insight in this IDE might be able to point me in the right direction for accomplishing this. Here is the process that is followed when one of these functions is executed.
void InjectionGUI::copyInjection_Clicked(QTreeWidgetItem *itemToCopy)
{
InjectionData copyInjectionData; //first use data from the tree widget row
QString converter = itemToCopy->text(0); //to find the vector item that will be copied
int id = converter.toInt();
int nameNumber;
copyInjectionData = qTreeInjectionData.at(id);
qTreeInjectionData.append(copyInjectionData);
buildTreeWidget();
}
void InjectionGUI::slotInjectionCopy()
{
if(ui->treeWidgetInjections->currentItem() == 0)
{
QMessageBox invalidSelection;
invalidSelection.setText("No row selected to copy");
invalidSelection.setWindowTitle("Error");
invalidSelection.exec();
}
else
{
copyInjection_Clicked(ui->treeWidgetInjections->currentItem());
}
}
I'm not too sure what code will be relevant towards making this change; so if there is additional structure that anyone would like to see please just requested. I'm pretty sure that my problem or my solution is going to lie in the way that I'm using current item. After reviewing the documentation from Qt's website I'm still unsure how I would change this to allow multiple selections to be passed through the function. Please only provide constructive feedback; I'm only interested in learning and accomplishing a solution. Thanks in advance.
UPDATE! SOLVED!!!
Just thought it might be nice to show what this looked like implemented:
QList<QTreeWidgetItem *> items = ui->treeWidgetInjections->selectedItems();
for(int i = 0; i < items.size(); i++)
{
QTreeWidgetItem *qTreeWidgetitem = new QTreeWidgetItem;
qTreeWidgetitem = items.at(i);
copyInjection_Clicked(qTreeWidgetitem);
}
If you need to know which items are selected, you can use
QList<QTreeWidgetItem *> QTreeWidget::selectedItems() const
to have a list of all the currently selected items in the tree.
Then you may call your function once for every item in the list, or you can overload your function to take as argument a QList<QTreeWidgetItem *> and then run through the list inside the called function.