Update cell in QTableView with new value - c++

I'm new, I'm learning to program in Qt and my English is not very good, my problem is that when I update a cell in a QTableView to use its value in another cell, it uses the previous value and not the new, I show them the code as I am doing, thanks.
bool MainWindow::eventFilter(QObject * watched, QEvent * event)
{
if(event->type() == QEvent::KeyPress)
{
QKeyEvent *ke = static_cast<QKeyEvent *>(event);
qDebug() << ke->type();
if(ke->key() == Qt::Key_Enter || ke->key() == Qt::Key_Return)
{
int fila = ui->tableView->currentIndex().row();
int col = ui->tableView->currentIndex().column();
double valor1 = ui->tableView->model()->data(ui->tableView->model()->index(fila,1)).toDouble();
double valor2 = ui->tableView->model()->data(ui->tableView->model()->index(fila,3)).toDouble();
if(col == 1 || col == 3)
{
ui->tableView->model()->setData(ui->tableView->model()->index(fila,col + 1),2.0*valor1);
ui->tableView->model()->setData(ui->tableView->model()->index(fila,col + 3),200.0*valor1/valor2);
}
}
}
return false;
}

If you are inside a custom data model, (perhaps inheriting from QAbstractTableModel, since we're discussing QTableViews), you can inform the view that a change of data has occurred by emitting the QAbstractItemModel::dataChanged() signal.
Here's how I do it.
Updating an entire row:
QModelIndex startOfRow = this->index(row, 0);
QModelIndex endOfRow = this->index(row, Column::MaxColumns);
//Try to force the view(s) to redraw the entire row.
emit QAbstractItemModel::dataChanged(startOfRow, endOfRow);
Updating an entire column, but only the Qt::DecorationRole:
QModelIndex startOfColumn = this->index(0, mySpecificColumn);
QModelIndex endOfColumn = this->index(numRows, mySpecificColumn);
//Try to force the view(s) to redraw the column, by informing them that the DecorationRole data in that column has changed.
emit QAbstractItemModel::dataChanged(startOfColumn, endOfColumn, {Qt::DecorationRole});
By adding convenience functions like UpdateRow(row) and UpdateColumn(column) to your item model, you can call those functions from outside of the model itself, if you change the data externally.
You don't want to tell a view to update itself - what if there are more than one view looking at the same model? Let the model to inform all the attached views that it has changed.

This is the code that I use if anyone had the same problem.
connect(ui->tableView->model(),SIGNAL(dataChanged(QModelIndex,QModelIndex)),SLOT(UpdateData(QModelIndex,QModelIndex)));
void MainWindow::UpdateData(const QModelIndex & indexA, const QModelIndex & indexB)
{
int col = indexA.column();
int fila = indexA.row();
if(col == 1 || col == 3)
{
double valor1 = ui->tableView->model()->data(ui->tableView->model()->index(fila,1)).toDouble();
double valor2 = ui->tableView->model()->data(ui->tableView->model()->index(fila,3)).toDouble();
ui->tableView->model()->setData(ui->tableView->model()->index(fila ,col + 1),2.0*valor1);
ui->tableView->model()->setData(ui->tableView->model()->index(fila,col + 3),(200.0*valor1/valor2));
}
}
This will update the cell value that depends on another cell that has been updated.

This seems to be quite ditry approach to do this for several reasons:
If you want the same value to be presented in different columns, or you have a column that depends on the value of another column, then you should probably design accordingly the model that feeds the view.
setData that you are calling is a pure virtual function, that is implemented in the model. So instead of catching Enter/Return key presses and acting on them you might want to modify the setData implementation. Because this way of another all model changes pass through this function. Dont forget to emit dataChanged signals for the indexes that you change.

Related

QT Multiple item selection from a list of files and folders

I'm developing a Qt C++ application in Unix and I've been trying to do something similar to what this image shows:
As you can see, there is a list of files and folders and a user can select multiple of them (if a folder is selected, all childs also get selected). I don't really care if the folder/file icons are shown.
I was able to create a list of QDir which stores all the files and folders paths given a root path. The problem is that I don't really know which widgets to use to design the selection panel.
By the way, the lis of QDir is a vector, but it can be easily modified to anything else.
Thanks!
You can try to make proxy model for QFileSystemModel, override flags() with Qt::ItemIsUserCheckable, override setData() and apply the model to QTreeView. Full example can be found at https://github.com/em2er/filesysmodel. This code is just a concept, i have not tested it thoroughly, but you can take some ideas from it. It will look smth like on the screenshot:
.
Also you can combine it with Merged Proxy Model to display multiple starting paths at one view.
You might want to consider the QTreeWidget, or it's a tad more advanced version - QTreeView and an appropriate data model.
As some users suggested, I ended up using QFileSystemModel. I'm gonna give a full description of how I implemented it, in case someone else comes up with this problem and needs a clear response.
First of all, a QFileSystemModel is a file tree without checkboxes, to add them, a new class which extends QFileSystemModel and at least 3 methods must be overriden.
class FileSelector : public QFileSystemModel
{
public:
FileSelector(const char *rootPath, QObject *parent = nullptr);
~FileSelector();
bool setData(const QModelIndex& index, const QVariant& value, int role);
Qt::ItemFlags flags(const QModelIndex& index) const;
QVariant data(const QModelIndex& index, int role) const;
private:
QObject *parent_;
/* checklist_ stores all the elements which have been marked as checked */
QSet<QPersistentModelIndex> checklist_;
};
When creating the model a flag, to indicate that it should have a checkable box, must be set. This is why we will use the flags function:
Qt::ItemFlags FileSelector::flags(const QModelIndex& index) const
{
return QFileSystemModel::flags(index) | Qt::ItemIsUserCheckable;
}
When a click is made in the checkbox, the method setData will be called, with the index of the element that was clicked (not the checkbox itself, but the :
bool FileSelector::setData(const QModelIndex& index, const QVariant& value, int role)
{
if (role == Qt::CheckStateRole && index.column() == 0) {
QModelIndexList list;
getAllChildren(index, list); // this function gets all children
// given the index and saves them into list (also saves index in the head)
if(value == Qt::Checked)
{
for(int i = 0; i < list.size(); i++)
{
checklist_.insert(list[i]);
// signals that a change has been made
emit dataChanged(list[i], list[i]);
}
}
else if(value == Qt::Unchecked)
{
for(int i = 0; i < list.size(); i++)
{
checklist_.remove(list[i]);
emit dataChanged(list[i], list[i]);
}
}
return true;
}
return QFileSystemModel::setData(index, value, role);
}
When dataChanged is signaled or you open a new path of the tree, the data function will be called. Here you have to make sure to only display the checkbox at the first column (next to the filename), and to retrieve the state of the checkbox, to mark it as checked/unchecked.
QVariant FileSelector::data(const QModelIndex& index, int role) const
{
if (role == Qt::CheckStateRole && index.column() == 0) {
if(checklist_.contains(index)) return Qt::Checked;
else return Qt::Unchecked;
}
return QFileSystemModel::data(index, role);
}
The only thing I was not able to accomplish was getting all childs, since the folders must be open to retrieve the childs. So a closed folder won't have any child until you open it.
Hope this can help someone who has the same problem as I did!

Qt checkboxes in QTableView

I'm using this code to query sqlite and put the results in a QTableView.
//MainWindow.cpp
void MainWindow::on_pushButton_clicked()
{
QSqlQueryModel * modal=new QSqlQueryModel();
connOpen();
QSqlQuery* qry=new QSqlQuery(mydb);
qry->prepare("select * from database");
qry->exec();
modal->setQuery(*qry);
//from stack
modal->insertColumn(0);
ui->tableView->setModel(modal);
//from stack
ui->tableView->resizeColumnsToContents();
int p;
for(p=0; p<modal->rowCount(); p++)
{
ui->tableView->setIndexWidget(modal->index(p,0),new QCheckBox());
}
connClose();
qDebug() <<(modal->rowCount());
}
I've seen several examples of the web for adding checkboxes to a column, but I'm not quite sure what to use for my simple example.
This answer suggests a few lines that doesn't seem standard.
There are more examples like this and this one that appear to outline what I need, but it's unclear to where you place the code.
What I intend to do is to have column 1 checkable. On next btn press, If checked those rows of data get written to a file.
I still need to understand how to loop thru the selected data, or perhaps I need to get the ids of the checked rows and do another query.
Questions:
How do you add 1 column of editable checkboxes to QTableView?
How do you loop through values in the QTableView data, so values of the checked rows can be accessed?
How do you check all/none?
I think the best way to have a column of checkable cells is to create your item model, e.g. by subclassing the QSqlQueryModel.
You must reimplement the flags() method to make checkable the cells.
Also you need to reimplement the data() method to return the check state and the setData() method and to set the check state. You must implement your own logic to keep track of the check state of every rows (e.g. using an array of Qt::CheckState that you must initialize and resize when the model data changes).
Yuo can start with something like this:
class MyModel : public QSqlQueryModel
{
public:
Qt::ItemFlags flags(const QModelIndex & index) const
{
if(index.column() == 0)
return QSqlQueryModel::flags(index) | Qt::ItemIsUserCheckable;
return QSqlQueryModel::flags(index);
}
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const
{
if(index.column() == 0 && role == Qt::CheckStateRole)
{
//implement your logic to return the check state
//....
}
else
return QSqlQueryModel::data(index, role);
}
bool setData(const QModelIndex & index, const QVariant & value, int role = Qt::EditRole)
{
if(index.column() == 0 && role == Qt::CheckStateRole)
{
//implement your logic to set the check state
//....
}
else
QSqlQueryModel::setData(index, value, role);
}
};
Se also:
Model Subclassing
QAbstractItemModel documentation

Custom delegate Paint persist when editing

I am trying to write custom delegate and custom model as a part of learning Qt.
I made a simple custom model based on QAbstractTableModel. I did not do anything complicated. It only generates the data in its constructor as well as minimally implement the pure virtual function.
I made a custom delegate which display numerical data in terms of bars. I also implemented a spin box as an editor to edit data.
The program works well. I can view, edit and modify data through a QTableView with the delegate set.
But there is a small problem. When I call the editor, the data bar persists, which means I see the data bar at the background and the spin box on top.
Initially, I think it is because the Qt::EditRole in the QAbstractTableModel::data() has not been set properly. But, surprisingly, I find that the Qt::EditRole has never been called.
So, there are two question:
How to remove the data bar when I am having the spin box editor?
Why is the EditRole never been called in my custom model?
Here is part of my code:
My Custom Model:
MyModel::MyModel(QObject* parent):QAbstractTableModel(parent)
{
for (int i = 0; i < 10; ++i)
localData.push_back(i*i);
}
QVariant MyModel::data(const QModelIndex &index, int role) const
{
switch(role)
{
case Qt::EditRole:
qDebug() << "EditRole"; //Never Print Out
return 0;
case Qt::DisplayRole :
if (index.column() == 0)
return (index.row());
if (index.column() == 1)
return (localData.at(index.row()));
default:
return QVariant();
}
}
My Custom Delegate:
void MyDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
{
painter->save();
painter->setPen(Qt::red);
painter->setBrush(Qt::red);
double factor = 0;
if (index.data().toDouble() > 100)
factor = 1;
else
factor = index.data().toDouble() / (double) (100.0);
painter->drawRect(option.rect.x()+5, option.rect.y()+3, (option.rect.width()-10)*factor, option.rect.height()-6);
painter->restore();
}
QWidget* MyDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QSpinBox* box = new QSpinBox(parent);
box->setMinimum(0);
box->setMaximum(100);
return box;
}
Try to set setAutoFillBackground(true) for your view
Editrole is not called because your custom editor does not query the model for that data. You don not set any value for your spin box. Try to set it as:
box->setValue(model.data(Qt::EditRole));
in the MyDelegate::createEditor() function.

QSortFilterProxyModel and lazily populated treeviews

I have implemented a lazily populated treeview by subclassing QAbstractItemModel. The implementation looks something like:
https://gist.github.com/gnufied/db9c4d805e2bb24d8c23
(I am not pasting code inline, so as to mess with messaging)
It is basically a tree representation of hierarchical data stored in table. Now, I want users to be able to sort the rows based on columns. Where columns are, "count" or "reference count". These values are basically integers.
The implementation on its own works, until I throw in QSortFilterProxyModel and I start to get lots of empty rows in the view. The hard problem is, this tends to happen only when I have lots of rows (like thousands or so).
The code for implementing sorting proxy is:
rootItem = RBKit::SqlConnectionPool::getInstance()->rootOfSnapshot(snapShotVersion);
model = new RBKit::HeapDataModel(rootItem, this);
proxyModel = new SortObjectProxyModel(this);
proxyModel->setSourceModel(model);
ui->treeView->setModel(proxyModel);
ui->treeView->setSortingEnabled(true);
I have subclassed QSortFilterProxyModel class and subclass implementation is really simple:
https://gist.github.com/gnufied/115f1a4fae3538534511
The documentation does say -
"This simple proxying mechanism may need to be overridden for source models with more complex behavior; for example, if the source model provides a custom hasChildren() implementation, you should also provide one in the proxy model."
But beyond that, I am not sure - what I am missing.
So, I think I have found the solution and the fix appears to be reimplementing fetchMore in proxy model subclass, because inserted rows reported by source model, do not match place where rows actually exist in view (Rows in view are owned by proxy model), so this seems to have fixed it for me:
#include "sortobjectproxymodel.h"
SortObjectProxyModel::SortObjectProxyModel(QObject *parent) :
QSortFilterProxyModel(parent)
{
}
bool SortObjectProxyModel::hasChildren(const QModelIndex &parent) const
{
const QModelIndex sourceIndex = mapToSource(parent);
return sourceModel()->hasChildren(sourceIndex);
}
int SortObjectProxyModel::rowCount(const QModelIndex &parent) const
{
const QModelIndex sourceIndex = mapToSource(parent);
return sourceModel()->rowCount(sourceIndex);
}
bool SortObjectProxyModel::canFetchMore(const QModelIndex &parent) const
{
if(!parent.isValid())
return true;
else {
const QModelIndex sourceIndex = mapToSource(parent);
return sourceModel()->canFetchMore(sourceIndex);
}
}
void SortObjectProxyModel::fetchMore(const QModelIndex &parent)
{
if (parent.isValid() && parent.column() == 0) {
int row = parent.row();
int startRow = row + 1 ;
const QModelIndex sourceIndex = mapToSource(parent);
RBKit::HeapItem *item = static_cast<RBKit::HeapItem *>(sourceIndex.internalPointer());
if (!item->childrenFetched) {
qDebug() << "Insert New Rows at :" << startRow << " ending at : " << startRow + item->childrenCount();
beginInsertRows(parent, startRow, startRow + item->childrenCount());
item->fetchChildren();
endInsertRows();
}
}
}
Thank you for responding. At this point I really don't care about resorting rows that were lazy loaded (when a node was expanded), so I went ahead and disabled sortingEnabled and disabled dynamicSortFiltertoo.
The new code looks like:
rootItem = RBKit::SqlConnectionPool::getInstance()->rootOfSnapshot(snapShotVersion);
model = new RBKit::HeapDataModel(rootItem, this);
proxyModel = new SortObjectProxyModel(this);
proxyModel->setSourceModel(model);
proxyModel->sort(2, Qt::DescendingOrder);
proxyModel->setDynamicSortFilter(false);
ui->treeView->setModel(proxyModel);
That still leaves with empty rows though.
In my oppinion You don't need to subclass QSortFilterProxyModel for sorting on the top layer. If sortingEnabled == true for your view then the view will perform sorting on the proxy model, which is not desirable as the model should sort itself. What you need is to to call proxyModel->sort(desiredColumn) and that will display your model sorted in the view without altering your data. By default the QSortFilterProxyModel has its dynamicSortFilter property on, which will cause the proxy model to automatically re-sort when data changes or row is inserted or removed. I didn't see emitting dataChanged signal anywhere in your HeapDataModel, so maybe that could be a hint for you to get dynamically sorted rows. If you need to sort subitems then it goes little more complicated and then maybe you'll need to subclasss QSortFilterProxyModel. These model-view abstractions are hard to learn but once you get it you can do miracles rapidly.
rootItem = RBKit::SqlConnectionPool::getInstance()->rootOfSnapshot(snapShotVersion);
model = new RBKit::HeapDataModel(rootItem, this);
proxyModel = new SortObjectProxyModel(this);
proxyModel->setSourceModel(model);
proxyModel->sort(column);
ui->treeView->setModel(proxyModel);

QTableView: When selecting cells, how do I make the first cell selected the current index?

I have a simple class which inherits QTableView and I want the following behavior: when the user selects a few cells, I want the first cell selected to be set as the current index.
So for example if I select from (0, 0) towards (2, 2), when I start typing the text would show up in (0, 0), not (2, 2) which seems to be the default.
I have tried overriding the setSelection function with the following:
void SampleTable::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command)
{
if((command & QItemSelectionModel::Current) != 0)
{
QModelIndex curr = indexAt(rect.topLeft());
selectionModel()->select(curr, QItemSelectionModel::Current);
command ^= QItemSelectionModel::Current;
}
QTableView::setSelection(rect, command);
}
but to no avail. It seems to have something to do with the mouse events, but I can't quite locate the problem in the source code and I'm hoping there's an easier way anyway.
What are you trying to achieve? If you'd like the user to edit/select a single cell only, use setSelectionBehaviour to force this. Otherwise you could try chinfoo's idea but make sure to communicate the behavior in a way the user is able to understand it (i.e. he's able to see that his edit will change the first cell/row).
I figured out the problem and how to fix it, but it's not pretty. The problem is in the mouse moved event for a QAbstractItemView. After much debugging and searching through the source code I found this in qabstractitemview.cpp:
void QAbstractItemView::mouseMoveEvent(QMouseEvent *event)
...
if (index.isValid()
&& (index != d->selectionModel->currentIndex())
&& d->isIndexEnabled(index))
d->selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
}
I fixed it by giving my class a QModelIndex member which stores the last location of the top left QModelIndex (set in my overriden version of setSelection like above) and then I overrode the mouseMoveEvent with this:
void SampleTable::mouseMoveEvent(QMouseEvent *event)
{
QTableView::mouseMoveEvent(event);
if (state() == ExpandingState || state() == CollapsingState || state() == DraggingState || state() == EditingState)
return;
if ((event->buttons() & Qt::LeftButton)) {
if (m_topLeft.isValid())
{
selectionModel()->setCurrentIndex(m_topLeft, QItemSelectionModel::NoUpdate);
}
}
}
Not a pretty solution, but it works.
The QtableWidget class has a signal itemSelectionChanged(), connect it to your custom slot. In that slot, use selectedIndexes() to get all indexes, then use setCurrentIndex() to set the cell which you want to be the current index.