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.
Related
I have a QAbstractTableModel that has a list of custom items it displays, this TableModel is the model of a TableView. How can i refresh the TableView every x seconds? I tried beginInsertRows and endInsertRows, but this caused laggs because im editing too many items per second, so i only want to refresh it every x seconds.
use QTimer
for example,
QTimer *timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(processOneThing()));
timer->start(1000);
in processOneThing , you can write the code of refreshing data and set the timer again using timer->start(1000);
Using beginInsertRows and endInsertRows could potentially cause a whole lot of reorgansation internally that is not necessary. If the structure of the model (i.e. ordering, number of items,etc.) doesn't change, only the display content, you're far better off emitting the dataChanged signal.
That signal tells the connected views to refresh, and they'll only redraw the items that are visible within the viewport, they don't process items that are hidden.
#include <QtWidgets/QApplication>
#include <QtCore/qtimer.h>
#include <QtWidgets/qtableview.h>
#include <QtCore/QAbstractTableModel>
class TableModel : public QAbstractTableModel {
public:
TableModel(QObject *parent = nullptr) : QAbstractTableModel(parent) {
connect(&timer, &QTimer::timeout, [=]() {
emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
});
timer.start(1000);
}
virtual int rowCount(QModelIndex const &index = QModelIndex()) const { return index.parent().isValid() ? 0 : 5; }
virtual int columnCount(QModelIndex const &index = QModelIndex()) const { return index.parent().isValid() ? 0 : 10; }
virtual QVariant data(QModelIndex const &index, int role = Qt::DisplayRole) const {
QVariant value;
if (index.isValid() && role == Qt::DisplayRole) {
value = QString("X %1; Y: %2").arg(qrand()).arg(qrand());
}
return value;
}
private:
QTimer timer;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
TableModel model;
QTableView view;
view.setModel(&model);
view.show();
return a.exec();
}
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 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();
}
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"