Why can't I update the QTableView model inside a thread automatically? - c++

Introduction:
I'm using QT5 and I want to update a TableView where my model for the TableView is updated inside a thread.
*Method
I create a table like this and set it's columns and rows.
/* TableView */
QStandardItemModel* looping_terminal_model = new QStandardItemModel(4, 2);
looping_terminal_model->setHeaderData(0, Qt::Horizontal, QObject::tr("ID"));
looping_terminal_model->setHeaderData(1, Qt::Horizontal, QObject::tr("DATA"));
ui->looping_terminal_tableView->setEditTriggers(QAbstractItemView::NoEditTriggers);
ui->looping_terminal_tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
ui->looping_terminal_tableView->setModel(looping_terminal_model);
Then I starting the thread.
/* Threads */
this->loop_terminal = new Loop_terminal(j1939, &start_loop_terminal_thread, looping_terminal_model);
loop_terminal->start();
Inside the loop_terminal class, it looks like this
Loop_terminal::Loop_terminal(J1939* j1939, bool* start_loop_terminal_thread, QStandardItemModel* looping_terminal_model){
this->j1939 = j1939;
this->start_loop_terminal_thread = start_loop_terminal_thread;
this->looping_terminal_model = looping_terminal_model;
i = 0;
}
void Loop_terminal::run(){
while(1){
if(j1939->ID_and_data_is_updated){
j1939->ID_and_data_is_updated = false;
}
QModelIndex index = looping_terminal_model->index(1,1,QModelIndex());
// 0 for all data
looping_terminal_model->setData(index,this->i);
i++;
msleep(1000);
}
}
Result
The value can be shown in the blue cell. But it only updates if I click on the blue cell.
Discussion:
Here is the problem
looping_terminal_model->setData(index,this->i);
Every time I updated an index, I need to press on the blue TableView cell, so it can updates. Or else, nothing happens. If I minimize the window, then it updates, but I can't see that.
What should I do, to make sure that the ui can update, even if it's outside of the thread?

No, you cannot and should not. Models are intensively used by the view so they must live (and be modified) in the same thread as the view as they are not thread-safe. The solution is to send the information to the main thread using signals and there just update the model.

Related

Show Last element in QListView

It sounds trivial, but I could not find the function to show the last added element in a QListView.
It works with a model
// Create model
model = new QStringListModel(this);
// Make data
QStringList List;
// Populate our model
model->setStringList(List);
// Glue model and view together
listView->setModel(model);
Elements are added with
void WidgetMessageList::addString(const QString & message)
{
if(model->insertRow(model->rowCount())) {
QModelIndex index = model->index(model->rowCount() - 1, 0);
model->setData(index, message);
}
}
In this function the shown element should also be the last.
QAbstractItemView::scrollTo
Scrolls the view if necessary to ensure that the item at index is
visible. The view will try to position the item according to the given
hint.
http://doc.qt.io/archives/qt-4.8/qabstractitemview.html#scrollTo
Create a class attibute to hold the last index
Connect QAbstractItemModel::rowsInserted to a slot in your application
In the slot update the index accordingly

Qt table widget, button to delete row

I have a QTableWidget and for all rows I set a setCellWidget at one column to a button.
I would like to connect this button to a function that delets this row.
I tried this code, which does not work, because if I simply click my button I do not set the current row to the row of the button.
ui->tableWidget->insertRow(ui->tableWidget->rowCount());
QPushButton *b = new QPushButton("delete",this);
ui->tableWidget->setCellWidget(ui->tableWidget->rowCount()-1,0,b);
connect(d,SIGNAL(clicked(bool)),this,SLOT(deleteThisLine()));
...
void MainWindow::deleteThisLine()
{
int row = ui->tableWidget->currentRow();
ui->tableWidget->removeRow(row);
}
How can I connect my button to a function in a way that the function knows which button (at which row) was pressed?
To remove the row we must first get the row, if we are inserting widgets inside the cells the currentRow() method will not return the appropriate row, in many cases it will return the row of the last cell without widget that has been selected.
For that reason you must opt for another solution, for this case we will use the indexAt() method of QTableWidget, but for this we need to know the position in pixels of the cell. when one adds a widget to a cell, this cell will be the parent of the widget, so we can access from the button to the cell using the parent() method, and then get the position of the cell with respect to the QTableWidget and use it in indexAt(). To access the button we will use the sender().
When the current cell is removed the focus is lost, a possible solution is to place the focus again in another cell.
void MainWindow::deleteThisLine()
{
//sender(): QPushButton
QWidget *w = qobject_cast<QWidget *>(sender()->parent());
if(w){
int row = ui->tableWidget->indexAt(w->pos()).row();
ui->tableWidget->removeRow(row);
ui->tableWidget->setCurrentCell(0, 0);
}
}
Use this connection way to connect signal to a slot:
connect(ui->btnDelete, &QPushButton::clicked, this,&MainWindow::deleteRow);
And delete for example a row on call function:
void MainWindow::deleteRow()
{
int row = ui->tableWidget->currentRow();
ui->tableWidget->removeRow(row);
}
Create a custom class, where you pass the created push button object and the row index. From your custom push button class, handle the push button press event and emit a custom signal (it will carry the index number) handled from the object where your custom pushbutton is created. Some related code are below, to give you a hint:
.h
class mypushbutton {
explicit mypushbutton(QObject *parent = 0, QPushButton *pushbutton = 0, int index = 0);
signal:
void deleteRow(int index);
}
.cpp
mypushbutton() {
connect(pushbutton, SIGNAL(clicked(bool)), this, SLOT(actionButtonClick(bool)));
}
actionbuttonclicked() { emit deleteRow(index);}

