QTableView: change rowCount on the fly - c++

I use a QTableView to show read-only data. The model is based upon a QList of a custom type that contains another QList. Something like this:
typedef struct
{
int range;
QString description;
} Field;
typedef struct
{
QString name;
QList<Field> fields;
} Item;
QList<Item> items;
In my QAbstractTableModel implementation I have a slot that selects the current item:
void setCurrentItem(int idx)
{
// checks for errors (omissis)
currentItemIdx = idx;
// ask to redraw the table
emit dataChanged(this->index(0, 0), this->index(rowCount(), columnCount()));
}
All the model's logic rely on that item, for example:
int MyModel::rowCount(const QModelIndex&) const {
return items.at(m_currentItemIdx).fields.count();
}
QVariant MyModel::data(const QModelIndex &index, int role) const {
if (!index.isValid()) return QVariant();
if (index.row() >= items.at(m_currentItemIdx).fields.count()) return QVariant();
Field f = items.at(m_currentItemIdx).fields.at(index.row);
switch (role) {
case Qt::DisplayRole:
switch (index.column()){
case 0:
return f.range;
break;
case 1:
return f.description;
break;
default:
return QVariant();
}
break;
}
return QVariant();
}
The problem is when I change the current item using setCurrentItem() the code uses the new data, but the QTableView doesn't change its rows and then doesn't request the new content.
I thought emit dataChanged() was enought but it seems it doesn't.
What should I do to notify the view that I've changed the size of my model?
I'm trying to avoid the insert/remove row mechanism because I don't want to change the actual data in the QList, I just want to pick up a different set of information.

Call the QAbstractItemModel::​beginResetModel() before and the QAbstractItemModel::​endResetModel() after changing your model data.
Like this:
void setCurrentItem(int idx)
{
beginResetModel();
currentItemIdx = idx;
endResetModel();
}

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.

Add virtual column to Qt SQL model using a proxy

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);
}

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.

Show different levels of C++ DOM model in different QML views

I need to create a QML application with pages of items that are defined by a xml file. The xml must be parsed in C++. Each page of items will be a StackView page containing a ListView of the items. Each item on a page has several values defining text, colour, size, etc.
As a start, my DOM model creation is based on the Qt Simple DOM Model Example. The model is wrapped with a QAbstractItemModel.
I've exposed the C++ model to QML using the rootContext->setContextProperty.
I'm struggling with splitting the data between the StackView pages. I assume that I need to assign the different levels of the hierarchical model(page parent and item children) to different UserRoles in order to filter them to the QML views but I'm struggling to find any suitable examples of how to go about this.
So my question is:
Can you show me an example of assigning UserRoles to a C++ DOM Model and the associated data method for returning the item data by UserRole and hierarchical level?
or
Am I going in the wrong direction and there's a better way to achieve this?
OK, I've now got this going. After some more playing I decided to select the data by xml Node name - I used "Page" & "Item". If anyone else is looking at hierarchical models in C++ and views in QML the other key requirement I found to implementing a solution is the QML DelegateModel (formally VisualDataModel). Here are the snippets that directly answer the questions...
From DomModel.h
public:
enum menuRoles
{
PageNumberRole = Qt::UserRole + 1,
PageNameRole,
ItemNumberRole,
ItemNameRole
};
...
QVariant data(const QModelIndex &index, int role) const;
...
protected:
QHash<int, QByteArray> roleNames() const;
From DomModel.cpp
QVariant DomModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
else
{
DomItem *item = static_cast<DomItem*>(index.internalPointer());
QDomNode node = item->node();
if(node.nodeName() == "Page")
{
switch (role)
{
case PageNumberRole:
return node.attributes().namedItem("number").nodeValue();
break;
case PageNameRole:
return node.attributes().namedItem("name").nodeValue();
break;
default:
return QVariant();
break;
}
}
else if(node.nodeName() == "Item")
{
switch (role)
{
case ItemNumberRole:
return node.attributes().namedItem("number").nodeValue();
break;
case ItemNameRole:
return node.attributes().namedItem("name").nodeValue();
break;
default:
return QVariant();
break;
}
}
else
return QVariant();
}
}
...
QHash<int, QByteArray> DomModel::roleNames() const
{
// This tells the subscribing views what data roles are available
// Any changes must be reflected in the DomModel::data function
QHash<int, QByteArray> roles;
roles[PageNumberRole] = "pageNumber";
roles[PageNameRole] = "pageName";
roles[ItemNumberRole] = "itemNumber";
roles[ItemNameRole] = "itemName";
return roles;
}