Unexpected behavior in Qt Model View classes - c++

I have a piece of code in my application as follows :
....
QStandardItemModel* authorModel = getAuthorModel(author);
// Create result tab
QTableView* tblView = new QTableView();
tblView->setModel(authorModel);
controller.queryAuthor(author, authorModel);
qDebug() << authorModel->setHeaderData(0, Qt::Horizontal, QVariant("Author Name"), Qt::DisplayRole);
qDebug() << authorModel->setHeaderData(1, Qt::Horizontal, QVariant("Author Id"), Qt::DisplayRole);
int tabIdx = ui->mainTabWidget->addTab(tblView, author);
ui->mainTabWidget->setCurrentIndex(tabIdx);
tblView->setColumnHidden(1, true);
This code is called multiple times creating different tableviews. When authorModel is empty, then setting headerdata fails, also, setColumnHidden fails and once the data is populated default numeric headers are shown and column 1 is visible. Both qDebug statements return false.
However, when the same populated model is used to create a new table view, in the new view column 1 is hidden without issues, and headers are set as they should be. Both qDebug statements return true.
What is the problem and how can it be alleviated ?

Stepping into QStandardItemmodel implementation shows that for these functions unless the column exists to begin with updating header data has no effect.
This can thus be worked around by setting the number of columns your model is designed to use before by using
authorModel->setColumnCount(2);
This way even if the model data is empty column count will return 2 and the calls to set header data should be fine in your case

Related

Transfer a list from a foreign key to an external combobox

