Add virtual column to Qt SQL model using a proxy - c++

I display an SQL table in a view using a QSqlTableModel.
I want to display an additional status column based on the row data, for that I use a custom QIdentityProxyModel where I increase the columnCount and return data for that new virtual column which does not exist in the QSqlTableModel.
int MyProxyModel::columnCount(const QModelIndex& parent) const
{
return sourceModel() ? (sourceModel()->columnCount() + 1) : 0;
}
QVariant MyProxyModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (section == columnCount()-1 &&
orientation == Qt::Horizontal &&
role == Qt::DisplayRole)
{
return tr("MyHeader");
}
return QIdentityProxyModel::headerData(section, orientation, role);
}
QVariant MyProxyModel::data(const QModelIndex &proxyIndex, int role) const
{
qDebug() << "data " << proxyIndex.row() << proxyIndex.column();
// ...never called for my extra column (== columnCount()-1)
if (proxyIndex.column() == columnCount()-1 && role == Qt::DisplayRole)
return QString("I am virtual");
return QIdentityProxyModel::data(proxyIndex, role);
}
Edit: I changed the code for something more simple regarding to the comments. I still have the same problem.
My problem is that the view never asks data for my virtual column, it calls data() for all other columns of the actual SQL table but not the last virtual one, what have I missed ?
Also, the header data is working well for my extra column, the problem is only with the data. The view draws the extra column, but content is empty (even alternating row background is not painted).
Thx !

The view needs to get the QModelIndex objects for the virtual column, so I also needed to override the index function in the proxy :
QModelIndex MyProxyModel::index(int row, int column, const QModelIndex &parent) const
{
if (column == columnCount()-1)
return createIndex(row, column);
return QIdentityProxyModel::index(row, column);
}
I didn't mind the parent because I only have a table (from database), though I do not know how it could be dealt with if needed because createIndex does not allow to specify a parent.

The m_mySqlTableColumnCount member is unnecessary. You'd have to ensure it's always correct by listening to the source model's signals that update the column count. Alas, it's unnecessary. You want to pass the column count request through to the source model:
int MyProxyModel::columnCount(const QModelIndex& parent) const
{
return sourceModel() ? (QIdentityProxyModel::columnCount() + 1) : 0;
}
Then:
QVariant MyProxyModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (section == columnCount()-1 &&
orientation == Qt::Horizontal &&
role == Qt::DisplayRole)
{
return tr("MyHeader");
}
return QIdentityProxyModel::headerData(section, orientation, role);
}
QVariant MyProxyModel::data(const QModelIndex &proxyIndex, int role) const
{
if (proxyIndex.column() == columnCount()-1) {
qDebug() << proxyIndex.row() << proxyIndex.column();
...
}
return QIdentityProxyModel::data(proxyIndex, role);
}

Related

Qt: Updating data in a TableView with QAbstractTableModel

I try to implement a table view with two columns. The right columns shows parameters, which should not be changed by runtime, and left column shows values, that should be updated constantly at runtime. For this I implement a data model (derived from QAbstractTableModel). After setting up this I got a table with 3 rows and 2 columns and the right columns shows the parameters. The left columns however remains empty. And after hours I didn't find a solution for this.
This is the relevant code of my data model I think:
QVariant DataTableModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid()) {
return QVariant();
}
if (index.row() >= m_parameter.size()) {
return QVariant();
}
if (role == Qt::DisplayRole || role == Qt::EditRole) {
switch (index.column()) {
case 0:
return m_parameter.at(index.row());
break;
case 1:
return m_value.at(index.row());
break;
}
}
return QVariant();
}
bool DataTableModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
if (!index.isValid() && role != Qt::EditRole) {
return false;
}
//change only values in column 1
if (index.column() == 1) {
m_value.replace(index.row(), value.toString());
emit dataChanged(index, index, { role });
return true;
}
return true;
}
bool DataTableModel::insertRows(int row, int count, const QModelIndex& parent)
{
//the following two methods emits signals that tells the view that the data should be changed
beginInsertRows(QModelIndex(), row, row + count - 1);
for (int i = 0; i < count; ++i) {
m_parameter.insert(row, "");
m_value.insert(row, "");
}
endInsertRows();
return true;
}
In the main class I have a function that calles the functions of the model:
void QVideoMeter::QAddTableContent()
{
QVariant value(QValueList());
m_tableDataModel->insertRows(0, 3, QModelIndex()); //insert 3 rows
m_tableDataModel->AddData(QParameterList());
m_tableDataModel->setData(QModelIndex(), value, 2);
}
QList<QString> QVideoMeter::QValueList()
{
QList<QString> values;
values.append("Test");
values.append("Hallo");
values.append("Welt");
return values;
}
Please, can someone of you review my code and tell me what I'm doing wrong?
Refer to the below two links. They provide the necessary concepts and examples:
QAbstractItemModel Subclass
Editable Tree Model Example
An editable model needs to provide implementations of setData() and
setHeaderData(), and must return a suitable combination of flags from
its flags() function.
Since this example allows the dimensions of the model to be changed,
we must also implement insertRows(), insertColumns(), removeRows(),
and removeColumns().
void MainWindow::insertRow()
{
const QModelIndex index = view->selectionModel()->currentIndex();
QAbstractItemModel *model = view->model();
if (!model->insertRow(index.row()+1, index.parent()))
return;
updateActions();
for (int column = 0; column < model->columnCount(index.parent()); ++column) {
const QModelIndex child = model->index(index.row() + 1, column, index.parent());
model->setData(child, QVariant(tr("[No data]")), Qt::EditRole);
}
}

Why can't you use setData() to set background color of a Cell in QTreeView?

