How to loop over QAbstractItemView indexes? - qt

I want to fire QAbstractItemView::doubleClicked slot programaticaly for an item that has specific text. I want to do this using QAbstractItemView class and not it's implementations if possible.
This task boils down to looping over items and comparing strings. But I cannot find any method that would give me all QModelIndexes. The only method that gives any QModelIndex without parameters is QAbstractItemView::rootIndex. But when I look into QModelIndex docs, I again cannot see a way to access it's children and siblings.
So how to access all QModelIndexes in QAbstractItemView?

The indexes are provided by the model, not by the view. The view provides the rootIndex() to indicate what node in the model it considers as root; it might be an invalid index. Otherwise it has nothing to do with the data. You have to traverse the model itself - you can get it from view->model().
Here's a depth-first walk through a model:
void iterate(const QModelIndex & index, const QAbstractItemModel * model,
const std::function<void(const QModelIndex&, int)> & fun,
int depth = 0)
{
if (index.isValid())
fun(index, depth);
if ((index.flags() & Qt::ItemNeverHasChildren) || !model->hasChildren(index)) return;
auto rows = model->rowCount(index);
auto cols = model->columnCount(index);
for (int i = 0; i < rows; ++i)
for (int j = 0; j < cols; ++j)
iterate(model->index(i, j, index), model, fun, depth+1);
}
The functor fun gets invoked for every item in the model, starting at root and going in depth-row-column order.
E.g.
void dumpData(QAbstractItemView * view) {
iterate(view->rootIndex(), view->model(), [](const QModelIndex & idx, int depth){
qDebug() << depth << ":" << idx.row() << "," << idx.column() << "=" << idx.data();
});
}

Related

Iterate through all the children of a tree item

Currently i am iterating through all the child items of a Tree item using this code.
void iterate(const QModelIndex & index, const QAbstractItemModel * model)
{
if (index.isValid())
{
// Do action here
}
if (!model->hasChildren(index) || (index.flags() &
Qt::ItemNeverHasChildren))
{
return;
}
auto rows = model->rowCount(index);
for (int i = 0; i < rows; ++i)
iterate(model->index(i, 0, index), model);
}
i need to Iterate using TreeItem instead of index
void iterate(TreeItem *item, const QAbstractItemModel * model)
One method i can think of is getting the index of the item using createIndex function and the above code will work with it.
Is there any other method to do so ?
If you are using the QStandardItemModel you can use the indexFromItem methods:
QModelIndex QStandardItemModel::indexFromItem(const QStandardItem *item) const
In case you are using your own implementation of you might provide your own indexFromItem and itemFromIndex methods.
Another solution might involve calling QAbstractItemModel::createIndex.

Creating a QModelIndex

I have spent the last week struggling to create a QModelIndex given a row and a column.
Alternatively, I would settle for changing the value of row() in an already existing QModelIndex.
Any help would be appreciated.
Edit:
QModelIndex nIndex = QAbstractItemModel::createIndex(1, 2);
int b = nIndex.row();
qInfo() << "b" << b;
Fails with error:
cannot call member function ‘QModelIndex QAbstractItemModel::createIndex(int, int, void*) const’ without object
QModelIndex nIndex = QAbstractItemModel::createIndex(1, 2);
^
The goal at hand is this:
I have a function:
void MyClass::doStuff(QModelIndex index)
Inside that class, I essentially do the following:
if (index.column() != 1)
{
int a=index.row();
}
So my goal is to call that function from a different class and pass it a QModelIndex, but for that index to have been created with a row/column I specify.
I'm not sure this is what you want, but you can just create a QModelIndex with the method QAbstractItemModel::index(row, column) ( http://doc.qt.io/qt-5/qabstractitemmodel.html#index )!? On the other hand that seems to be to simple for you to struggle with it for so long, maybe explain a little bit more.
Example:
QAbstractTableModel *model = ...;
// then you can do something like
QModelIndex nIndex = model->index(1,2);
int b = nIndex.row();
qInfo() << "b" << b;
You can get a new index from the appropriate model, using its index() method.
If you already have an index from the model, with the same parent as your desired index, then you can get another index using the sibling() method of that index:
void MyClass::doStuff(const QModelIndex& index)
{
// get the value at row zero, same column
const QModelIndex header = index.sibling(0, index.column());
}
The index itself is immutable once created - you can't change its row, column or parent (other than by invalidating it with changes to the model behind its back).

