I am trying to get a simple QAbstractItemModel to show up in a QListView but am struggling a bit. Here is my model implementation:
TestModel::TestModel(QObject *parent) : QAbstractItemModel(parent)
{
}
QVariant TestModel::data(const QModelIndex &index, int role) const
{
if(role == Qt::DisplayRole)
{
return QVariant("FRED");
}
return QVariant();
}
Qt::ItemFlags TestModel::flags(const QModelIndex &index) const
{
return Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled;
}
QVariant TestModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if(role == Qt::DisplayRole)
{
return QVariant("BING");
}
return QVariant();
}
QModelIndex TestModel::index(int row, int column, const QModelIndex &parent) const
{
return createIndex(row, column);
}
QModelIndex TestModel::parent(const QModelIndex &index) const
{
return createIndex(0, 0);
}
int TestModel::rowCount(const QModelIndex &parent) const
{
return 1;
}
int TestModel::columnCount(const QModelIndex &parent) const
{
return 1;
}
If I set the model of my QListView to an instance of the above model then nothing shows up. However, if I use a QTableView instead then it is populated as expected.
What do I need to do to get this simple example to work with a QListView?
Thanks,
Alan
List model has no parent hierarchy.
Hence, parent method shall be implemented as,
QModelIndex TestModel::parent(const QModelIndex &index) const
{
return QModelIndex();
}
Related
I have created a model based off of QAbstractTableModel that allows the user to edit data in that model. The model is displayed in a QTableView in a QMainWindow. So far in my model I am able to make the cells editable, and save whatever the user types in after editing is finished.
The issue is that when the user begins editing, it 'clears' the previous contents of that cell. So if for example I only wanted to change the spelling of a string in a cell, I have to re-type the entire value. I would like when editing that the editor would start with the data that is already in the model, rather than empty.
How can I do that?
Example of the issue:
Before I begin editing a cell:
As soon as I begin editing, the cell is empty. I would like it to star with the previous value already in the model:
Here is a minimal example of my model. My actual model is much larger and uses a struct instead of just a 2D array of QVariants to store the data.
Header:
const int COLS= 2;
const int ROWS= 6;
class EditableTableModel : public QAbstractTableModel
{
Q_OBJECT
private:
QVariant tableData[ROWS][COLS];
public:
EditableTableModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(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;
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
signals:
void editCompleted(QString);
};
Implementation:
EditableTableModel::EditableTableModel(QObject *parent)
: QAbstractTableModel(parent)
{
}
int EditableTableModel::rowCount(const QModelIndex & /*parent*/) const
{
return ROWS;
}
int EditableTableModel::columnCount(const QModelIndex & /*parent*/) const
{
return COLS;
}
QVariant EditableTableModel::data(const QModelIndex &index, int role) const
{
int row = index.row();
int col = index.column();
switch (role) {
case Qt::DisplayRole:
return tableData[row][col];
}
return QVariant();
}
bool EditableTableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (role == Qt::EditRole) {
if (!checkIndex(index))
return false;
tableData[index.row()][index.column()] = value;
return true;
}
return false;
}
QVariant EditableTableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
switch (section) {
case 0:
return QString("First Name");
case 1:
return QString("Last Name");
}
}
return QVariant();
}
Qt::ItemFlags EditableTableModel::flags(const QModelIndex &index) const
{
return Qt::ItemIsEditable | QAbstractTableModel::flags(index);
}
Data should be returned for Qt::EditRole in the data() method. The following should work:
QVariant EditableTableModel::data(const QModelIndex &index, int role) const
{
int row = index.row();
int col = index.column();
switch (role) {
case Qt::DisplayRole:
case Qt::EditRole: // <-- add this line
return tableData[row][col];
}
return QVariant();
}
Note that the above switch-case uses something known as fallthrough, so that the switch-case will match for both Qt::DisplayRole and Qt::EditRole.
I have a sqlite table containing (among other things) a "position" and a "state" field.
I want to display this table in a QTableView with the position as the header column and the state in the right column like this :
id | 1 | 2 | 3
1 | A | |
2 | | | E
which represents the following database entries :
id | position | state
1 | 1 | A
2 | 3 | E
What would be the best way to do something like this?
Edit : not sure if this changes anything but I need the QTableView to be editable too (by overriding setData() method of QSqlQueryModel)
The simplest option for this case is to create a class that inherits from QSqlTableModel and modify the necessary functions as shown below:
sqltablemodel.h
#ifndef SQLTABLEMODEL_H
#define SQLTABLEMODEL_H
#include <QSqlTableModel>
class SqlTableModel : public QSqlTableModel
{
const QString stateName = "state";
const QString positionName = "position";
public:
int columnCount(const QModelIndex &parent = QModelIndex()) const;
void setTable(const QString &tableName);
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
Qt::ItemFlags flags(const QModelIndex &index) const;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
private:
int max_position;
int index_position;
int index_state;
void reset();
};
#endif // SQLTABLEMODEL_H
sqltablemodel.cpp
#include "sqltablemodel.h"
#include <QBrush>
#include <QSqlQuery>
#include <QSqlTableModel>
#include <QSqlRecord>
#include <QTimer>
int SqlTableModel::columnCount(const QModelIndex &parent) const
{
return QSqlTableModel::columnCount(parent)+ max_position;;
}
void SqlTableModel::setTable(const QString &tableName)
{
QSqlTableModel::setTable(tableName);
index_position = fieldIndex(positionName);
index_state = fieldIndex(stateName);
reset();
}
QVariant SqlTableModel::data(const QModelIndex &index, int role) const
{
if(role == Qt::ForegroundRole){
return QBrush(Qt::black);
}
const int number_of_columns = QSqlTableModel::columnCount();
if(index.column()>= number_of_columns){
if(role==Qt::DisplayRole){
int position = QSqlTableModel::data(this->index(index.row(), index_position), Qt::DisplayRole).toInt();
if(index.column() == number_of_columns + position - 1){
return QSqlTableModel::data(this->index(index.row(), index_state), Qt::DisplayRole).toString();
}
}
}
return QSqlTableModel::data(index, role);
}
QVariant SqlTableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if(orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= QSqlTableModel::columnCount())
return section - QSqlTableModel::columnCount() + 1;
return QSqlTableModel::headerData(section, orientation, role);
}
Qt::ItemFlags SqlTableModel::flags(const QModelIndex &index) const
{
if(index.column() >= QSqlTableModel::columnCount()){
return Qt::ItemIsSelectable| Qt::ItemIsEditable| Qt::ItemIsEnabled;
}
return QSqlTableModel::flags(index);
}
bool SqlTableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if(role==Qt::EditRole){
const int number_of_columns = QSqlTableModel::columnCount();
if(index.column() >= number_of_columns){
bool result1 = QSqlTableModel::setData(this->index(index.row(), index_position), index.column()-number_of_columns +1, role);
bool result2 = QSqlTableModel::setData(this->index(index.row(), index_state), value, role);
return result1 && result2;
}
if(index.column() == index_position){
QTimer::singleShot(0, this, &SqlTableModel::reset);
}
}
return QSqlTableModel::setData(index, value, role);
}
void SqlTableModel::reset()
{
QSqlQuery q;
q.exec(QString("SELECT MAX(%1) FROM %2").arg(positionName).arg(tableName()));
int val;
while (q.next()) {
val = q.value(0).toInt();
}
if(val != max_position){
beginResetModel();
max_position = val;
endResetModel();
}
}
Input:
id |position |state
1 |1 |A
2 |2 |S
3 |1 |C
4 |4 |B
5 |3 |V
Output:
The complete example can be found in the following link
I've implemented QIdentityProxyModel like this:
class ProxyModel : public QIdentityProxyModel
{
Q_OBJECT
public:
ProxyModel(QObject *parent)
{
entriesPerPage = 3;
page = 1;
}
inline int getEntriesPerPage() const
{ return entriesPerPage; }
inline void setEntriesPerPage(int value)
{ entriesPerPage = value; }
inline qint64 getPage() const
{ return page; }
inline void setPage(const qint64 &value)
{ page = value; }
inline int rowCount(const QModelIndex &parent = QModelIndex()) const override{
Q_UNUSED(parent)
if(!sourceModel())
return 0;
return entriesPerPage * page <= sourceModel()->rowCount()
? entriesPerPage
: sourceModel()->rowCount() - entriesPerPage * (page - 1);
}
inline int columnCount(const QModelIndex &parent = QModelIndex()) const override{
Q_UNUSED(parent);
return 6;
}
QModelIndex ProxyModel::mapFromSource(const QModelIndex &sourceIndex) const
{
if(!sourceModel() && !proxyIndex.isValid())
return QModelIndex();
return sourceIndex.isValid()
? createIndex(sourceIndex.row() % entriesPerPage, sourceIndex.column(), sourceIndex.internalPointer())
: QModelIndex();
}
QModelIndex ProxyModel::mapToSource(const QModelIndex &proxyIndex) const
{
if(!sourceModel() && !proxyIndex.isValid())
return QModelIndex();
QModelIndex remapped = createIndex(proxyIndex.row() ,
proxyIndex.column(),
proxyIndex.internalPointer());
return QIdentityProxyModel::mapToSource(remapped);
}
private:
int entriesPerPage;
qint64 page;
};
When I insert a row in the sourceModel() with index more then entriesPerPage, view displays empty rows, so that row number is more then entriesPerPage, although rowCount() return number equal to entriesPerPage.
How can I get rid of empty rows?
First. It is a bad practice to override rowCount/mapFromSource for QIdentityProxyModel. I propose you to use QAbstractProxyModel to have more clear code.
Main. Your problem is in getEntriesPerPage/setPage methods. You need to call beginResetModel/endResetModel after updating such data.
inline void setPage(const qint64 &value)
{
beginResetModel();
page = value;
endResetModel();
}
Offtopic: it is pretty cool, that you have to code with Qt in BSUIR. Who is your teacher?
I have a model written based on this example. This model is connected to a listview to display dynamic data. I have made sure the model's datachanged() signal is emitted whenever the source data is being changed. The list view does not update on the datachanged() signal. Here is my code:
DataModel.h:
class DataList {
public:
DataList(const QString &name, const QString &value, const QString &category);
QString name() const;
QString value() const;
QString category() const;
private:
QString m_name;
QString m_value;
QString m_category;
};
class DataModel : public QAbstractListModel {
Q_OBJECT
public:
enum AVRoles {
NameRole = Qt::UserRole + 1,
ValueRole,
CategoryRole
};
DataModel(QAbstractItemModel *parent = 0);
virtual ~DataModel() {}
void addData(const DataList &datalist);
int rowCount(const QModelIndex & parent = QModelIndex()) const;
QVariant data(const QModelIndex & index, int role=Qt::DisplayRole) const;
protected:
QHash<int, QByteArray> roleNames() const;
private:
QList<DataList> m_datalist;
public slots:
void onInitialPolygonAreaChanged(qreal);
};
DataModel.cpp:
DataList::DataList(const QString &name, const QString &value, const QString &category) {
m_name = name;
m_value = value;
m_category = category;
}
QString DataList::name() const {
return m_name;
}
QString DataList::value() const {
return m_value;
}
QString DataList::category() const {
return m_category;
}
DataModel::DataModel(QAbstractItemModel *parent) :
QAbstractListModel(parent) {
}
void DataModel::addData(const DataList &datalist){
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_datalist << datalist;
endInsertRows();
}
int DataModel::rowCount(const QModelIndex &parent) const{
Q_UNUSED(parent);
return m_datalist.count();
}
QVariant DataModel::data(const QModelIndex &index, int role) const {
if (index.row() < 0 || index.row() >= m_datalist.count())
return QVariant();
const DataList &datalist = m_datalist[index.row()];
if(role ==NameRole )
return datalist.name();
else if(role == ValueRole)
return datalist.value();
else if(role == CategoryRole)
return datalist.category();
return QVariant();
}
QHash<int, QByteArray> DataModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[NameRole] = "name";
roles[ValueRole] = "value";
roles[CategoryRole] = "category";
return roles;
}
void DataModel::onInitialPolygonAreaChanged(qreal ) {
//0 is the index of iPA
qDebug() << "IPA from model" ;
QVector<int> roleVector;
roleVector.append(ValueRole);
roleVector.append(NameRole);
roleVector.append(CategoryRole);
roleVector.append(Qt::DisplayRole);
emit dataChanged(index(0),index(0),roleVector);
}
Mainwindow.cpp:
model.addData(DataList(QString("Initial Polygon Area"),QString::number(av1.initialPolygonArea()) + " sq.km",QString("Area")));
View->rootContext()->setContextProperty("AVModel",&model);
connect(&av1,SIGNAL(initialPolygonAreaChanged(qreal)),&model,SLOT(onInitialPolygonAreaChanged(qreal)));
view.qml:
ListView {
id: view
anchors.top: parent.top
anchors.bottom: parent.bottom
width: parent.width
model: AVModel
delegate: Text { text: name + " " + value; font.pixelSize: 18 ; color: "white"}
}
I'm using a custom class, GpibDevicesModel, inheriting from QAbstractTableModel, and the compiler says that there is an undefined reference on the constructor. But I don't understand why.
Here is the code:
MainWindow.h
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
//...
private:
GpibDevicesModel *m_gpibDevicesModel;
};
MainWindow.cpp
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
//...
//undefined reference is located here
m_gpibDevicesModel = new GpibDevicesModel;
//...
}
GpibDevicesModel.h
#ifndef GPIBDEVICESMODEL_H
#define GPIBDEVICESMODEL_H
#include <QAbstractTableModel>
class MeasureDevice;
class GpibDevicesModel : public QAbstractTableModel
{
Q_OBJECT
public:
enum Columns{
NameCol = 0,
AddressCol,
IdCol,
TypeCol,
ConnectedCol,
EndCol //Should always be at end
};
//constructor and destructor
explicit GpibDevicesModel(QObject *parent = 0);
~GpibDevicesModel();
protected:
//Reimplemented from QAbstractTableModel
QVariant data(const QModelIndex &index, int role) const;
int rowCount(const QModelIndex &parent) const;
int columnCount(const QModelIndex &parent) const;
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
//Data list getter and setter
QList<MeasureDevice *> measureDevices() const;
void setMeasureDevices(const QList<MeasureDevice *> &measureDevices);
private:
QList<MeasureDevice *> m_measureDevices;
};
#endif // GPIBDEVICESMODEL_H
GpibDevicesModel.cpp
#include "gpibdevicesmodel.h"
#include "measuredevice.h"
#include <QIcon>
GpibDevicesModel::GpibDevicesModel(QObject *parent)
: QAbstractTableModel(parent)
{}
GpibDevicesModel::~GpibDevicesModel()
{}
QVariant GpibDevicesModel::data(const QModelIndex &index, int role) const
{
if(!index.isValid() || index.row() >= m_measureDevices.size())
return QVariant();
switch(role){
//This is roles for ComboBox
case Qt::DisplayRole:
switch(index.column()){
case NameCol: return m_measureDevices.at(index.row())->displayName();
case AddressCol:return m_measureDevices.at(index.row())->gpibName();
case IdCol: return m_measureDevices.at(index.row())->property("internal_id").toString();
case TypeCol: return m_measureDevices.at(index.row())->typeString();
}
break;
case Qt::DecorationRole:
switch(index.column()){
case ConnectedRole: {
MeasureDevice *md = m_measureDevices.at(index.row());
if(md->isConnected()) return QIcon(":/Icons/green_led.png");
else return QIcon(":/Icons/red_led.png");
}
}
break;
}
return QVariant();
}
int GpibDevicesModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_measureDevices.size();
}
int GpibDevicesModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return EndCol;
}
QVariant GpibDevicesModel::headerData(int section, Qt::Orientation orientation, int role) const
{
static QStringList headers = QStringList() << tr("Name") << tr("Address") << tr("Id")
<< tr("Type") << tr("Connected") ;
if(role == Qt::DisplayRole && orientation == Qt::Vertical)
return QVariant(); //return section; //uncomment here to displayed row number
if(section >= headers.size())
return QVariant();
switch(role){
case Qt::DisplayRole: return headers.at(section);
}
return QVariant();
}
QList<MeasureDevice *> GpibDevicesModel::measureDevices() const
{
return m_measureDevices;
}
void GpibDevicesModel::setMeasureDevices(const QList<MeasureDevice *> &measureDevices)
{
beginResetModel();
m_measureDevices = measureDevices;
endResetModel();
}
And here is the error message from the compiler
mainwindow.cpp:1430: undefined reference to GpibDevicesModel::GpibDevicesModel(QObject*)'
I think that will be a stupid thing, as always... But I cannot figure out this error. I'm using another custom model in the same way and I don't have any problem.
As I guessed, it was stupid...
I resolved the problem by removing manually the build folder and the Makefiles (makefile, makefile.debug and makefile.release), then I run again qmake -> build and it was ok. So this was not a problem from code, maybe the makefiles was in read-only for an unknown reason.
Thanks for your help and your time Frogatto and Frank Osterfeld.