Initially, I used qsqlrelationtablemodel in my database project. And the values were entered into the database using the combobox inside the tableView. And it looked something like this:
enter image description here
And the code looked very simple:
model->setTable(sql_query);
model->setRelation(1, QSqlRelation("my_schema.aircrafts","id_aircraft","registration_number"));
model->setRelation(3, QSqlRelation("my_schema.airports","id_airport","name_airport"));
model->setRelation(4, QSqlRelation("my_schema.airports","id_airport","name_airport"));
ui->tableView->setModel(model);
ui->tableView->setItemDelegateForColumn(1, new QSqlRelationalDelegate(this));
ui->tableView->setItemDelegateForColumn(3, new QSqlRelationalDelegate(this));
ui->tableView->setItemDelegateForColumn(4, new QSqlRelationalDelegate(this));
But now I have redone the data entry on the forms and there are no problems with transferring information to qlineedit. There are difficulties with transferring data from foreign key's to an external combobox.
Now it looks like this (the value of the combobox does not change when you click on another row of the tableView):
enter image description here
Now I'm using this code, but I don't understand how to pass the value of the selected index in the tableView to the combobox.
QString query = QString("SELECT * FROM my_schema.route WHERE id_route = '%1'").arg(id);
QSqlQuery qw;
qw.exec(query);
while(qw.next())
{
ui->lineEdit->setText(qw.value(1).toString());
ui->lineEdit_2->setText(qw.value(2).toString());
ui->lineEdit_3->setText(qw.value(3).toString());
ui->comboBox_2->setModel(model);
ui->comboBox_2->setModelColumn(4);
}
I would like to see something like this result:
enter image description here
Please tell me in the most accessible form how to achieve this
You have model and tableview for fetching all columns for clicked row by using QAbstractItemView::clicked(const QModelIndex &index) signal for receive event. And implement the slot that loops all columns in selected row like this example snippet:
// create query with join for combobox model only containing desired values not foreign keys
// after that create QStringList and fill it with query result
// set stringlistmodel to combobox
ui->comboBox_2->setModel(stringlistmodel); // this is different from tableview's model
connect(ui->tableView, &QTableView::clicked, [this](const QModelIndex &index)
{
auto row{index.row()};
ui->lineEdit->setText(model.data(model.index(row, 0).toString());
//...
ui->comboBox_2->setCurrentIndex(model.data(model.index(row, 4).toInt());
//...
});

Qt TreeView problem when used with sort model

I have a problem where i did not find a solution so far, actually the question is when and for what QModelIndex we have to call beginInsertColumns and similar functions.
My example is the standard Qt example from here:
http://doc.qt.io/qt-5/qtwidgets-itemviews-editabletreemodel-example.html
It is also delivered with every qt installation, e.g. C:\Qt\Qt5.12.0\Examples\Qt-5.12.0\widgets\itemviews\editabletreemodel
When just executed, it works fine, but now lets insert a QSortFilterProxyModel, just to make it sortable. We change the MainWindow c'tor to
...
QFile file(":/default.txt");
file.open(QIODevice::ReadOnly);
TreeModel *model = new TreeModel(headers, file.readAll());
file.close();
QSortFilterProxyModel* sort = new QSortFilterProxyModel( this );
sort->setSourceModel( model );
view->setModel( sort );
...
Now execute the example, expand one or two of the root nodes and select the menu entry "insert column" you will see, that only the root nodes have a new column, all their children do not have the new column. It works without the proxy. Now my question, do i have to emit beginInsertColumns for every QModelIndex that has children?
How can i fix this?
Note, this is not only a problem with qt 5.12, but also with many earlier releases.
I've always had problems using QSortFilterProxyModel, and as far as I understood they are often related to the index mapping between with the source model.
I managed to solve it using the QSortFilterProxyModel::invalidate() method that will reset the mapping, without spoiling the current state of the view.
Modify the MainWindow::insertColumn as follows:
bool MainWindow::insertColumn()
{
QAbstractItemModel *model = view->model();
int column = view->selectionModel()->currentIndex().column();
bool changed = false;
// Insert a column in the parent item.
QSortFilterProxyModel* sfpm = qobject_cast<QSortFilterProxyModel*>(model);
if (sfpm)
{
changed = sfpm->insertColumn(column + 1);
if (changed)
sfpm->setHeaderData(column + 1, Qt::Horizontal, QVariant("[No header]"), Qt::EditRole);
sfpm->invalidate();
}
updateActions();
return changed;
}
It is not an optimal solution, and I don't know if there is a better way, but works for me.

Add QTableView to QComboBox

I'm retrieving a set of results from the database and I want to populate the QComboBox with the resulting columns from the database (each row of the QComboBox should have the same columns as database result) and after that I would like to be able to retrieve from one row of the QComboBox a specific column and use it further in the app. I'm thinking if it would be possible to add the QTableView to QComboBox. I'm want to do this because I want to add more meaning to the results in a way that some result columns are just plain numbers and other are the description information.
I found out that it would be possible to concatenate the result and populate the QComboBox but this will leave me with only one value for each row to work with and I have to explode the string to obtain the exact part that it is needed to work with.
The popup that comes by default is a QListView, this can be changed with any object that inherits from QAbstractItemView, and in this case a QTableView will be used for it to use the setView() method, the result when clicking only should return a item of the selected row, then to set the column to be displayed after being selected will use the method setModelColumn() indicating the position of the column, but before that the model is set to the QComboBox using the method setModel().
# my model
model = new QSqlTableModel;
model->setTable("person");
model->select();
# setModel
comboBox->setModel(model);
# select column
comboBox->setModelColumn(1);
QTableView *view = new QTableView(this);
comboBox->setView(view);
Note: The model is set to QComboBox, not to QTableView. Also you could have problems with the width of QTableView, so we must resize, in my case use the following:
view->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
view->setMinimumWidth(500);
The complete example can be found in the following link

list of SelectedRows in QTableWidget

I have problem getting selected rows from QTableWidget. I have table like this:
[id] [ key ]
0 test
1 pass
I want to get every row's key values. I tried QTableWidget->selectedIndexes(); but it says it's protected and I can't access that. When I tried QTableWidget->SelectionModel->selectedIndexes, I don't know how to loop through list and get the key values. Do anyone know better way how can I do it?
Regards.
I'm assuming that you set the selection behavior of your table widget to select rows.
You can always access the so-called "selection model" of any item view/widget. QTableWidget inherits from QAbstractItemView, which gives you access to this special model. This model can tell you the selected rows as a list of QModelIndex, which can then tell you the row. Once you've got them, you can access the table content, in your case the text in the column with index 1 (key column).
static const KEY_COLUMN = 1;
QList<QString> selectedKeys;
QItemSelectionModel *selectionModel = ui->tableWidget->selectionModel();
foreach(QModelIndex index, selectionModel->selectedRows())
selectedIDs << ui->tableWidget->item(index->row(), KEY_COLUMN)->text();
Because you are using QTableWidget, you probably want to be calling selectedItems(). Your results will be based on what you have set the selection behavior to, via setSelectionBehavior()
When you have a list of items, you can specifically get the second column item (if it wasn't selected already):
QTableWigetItem *keyItem = table->item(anItem->row(), 1);
QString val = keyItem->text();

Why do INSERT and DELETE screw up QTableView (Qt, C++, sqlite)?

After setting an INSERT or DELETE query on QSqlQueryModel, my QTableView becomes screwed up. For example I hid the ID column by calling view->hideColumn(ID); but after an INSERT or DELETE the ID column becomes visible.
How can I automatically reset my view to the previous settings in these cases?
I guess the problem is in QSqlQueryModel::setQuery you're eventually calling every time content gets reloaded and rows inserts\deletes. Looking at the setQuery implementation I would suggest: depending on the query your model can be reset including columns settings change which should trigger view columns update.
As Qt documentation suggests:
The QSqlQueryModel class provides a
read-only data model for SQL result
sets.
so I would use direct QSqlQuery calls for the data updates and then would reload the model with the same query. Or consider switching to QSQLTableModel, which is quite handy for single table content manipulation and supports inserts updates and deletes. See if an example below would work for you:
set up database, view and model:
QSqlTableModel *_model;
QTableView *_view;
...
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName(":memory:");
db.open() ;
QSqlQuery query;
query.prepare("CREATE TABLE IF NOT EXISTS person (id INTEGER UNIQUE PRIMARY KEY, name VARCHAR(30))");
query.exec();
query.prepare("INSERT INTO person (name) VALUES ('test1')");
query.exec();
query.prepare("INSERT INTO person (name) VALUES ('test2')");
query.exec();
_model = new QSqlTableModel(this, db);
_model->setTable("person");
_model->setEditStrategy(QSqlTableModel::OnManualSubmit);
_model->select();
_model->setHeaderData(1, Qt::Horizontal, tr("name"));
_model->setSort(1, Qt::AscendingOrder);
_view = new QTableView(this);
_view->setModel(_model);
_view->hideColumn(0);
add new row:
QSqlRecord record;
_model->insertRecord(-1, record);
delete selected row(s):
QModelIndexList selected = _view->selectionModel()->selectedIndexes();
for (int i = 0; i < selected.size(); ++i)
_model->removeRows(selected.at(i).row(), 1);
submit changes:
_model->submitAll();
hope this helps, regards