How to get the status of hardcoded QCheckBoxes in QSqlQueryModel?

I have a QTableView that shows QSqlQueryModel. The model contains checkboxes that are created in each row in the first column (which contain the ref_no; the primary-key in my db) as follow:
void MainWindow::showM(model){
ui->tableView->setModel(model);
ui->tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
for( int i = 0; p<model->rowCount(); i++)
{
QCheckBox *checkBox = new QCheckBox();
ui->tableView->setIndexWidget(model->index(i,0),checkBox);
}
ui->tableView->show();
}
... and it's working fine, displaying all the information I need plus the checkboxes.
Now, I need to get the ref_no where the adjacent checkbox is checked.
How to do that ?
Use QSignalMapper (or an ad hoc solution involving a mapper using sender(), or lambdas). For instance define a member for the mapping:
QHash<QCheckBox *, int> m_mapping;
Then in your code hook it up like this:
QCheckBox *checkBox = new QCheckBox();
ui->tableView->setIndexWidget(model->index(i,0),checkBox);
m_mapping[checkBox] = i;
connect(checkBox, &QCheckBox::toggled, this, &MainWindow::onCheckBoxToggled);
Then define a slot like this:
// for the love of kittens use proper names for methods
void MyWindow::onCheckBoxToggled(bool toggled) {
QCheckBox *box = static_cast<QCheckBox *>(sender());
const int id = m_mapping.value(box);
// do something
}
Or, if you fancy lambdas, you can do the above with a capture:
connect(checkBox, &QCheckBox::toggled,
[i](bool toggled){ /* use i, toggled */ });
All of that having being said, I would strongly recommend against the idea of creating QCheckBoxes and using setIndexWidget. Instead, employ a proxy model that enriches your column by returning the Qt::ItemIsUserCheckable flag and handles reads and writes for the Qt::CheckStateRole.

Restore original order in QTableView/QSortFilterProxy

I have a QTableView with QSortFilterProxyModel between the view and the model (QStandardItemModel). The problem is when I call sort() I'm unable to restore original order of lines in a table. I was trying to acheve that by changing model proxy to QIdentityProxy on-the-fly but to no avail, as the only change is that lines are renumbered but order is kept sorted.
Is it possible to somehow "unsort" data? I think, that code is unnecessary in this case, but will post if asked.
I'm using Qt5 on Win x64
P.S.: The same problem was posted here back in 2009 but never was answered.
To restore initial unsorted state ( tested )
sortModel->setSortRole(Qt::InitialSortOrderRole);
sortModel->invalidate();
QSortFilterProxyModel::​setSortRole(int role)
The point is to sort manually deciding between sort by column -1 (restore) and normal column number, and intercept communication between QHeaderView and QSortFilterProxyModel somehow.
So, using some insight from #vahancho's answer, I've managed to implement sorting like this:
class ProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
ProxyModel(QObject* parent = 0);
signals:
void askOrder(int column, Qt::SortOrder order);
public slots:
//! override of automatically called function
void sort(int column, Qt::SortOrder order)
{
emit askOrder(column, order);
}
//! real sorting happens here
void doSort(int column, Qt::SortOrder order)
{
QSortFilterProxyModel::sort(column, order);
}
};
and on parent's side I made proper connection and checks:
ResultsTable::ResultsTable(QWidget *parent) : QTableView(parent)
{
/*...*/
p_Header = new QHeaderView(this);
p_Sort = new ProxyModel(this);
connect(this, &ResultsTable::doSort, p_Sort, &ProxyModel::doSort);
connect(p_Sort, &ProxyModel::askOrder, this, &ResultsTable::setSorting);
/*...*/
setSortingEnabled(true);
}
void ResultsTable::setSorting(int column, Qt::SortOrder order)
{
if (p_Header->sortIndicatorOrder() == Qt::AscendingOrder && p_Header->isSortIndicatorShown() && m_PreviousSort == column)
{
p_Header->setSortIndicator(column, Qt::DescendingOrder);
p_Header->setSortIndicatorShown(false);
column = -1;
}
else
{
p_Header->setSortIndicatorShown(true);
}
m_PreviousSort = column;
emit doSort(column, order);
}
this way I can use automatic sorting treating done by QTableView when sortingEnabled is true. I've tried to research what happens inside of Qt when table header is clicked to induce sorting, but failed, so stopped with this solution.
I'm still unsure if it's right that this way QTableView is responsible for setting correct sort indication, and not QHeaderView itself (as I thought this functionality should to belong to the header).
My understanding is that you need to return your sorting to its default state? What if you override the QSortFilterProxyModel::lessThan() function in the way that it returns the default value when you want to reset sorting, i.e.:
return QSortFilterProxyModel::lessThan(left, right);
,and custom sorting results when sorting "enabled"? I think you will also need to reset your proxy model to its original state with QAbstractItemModel::reset(). However, it will repopulate the whole model data and you will lost selection information.
I like to use top-left corner button to restore order (that is, sort by row number to which that button is the header). This works with standard classes, in pyqt 5.9:
def __init__(self):
#...
tb = self.tableView # has sorting enabled and SortIndicator shown
proxy = QSortFilterProxyModel(self)
proxy.setSourceModel(self.model)
tb.setModel(proxy)
btn = tb.findChild(QAbstractButton)
if btn:
btn.disconnect()
btn.clicked.connect(self.disableSorting)
tb.horizontalHeader().setSortIndicator(-1, 0)
def disableSorting(self):
self.tableView.model().sort(-1)
self.tableView.horizontalHeader().setSortIndicator(-1, 0)
This is what worked best for me:
QSortFilterProxyModel *proxyModel = myTableView->model ();
proxyModel->sort (-1);
Note that, this won't update the header indicator of a TableView, so before calling the code above, I'm also calling:
myTableView->horizontalHeader ()->setSortIndicator (0, Qt::DescendingOrder);
I don't really like it, but I haven't found a way of using the TableView or HorizontalHeader to reset the QSortFilterProxyModel sorting.
This is the full code:
// Block QHeaderView signals so sorting doesn't happen twice
myTableView->horizontalHeader ()->blockSignals (true);
// Update the sort indicator to be the same as it was when the TableView was created
myTableView->horizontalHeader ()->setSortIndicator (0, Qt::DescendingOrder);
// Reset sorting
QSortFilterProxyModel *proxyModel = myTableView->model ();
proxyModel->sort (-1);
// Unblock QHeaderView signals
myTableView->horizontalHeader ()->blockSignals (false);
NOTE: I'm blocking the horizontal header signals temporarily to prevent QSortFilterProxyModel sort from executing twice.
From the docs:
QSortFilterProxyModel can be sorted by column -1, in which case it returns to the sort order of the underlying source model.
So, as #eTHP says, sort(-1) does the trick!

