I want to start off making my own models for Qt list views, and I thought that I would start by wrapping a QStringListModel in my own QAbstractItemModel, then render it in a list view. However, it only renders a blank white square, instead of the list I expect. I don't really know what could be happening, given that all I'm doing is delegating all calls to the QStringListModel. Perhaps there's some aspects of the QStringListModel that are called by the QListView that are not mandated by the QAbstractItemModel pure virtual methods? Or maybe it's related to storage of the QStringList somehow?
My attempt is below. The header:
class DelegatingItemModel: public QAbstractItemModel {
public:
DelegatingItemModel();
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &index) const override;
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
private:
QAbstractItemModel* innerModel;
};
This is the implementation:
#include "delegating_item_model.hh"
DelegatingItemModel::DelegatingItemModel() {
QStringList available = {"foo", "bar", "baz"};
this->innerModel = new QStringListModel(available);
}
QVariant DelegatingItemModel::data(const QModelIndex &index, int role) const {
return innerModel->data(index, role);
}
int DelegatingItemModel::columnCount(const QModelIndex &parent) const {
return innerModel->columnCount(parent);
}
int DelegatingItemModel::rowCount(const QModelIndex &parent) const {
return innerModel->rowCount(parent);
}
QModelIndex DelegatingItemModel::parent(const QModelIndex &index) const {
return innerModel->parent(index);
}
QModelIndex DelegatingItemModel::index(int row, int column, const QModelIndex &parent) const {
return innerModel->index(row, column, parent);
}
And this is the entry point:
int main(int argc, char** argv) {
qDebug() << "Starting up";
QApplication app(argc, argv);
QMainWindow mainWindow;
QListView* listView = new QListView;
DelegatingItemModel* theModel = new DelegatingItemModel;
listView->setModel(theModel);
mainWindow.setCentralWidget(listView);
mainWindow.show();
return app.exec();
}
Your view will get data from the model only if the given index is linked to its model. If you print a trace in the data() method, you will see that it's never called.
So, you cannot return a new index created by your inner list model because it will be linked to the list and not your own model. For example:
QModelIndex DelegatingItemModel::index(int row, int column, const QModelIndex &parent) const {
//return innerModel->index(row, column, parent);
if (parent.isValid()) // It's a list. Not a tree
return QModelIndex();
return createIndex(row, column); // Create a index for your own model.
}
To be full compliant, you should convert the index in data():
QVariant DelegatingItemModel::data(const QModelIndex &index, int role) const {
QModelIndex const innerIndex(innerModel->index(index.row(), index.column()));
return innerModel->data(innerIndex, role);
}
Related
I have QTreeView with QAbstractItemModel. Some particular columns are supposed to have user defined checkboxes. I have done so by overriding QAbstractItemModel::data() function and by sending check state for Qt::CheckStateRole role as shown in the code.
I am getting checkboxes and am able to check and uncheck them successfully.
But the requirement is to customize some of these checkboxes. Basically I need to differentiate some checkboxes from the others by any method for eg: fill the checkbox with blue, make the boundary of the checkbox blue or any other method. But I am not sure how to change checkbox styling as I am creating checkbox via model.
QVariant MyModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (role == Qt::CheckStateRole && index.column() == COLUMN_WITH_CHECKBOX)
{
//return Qt::Checked or Qt::Unchecked here
}
//...
}
bool MyModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (!index.isValid())
return false;
if (role == Qt::CheckStateRole)
{
if ((Qt::CheckState)value.toInt() == Qt::Checked)
{
//user has checked item
return true;
}
else
{
//user has unchecked item
return true;
}
}
return false;
}
First you need is implement your own ItemDelegate
class CheckedDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
CheckedDelegate(QObject *parent = nullptr);
~CheckedDelegate();
QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const;
void setEditorData(QWidget *editor, const QModelIndex& index) const;
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex& index) const;
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex& index) const;
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex& index) const;
};
In this delegate you must implement custom editor and custom item painting. To create custom editor:
QWidget *CheckedDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QCheckBox *chBox = new QCheckBox(parent);
//customize editor checkbox
QString strQss = "QCheckBox::indicator:checked { image: url(:/icons/pic/checkboxChecked.png); } ";
strQss.append("QCheckBox::indicator:unchecked { image: url(:/icons/pic/checkboxUnchecked.png); }");
chBox->setStyleSheet(strQss);
return chBox;
}
void CheckedDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
QCheckBox *chBox = dynamic_cast<QCheckBox*> (editor);
if (index.data(Qt::CheckStateRole).toInt() == Qt::Checked)
{
chBox->setChecked(true);
}
else
{
chBox->setChecked(false);
}
}
void CheckedDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
QCheckBox *chBox = dynamic_cast<QCheckBox*> (editor);
model->setData(index, chBox->isChecked() ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole);
}
void CheckedDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
editor->setGeometry(GetCheckboxRect(option));
}
To calculate checkbox geometry use
QRect GetCheckboxRect(const QStyleOptionViewItem &option)
{
QStyleOptionButton opt_button;
opt_button.QStyleOption::operator=(option);
QRect sz = QApplication::style()->subElementRect(QStyle::SE_ViewItemCheckIndicator, &opt_button);
QRect r = option.rect;
// center 'sz' within 'r'
double dx = (r.width() - sz.width()) / 2;
double dy = (r.height()- sz.height()) / 2;
r.setTopLeft(r.topLeft() + QPoint(qRound(dx),qRound(dy)));
r.setWidth(sz.width());
r.setHeight(sz.height());
return r;
}
Then implement custom painting. In this example I use pixmaps to customize checkbox so I also paint only pixmaps.
void CheckedDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyleOptionViewItem opt = option;
QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter);
if (index.data(Qt::CheckStateRole).toInt() == Qt::Checked) {
QApplication::style()->drawItemPixmap(painter, GetCheckboxRect(option), Qt::AlignLeft | Qt::AlignVCenter, QPixmap(":/icons/pic/checkboxChecked.png"));
} else {
QApplication::style()->drawItemPixmap(painter, GetCheckboxRect(option), Qt::AlignLeft | Qt::AlignVCenter, QPixmap(":/icons/pic/checkboxUnchecked.png"));
}
}
And set your delegate (in my example I have TableTiew not TreeView)
CheckedDelegate *chDel = new CheckedDelegate(this);
ui->tableView->setItemDelegateForColumn(1, chDel);
I've been struggling for a few days trying to figure out how to display some pretty simple data in a QML window. I realize there are many ways to accomplish this task, and in this case, I'd prefer to find out how to use QAbstractTableModel.
I have a row of data, each row containing two items, a name and a value (name/value or key/value pair). I've subclassed the QAbstractTableModel to pass this data to the QML. Here is the code I have so far; it is mostly based on the tutorial that can be found here (which is also very old): https://doc.qt.io/archives/4.6/itemviews-addressbook.html.
databridge.h
#include <QObject>
#include <QAbstractTableModel>
#include <QPair>
class DataBridge : public QAbstractTableModel
{
Q_OBJECT
public:
explicit DataBridge();
explicit DataBridge(QList<QPair<QString, QString>> listOfPairs);
int rowCount(const QModelIndex &parent) const;
int columnCount(const QModelIndex &parent) const;
QVariant data(const QModelIndex &index, int role) const;
bool setData(const QModelIndex &index, const QVariant &value, int role);
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
bool insertRows(int row, int count, const QModelIndex &parent);
bool removeRows(int row, int count, const QModelIndex &parent);
Qt::ItemFlags flags(const QModelIndex &index) const;
QList<QPair<QString, QString>> getList();
signals:
public slots:
private:
QList<QPair<QString, QString>> m_listOfPairs;
};
#endif // DATABRIDGE_H
databridge.cpp
#include "databridge.h"
DataBridge::DataBridge()
{}
DataBridge::DataBridge(QList<QPair<QString, QString> > listOfPairs)
{ m_listOfPairs = listOfPairs; }
int DataBridge::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return m_listOfPairs.size();
}
int DataBridge::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
//Number of columns is always 2 in this kata
return 2;
}
QVariant DataBridge::data(const QModelIndex &index, int role) const
{
if(!index.isValid())
return QVariant();
if(index.row() >= m_listOfPairs.size() || index.row() < 0)
return QVariant();
if(role == Qt::DisplayRole)
{
QPair<QString, QString> pair = m_listOfPairs.at(index.row());
if(index.column() == 0)
return pair.first;
else
return pair.second;
}
return QVariant();
}
bool DataBridge::setData(const QModelIndex &index, const QVariant &value, int role)
{
//TODO
return QVariant();
}
QVariant DataBridge::headerData(int section, Qt::Orientation orientation, int role) const
{
//TODO
return QVariant();
}
bool DataBridge::insertRows(int row, int count, const QModelIndex &parent)
{
//TODO
return true;
}
bool DataBridge::removeRows(int row, int count, const QModelIndex &parent)
{
//TODO
return true;
}
Qt::ItemFlags DataBridge::flags(const QModelIndex &index) const
{
if(!index.isValid())
return Qt::ItemIsEnabled;
return QAbstractTableModel::flags(index) | Qt::ItemIsEditable;
}
QList<QPair<QString, QString> > DataBridge::getList()
{
return m_listOfPairs;
}
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QPair>
#include "databridge.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
QList<QPair<QString, QString>> inserter;
inserter.append(QPair<QString, QString>("0", "zero"));
inserter.append(QPair<QString, QString>("1", "one"));
inserter.append(QPair<QString, QString>("2", "two"));
inserter.append(QPair<QString, QString>("3", "three"));
inserter.append(QPair<QString, QString>("4", "four"));
DataBridge * bridge = new DataBridge(inserter);
engine.rootContext()->setContextProperty("bridge", bridge);
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
main.qml
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Component{
id: myComponent
Row{
id: thisRow
Text{
text: bridge.data(index, 0)
}
Text{
text: "\t.\t.\t.\t"
}
Text{
text: bridge.data(index, 0)
}
}
}
Column{
id: thisColumn
anchors.horizontalCenter: parent.horizontalCenter
Repeater{
id: myRepeater
delegate: myComponent
model: bridge
}
}
Component.onCompleted: {
console.log("DEBUG main.qml");
}
}
Please ignore the functions with TODO in them in the databridge.cpp. I didn't include the bodies for the sake of brevity.
The first problem I'm facing is that when my repeater in the main.qml calls bridge.data(index, 0), the index is determined to be invalid in the first if statement of the data function (if(!index.isValid()). I'm not sure why this is happening. The second issue I can see, though I haven't gotten that far yet, is how can I tell the data function if I'm calling column 0 or column 1? It checks for this later in the function, and returns the pair relevant to the column requested. I'm guessing that in myComponent, I need something more specific to request the data from whichever column, but I'm not sure what that would be?
Any help with this would be immensely appreciated. Thank you in advance!
QModelIndex Documentation
Another link to the example referenced at the top
QAbstractTableModel Documentation
Qt Role Enum Documentation
I was able to find a solution, but I'm not sure if it's the correct one. I've added a function to the databridge class that takes integer input from the QML, finds a corresponding QModelIndex, and pulls the data from the model that way. This is what the code looks like:
From databridge.cpp
QString DataBridge::getThisItem(int index, int column)
{
QModelIndex currentIndex = QAbstractTableModel::index(index, column);
QString retVal = data(currentIndex, 0).toString();
return retVal;
}
Corresponding QML Repeater Change
Component{
id: myComponent
Row{
id: thisRow
Text{
text: bridge.getThisItem(index, 0)
}
Text{
text: "\t.\t.\t.\t"
}
Text{
text: bridge.getThisItem(index, 1)
}
}
}
This produces the desired output, that being:
I am interested to know if this is a good implementation, or if there is a better way to do this. Thanks again!
I'm working on a dialog class with a QListView and a customized model inheriting QAbstractListModel. Items on my list are custom widgets with several labels.
I managed to make a checkbox displayed for each item by reimplementing data(), setData() and flags() methods of my model, but when I run my code and click on a checkbox associated to one of the item, the checkbox doesn't appear as checked (remains unchecked).
Here's my code:
mymodel.h
class MyModel : public QAbstractListModel
{
Q_OBJECT
public:
MyModel(QObject *parent);
int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE ;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
bool setData(const QModelIndex & index, const QVariant & value, int role = Qt::EditRole) Q_DECL_OVERRIDE;
Qt::ItemFlags flags(const QModelIndex & index) const Q_DECL_OVERRIDE ;
QSet<QPersistentModelIndex> checkedItems;
};
mymodel.cpp
QVariant MyModel::data(const QModelIndex &index, int role) const
{
if(role == Qt::CheckStateRole)
{
if(checkedItems.contains(index))
return Qt::Checked;
else
return Qt::Unchecked;
}
return QVariant();
}
bool MyModel::setData(const QModelIndex & index, const QVariant & value, int role)
{
if(role == Qt::CheckStateRole)
{
if(value == Qt::Checked)
checkedItems.insert(index);
else
checkedItems.remove(index);
emit dataChanged(index, index);
}
return true;
}
Qt::ItemFlags MyModel::flags(const QModelIndex &index) const
{
return QAbstractListModel::flags(index) | Qt::ItemIsUserCheckable;
}
mydelegate.h
class MyDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
MyDelegate(QObject* parent = 0) : QStyledItemDelegate(parent) {}
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const;
};
mydelegate.cpp
QSize MyDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
return QSize(400, 120);
}
In the constructor of mydialog.cpp
model = new MyModel(this);
ui->list->setModel(model);
ui->list->setItemDelegate(new MyDelegate(this));
I've tried adding flags Qt::ItemIsEnabled and Qt::ItemIsEditable but it didn't change anything.
I'm not very familiar with view/model implementation so far, although I've read Qt docs.
Thx for the help !
To be more precise about my comment, here is what I had in mind
bool MyModel::setData(const QModelIndex & index, const QVariant & value, int role)
{
if(role == Qt::CheckStateRole)
{
if(value == Qt::Checked)
checkedItems.insert(index);
self.checkBoxList[index.row()][index.column()].setChecked(True)
else
checkedItems.remove(index);
self.checkBoxList[index.row()][index.column()].setChecked(False)
emit dataChanged(index, index);
}
return true;
}
I just do not have all of your code so I do not know how your self.checkBoxList is done. Are the checkBox child of the table?
The following code is from a Qt demo. This is a model for QTreeView.
TreeItem class below represent each node in tree, it can have child nodes.
class TreeItem
{
public:
explicit TreeItem(const QList<QVariant> &data, TreeItem *parentItem = 0);
~TreeItem();
void appendChild(TreeItem *child);
TreeItem *child(int row);
int childCount() const;
int columnCount() const;
QVariant data(int column) const;
int row() const;
TreeItem *parentItem();
private:
QList<TreeItem*> m_childItems;
QList<QVariant> m_itemData;
TreeItem *m_parentItem;
};
TreeModel class below is the main model. It only contains one root node which contain all other child nodes.
class TreeModel : public QAbstractItemModel
{
Q_OBJECT
public:
explicit TreeModel(const QString &data, QObject *parent = 0);
~TreeModel();
QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE;
Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE;
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
QModelIndex index(int row, int column,
const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
QModelIndex parent(const QModelIndex &index) const Q_DECL_OVERRIDE;
int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
private:
void setupModelData(const QStringList &lines, TreeItem *parent);
TreeItem *rootItem;
};
I am using QML and I can get this model displayed in tree however I also want to display in a ListView.
For the ListView, I only want to display one tier at one time (1st childrens). When user click on any of the item, it should clear and show that item children. How do I do that?
My Qml code is below. It displays all first tier children which is great but I need to show children when user clicks on an item. My thought is need extract the sub model and point to it, but how?
Item {
width: parent.width
height: parent.height
ListView {
//anchors.top: myImage.bottom
anchors.fill: parent
id: list
spacing: 4
model: treeModel
delegate: listDelegate
}
Component {
id: listDelegate
Rectangle
{
id: delegateRect
height: image.height
width: 500
Image {
id: image
source: "qrc:/icons/resources/element.ico"
}
Text {
id: t
anchors.left: image.right
anchors.leftMargin: 20
anchors.centerIn: delegateRect
text: title + "/" + summary
//text: display
}
MouseArea {
anchors.fill: parent
onClicked: {
list.currentIndex = index
console.log("Item clicked, index = " + index)
// I think I should change model here to sub model but how do I do it?
//list.model = treeModel.data(index)
}
}
}
}
}
You should have a look at the QAbstractProxyModel and derived classes in the Qt documentation http://doc.qt.io/qt-5/qabstractproxymodel.html.
A proxy model is used to map model indexes (if you want to modify the layout, or do sorting/filtering on data) and do data processing (modify the data returned from the source models data method).
What you need to do is to add a property to select the new root (the item that should be the root item of the extracted model) and implement the two methods mapFromSource(const QModelIndex &) and mapToSource(const QModelIndex &). These methods map the model index given to the view (and only valid in your proxy model) to a model index that is valid for the source model based on the currently set root property (and vice versa).
In addition you should also reimplement the roleNames() method to forward the rolenames defined by the source model to be able to access the data from inside QML.
I've got a QML-application containing a TableView with two columns. One of them is a CheckBox. Now I created a model derived from QAbstractTableModel. Reading data for the ordinary text-column already works but how do I sync the checked-property for my CheckBox with the model?
Currently I can't even set it checked via model. You find the relevant code below.
tablemodel.cpp
TableModel::TableModel(QObject *parent) :
QAbstractTableModel(parent)
{
list.append("test1");
list.append("test2");
}
int TableModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return list.count();
}
int TableModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return 2;
}
QVariant TableModel::data(const QModelIndex & index, int role) const {
if (index.row() < 0 || index.row() >= list.count())
return QVariant();
if (role == NameRole)
return list[index.row()]
else if (role== EnabledRole){
//list is not QList<QString>, its a custom class saving a String and a boolean for the checked state
return list[index.row()].isEnabled();
}
else {
return QVariant();
}
}
QHash<int, QByteArray> TableModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[NameRole] = "name";
roles[EnabledRole] = "enabled";
return roles;
}
tablemodel.hpp
class TableModel : public QAbstractTableModel
{
Q_OBJECT
public:
enum Roles {
NameRole = Qt::UserRole + 1,
EnabledRole
};
explicit TableModel(QObject *parent = 0);
int rowCount(const QModelIndex &parent = QModelIndex()) const;
int columnCount(const QModelIndex & parent = QModelIndex()) const;
Q_INVOKABLE QVariant data (const QModelIndex & index, int role) const;
protected:
QHash<int, QByteArray> roleNames() const;
private:
QList<QString> list;
main.qml
TableView {
id: Table
model: TableModel
TableViewColumn {
role: "name"
}
TableViewColumn {
role: "enabled"
delegate: CheckBox {
//how to get the right state from the model
//checked: ??
}
}
}
main.cpp
QQmlApplicationEngine engine;
QQmlContext * context = new QQmlContext(engine.rootContext());
TableModel tableModel;
context->setContextProperty("tableModel",&tableModel);
QQmlComponent component(&engine, QUrl("qrc:///main.qml"));
QQuickWindow * window = qobject_cast<QQuickWindow*>(component.create(context));
window->show();
You can emit signal from qml, when clicked on checkbox; connect this signal to your model slot and do something
main.qml
TableView {
id: table
objectName: "myTable"
signal checkedChanged(int index, bool cheked)
TableViewColumn {
role: "enabled"
delegate: CheckBox {
onClicked: table.checkedChanged(styleData.row, checked);
}
}
main.cpp
QQuickItem *obj = engine.rootObjects().at(0)->findChild<QQuickItem*>(QStringLiteral("myTable"));
QObject::connect(obj,SIGNAL(checkedChanged(int,bool)),tableModel,SLOT(mySlot(int,bool)));