How to serialize a QAbstractItemModel into QDataStream?

I've set up a QAbstractItemModel and filled that with data. My QTreeView widget displays every data in that model properly.
Now, I would like to store that model serialized in a binary file (and later of cource load that binary file back into a model). Is that possible?
The particulars of model serialization depend somewhat on the model's implementation. Some gotchas include:
Perfectly usable models might not implement insertRows/insertColumns, preferring to use custom methods instead.
Models like QStandardItemModel may have underlying items of varying types. Upon deserialization, the prototype item factory will repopulate the model with clones of one prototype type. To prevent that, the items't type identifier must be exposed for serialization, and a way provided to rebuild the item of a correct type upon deserialization.
Let's see one way of implementing it for the standard item model. The prototype polymorphic item class can expose its type via a data role. Upon setting this role, it should re-create itself with a correct type.
Given this, a universal serializer isn't feasible.
Let's look at a complete example, then. The behaviors necessary for a given model type must be represented by a traits class that parametrizes the serializer. The methods reading data from the model take a constant model pointer. The methods modifying the model take a non-constant model pointer, and return false upon failure.
// https://github.com/KubaO/stackoverflown/tree/master/questions/model-serialization-32176887
#include <QtGui>
struct BasicTraits {
BasicTraits() {}
/// The base model that the serializer operates on
typedef QAbstractItemModel Model;
/// The streamable representation of model's configuration
typedef bool ModelConfig;
/// The streamable representation of an item's data
typedef QMap<int, QVariant> Roles;
/// The streamable representation of a section of model's header data
typedef Roles HeaderRoles;
/// Returns a streamable representation of an item's data.
Roles itemData(const Model * model, const QModelIndex & index) {
return model->itemData(index);
}
/// Sets the item's data from the streamable representation.
bool setItemData(Model * model, const QModelIndex & index, const Roles & data) {
return model->setItemData(index, data);
}
/// Returns a streamable representation of a model's header data.
HeaderRoles headerData(const Model * model, int section, Qt::Orientation ori) {
Roles data;
data.insert(Qt::DisplayRole, model->headerData(section, ori));
return data;
}
/// Sets the model's header data from the streamable representation.
bool setHeaderData(Model * model, int section, Qt::Orientation ori, const HeaderRoles & data) {
return model->setHeaderData(section, ori, data.value(Qt::DisplayRole));
}
/// Should horizontal header data be serialized?
bool doHorizontalHeaderData() const { return true; }
/// Should vertical header data be serialized?
bool doVerticalHeaderData() const { return false; }
/// Sets the number of rows and columns for children on a given parent item.
bool setRowsColumns(Model * model, const QModelIndex & parent, int rows, int columns) {
bool rc = model->insertRows(0, rows, parent);
if (columns > 1) rc = rc && model->insertColumns(1, columns-1, parent);
return rc;
}
/// Returns a streamable representation of the model's configuration.
ModelConfig modelConfig(const Model *) {
return true;
}
/// Sets the model's configuration from the streamable representation.
bool setModelConfig(Model *, const ModelConfig &) {
return true;
}
};
Such a class must be implemented to capture the requirements of a particular model. The one given above is often sufficient for basic models. A serializer instance takes or default-constructs an instance of the traits class. Thus, traits can have state.
When dealing with streaming and model operations, either can fail. A Status class captures whether the stream and model are ok, and whether it's possible to continue. When IgnoreModelFailures is set on the initial status, the failures reported by the traits class are ignored and the loading proceeds in spite of them. QDataStream failures always abort the save/load.
struct Status {
enum SubStatus { StreamOk = 1, ModelOk = 2, IgnoreModelFailures = 4 };
QFlags<SubStatus> flags;
Status(SubStatus s) : flags(StreamOk | ModelOk | s) {}
Status() : flags(StreamOk | ModelOk) {}
bool ok() const {
return (flags & StreamOk && (flags & IgnoreModelFailures || flags & ModelOk));
}
bool operator()(QDataStream & str) {
return stream(str.status() == QDataStream::Ok);
}
bool operator()(Status s) {
if (flags & StreamOk && ! (s.flags & StreamOk)) flags ^= StreamOk;
if (flags & ModelOk && ! (s.flags & ModelOk)) flags ^= ModelOk;
return ok();
}
bool model(bool s) {
if (flags & ModelOk && !s) flags ^= ModelOk;
return ok();
}
bool stream(bool s) {
if (flags & StreamOk && !s) flags ^= StreamOk;
return ok();
}
};
This class could also be implemented to throw itself as an exception instead of returning false. This would make the serializer code a bit easier to read, as every if (!st(...)) return st idiom would be replaced by simpler st(...). Nevertheless, I chose not to use exceptions, as typical Qt code doesn't use them. To completely remove the syntax overhead of detecting traits methods and stream failures, one would need to throw in the traits methods instead of returning false, and use a stream wrapper that throws on failure.
Finally, we have a generic serializer, parametrized by a traits class. The majority of model operations are delegated to the traits class. The few operations performed directly on the model are:
bool hasChildren(parent)
int rowCount(parent)
int columnCount(parent)
QModelIndex index(row, column, parent)
template <class Tr = BasicTraits> class ModelSerializer {
enum ItemType { HasData = 1, HasChildren = 2 };
Q_DECLARE_FLAGS(ItemTypes, ItemType)
Tr m_traits;
Headers for each orientation are serialized based on the root item row/column counts.
Status saveHeaders(QDataStream & s, const typename Tr::Model * model, int count, Qt::Orientation ori) {
Status st;
if (!st(s << (qint32)count)) return st;
for (int i = 0; i < count; ++i)
if (!st(s << m_traits.headerData(model, i, ori))) return st;
return st;
}
Status loadHeaders(QDataStream & s, typename Tr::Model * model, Qt::Orientation ori, Status st) {
qint32 count;
if (!st(s >> count)) return st;
for (qint32 i = 0; i < count; ++i) {
typename Tr::HeaderRoles data;
if (!st(s >> data)) return st;
if (!st.model(m_traits.setHeaderData(model, i, ori, data))) return st;
}
return st;
}
The data for each item is serialized recursively, ordered depth-first, columns-before-rows. Any item can have children. Item flags are not serialized; ideally this behavior should be parametrized in the traits.
Status saveData(QDataStream & s, const typename Tr::Model * model, const QModelIndex & parent) {
Status st;
ItemTypes types;
if (parent.isValid()) types |= HasData;
if (model->hasChildren(parent)) types |= HasChildren;
if (!st(s << (quint8)types)) return st;
if (types & HasData) s << m_traits.itemData(model, parent);
if (! (types & HasChildren)) return st;
auto rows = model->rowCount(parent);
auto columns = model->columnCount(parent);
if (!st(s << (qint32)rows << (qint32)columns)) return st;
for (int i = 0; i < rows; ++i)
for (int j = 0; j < columns; ++j)
if (!st(saveData(s, model, model->index(i, j, parent)))) return st;
return st;
}
Status loadData(QDataStream & s, typename Tr::Model * model, const QModelIndex & parent, Status st) {
quint8 rawTypes;
if (!st(s >> rawTypes)) return st;
ItemTypes types { rawTypes };
if (types & HasData) {
typename Tr::Roles data;
if (!st(s >> data)) return st;
if (!st.model(m_traits.setItemData(model, parent, data))) return st;
}
if (! (types & HasChildren)) return st;
qint32 rows, columns;
if (!st(s >> rows >> columns)) return st;
if (!st.model(m_traits.setRowsColumns(model, parent, rows, columns))) return st;
for (int i = 0; i < rows; ++i)
for (int j = 0; j < columns; ++j)
if (!st(loadData(s, model, model->index(i, j, parent), st))) return st;
return st;
}
The serializer retains a traits instance, it can also be passed one to use.
public:
ModelSerializer() {}
ModelSerializer(const Tr & traits) : m_traits(traits) {}
ModelSerializer(Tr && traits) : m_traits(std::move(traits)) {}
ModelSerializer(const ModelSerializer &) = default;
ModelSerializer(ModelSerializer &&) = default;
The data is serialized in following order:
model configuration,
model data,
horizontal header data,
vertical header data.
Attention is paid to versioning of both the stream and the streamed data.
Status save(QDataStream & stream, const typename Tr::Model * model) {
Status st;
auto version = stream.version();
stream.setVersion(QDataStream::Qt_5_4);
if (!st(stream << (quint8)0)) return st; // format
if (!st(stream << m_traits.modelConfig(model))) return st;
if (!st(saveData(stream, model, QModelIndex()))) return st;
auto hor = m_traits.doHorizontalHeaderData();
if (!st(stream << hor)) return st;
if (hor && !st(saveHeaders(stream, model, model->rowCount(), Qt::Horizontal))) return st;
auto ver = m_traits.doVerticalHeaderData();
if (!st(stream << ver)) return st;
if (ver && !st(saveHeaders(stream, model, model->columnCount(), Qt::Vertical))) return st;
stream.setVersion(version);
return st;
}
Status load(QDataStream & stream, typename Tr::Model * model, Status st = Status()) {
auto version = stream.version();
stream.setVersion(QDataStream::Qt_5_4);
quint8 format;
if (!st(stream >> format)) return st;
if (!st.stream(format == 0)) return st;
typename Tr::ModelConfig config;
if (!st(stream >> config)) return st;
if (!st.model(m_traits.setModelConfig(model, config))) return st;
if (!st(loadData(stream, model, QModelIndex(), st))) return st;
bool hor;
if (!st(stream >> hor)) return st;
if (hor && !st(loadHeaders(stream, model, Qt::Horizontal, st))) return st;
bool ver;
if (!st(stream >> ver)) return st;
if (ver && !st(loadHeaders(stream, model, Qt::Vertical, st))) return st;
stream.setVersion(version);
return st;
}
};
To save/load a model using the basic traits:
int main(int argc, char ** argv) {
QCoreApplication app{argc, argv};
QStringList srcData;
for (int i = 0; i < 1000; ++i) srcData << QString::number(i);
QStringListModel src {srcData}, dst;
ModelSerializer<> ser;
QByteArray buffer;
QDataStream sout(&buffer, QIODevice::WriteOnly);
ser.save(sout, &src);
QDataStream sin(buffer);
ser.load(sin, &dst);
Q_ASSERT(srcData == dst.stringList());
}
The same way you serialize anything, just implement an operator or method which writes each data member to a data stream in sequence.
The preferable format is to implement those two operators for your types:
QDataStream &operator<<(QDataStream &out, const YourType &t);
QDataStream &operator>>(QDataStream &in, YourType &t);
Following that pattern will allow your types to be "plug and play" with Qt's container classes.
QAbstractItemModel does not (or should not) directly hold the data, it is just a wrapper to an underlying data structure. The model only serves to provide an interface for a view to access the data. So in reality you shouldn't serialize the actual model, but the underlying data.
As of how to serialize the actual data, it depends on the format of your data, which as of now remains a mystery. But since it is a QAbstractItemModel I assume it is a tree of some sort, so generally speaking, you have to traverse the tree and serialize every object in it.
Make a note that when serializing a single object, the serialization and deserialization are a blind sequence, but when dealing with a collection of objects, you may have to account for its structure with extra serialization data. If your tree is something like an array of arrays, as long as you use Qt's container classes this will be taken care of for you, all you will need is to implement the serialization for the item type, but for a custom tree you will have to do it yourself.

