I have created a very simple example of QListView with a custom QAbstractListModel. The QListView is displayed but it is empty.
What am I doing wrong?
Code:
#include <QListView>
#include <QAbstractListModel>
#include <QApplication>
class DataModel: public QAbstractListModel
{
public:
DataModel() : QAbstractListModel() {}
int rowCount( const QModelIndex & parent = QModelIndex() ) const { return 2; }
QVariant data( const QModelIndex & index, int role = Qt::DisplayRole ) const
{
return "a";
}
};
int main( int argc, char **argv)
{
QApplication app(argc, argv, true);
QListView *lv = new QListView();
DataModel d;
lv->setModel( &d );
lv->show();
app.setMainWidget(lv);
app.exec();
}
Thanks!
The fix to the previous code is to set the parent of the model to the QListView:
DataModel d(lv);
But this raises a question, where is the model/view independence if the model has to have a reference to the view?
What if I want to use this model in two different views?
Your methods data should return "a" only if role = Qt::DisplayRole. Otherwise, it returns "a" for every role.
So, add a simple test and it will work :
QVariant data( const QModelIndex & index, int role = Qt::DisplayRole ) const
{
if ( role == Qt::DisplayRole ) {
return "a";
}
return QVariant();
}
Related
I would like to extend the QFileSystemModel. I would like to have a custom role IsSelectedRole where I can store whether a bool value which stores whether a file got selected in a QML TreeView. More precisely, I don't know how to set the setData and data function of my QFileSystemModel derived class, like where to store the data. I guess making my own tree of bool variables should work, but I hope that there is an easier way.
Qt already has a model that stores item selections: QItemSelectionModel. This model is used by the widget views as well as the QML views. All that's left for you to do is to overlay the data from that model on top of the data from QFileSystemModel, in a viewmodel.
You definitely shouldn't be deriving from QFileSystemModel. The viewmodel maintains the state for a single view only, and should be a proxy that overlays your role on top of the underlying model.
The proxy doesn't have to assume anything about the underlying model, and can work on any model, not just the QFileSystemModel.
For example:
// https://github.com/KubaO/stackoverflown/tree/master/questions/filesystem-model-select-50132273
#include <QtWidgets>
#include <algorithm>
class SelectionProxy : public QIdentityProxyModel {
Q_OBJECT
Q_PROPERTY(QItemSelectionModel* selectionModel
READ selectionModel WRITE setSelectionModel NOTIFY selectionModelChanged)
Q_PROPERTY(QVector<int> roles READ roles WRITE setRoles)
using self_t = SelectionProxy;
using base_t = QIdentityProxyModel;
using model_t = QItemSelectionModel;
QPointer<QItemSelectionModel> m_model;
QVector<QMetaObject::Connection> m_modelConnections;
QVector<int> m_roles{IsSelectedRole};
QModelIndex topLeft() const {
return sourceModel()->index(0, 0);
}
QModelIndex bottomRight() const {
return sourceModel()->index(sourceModel()->rowCount()-1, sourceModel()->columnCount()-1);
}
void onSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected) {
auto sel = selected;
sel.merge(deselected, QItemSelectionModel::Select);
for (auto &range : qAsConst(sel)) {
auto topLeft = mapFromSource(range.topLeft());
auto bottomRight = mapFromSource(range.bottomRight());
emit dataChanged(topLeft, bottomRight, m_roles);
}
}
void onModelChanged(QAbstractItemModel *model) {
setSourceModel(model);
}
bool check(const QModelIndex &index, int role) const {
return index.isValid() && m_model && m_roles.contains(role);
}
public:
static constexpr int IsSelectedRole = Qt::UserRole + 44;
SelectionProxy(QObject *parent = {}) : QIdentityProxyModel(parent) {}
QItemSelectionModel *selectionModel() const { return m_model; }
virtual void setSelectionModel(QItemSelectionModel *model) {
if (model == m_model) return;
for (auto &conn : m_modelConnections)
disconnect(conn);
m_model = model;
m_modelConnections = {
connect(m_model, &model_t::selectionChanged, this, &self_t::onSelectionChanged),
connect(m_model, &model_t::modelChanged, this, &self_t::onModelChanged) };
setSourceModel(model->model());
emit selectionModelChanged(m_model);
}
Q_SIGNAL void selectionModelChanged(QItemSelectionModel *);
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override {
if (!check(index, role))
return base_t::data(index, role);
return m_model->isSelected(mapToSource(index));
}
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override {
if (!check(index, role))
return base_t::setData(index, value, role);
using Sel = QItemSelectionModel;
m_model->select(mapToSource(index), value.toBool() ? Sel::SelectCurrent : (Sel::Deselect | Sel::Current));
return true;
}
QVector<int> roles() const { return m_roles; }
void setRoles(QVector<int> roles) {
std::sort(roles.begin(), roles.end());
if (roles == m_roles)
return;
std::swap(roles, m_roles);
if (!m_model)
return;
QVector<int> allRoles;
std::merge(roles.begin(), roles.end(), m_roles.begin(), m_roles.end(), std::back_inserter(allRoles));
emit dataChanged(topLeft(), bottomRight(), allRoles);
}
void setRole(int role) {
setRoles({role});
}
};
#include "main.moc"
A simple demonstrator sets up two views of the filesystem model; the bottom view shows true/false indicating the selection status of the first view.
int main(int argc, char **argv) {
QApplication app{argc, argv};
QWidget win;
QVBoxLayout layout{&win};
QTreeView left, right;
layout.addWidget(&left);
layout.addWidget(&right);
QFileSystemModel model;
SelectionProxy selProxy;
model.setRootPath(QStandardPaths::writableLocation(QStandardPaths::HomeLocation));
auto rootPathIndex = model.index(model.rootPath());
left.setModel(&model);
left.setSelectionMode(QAbstractItemView::MultiSelection);
left.setRootIndex(rootPathIndex);
selProxy.setRole(Qt::DisplayRole);
selProxy.setSelectionModel(left.selectionModel());
right.setModel(&selProxy);
right.setRootIndex(selProxy.mapFromSource(rootPathIndex));
for (int col : {1,2,3})
right.hideColumn(col);
win.show();
return app.exec();
}
I am developing a GUI for a SQLite database in Qt5. I use QSqlQueryModel and QTableView for storing and displaying the data.
I then created a custom delegate to replace the numeric values of certain columns with their literals in the table view (e.g. 1 = "Hello", 2 = "World") using a switch statement.
The delegate displays the data as it should and works functionally. However, the columns that the custom delegate paints over have a different format compared to the default paint method of QStyledItemDelegate. The values are up in the top left rather than centre left, the altered column no longer automatically expands the column to display the full values, and the cells in column do not turn blue or have the dotted outline when selected.
I created this example program:
#include <QApplication>
#include <QModelIndex>
#include <QPainter>
#include <QStandardItemModel>
#include <QStyledItemDelegate>
#include <QTableView>
class TestDelegate: public QStyledItemDelegate {
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index)
const Q_DECL_OVERRIDE
{
if (index.column() == 0) {
int value = index.model()->data(index, Qt::DisplayRole).toInt();
QString str;
switch (value) {
case 1:
str = "Hello0000";
break;
case 2:
str = "World0000";
break;
}
if (option.state.testFlag (QStyle::State_Selected)) {
painter->fillRect(option.rect, option.palette.highlight());
qApp->style()->drawItemText(painter, option.rect, option.displayAlignment, option.palette, true, str, QPalette::HighlightedText);
} else {
painter->drawText(option.rect, option.displayAlignment, str);
}
} else {
return QStyledItemDelegate::paint(painter, option, index);
}
}
};
int main(int argc, char **argv) {
QApplication app(argc, argv);
QStandardItemModel model(2, 2);
model.setHorizontalHeaderItem(0, new QStandardItem(QString("A")));
model.setHorizontalHeaderItem(1, new QStandardItem(QString("B")));
model.setData(model.index(0, 0, QModelIndex()), 1);
model.setData(model.index(1, 0, QModelIndex()), 2);
model.setItem(0, 1, new QStandardItem(QString("Hello")));
model.setItem(1, 1, new QStandardItem(QString("World0000")));
QTableView view;
view.setItemDelegate(new TestDelegate);
view.setModel(&model);
view.resizeColumnsToContents();
view.show();
app.exec();
}
This fixes the text alignment by adding options.displayAlignment to painter->drawText(); I have also added additional code in the if(option.state & QStyle::State_Selected) statement that paints the cell according to its selection state. So if it isn't selected the text is black, if it is the text turns white and the background blue. However I still cannot get the columns to expand to fit the cells' content or add a dotted line around the outside of the cell as it does with the standard delegate.
Is there a simple way to maintain the default style of the table view when using my custom paint method?
The delegate is a rather circuitous and unnecessary way of going about it. We already have a view that paints the elements perfectly fine, no need to redo that. We only need to pass modified data to the view. Thus we insert a QIdentityProxyModel viewmodel between the source and the view.
// https://github.com/KubaO/stackoverflown/tree/master/questions/proxy-reformat-39244309
#include <QtWidgets>
class RewriteProxy : public QIdentityProxyModel {
QMap<QVariant, QVariant> m_read, m_write;
int m_column;
public:
RewriteProxy(int column, QObject * parent = nullptr) :
QIdentityProxyModel{parent}, m_column{column} {}
void addReadMapping(const QVariant & from, const QVariant & to) {
m_read.insert(from, to);
m_write.insert(to, from);
}
QVariant data(const QModelIndex & index, int role) const override {
auto val = QIdentityProxyModel::data(index, role);
if (index.column() != m_column) return val;
auto it = m_read.find(val);
return it != m_read.end() ? it.value() : val;
}
bool setData(const QModelIndex & index, const QVariant & value, int role) override {
auto val = value;
if (index.column() == m_column) {
auto it = m_write.find(value);
if (it != m_write.end()) val = it.value();
}
return QIdentityProxyModel::setData(index, val, role);
}
};
int main(int argc, char ** argv) {
QApplication app{argc, argv};
QStandardItemModel model{2,2};
model.setData(model.index(0, 0), 1);
model.setData(model.index(1, 0), 2);
model.setData(model.index(0, 1), "Zaphod");
model.setData(model.index(1, 1), "Beeblebrox");
RewriteProxy proxy{0};
proxy.setSourceModel(&model);
proxy.addReadMapping(1, "Hello");
proxy.addReadMapping(2, "World");
QTableView ui;
ui.setModel(&proxy);
ui.show();
return app.exec();
}
i got a godgiven list of xyz (the code says int, just an example) glued into a QList (to big to move anywhere). How can I create a Model View for that? I allready read the Qt doc which tells me, I have to reimplement data, index, parent, rowCount, columnCount functions. But the preprocessor/compiler cries for more reimplemented functions? I allready read a chapter in my Qt Book but it did not help either. Here my hacked away code:
class XModel : public QAbstractListModel
{
Q_OBJECT
public:
explicit XModel(QList<int> *valuelist, QObject *parent = 0);
virtual ~XModel();
int rowCount(const QModelIndex &) const;
int columnCount(const QModelIndex &) const;
QModelIndex index( int row, int column, const QModelIndex & parent = QModelIndex()) const;
QModelIndex parent(const QModelIndex &index) const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
private:
QList<int>* blah;
signals:
public slots:
};
XModel::XModel(QList<int> *valuelist, QObject *parent) :
QAbstractListModel(parent),
blah(valuelist)
{
}
XModel::~XModel()
{
}
int XModel::rowCount(const QModelIndex &) const
{
return blah->size();
}
int XModel::columnCount(const QModelIndex &) const
{
return 1;
}
QModelIndex XModel::index(int row, int column, const QModelIndex &parent) const
{
return createIndex(row, column, (void)&(blah->at(row)));
}
QModelIndex XModel::parent(const QModelIndex &index) const
{
return createIndex(index->row(), index->column(), NULL);
}
QVariant XModel::data(const QModelIndex &index, int role = Qt::DisplayRole) const
{
return QVariant(blah->at(index.row()));
}
Do I even have to use QAbstractItemModel or does QAbstractListModel work the exact same way? How do I give the Model the source of the actual data? Is this only within data function? Please tell me what I am doing wrong, I do not see it and give advice on howto do it properly, (good) howtos welcome.
This is fixed, but...
EDIT:
Widget::Widget(QWidget *parent)
: QWidget(parent),
valuelist(),
xm(&valuelist) //xm = XModel
{
valuelist.append(1);
valuelist.append(2);
valuelist.append(3);
valuelist.append(4);
valuelist.append(5);
valuelist.append(6);
valuelist.append(7);
valuelist.append(8);
valuelist.append(9);
view = new QListView(this);
view->setModel(&xm);
//how to force the XModel to reread the QList`?
view->show();
}
Add to and remove data from XModel and have XModel modify the underlying list (reference?) for you:
Widget::Widget(QWidget *parent)
: QWidget(parent),
valuelist(),
xm(&valuelist) //xm = XModel
{
xm.append(1);
xm.append(2);
xm.append(3);
xm.append(4);
xm.append(5);
xm.append(6);
xm.append(7);
xm.append(8);
xm.append(9);
view = new QListView(this);
view->setModel(&xm);
xm.append(10); // should call beginInsertRows, append to valuelist, and call endInsertRows.
Q_ASSERT(valuelist.contains(10));
view->show();
}
Otherwise, you could perhaps create a mix of QObject and QList that can emit signals to notify XModel of changes, but I think the first approach is better.
Edit:
Here is a silly example. It will create a list-backed model that will append more elements to the list every second:
#include <QtGui/QApplication>
#include <QtGui/QListView>
#include <QtCore/QAbstractListModel>
#include <QtCore/QTimer>
class ListBackedModel : public QAbstractListModel
{
Q_OBJECT
QList<int>* m_list;
public:
ListBackedModel(QList<int>* list, QObject* parent = 0)
: QAbstractListModel(parent)
, m_list(list)
{}
~ListBackedModel()
{}
int rowCount(const QModelIndex &parent = QModelIndex()) const
{
Q_UNUSED(parent);
return m_list->size();
}
QVariant data(const QModelIndex &index, int role) const
{
if (index.row() >= rowCount()) { return QVariant(); }
if (index.row() < 0) { return QVariant(); }
int element = m_list->at(index.row());
if (Qt::DisplayRole == role) {
return QString::number(element);
}
if (Qt::ToolTipRole == role) {
return tr("%1 is element #%2").arg(element).arg(index.row() + 1);
}
return QVariant();
}
void append(int element)
{
/*
First is the new index of the first element that will be inserted.
Last is the new index of the last element that will be inserted.
Since we're appending only one element at the end of the list, the
index of the first and last elements is the same, and is equal to
the current size of the list.
*/
int first = m_list->size();
int last = first;
beginInsertRows(QModelIndex(), first, last);
m_list->append(element);
endInsertRows();
}
void startAddingMoreElements()
{
QTimer::singleShot(1000, this, SLOT(addMoreElements()));
}
private slots:
void addMoreElements()
{
append(qrand() % 100);
startAddingMoreElements();
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QList<int> list;
list << 1 << 10 << 34 << 111;
ListBackedModel model(&list);
QListView listView;
listView.setModel(&model);
listView.show();
model.startAddingMoreElements();
return a.exec();
}
#include "main.moc"
I'm testing QTreeView functionality right now, and i was amazed by one thing. It seems that QTreeView memory consumption depends on items count O_O. This is highly unusual, since model-view containers of such type only keeps track for items being displayed, and rest of items are in the model. I have written a following code with a simple model that holds no data and just reports that it has 10 millions items. With MFC, Windows API or .NET tree / list with such model will take no memory, since it will display only 10-20 visible elements and will request model for more upon scrolling / expanding items. But with Qt, such simple model results in ~300Mb memory consumtion. Increasing number of items will increase memory consumption. Maybe anyone can hint me what i'm doing wrong? :)
#include <QtGui/QApplication>
#include <QTreeView>
#include <QAbstractItemModel>
class CModel : public QAbstractItemModel
{
public: QModelIndex index
(
int i_nRow,
int i_nCol,
const QModelIndex& i_oParent = QModelIndex()
) const
{
return createIndex( i_nRow, i_nCol, 0 );
}
public: QModelIndex parent
(
const QModelIndex& i_oInex
) const
{
return QModelIndex();
}
public: int rowCount
(
const QModelIndex& i_oParent = QModelIndex()
) const
{
return i_oParent.isValid() ? 0 : 1000 * 1000 * 10;
}
public: int columnCount
(
const QModelIndex& i_oParent = QModelIndex()
) const
{
return 1;
}
public: QVariant data
(
const QModelIndex& i_oIndex,
int i_nRole = Qt::DisplayRole
) const
{
return Qt::DisplayRole == i_nRole ? QVariant( "1" ) : QVariant();
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTreeView oWnd;
CModel oModel;
oWnd.setUniformRowHeights( true );
oWnd.setModel( & oModel );
oWnd.show();
return a.exec();
}
If i replace QTreeView with QTableView in sample source, memory will not be consumed. So it seems that QListView and QTreeView are not intended to be used with very big amount of data and QTableView must be used instead.
Alright, I have a really basic QStandardItemModel, filled with some numbers. I managed to display it in a QTableView, it's ok. I created a new model ( subclass either of QAbstractItemModel or QAbstractProxyModel ), which is some kind of a layer of an existing model - it is needed to set the sourcemodel, and this new layer should do some transformations on the real one.
My problem is, that in the top layer, say "layer model" the data( const QModelIndex & index, int role ) member function never called, however i would like to alter the displaying methods by the role parameter.
Here is a sample code, which demonstrates, that the original model's data(index,role) is always called, whilst the layer model's data(index,role) never. Why? How can the QTableView object "skip" the top layer's data(index,role) ?
#include <QtGui/QApplication>
#include <QtGui>
#include <QStandardItemModel>
class MyModel : public QStandardItemModel
{
public:
MyModel(const int r, const int c, QObject* parent = 0) : QStandardItemModel(r,c,parent) {}
QVariant data ( const QModelIndex & index, int role = Qt::DisplayRole ) const {
qDebug() << "mymodel data";
return this->itemFromIndex(index)->data(role);
}
};
class MyProxyModel : public QAbstractProxyModel
{
public:
MyProxyModel(QObject* parent = 0) : QAbstractProxyModel(parent) {}
QModelIndex index ( int row, int column, const QModelIndex & parent = QModelIndex() ) const {
return this->sourceModel()->index(row,column,parent);
}
QModelIndex parent ( const QModelIndex & index ) const {
return this->sourceModel()->parent(index);
}
QModelIndex mapFromSource ( const QModelIndex & sourceIndex ) const
{
return sourceIndex;
}
QModelIndex mapToSource ( const QModelIndex & proxyIndex ) const
{
return proxyIndex;
}
QVariant data ( const QModelIndex & index, int role = Qt::DisplayRole ) const {
qDebug() << "myproxymodel data";
return this->sourceModel()->data(index,role);
}
int rowCount ( const QModelIndex & parent = QModelIndex() ) const {
return this->sourceModel()->rowCount(parent);
}
int columnCount ( const QModelIndex & parent = QModelIndex() ) const {
return this->sourceModel()->columnCount(parent);
}
};
int main(int argc, char *argv[])
{
QApplication app(argc,argv);
MyModel model(8, 2);
MyProxyModel mymodel;
mymodel.setSourceModel(&model);
QTableView tableView;
tableView.setModel(&mymodel);
tableView.horizontalHeader()->setStretchLastSection(true);
for (int row = 0; row < 8; ++row) {
for (int column = 0; column < 2; ++column) {
QModelIndex index = model.index(row, column, QModelIndex());
model.setData(index, QVariant((row+1) * (column+1)));
}
}
tableView.show();
return app.exec();
}
Because QTableView uses the model index to retrieve the data, probably something like this.
QModelIndex index = model->index(row, column, parentIndex);
index.data(Qt::DisplayRole);
And you are returning the model index of the source model instead of an index to your proxy model:
QModelIndex index ( int row, int column, const QModelIndex & parent = QModelIndex() ) const {
return this->sourceModel()->index(row,column,parent);
}
Try to convert the model index into an index to your proxy model
QModelIndex index ( int row, int column, const QModelIndex & parent = QModelIndex() ) const {
return this->createIndex(row,column,row);
}
Don't forget to rewrite the map to source and map from source functions.
Solution
class MyTableProxyModel : public QAbstractProxyModel
{
Q_OBJECT
public:
MyTableProxyModel (QObject* parent = 0)
: QAbstractProxyModel(parent) {
}
QModelIndex index(int row, int column, const QModelIndex& parent=QModelIndex()) const {
return createIndex(row,column,row);
}
QModelIndex parent(const QModelIndex &index) const {
//Works only for non-tree models
return QModelIndex();
}
QModelIndex mapFromSource(const QModelIndex &source) const {
return index(source.row(), source.column(), source.parent());
}
QModelIndex mapToSource(const QModelIndex &proxy) const {
return (sourceModel()&&proxy.isValid())
? sourceModel()->index(proxy.row(), proxy.column(), proxy.parent())
: QModelIndex();
}
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const {
qDebug() << "myproxymodel data";
return mapToSource(index).data(role);
}
int rowCount ( const QModelIndex & parent = QModelIndex() ) const {
return sourceModel() ? sourceModel()->rowCount(parent) : 0;
}
int columnCount ( const QModelIndex & parent = QModelIndex() ) const {
return sourceModel() ? sourceModel()->columnCount(parent) : 0;
}
};