I have inherited QAbstractTableModel as described here. I have coded setData as:
bool TableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if(role == Qt::EditRole)
{
Data[index.row()][index.column()]= value.toString();
qDebug()<<Data[index.row()][index.column()];//to check entered data passed or not
}
return true;
}
Qt::ItemFlags TableModel::flags(const QModelIndex &index) const
{
return Qt::ItemIsEditable|Qt::ItemIsEnabled|Qt::ItemIsSelectable;
}
and in mainwindow I have connected model and view as:
connect(model,SIGNAL(dataChanged(QModelIndex,QModelIndex)),ui->tableView,SLOT(dataChanged(QModelIndex,QModelIndex)));
but after editing data is not updated in view. I tried with emitting dataChanged signal from setData function but it is not working. What I missed in my code?
data function:
QVariant TableModel::data(const QModelIndex &index, int role) const
{
switch(role)
{
case Qt::DisplayRole:
if(index.column()==0)
{
return index.row()+1;
}
break;
case Qt::DecorationRole:
if(index.column()==1)
{
QColor c;
c.setRgb(0,200,200,200);
return c;
}
break;
case Qt::ToolTipRole:
if(index.column()==2)
{
return "colum 3";
}
break;
case Qt::StatusTipRole:
return "Not editable";
break;
case Qt::FontRole:
{
QFont f;
f.setFamily("Times");
f.setBold(true);
f.setKerning(true);
return f;
}
break;
case Qt::TextAlignmentRole:
{
return Qt::AlignCenter;
}
break;
case Qt::BackgroundRole:
if(index.column()==2)
{
QColor b;
b.setRgb(100,100,250,200);
return b;
}
break;
case Qt::ForegroundRole:
{
QColor b;
b.setRgb(0,100,250,200);
return b;
}
case Qt::CheckStateRole:
if(index.column()==3)
{
if(index.row()==1) return true;
return false;
}
break;
case Qt::InitialSortOrderRole:
return Qt::AscendingOrder;// use not clear
}
return QVariant();
}
There are some notes here that can be taken.
You should emit the dataChanged() signal once the data is updated properly.
You do not seem to use the 2D array variable called "Data" in your data() function that you set up properly in the setData() method. You need to use the (same) variable at both places.
You should not connect the dataChanged() signal to any slot. It will work out automatically for you.
Related
Quite often I find myself in need of some custom scheme model, mandating the implementation of more and more models, made even more tedious by the inability of QObject derived classes to be templates.
Qt has the QStandardItemModel but that seems a bit verbose and inconvenient to use, especially from the qml side, and total overkill for a basic list model.
There is also the basic qml ListModel, but that is limited and not elegant to use on the C++ side, and I do suspect a tad more bloated than it needs to be.
Qt has QVariant, which is what its model/view architecture uses internally, so it is surprising that the framework doesn't provide something as simple as:
// qml code
VarMod {
roles: ["name", "age", "weight"]
Component.onCompleted: {
insert(["Jack", 34, 88.5], -1) // qml doesn't support
insert(["Mary", 26, 55.3], -1) // default arg values
}
}
// cpp code
VarMod vm { "name", "age", "weight" }; // member declaration
vm.insert({ "Jack", 34, 88.5 });
vm.insert({ "Mary", 26, 55.3 });
And here it is.
Note that you do have to be responsible with the parameters, as there is no type safety, in fact it has implicit analog to ListModel's dynamicRoles - that is, it will accept and work with any QVariant compatible value on every role slot.
As for memory efficiency, consider that QVariant has 8 bytes for data, plus 4 bytes for type id, plus another 4 bytes of padding, for a total of 16 bytes. That is not insignificant if you are using it for small data types, like say bool, so in case you have a data scheme that has a lot of small (1 - 4 bytes) fields and a scores of items, implementing a full model will still be the better option. It is still a lot better than the generic object model I am using, which has to carry the bloat of QObject, and even more significant in the case of qml objects.
Additionally, QVariant being 16 bytes, I opted to not use the convenience of QVariantList for data storage, which has an underlying QList, making the situation worse than it needs to be. Although that is fixed in Qt 6, which gets rid of QList as it is, and replaces it with an alias of QVector. Still, std::vector helps to avoid that in any case, plus it might actually be a tad faster, since it doesn't have to deal with COW and atomic ref counters. There are several auxiliary methods to help with pre-allocation and release of memory as well.
The model has a safeguard against the change the roles for obvious reasons, the latter is primarily intended to be initialized just once, but there is reset() that is intended to be used in a more dynamic qml context, making it possible to redefine the model schema on the fly and provide a compatible delegate. For the sake of certainty, the roles can only be redefined after the model has been explicitly reset.
There is a minute difference in inserting, on the c++ side, the parameter pack is passed wrapped in {}, in qml it is wrapped in [], both leveraging implicit conversion in the context specific way. Also, note that qml currently doesn't support omitting parameters with default values provided on the c++ side, so for appending you do have to provide an invalid index. Naturally, it would be trivial to add convenience methods for appending and prepending if needed.
In addition to the syntax example of the question, it is also possible to add multiple items at once, from "declarative-y" qml structure such as:
let v = [["Jack", 34, 88.5],
["Mary", 26, 55.3],
["Sue", 22, 69.6]]
vm.insertList(v, -1)
Finally, type safety is possible to implement if the scenario really calls for it, then each role can be specified with the expected type to go with it, such as:
VarMod vm {{"name", QMetaType::QString},
{"age", QMetaType::Int},
{"weight", QMetaType::QReal}};
and then iterating and making the necessary checks to ensure type safety when inserting.
Update: I also added serialization, and save/load from disk features, note that this will serialize the data together with the mode schema.
class VarMod : public QAbstractListModel {
Q_OBJECT
Q_PROPERTY(QVariantList roles READ roles WRITE setRoles NOTIFY rolesChanged)
QVariantList vroles;
QVariantList roles() const { return vroles; }
QHash<int, QByteArray> _roles;
std::vector<std::vector<QVariant>> _data;
inline bool checkArgs(int rc) const {
if (rc == _roles.size()) return true;
qWarning() << "arg size mismatch, got / expected" << rc << _roles.size();
return false;
}
inline bool inBounds(int i, bool ok = false) const {
if (i > -1 && i < (int)_data.size()) return true;
if (!ok) qWarning() << "out of bounds" << i; // do not warn if intentionally appending
return false;
}
inline bool validRole(int r) const { return (r > -1 && r < _roles.size()); }
protected:
QHash<int, QByteArray> roleNames() const override { return _roles; }
int rowCount(const QModelIndex &) const override { return _data.size(); }
QVariant data(const QModelIndex &index, int r) const override {
r = r - Qt::UserRole - 1;
if (inBounds(index.row()) && validRole(r)) return _data[index.row()][r];
return QVariant();
}
public:
VarMod() {} // for qml
VarMod(std::initializer_list<QByteArray> r) {
int rc = Qt::UserRole + 1;
for (const auto & ri : r) {
_roles.insert(rc++, ri);
vroles << QString::fromLatin1(ri);
}
rolesChanged();
}
inline void insert(std::initializer_list<QVariant> s, int i = -1) {
if (!checkArgs(s.size())) return;
insert(QVariantList(s), i);
}
inline bool setItem(int i, std::initializer_list<QVariant> s) {
if (checkArgs(s.size())) return setItem(i, QVariantList(s));
return false;
}
void setRoles(QVariantList r) {
if (_roles.empty()) {
int rc = Qt::UserRole + 1;
for (const auto & vi : r) _roles.insert(rc++, vi.toByteArray());
vroles = r;
rolesChanged();
} else qWarning() << "roles are already initialized";
}
void read(QDataStream & d) {
reset();
QVariantList vr;
d >> vr;
quint32 s;
d >> s;
_data.resize(s);
for (uint i = 0; i < s; ++i) {
_data[i].reserve(vr.size());
for (int c = 0; c < vr.size(); ++c) {
QVariant var;
d >> var;
_data[i].push_back(std::move(var));
}
}
setRoles(vr);
beginResetModel();
endResetModel();
}
void write(QDataStream & d) const {
d << vroles;
d << (quint32)_data.size();
for (const auto & v : _data) {
for (const auto & i : v) d << i;
}
}
public slots:
void insert(QVariantList s, int i) {
if (!inBounds(i, true)) i = _data.size();
if (!checkArgs(s.size())) return;
beginInsertRows(QModelIndex(), i, i);
_data.insert(_data.begin() + i, { s.cbegin(), s.cend() });
endInsertRows();
}
void insertList(QVariantList s, int i) {
if (!inBounds(i, true)) i = _data.size();
int added = 0;
for (const auto & il : s) {
QVariantList ll = il.value<QVariantList>();
if (checkArgs(ll.size())) {
_data.insert(_data.begin() + i + added++, { ll.cbegin(), ll.cend() });
}
}
if (added) {
beginInsertRows(QModelIndex(), i, i + added - 1);
endInsertRows();
}
}
bool setData(int i, int r, QVariant d) {
if (!inBounds(i) || !validRole(r)) return false;
_data[i][r] = d;
dataChanged(index(i), index(i));
return true;
}
bool setDataStr(int i, QString rs, QVariant d) { // a tad slower
int r = _roles.key(rs.toLatin1()); // role is resolved in linear time
if (r) return setData(i, r - Qt::UserRole - 1, d);
qWarning() << "invalid role" << rs;
return false;
}
bool setItem(int i, QVariantList d) {
if (!inBounds(i) || !checkArgs(d.size())) return false;
_data[i] = { d.cbegin(), d.cend() };
dataChanged(index(i), index(i));
return true;
}
QVariantList item(int i) const {
if (!inBounds(i)) return QVariantList();
const auto & v = _data[i];
return { v.begin(), v.end() };
}
QVariant getData(int i, int r) const {
if (inBounds(i) && validRole(r)) return _data[i][r];
return QVariant();
}
QVariant getDataStr(int i, QString rs) const {
int r = _roles.key(rs.toLatin1()); // role is resolved in linear time
if (r) return getData(i, r);
qWarning() << "invalid role" << rs;
return QVariant();
}
QVariantList take(int i) {
QVariantList res = item(i);
if (res.size()) remove(i);
return res;
}
bool swap(int i1, int i2) {
if (!inBounds(i1) || !inBounds(i2)) return false;
std::iter_swap(_data.begin() + i1, _data.begin() + i2);
dataChanged(index(i1), index(i1));
dataChanged(index(i2), index(i2));
return true;
}
bool remove(int i) {
if (!inBounds(i)) return false;
beginRemoveRows(QModelIndex(), i, i);
_data.erase(_data.begin() + i);
endRemoveRows();
return true;
}
void clear() {
beginResetModel();
_data.clear();
_data.shrink_to_fit();
endResetModel();
}
void reset() {
clear();
_roles.clear();
vroles.clear();
rolesChanged();
}
void reserve(int c) { _data.reserve(c); }
int size() const { return _data.size(); }
int capacity() const { return _data.capacity(); }
void squeeze() { _data.shrink_to_fit(); }
bool fromFile(QString path) {
QFile f(path);
if (!f.open(QIODevice::ReadOnly)) return false;
QDataStream d(&f);
read(d); // assumes correct data
return true;
}
bool toFile(QString path) const {
QFile f(path);
if (!f.open(QIODevice::WriteOnly)) return false;
QDataStream d(&f);
write(d);
return true;
}
signals:
void rolesChanged();
};
I also created this sorting/filtering view to supplement the model:
class View : public QSortFilterProxyModel {
Q_OBJECT
Q_PROPERTY(QJSValue filter READ filter WRITE set_filter NOTIFY filterChanged)
Q_PROPERTY(bool reverse READ reverse WRITE setReverse NOTIFY reverseChanged)
bool reverse() const { return _reverse; }
void setReverse(bool v) {
if (v == _reverse) return;
_reverse = v;
reverseChanged();
sort(0, (Qt::SortOrder)_reverse);
}
bool _reverse = false;
mutable QJSValue m_filter;
QJSValue & filter() const { return m_filter; }
void set_filter(QJSValue & f) {
if (!m_filter.equals(f))
m_filter = f;
filterChanged();
invalidateFilter();
}
}
public:
View(QObject *parent = 0) : QSortFilterProxyModel(parent) { sort(0, (Qt::SortOrder)_reverse); }
signals:
void filterChanged();
void reverseChanged();
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &) const override {
if (!m_filter.isCallable()) return true;
VarMod * vm = qobject_cast<VarMod *>(sourceModel());
if (!vm) {
qWarning() << "model is not varmod";
return true;
}
return m_filter.call({_engine->toScriptValue(vm->item(sourceRow))}).toBool();
}
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override {
VarMod * vm = qobject_cast<VarMod *>(sourceModel());
if (!vm) {
qWarning() << "model is not varmod";
return false;
}
return vm->getData(left.row(), sortRole()) < vm->getData(right.row(), sortRole());
}
};
For sorting, you just have to specify the sorting role, note that it is the index of the "column" rather than the int value from the roles hash. For filtering it works via a qml functor that receives the model item as a JS array, and expects to return a bool, a c++ functor can be easily added via std::function if needed. Also note that it needs a pointer to the actual qml engine.
View {
id: vv
sourceModel: vm
sortRole: sr.value
reverse: rev.checked
filter: { sa.value; o => o[1] < sa.value } // "capturing" sa.value to react to value changes
}
I am using this Example http://doc.qt.io/qt-5/qtwidgets-itemviews-editabletreemodel-example.html and need to pass a Color as Forgoundroll to the data, but cant figure it out.
In the treemodel.cpp i have altered the data as following..
QVariant TreeModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (role != Qt::DisplayRole && role != Qt::EditRole && role != Qt::ForegroundRole)
return QVariant();
TreeItem *item = getItem(index);
if(role==Qt::ForegroundRole) {
QBrush redBackground(QColor(Qt::red));
return redBackground;
} else
return item->data(index.column());
}
... which works (items get the red color, but need to control the color from the mainwindow.cpp and let user set it and have different colors per column/row. Apparently i need to alter the Treemodel:setdata method, but cant figure it out.
So seeking for the setdata method..
bool TreeModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (role != Qt::EditRole && role != Qt::ForegroundRole )
return false;
TreeItem *item = getItem(index);
bool result;
if(role==Qt::ForegroundRole ) {
//do what ???????
} else {
result= item->setData(index.column(), value);
}
if (result) emit dataChanged(index, index);
return result;
}
From mainwindow.cpp i need to set it like ..
model->setData(child, QVariant(rowdata.at(column)), Qt::EditRole); // this provides the text of the inserted row
model->setData(child, QVariant(QBrush(Qt::red)), Qt::ForegroundRole); // this to provide its color
... but i get the text of the color #ffffff instead (in red color though).
Any help would be appreciated. Thanks
You have to save the color somewhere. One option is to add it to the TreeItem:
class TreeItem
{
public:
...
void setColor(const QColor& color) { this->color = color; }
const QColor& getColor() const { return color; }
private:
...
QColor color;
};
And in the model, you simply set the color if it's the appropriate role, something along these lines:
bool TreeModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
TreeItem *item = getItem(index);
bool result = false;
switch (role)
{
case Qt::EditRole:
result = item->setData(index.column(), value);
break;
case Qt::ForegroundRole:
item->setColor(value.value<QColor>());
result = true;
break;
default:
break;
}
if (result)
emit dataChanged(index, index);
return result;
}
And similarly in getData(), you return something like item->getColor().
Also, you don't have to use QBrush, AFAIK you can simply return QColor as a ForegroundRole.
I am learning the Model-View in Qt by the AddressBook example. https://doc.qt.io/qt-5/qtwidgets-itemviews-addressbook-example.html
And I find something interesting. The code construct a TableModel class besed on QAbstractTableModel. In the override setData function, it emit the dataChanged signal. But, there is no signal emit in removeRows/insertRows. Then, how can these function update the View.
bool TableModel::removeRows(int position, int rows, const QModelIndex &index)
{
Q_UNUSED(index);
beginRemoveRows(QModelIndex(), position, position + rows - 1);
for (int row = 0; row < rows; ++row) {
listOfPairs.removeAt(position);
}
endRemoveRows();
return true;
}
bool TableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.isValid() && role == Qt::EditRole) {
int row = index.row();
QPair<QString, QString> p = listOfPairs.value(row);
if (index.column() == 0)
p.first = value.toString();
else if (index.column() == 1)
p.second = value.toString();
else
return false;
listOfPairs.replace(row, p);
emit(dataChanged(index, index));
return true;
}
return false;
}
Make note of function calls beginRemoveRows() and endRemoveRows() in the function removeRows() of your posted code.
The beginRemoveRows() function, emits a signal rowsAboutToBeRemoved(). This is how connected views can know about the deletion and the underlying connected views must handle before data is removed.
Look the note in below documentation:
https://doc.qt.io/qt-5/qabstractitemmodel.html#beginRemoveRows
I have a matrix of data, I simply stored it as a QList of QStringLists, all containing an equal number of QStrings. In this way, the data looks almost like a spreadsheet.
I use a QTableView to present this data to the user:
void DialogwitQTableView::setData(QList<QStringList> s)
{
/* Create the data model */
// 1. give it some headers
QStandardItemModel model = new QStandardItemModel(s.count(),25,this); //x Rows and 25 Columns
model->setHorizontalHeaderItem(0, new QStandardItem(QString("Column 1")));
model->setHorizontalHeaderItem(1, new QStandardItem(QString("Column 2")));
// ...
model->setHorizontalHeaderItem(24, new QStandardItem(QString("Column 25")));
// 2. populate the model with the data
for(int i = 0; i < s.count() ; i++)
{
for(int j = 0; j < s[i].count() ; j++)
model->setItem(i,j,new QStandardItem(QString(s[i][j])));
}
ui->NameOfTheTableView->setModel(model);
}
Now, if the user wants to change some of the data, he will just doubleclick in the QTableView in the Dialogbox and edits what he wants.
How do I get that edit also in the data? How can I adapt the QStringList with that new information?
If I search for documentation, I mostly find QTableViews linked to databases, but I don't see how this will work with a simple datastructure in memory. If I go to QtDesigner and click on "go to slots" for the TableView, I also do not see a slot called "datachanged" or anything similar.
Any thoughts? I feel pretty stuck and I am probably overviewing something, any tip is very welcome.
Looking at the doco, a QTableView inherits 6 signals from QAbstractItemView
http://doc.qt.digia.com/qt/qabstractitemview.html#signals
This class has all sorts of functionality for capturing edits, and edit triggers.
Once you can catch when the data is changed you can recommit it back to your model if you are using an MVC view. I am sure there are a lot of examples.
Hope that helps.
I think that for more complicated cases it's always best to use the abstract classes, more specifically QAbstractTableModel in this case.
Looking at this file, I just replaced Contact with StringList and changed the getters and setters. Check it out:
https://doc.qt.io/qt-5/qtwidgets-itemviews-addressbook-tablemodel-cpp.html
TableModel::TableModel(QObject *parent) :
QAbstractTableModel(parent)
{
}
TableModel::TableModel(QList<QStringList> stringLists, QObject *parent) :
QAbstractTableModel(parent),
stringLists(stringLists)
{
}
int TableModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return stringLists.size();
}
int TableModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return 2; // put the amount of columns here
}
QVariant TableModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) return QVariant();
if (index.row() >= stringLists.size() || index.row() < 0) return QVariant();
if (role == Qt::DisplayRole) {
const auto &strings = stringLists.at(index.row());
if (index.column() == 0)
return strings[0];
else if (index.column() == 1)
return strings[1];
}
return QVariant();
}
QVariant TableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role != Qt::DisplayRole)
return QVariant();
if (orientation == Qt::Horizontal) {
switch (section) {
case 0:
return tr("String 1");
case 1:
return tr("String 2");
default:
return QVariant();
}
}
return QVariant();
}
bool TableModel::insertRows(int position, int rows, const QModelIndex &index)
{
Q_UNUSED(index);
beginInsertRows(QModelIndex(), position, position + rows - 1);
for (int row = 0; row < rows; ++row)
stringLists.insert(position, { QString(), QString() });
endInsertRows();
return true;
}
bool TableModel::removeRows(int position, int rows, const QModelIndex &index)
{
Q_UNUSED(index);
beginRemoveRows(QModelIndex(), position, position + rows - 1);
for (int row = 0; row < rows; ++row)
stringLists.removeAt(position);
endRemoveRows();
return true;
}
bool TableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.isValid() && role == Qt::EditRole) {
int row = index.row();
auto strings = stringLists.at(row);
if (index.column() == 0)
strings[0] = value.toString();
else if (index.column() == 1)
contact[1] = value.toString();
else
return false;
stringLists.replace(row, contact);
emit dataChanged(index, index, {role});
return true;
}
return false;
}
Qt::ItemFlags TableModel::flags(const QModelIndex &index) const
{
if (!index.isValid()) return Qt::ItemIsEnabled;
return QAbstractTableModel::flags(index) | Qt::ItemIsEditable;
}
QList<QStringList> TableModel::getStringLists() const
{
return stringLists;
}
I also highly recommend that you read this:
https://doc.qt.io/qt-5/modelview.html
Hope it helps.
If I search for documentation, I mostly find QTableViews linked to
databases, but I don't see how this will work with a simple
datastructure in memory.
QTableView is a part of Qt's Model/View framework. There are bunch of examples of model-views.
How do I get that edit also in the data? How can I adapt the
QStringList with that new information?
At least these solutions exists:
You can grab all data from QStandardItemModel via item method.
Connect to QStandardItemModel::itemChanged signal.
You can make your own model via subclassing (and I suggest to base on QAbstractTableModel) and implement several methods (data, setData + several utility methods).
Shown below is the my custom table model. I am trying to use that tablemodel together with a QTableView. If the method append of the table model is called I would expect the table view to update its contents. But it doesn't and I don't know why. If however I use that same table model together with a QListView, everything works fine, i.e. the list view does update its contents, when append of the table model gets called. Is there anything special I need to do in case of the QTableView?
class MyModel : public QAbstractTableModel
{
public:
MyModel(QObject* parent=NULL) : QAbstractTableModel(parent) {}
int rowCount(const QModelIndex &parent = QModelIndex()) const {
return mData.size();
}
int columnCount(const QModelIndex &parent = QModelIndex()) const {
return 2;
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const {
if (!index.isValid()) {
return QVariant();
}
if (role == Qt::DisplayRole) {
if (index.column()==0) {
return QVariant(QString::fromStdString(getFirst(index.row())));
}
if (index.column()==1) {
return QVariant(QString::fromStdString(getSecond(index.row())));
}
}
return QVariant();
}
void append(std::string const& first, std::string const& second) {
mData.push_back(std::make_pair(first, second));
emit dataChanged(index(mData.size()-1, 0), index(mData.size()-1, 1));
}
std::string const& getFirst(int i) const {
return mData[i].first;
}
std::string const& getSecond(int i) const {
return mData[i].second;
}
protected:
std::vector<std::pair<std::string, std::string> > mData;
};
As you're inserting a new row instead of changing existing data, you should use beginInsertRows and endInsertRows instead:
void append(std::string const& first, std::string const& second) {
int row = mData.size();
beginInsertRows( QModelIndex(), row, row );
mData.push_back(std::make_pair(first, second));
endInsertRows();
}
See if that helps.