I have a list of custom objects that I need to split across multiple pages of a SwipeView.
Right now, I implement a QAbstractListModel to provide the data to my QML view through custom roles.
But when I have more then a given number of object, I need to split my model in multiple chunks that will be displayed on different pages of a SwipeView.
If my model has 20 object, my SwipeView will have 2 pages with 12 Items in the first one and the 8 remainings in the second page for example but the number of items is, of course, dynamic.
I know that I can use a QSortProxyFilter or a DelegateModel to filter my model on a criteria but I don't know how I can use them to create groups usable as submodels for the content of the pages of the SwipeView. Because of course, I cannot just change the filter when the page change because that wouldn't make the items visible when swiping from one page to the other.
Thanks for any hint or idea on how to achieve this.
Why don't you include a model into another model? You can return the model to QML via QVariant::fromValue. See my example:
models.h
class ChildItem{
public:
ChildItem() {}
};
class ChildModel: public QAbstractListModel{
Q_OBJECT
public:
explicit ChildModel(QObject *parent = nullptr);
void addItem(const ChildItem &item);
int rowCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
signals:
void itemAdded();
protected:
QHash<int,QByteArray> roleNames() const;
private:
QVector<ChildItem> m_items;
};
class ModelItem{
public:
ModelItem() {}
ChildModel *childModel() const;
private:
ChildModel *m_childModel;
};
class MainModel: public QAbstractListModel{
Q_OBJECT
public:
explicit MainModel(QObject *parent = nullptr);
void addItem(const ModelItem &item);
int rowCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
signals:
void itemAdded();
protected:
QHash<int,QByteArray> roleNames() const;
private:
QVector<ModelItem> m_items;
};
models.cpp
ChildModel *ModelItem::childModel() const{
return m_childModel;
}
QVariant MainModel::data(const QModelIndex & index, int role) const {
if (index.row() < 0 || index.row() >= (int)m_items.size())
return QVariant();
const MainModel &item = m_items[index.row()];
switch (role) {
case MainModelRoles::ChildModel:{
return QVariant::fromValue(item.childModel());
}
default: {
break;
}
}
return QVariant();
}
QHash<int, QByteArray> MainModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[MainModelRoles::ChildModel] = "child_model";
return roles;
}
From within QML you can present model role name as a new model for another element. For example:
StackLayout which is your main element, includes Repeater.
Repeater model is MainModel class instance.
Repeater delegate includes ListView
ListView takes ChildModel as a model for itself (child_model reference)
So you can contain any amount of different models inside main model and expose them to QML as model property. Is this what you're looking for?
Related
I'm having a Listview which should show a preview of an image. For this I created a the item "ListItem"
class ListItem
{
public:
ListItem();
ListItem(QString name, QImage image);
QImage* previewIcon();
void setPreviewIcon(QImage icon);
QImage *image();
void setImage(QImage* image);
void setImage(QImage image);
void setName(QString name);
void setChecked(bool checked);
QString name();
bool checked();
private:
QImage m_preview;
QImage m_image;
QString m_name;
bool m_checked;
};
This model stores the image it self and a preview of it. This works fine for inserting and removing items:
class ListModel: public QAbstractListModel
{
Q_OBJECT
public:
ListModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const override;
bool removeRow(int row, const QModelIndex &parent = QModelIndex());
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
bool insertRow(int row, const QModelIndex &parent = QModelIndex());
bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
bool appendItem(ListItem* item);
bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild) override;
bool moveRow(const QModelIndex &sourceParent, int sourceRow, const QModelIndex &destinationParent, int destinationChild);
Qt::DropActions supportedDropActions() const override;
QHash<int, QByteArray> roleNames() const override;
private:
ListItem* getItem(const QModelIndex &index) const;
private:
QList<ListItem*> m_scannedDocuments;
};
This is the setup of the QListView:
m_scannedDocumentsModel = new ListModel();
m_scannedDocuments = new QListView();
m_scannedDocuments->setModel(m_scannedDocumentsModel);
m_scannedDocuments->setDragDropMode(QAbstractItemView::InternalMove);
m_scannedDocuments->setMovement(QListView::Snap);
m_scannedDocuments->setDefaultDropAction(Qt::MoveAction);
Dragging and droping is fine for the preview, the name and if it is checked or not. The problem is the image "m_image". When I'm doing a move in the view, the view calls insertRows() and inserts a new item and removes the old item, but does not call moveRows.
Why moveRows() is not called?
Here you can find the full implementation:
ListModel.cpp
ListModel.h
Another approach would be to create a userRole for the image it self. Here I tried to reimplement roleNames() as
QHash<int, QByteArray> ListModel::roleNames() const {
QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
roles[Qt::UserRole] = "Qt::UserRole";
return roles;
}
and check the roles in setdata()/data() but this didn't work either.
What is the best way to have a complex model and moving them in a listview?
As already answered in brief on the Qt interest list:
Because moveRows was added in Qt 5.x, and drag and drop in Qt 4, and still uses insert+remove for backwards compatibility.
I'd add that "moveRows" is I guess considered an "optimization" and not really a requirement for a model, and the view has no way to know if the move methods are really implemented. Since an editable model requires remove/insert functions, it's "safer" to call those by default.
You can re-implement the model's dropMimeData() method and call moveRows() yourself. But, the caveat is you should return false from the method if you do this. If you return true to a Qt::MoveAction, the view will still try to remove the row from the old position in the model (which is obviously not what you want).
This is my Container class defination
class Container
{
private:
std::string stdstrContainerName;
std::string stdstrPluginType;
std::string stdstrPluginName;
int iSegments;
float fRadius;
public:
Container();
explicit Container(std::string strContainerName , std::string
strPluginName , std::string strPluginType, int segments , float
radius );
~Container();
std::string GetName();
std::string GetType();
void SetName(std::string stdstrName);
};
I want the nodes of the TreeView to hold object of Container class as data.
This is the header file for the TreeItem class.
class TreeItem
{
public:
explicit TreeItem( const Container &data , TreeItem *parent = 0 );
~TreeItem();
TreeItem *parent();
TreeItem *child(int iNumber);
int childCount() const;
int childNumber() const;
Container data() const;
bool setData(const Container &data);
bool insertChildren(int position, int count );
bool removeChildren( int position , int count );
private:
QList<TreeItem*> childItems;
Container itemData;
TreeItem* parentItem;
};
The problem i am facing is while implementing the TreeModel functions.
how can i use Container as the data type instead of QVariant.
QVariant data(const QModelIndex &undex, int role) const override;
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const override;
bool setData( const QModelIndex &index , const QVariant &value , int
role = Qt::EditRole) override;
You can't use Container instead of QVariant, but you can use Container inside a QVariant. Have a look at Q_DECLARE_METATYPE.
Add it in the header file after the declaration of Container:
class Container
{
private:
// ...
public:
Container();
// ...
};
Q_DECLARE_METATYPE(Container); // You only need this once, so here is a good place
When you created the Container meta type, you can return it like this:
QVariant data(const QModelIndex &index, int role) const override {
TreeItem *item = ...;
return QVariant::fromValue(item->data());
}
And in your view you retrieve the Container like this:
QVariant v = model()->data(index, role);
Container c = v.value<Container>();
Beware that this only works within the same thread, else you'd need to register Container with qRegisterMetaType() and maybe even write a converter function from / to QVariant.
This doesn't apply to your situation, though, because model and view are not designed to live in different threads.
I'm trying to support rearranging a list of large objects in a QListView. It seems that the model/view framework insists on serializing the items which, in this case, is highly inefficient. I've tried to shorten things by using shared_ptr's, so only one bit of data needs passing, but even so, it feels very messy and is not at all reliable.
Worse still, during the move I notice the serialization functions are called repeatedly (dozens/hundreds of times). Surely they should only be called once each.
I'm sure I'm missing a trick, what's the correct way to achieve simple, internal move, list ordering without heavy serialize overhead?
class Big
{
public:
Big();
~Big();
...
};
using BigPtr = std::shared_ptr<Big>;
Q_DECLARE_METATYPE(BigPtr) // and qRegisterMetaTypeStreamOperators
// Required serialization operators
// This is very ugly, and what I want rid of...
QDataStream &operator<<(QDataStream &out, const BigPtr &big)
{
qDebug() << "Out <<";
out << (quint64)big.get();
return out;
}
// ...This too...
QDataStream &operator>>(QDataStream &in, BigPtr &big)
{
qDebug() << "In >>";
quint64 raw = 0;
in >> raw;
li.reset((LayerPtr::element_type *)raw);
return in;
}
class BigModel : public QAbstractListModel
{
Q_OBJECT
public:
BigModel (std::vector<BigPtr> &bigs, QObject *parent = Q_NULLPTR);
int rowCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
Qt::ItemFlags flags(const QModelIndex &index) const; // Qt::ItemIsDragEnabled or Qt::ItemIsDropEnabled depending on valid index
Qt::DropActions supportedDropActions() const; // return Qt::MoveAction
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex());
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex());
private:
std::vector<BigPtr> &_bigs;
};
...
// dialog setup...
ui->listView->setSelectionMode(QAbstractItemView::SingleSelection);
ui->listView->setSelectionBehavior(QAbstractItemView::SelectRows);
ui->listView->setEditTriggers(QAbstractItemView::DoubleClicked);
ui->listView->setDragEnabled(true);
ui->listView->setDragDropMode(QAbstractItemView::InternalMove);
ui->listView->setDropIndicatorShown(true);
ui->listView->viewport()->setAcceptDrops(true);
EDIT:
After more head scratching, I decided to overload the mimeData and dropMimeData functions and sub-class QMimeData to carry the BigPtr directly.
class BigMimeData : public QMimeData
{
Q_OBJECT
public:
BigMimeData(const QVariant &_big)
: big(_big)
{ }
bool hasFormat(const QString &/*mimeType*/) const { return true; }
QVariant big;
};
...
QMimeData *BigModel::mimeData(const QModelIndexList &indexes) const
{
if(indexes.count() > 0) {
return new BigMimeData(indexes.at(0).data());
}
return Q_NULLPTR;
}
bool BigModel::dropMimeData(const QMimeData *data, Qt::DropAction action,
int row, int /*column*/, const QModelIndex &parent)
{
if(!data || !(action == Qt::CopyAction || action == Qt::MoveAction)) {
return false;
}
// start of list
if(row == -1) {
row = rowCount(parent);
}
auto dragged = static_cast<const BigMimeData *>(data);
// setData with stored value
insertRow(row, parent);
setData(index(row, 0, parent), dragged->big);
return true;
}
Which works, but seriously!? All that just to enable simple list reordering in the UI? It can't be this complex surely?
BTW, a lot of these answer seem to be focusing on the underlying data structure, that's not the question. The problem is that simple internal move drag/drop rearrangement in a list control seem to be ridiculously complex in QListView because it requires costly serialization. It's a UI/Widgets/MVC question.
I just want to display elements from list using QML, but not using item roles.
For ex. I want to call a method getName() that returns the name of the item to be displayed.
Is it possible? I didn't find nothing clear reffering to this.
You can use one special role to return the whole item as you can see below:
template<typename T>
class List : public QAbstractListModel
{
public:
explicit List(const QString &itemRoleName, QObject *parent = 0)
: QAbstractListModel(parent)
{
QHash<int, QByteArray> roles;
roles[Qt::UserRole] = QByteArray(itemRoleName.toAscii());
setRoleNames(roles);
}
void insert(int where, T *item) {
Q_ASSERT(item);
if (!item) return;
// This is very important to prevent items to be garbage collected in JS!!!
QDeclarativeEngine::setObjectOwnership(item, QDeclarativeEngine::CppOwnership);
item->setParent(this);
beginInsertRows(QModelIndex(), where, where);
items_.insert(where, item);
endInsertRows();
}
public: // QAbstractItemModel
int rowCount(const QModelIndex &parent = QModelIndex()) const {
Q_UNUSED(parent);
return items_.count();
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const {
if (index.row() < 0 || index.row() >= items_.count()) {
return QVariant();
}
if (Qt::UserRole == role) {
QObject *item = items_[index.row()];
return QVariant::fromValue(item);
}
return QVariant();
}
protected:
QList<T*> items_;
};
Do not forget to use QDeclarativeEngine::setObjectOwnership in all your insert methods. Otherwise all your objects returned from data method will be garbage collected on QML side.
Hi I can't figure out why my data() function is never called when populating QTableView
I subclassed QSqlQueryModel. The header is like:
class TicketModel : public QSqlQueryModel
{
Q_OBJECT
public:
explicit TicketModel(QObject *parent = 0);
QVariant data(const QModelIndex &index, int role);
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
};
In the main window I set my model to the table
TicketModel *model = new TicketModel();
QSortFilterProxyModel *proxyModel = new QSortFilterProxyModel(this);
proxyModel->setSourceModel(model);
QSqlQuery *query = _tf->search(1);
model->setQuery(*query);
_ui->dashTable->setModel(proxyModel); // <<<<<<<<<<<<<< I setting model here too, didn't work
_ui->dashTable->setSortingEnabled(true);
_ui->dashTable->horizontalHeader()->setVisible(true);
_ui->dashTable->setSelectionBehavior(QAbstractItemView::SelectRows);
The TicketModel::headerData(...) is called but TicketModel::data(...) is never called when the table is created. Why? How can I get it to be called?
I hope I just overlooked something simple but I have been trying for a few hours to figure it out.
Thanks for the help!
You've got the signature wrong. You need a const.
QVariant data( const QModelIndex & index, int role = Qt::DisplayRole ) const