I'm using the following code to try to change the background color of a cell at a given QModelIndex.
ui->TreeView->model()->setData(index, QVariant(QBrush (QColor(Qt::green))) , Qt::BackgroundRole);
where index is given by the dataChanged() signal.
This isn't working. Any ideas why?
Here's my reimplemented setData function.
bool TreeModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
TreeItem *item = getItem(index); //gets item a given index
bool result = item->setData(index.column(), value);
if (result)
emit dataChanged(index, index);
return result;
}
And here is the setData method for the underlying item:
bool TreeItem::setData(int column, const QVariant &value)
{
if (column < 0 || column >= itemData.size())
return false;
itemData[column] = value;
return true;
}
Apologies for the vague question. I've managed to solve it by myself so I will post here in case anyone is ever stuck on a similar issue.
The problem for me was that I hadn't reimplemented QAbstractItemView's data() method to account for the new role.
QVariant TreeModel::data(const QModelIndex &index, int role) const
{
TreeItem *item = getItem(index);
if (role == Qt::BackgroundRole)
return item->data(index.column());
//and so on...
AFAIK the data() method gives the treeview the data out of the model that it needs to present. Within this method I hadn't accounted for the case when role == Qt::BackgroundRole so the view was never given the appropriate information out of the model.

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

QTableView and unique IDs

I'm new to Qt and coming from C# .Net. I am trying to replicate a fairly simple program I wrote in C# in Qt as a learning tool. I have a data model that inherits QAbstractTableModel and implements:
rowCount,
columnCount,
data,
setData,
headerData
flags
My data structure is a map
std::map<int, CBDataRow>
So the idea was that each row would have a unique int ID and a struct containing the rest of the row information.
What I am stuck on now is how to update my data model when the user makes an edit in the QTableView object. The setData function does get called. Here it is:
bool CBDatabaseModel::setData(const QModelIndex &index, const QVariant &value, int role) {
bool success = false;
if(role == Qt::EditRole) {
success = m_data.UpdateRow(index, value);
}
if(success) {
emit dataChanged(index, index);
return true;
} else {
return false;
}
}
Now you see that the UpdateRow() function gets called here on an edit. That function should find the unique id in the map and update the appropriate members of its CBDataRow struct. My problem is that I have no idea how to get the unique ID out of the QModelIndex object that gets passed into the edit function.
For example:
User edits the "CB Name" cell of row 3. The data in row three has a unique ID of 100. That value of 100 is in the QTableView in a hidden column, column index 0. So what I need to do is simply:
(Psuedo code)
it = m_data.find(unique_id);
it->second.cb_name = value.toString();
Since the user was editing column 1, how do i find the unique ID that is contained in column 0?
I would recommend to reimplement index() method of your model and there create indexes by using the call createIndex(row,col, unique_id);
Then in any place where you got QModelIndex, you can always extract unique_id = model_index.internalId();
In my opinion you can store your data in an array and index your element simply accessing through index.row():
QVector<CBDataRow> m_data;
....
bool CBDatabaseModel::setData(const QModelIndex &index, const QVariant &value, int role) {
bool success = false;
if(role == Qt::EditRole && index.row() < m_data.size()) {
success = m_data.at(index.row()).UpdateRow(index.column(), value);
}
if(success) {
emit dataChanged(index, index);
return true;
} else {
return false;
}
}
if you are worrying about element sorting you can derive your model from a QSortFilterProxyModel (instead of a QAbstractTableModel) and then reimplement
bool CBDatabaseModel::lessThan(const QModelIndex &left,
const QModelIndex &right) const
without define a internal id by yourself.
I hope this can help you.

QSortFilterProxyModel and columnCount

I have 2 models:
MyModel (inherits QAbstractItemModel, its tree) and MyProxyModel (inherits QSortFilterProxyModel).
Column count of MyModel is 1, and items in MyModel contain information which should be showed in QTableView using MyProxyModel. I used MyProxyModel with MyProxyModel::columnCount() == 5.
I overloaded function MyProxyModel::data(). But table view shows only data from column 1(MyModel::columnCount).
After debugging I found that MyProxyModel::data() gets only indexes with column < MyModel::columnCount() (It seems that it uses MyModel::columnCount() and ignores MyProxyModel::columnCount()).
In the table view header sections count is equal to MyProxyModel::columnCount() (It's OK ;)).
How can I show information in cells with column > MyModel::columnCount()?
MyModel.cpp:
int MyModel::columnCount(const QModelIndex& parent) const
{
return 1;
}
MyProxyModel.cpp:
int MyProxyModel::columnCount(const QModelIndex& parent) const
{
return 5;
}
QVariant MyProxyModel::data(const QModelIndex& index, int role) const
{
const int r = index.row(),
c = index.column();
QModelIndex itemIndex = itemIndex = this->index(r, 0, index.parent());
itemIndex = mapToSource(itemIndex);
MyModel model = dynamic_cast<ItemsModel*>(sourceModel());
Item* item = model->getItem(itemIndex);
if(role == Qt::DisplayRole)
{
if(c == 0)
{
return model->data(itemIndex, role);
}
return item->infoForColumn(c);
}
return QSortFilterProxyModel::data(index, role)
}
As Krzysztof Ciebiera had said, in not as many words: your data and columnCount methods are never called, because they are not declared correctly. The virtual methods you're supposed to implement have the signatures
int columnCount(const QModelIndex&) const;
QVariant data(const QModelIndex&, int) const;
while your methods have differing signatures
int columnCount(QModelIndex&) const;
QVariant data(QModelIndex&, int);
and thus they won't get called. Notice that your methods incorrectly expect a non-const reference to a model index. Your data() method also expects non-const object instance.