QTableView with icons in rows

I have a QTableView showing rows of a database table. In this table I have a column called data type and I have icon images for each type. How can I add these icons in front of each data type?
Here's a part of my code as requested by justanothercoder.
QString msgQueryString = "select MESSAGE_ID, DATA_TYPE from SER_MESSAGES where MESSAGE_ID > 500 ";
serendibMsgTableModel->setQuery(msgQueryString, *database);
serendibMsgTableModel->setHeaderData(0, Qt::Horizontal, tr("Message ID"));
serendibMsgTableModel->setHeaderData(1, Qt::Horizontal, tr("Data Type"));
serendibMsgProxyModel->setSourceModel(serendibMsgTableModel);
serendibMsgView->setModel(serendibMsgProxyModel);
"serendibMsgTableModel" is a QSqlQueryModel and "serendibMsgProxyModel" is a customized QSortFilterProxyModel. "serendibMsgView" is the QTableView I need the icons to be displayed, in the Data Type column.
Hope this helps for your answer.
I saw that you've already picked an answer but since you are learning Qt I'll add a few things.
Taking a look at the excellent Qt documentation I suggest you overwrite this in your model:
QVariant QSqlTableModel::data (
const QModelIndex & index,
int role = Qt::DisplayRole ) const   [virtual]
There are various roles (int role = Qt::DisplayRole):
enum Qt::ItemDataRole :
Each item in the model has a set of
data elements associated with it, each
with its own role. The roles are used
by the view to indicate to the model
which type of data it needs. Custom
models should return data in these
types.
Qt::DecorationRole : The data to be
rendered as a decoration in the form
of an icon. (QColor, QIcon or Qpixmap)
Thus, what you need to do is return a QIcon or QPixmap in the data() function for the DisplayRole.
Another approach which might be more appropriate is to make use of delegates: For example ColorListEditor
Set the DecorationRole of your items to the QPixmap you want and it should work.
edit:
I guess that the icon depends on the value in the data type column.
int rowCount = serendibMsgTableModel->rowCount();
for(int row = 0; row < rowCount; row++)
{
QModelIndex index = serendibMsgTableModel->index(row, 1);
QVariant value = serendibMsgTableModel->data(index);
static QPixmap s_invalidIcon(PATH_TO_INVALID_ICON);
static QPixmap s_type1Icon(PATH_TO_TYPE1_ICON);
static QPixmap s_type2Icon(PATH_TO_TYPE2_ICON);
QPixmap icon(s_invalidIcon);
if(value.toString() == "type1")
{
icon = s_type1Icon;
}
else if(value.toString() == "type2")
{
icon = s_type2Icon;
}
serendibMsgTableModel->setData(index, icon, Qt::DecorationRole);
}
Something like this should work.
Set the values before setModel.
I haven't tested it, but I think you should get the idea from this.