using QList<QStringList> to populate a QTableView. When something gets changed in the View, how do I get it back in the data?

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).

Qt - SelectedItems in QTableView

I'm trying to return a vector of selected rows from a QTableView widget(snippet copied below), however the values returned do not correspond to the selection and I believe I've not understood QModelIndexList/QModelIndex with respect to QTableView. Can you let me know where I'm wrong or the right way to access selected items from a QTableView?
C_model is of type QStandardItemModel
for(int i = 0; i < c_model->rowCount(); i++)
{
if (selectionModel->isRowSelected(i, QModelIndex()))
{
QStringList selection;
std::vector<std::string> currentIndexValues;
for (int j = 0; j < c_model->columnCount(); j++)
{
QVariant q = c_model->data(c_model->index(i, j));
selection.append(q.toString());
currentIndexValues.push_back(q.toString().toLocal8Bit().constData());
printf(" %s\t ", currentIndexValues[j].c_str());
}
printf("\n");
v_selectedItems.push_back(currentIndexValues);
}
}
Thanks
QAbstractItemView (the base class of QTableView) offers a QItemSelectionModel for this purpose. You can access that model via QTableView::itemSelectionModel() and then retrieve the selected rows via QItemSelectionModel::selectedRows():
QModelIndexList selectedRows = yourTableView->selectionModel()->selectedRows();
foreach( QModelIndex index, selectedRows )
{
int row = index.row();
}

Resources