I'm trying to display a user defined tree Family_tree through a TreeModel class derived from QAbstractItemModel containing a Family_tree ft_ member and the overrides for the index, parent, data etc methods.
I rewrote the methods using the Family_tree nodes and I'm calling them from MainWindow like shown below (I double checked Family_tree and it is being set up correctly). All I see is empty space with a single '1' appearing on the top left corner since I have columnCount return 1 for all the nodes and I'm starting by trying to display a tree with only one node (I only expect icons to show up).
Am I misinterpreting how this works in any way? I would really appreciate some insight, thank you for your time!
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
inh_pattern = AD;
patient = new Patient("Nancy Kalaj", Date(12, 12, 1997), 25, 'F', Genotype("DD"), Date());
family = new Family_tree(*patient);
selected = new SelectedPatient(); selected->setSelectedPatient(patient);
patientOverview = new PatientOverview(*patient, *selected, this); patientOverview->show();
patientRelatives = new PatientRelatives(*family, *selected, this);
treeModel = new TreeModel(*family);
QTreeView* view = new QTreeView(this);
view->setModel(treeModel);
//...
}
QModelIndex TreeModel::index(int row, int column, const QModelIndex& parent) const {
if (!hasIndex(row, column, parent)) {return QModelIndex();}
node* parentNode;
if (!parent.isValid()) {parentNode = ft_.get_root();}
else {parentNode = static_cast<node*>(parent.internalPointer());}
node* childNode = parentNode->getChildren()[row];
if (childNode) {return createIndex(row, column, childNode);}
else {return QModelIndex();}
}
QModelIndex TreeModel::parent(const QModelIndex& index) const {
if (!index.isValid()) {return QModelIndex();}
node* n = static_cast<node*>(index.internalPointer());
node* parentNode = n->getParent();
if (parentNode == ft_.get_root()) {return QModelIndex();}
return createIndex(row(parentNode), 0, parentNode);
}
bool TreeModel::hasChildren(const QModelIndex& parent) const {
if (!parent.isValid()) {
return true;
}
node* n = static_cast<node*>(parent.internalPointer());
return !n->getChildren().empty();
}
int TreeModel::rowCount(const QModelIndex& parent) const {
node* parentNode;
if (parent.column() > 0) {return 0;}
if (!parent.isValid()) {parentNode = ft_.get_root();}
else {parentNode = static_cast<node*>(parent.internalPointer());}
return parentNode->getChildren().size();
}
int TreeModel::columnCount(const QModelIndex& parent) const {
return 1;
}
QVariant TreeModel::data(const QModelIndex &index, int role) const {
if (!index.isValid()) {return QVariant();}
node* n = static_cast<node*>(index.internalPointer());
if (role == Qt::DecorationRole) {
if (index.column() == 0) {
QPixmap pixmap(16, 16);
pixmap.fill(Qt::black);
QPainter painter(&pixmap);
painter.setBrush(Qt::white);
painter.drawRect(0, 0, 15, 15);
return QIcon(pixmap);
}
else {return QVariant();}
}
return QVariant();
}
Qt::ItemFlags TreeModel::flags(const QModelIndex& index) const {
if (!index.isValid()) {return Qt::NoItemFlags;}
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
Related
I'm adding data loaded form a file on disk to a QTreeView , the file contains more than 60.000 rows and new 'rows'/data will be constantly added to it while its being executed, so each time the GUI is reloaded/reopened the file will contain more data.
I tried to load this data using another thread just to figure out that QAbstractItemModel is not thread safe.
Then I tried to created a 'lazy load' subclass of a QAbstractItemModel , that 'process' the items as they get visible in the
QTreeview.
At the beginning it worked great, as it doesn't freeze the entire GUI for like 10~ seconds at runtime anymore.
However when i scroll the QTreeView it get frozen for many seconds, i think its 'loading' the data.
I was reading the documentation of QAbstractItemModel and see that it have these two functions: fetchMore() and canFetchMore(), to control how the data is loaded, but i didn't understand how to use it and if they could help in this case.
How I could dynamically load/unload the data as it get visible in the QTreeView? and free the memory of the rows that are not visible/not visible anymore, instead of having the entire file loaded into the memory.
Here is what I already did, using Qt 6.4.
Reproducible example:
#include "treeview.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindowClass())
{
ui->setupUi(this);
QGridLayout* layout = new QGridLayout(this);
ui->centralWidget->setLayout(layout);
auto treeView = new TreeView(this);
layout->addWidget(treeView);
QShortcut *shortcut = new QShortcut(QKeySequence(Qt::Key_F2), this);
connect(shortcut, &QShortcut::activated, [=] {
treeView->setInfo();
qDebug() << "rowCount: " << treeView->model->rowCount();
});
}
treeview.h
class LazyLoadingModel : public QAbstractItemModel
{
Q_OBJECT
public:
LazyLoadingModel(QObject *parent = nullptr) : QAbstractItemModel(parent) {}
int rowCount(const QModelIndex &parent = QModelIndex()) const override
{
return m_rows.count();
}
int columnCount(const QModelIndex &parent = QModelIndex()) const override
{
return m_columns.count();
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
{
if (!index.isValid())
return QVariant();
int row = index.row();
int column = index.column();
if (row >= m_rows.count() || column >= m_columns.count())
return QVariant();
if (role == Qt::DisplayRole)
{
return m_rows[row][column];
}
return QVariant();
}
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override
{
if (!index.isValid())
return false;
int row = index.row();
int column = index.column();
if (row >= m_rows.count() || column >= m_columns.count())
return false;
if (role == Qt::EditRole)
{
m_rows[row][column] = value.toString();
emit dataChanged(index, index);
return true;
}
return false;
}
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override
{
if (!parent.isValid())
return createIndex(row, column);
else
return QModelIndex();
}
QModelIndex parent(const QModelIndex &index) const override
{
return QModelIndex();
}
void setColumns(const QStringList &columns)
{
m_columns = columns;
}
void addData(const QVector<QStringList> &row_info)
{
beginInsertRows(QModelIndex(), m_rows.count(), m_rows.count() + row_info.count() - 1);
for (const auto &row : row_info)
m_rows.append(row);
endInsertRows();
}
private:
QStringList m_columns;
QList<QStringList> m_rows;
};
class TreeView : public QTreeView {
Q_OBJECT
public:
LazyLoadingModel* model = new LazyLoadingModel();
TreeView(QWidget* parent = 0) : QTreeView(parent) {
setModel(model);
}
void setInfo() {
QElapsedTimer timer;
timer.start();
model->setColumns({ "Column 0", "Column 1", "Column 2", "Column 3" });
//
// Load the data from disk and parse it into a qvector
// or:
// Create some random data, just to debug the model:
QVector<QStringList> info{};
QStringList list;
for (size_t i = 0; i < 60000; i++) {
list = {};
QString str = QString::number(i);
for (size_t i = 0; i < 4; i++)
list << str;
info.emplace_back(list);
}
model->addData(info);
qint64 elapsed = timer.elapsed();
qDebug() << "[T] Elapsed time (ms):" << elapsed;
}
};
I am implementing a TreeView with drag and drop support. The desired behavior is to drag and drop only the children of the parents ("Parent-0", "Parent-1", and "Parent-2"). But in my current code, the parents can also be dragged and dropped, and this shouldn't happen.
Please run the codes to understand better what is happening.
Any ideas on how to implement this? I found on the web that this solution is related to the flags, but I don't know how to implement it.
The parent and childs are inserted in the void TreeModel::setupModelData() function of the TreeModel.
Also, I have another issue similar to this. I would like to add a ToolTip with a custom description for all the children, but I don't know how to implement it. I found that there is a function called setToolTip() but my TreeNode objects do not have that attribute. Maybe
I should inherit from another class because I don't have these attributes.
These are the codes:
TreeModel.h
#ifndef TREEMODEL_H
#define TREEMODEL_H
#include <QAbstractItemModel>
#include <QModelIndex>
#include <QVariant>
class TreeNode;
class TreeModel : public QAbstractItemModel
{
Q_OBJECT
public:
explicit TreeModel(const QStringList &modalities, QObject *parent = 0);
~TreeModel();
QVariant data(const QModelIndex &index, int role) 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;
Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE;
Qt::DropActions supportedDragActions() const Q_DECL_OVERRIDE;
Qt::DropActions supportedDropActions() const Q_DECL_OVERRIDE;
QStringList mimeTypes() const Q_DECL_OVERRIDE;
QMimeData *mimeData(const QModelIndexList &indexes) const Q_DECL_OVERRIDE;
bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex & parent) override;
private:
TreeNode * nodeForIndex(const QModelIndex &index) const;
void removeNode(TreeNode *node);
void setupModelData(const QStringList &lines, TreeNode *parent);
TreeNode *m_rootNode;
};
#endif // TREEMODEL_H
TreeModel.cpp
#include "TreeNode.h"
#include "TreeModel.h"
#include <QCoreApplication>
#include <QStringList>
#include <QMimeData>
#include <QIODevice>
#include <QDataStream>
#include <QDebug>
TreeModel::TreeModel(const QStringList &data, QObject *parent)
: QAbstractItemModel(parent)
{
QStringList rootData;
rootData << "Levels";
m_rootNode = new TreeNode(rootData[0], 0);
setupModelData(data, m_rootNode);
}
TreeModel::~TreeModel()
{
delete m_rootNode;
}
int TreeModel::columnCount(const QModelIndex &parent) const
{
return nodeForIndex(parent)->columnCount();
}
static const char s_treeNodeMimeType[] = "application/x-treenode";
//returns the mime type
QStringList TreeModel::mimeTypes() const
{
return QStringList() << s_treeNodeMimeType;
}
//receives a list of model indexes list
QMimeData *TreeModel::mimeData(const QModelIndexList &indexes) const
{
QMimeData *mimeData = new QMimeData;
QByteArray data; //a kind of RAW format for datas
//QDataStream is independant on the OS or proc architecture
//serialization of C++'s basic data types, like char, short, int, char *, etc.
//Serialization of more complex data is accomplished
//by breaking up the data into primitive units.
QDataStream stream(&data, QIODevice::WriteOnly);
QList<TreeNode *> nodes;
//
foreach (const QModelIndex &index, indexes) {
TreeNode *node = nodeForIndex(index);
if (!nodes.contains(node))
nodes << node;
}
stream << QCoreApplication::applicationPid();
stream << nodes.count();
foreach (TreeNode *node, nodes) {
stream << reinterpret_cast<qlonglong>(node);
}
mimeData->setData(s_treeNodeMimeType, data);
return mimeData;
}
bool TreeModel::dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
Q_ASSERT(action == Qt::MoveAction);
Q_UNUSED(column);
//test if the data type is the good one
if (!mimeData->hasFormat(s_treeNodeMimeType)) {
return false;
}
QByteArray data = mimeData->data(s_treeNodeMimeType);
QDataStream stream(&data, QIODevice::ReadOnly);
qint64 senderPid;
stream >> senderPid;
if (senderPid != QCoreApplication::applicationPid()) {
// Let's not cast pointers that come from another process...
return false;
}
TreeNode *parentNode = nodeForIndex(parent);
Q_ASSERT(parentNode);
int count;
stream >> count;
if (row == -1) {
// valid index means: drop onto item. I chose that this should insert
// a child item, because this is the only way to create the first child of an item...
// This explains why Qt calls it parent: unless you just support replacing, this
// is really the future parent of the dropped items.
if (parent.isValid())
row = 0;
else
// invalid index means: append at bottom, after last toplevel
row = rowCount(parent);
}
for (int i = 0; i < count; ++i) {
// Decode data from the QMimeData
qlonglong nodePtr;
stream >> nodePtr;
TreeNode *node = reinterpret_cast<TreeNode *>(nodePtr);
// Adjust destination row for the case of moving an item
// within the same parent, to a position further down.
// Its own removal will reduce the final row number by one.
if (node->row() < row && parentNode == node->parentNode())
--row;
// Remove from old position
removeNode(node);
// Insert at new position
//qDebug() << "Inserting into" << parent << row;
beginInsertRows(parent, row, row);
parentNode->insertChild(row, node);
endInsertRows();
++row;
}
return true;
}
Qt::DropActions TreeModel::supportedDragActions() const
{
return Qt::MoveAction;
}
QVariant TreeModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (role != Qt::DisplayRole)
return QVariant();
TreeNode *node = nodeForIndex(index);
return node->data(index.column());
}
Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::ItemIsDropEnabled;
return QAbstractItemModel::flags(index) | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
}
QVariant TreeModel::headerData(int section, Qt::Orientation orientation,
int role) const
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
return m_rootNode->data(section);
return QVariant();
}
//returns a pointer to the "index"
TreeNode * TreeModel::nodeForIndex(const QModelIndex &index) const
{
if (!index.isValid())
return m_rootNode;
else
return static_cast<TreeNode*>(index.internalPointer());
}
void TreeModel::removeNode(TreeNode *node)
{
const int row = node->row();
QModelIndex idx = createIndex(row, 0, node);
beginRemoveRows(idx.parent(), row, row);
node->parentNode()->removeChild(row);
endRemoveRows();
}
QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent) const
{
if (!hasIndex(row, column, parent))
return QModelIndex();
TreeNode *parentNode = nodeForIndex(parent);
TreeNode *childNode = parentNode->child(row);
if (childNode)
return createIndex(row, column, childNode);
else
return QModelIndex();
}
QModelIndex TreeModel::parent(const QModelIndex &index) const
{
TreeNode *childNode = nodeForIndex(index);
if (childNode == m_rootNode)
return QModelIndex();
TreeNode *parentNode = childNode->parentNode();
if (parentNode == m_rootNode)
return QModelIndex();
return createIndex(parentNode->row(), 0, parentNode);
}
int TreeModel::rowCount(const QModelIndex &parent) const
{
if (parent.column() > 0)
return 0;
TreeNode *parentNode = nodeForIndex(parent);
return parentNode->childCount();
}
Qt::DropActions TreeModel::supportedDropActions() const
{
return Qt::MoveAction;
}
void TreeModel::setupModelData(const QStringList &data, TreeNode * parent)
{
for (int i = 0; i < 3; ++i){
TreeNode * node1 = new TreeNode("Parent-" + QString::number(i), parent);
for (int j = 0; j < 3; ++j){
TreeNode * node2 = new TreeNode("P-"+ QString::number(i) +"- Child - "+ QString::number(j), node1);
node1->appendChild(node2);
}
parent->insertChild(i, node1);
}
}
TreeNode.h
#include "TreeModel.h"
#include <QCoreApplication>
#include <QStringList>
#include <QMimeData>
#include <QIODevice>
#include <QDataStream>
#include <QDebug>
TreeModel::TreeModel(const QStringList &data, QObject *parent)
: QAbstractItemModel(parent)
{
QStringList rootData;
rootData << "Levels";
m_rootNode = new TreeNode(rootData[0], 0);
setupModelData(data, m_rootNode);
}
TreeModel::~TreeModel()
{
delete m_rootNode;
}
int TreeModel::columnCount(const QModelIndex &parent) const
{
return nodeForIndex(parent)->columnCount();
}
static const char s_treeNodeMimeType[] = "application/x-treenode";
//returns the mime type
QStringList TreeModel::mimeTypes() const
{
return QStringList() << s_treeNodeMimeType;
}
//receives a list of model indexes list
QMimeData *TreeModel::mimeData(const QModelIndexList &indexes) const
{
QMimeData *mimeData = new QMimeData;
QByteArray data; //a kind of RAW format for datas
//QDataStream is independant on the OS or proc architecture
//serialization of C++'s basic data types, like char, short, int, char *, etc.
//Serialization of more complex data is accomplished
//by breaking up the data into primitive units.
QDataStream stream(&data, QIODevice::WriteOnly);
QList<TreeNode *> nodes;
//
foreach (const QModelIndex &index, indexes) {
TreeNode *node = nodeForIndex(index);
if (!nodes.contains(node))
nodes << node;
}
stream << QCoreApplication::applicationPid();
stream << nodes.count();
foreach (TreeNode *node, nodes) {
stream << reinterpret_cast<qlonglong>(node);
}
mimeData->setData(s_treeNodeMimeType, data);
return mimeData;
}
bool TreeModel::dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
Q_ASSERT(action == Qt::MoveAction);
Q_UNUSED(column);
//test if the data type is the good one
if (!mimeData->hasFormat(s_treeNodeMimeType)) {
return false;
}
QByteArray data = mimeData->data(s_treeNodeMimeType);
QDataStream stream(&data, QIODevice::ReadOnly);
qint64 senderPid;
stream >> senderPid;
if (senderPid != QCoreApplication::applicationPid()) {
// Let's not cast pointers that come from another process...
return false;
}
TreeNode *parentNode = nodeForIndex(parent);
Q_ASSERT(parentNode);
int count;
stream >> count;
if (row == -1) {
// valid index means: drop onto item. I chose that this should insert
// a child item, because this is the only way to create the first child of an item...
// This explains why Qt calls it parent: unless you just support replacing, this
// is really the future parent of the dropped items.
if (parent.isValid())
row = 0;
else
// invalid index means: append at bottom, after last toplevel
row = rowCount(parent);
}
for (int i = 0; i < count; ++i) {
// Decode data from the QMimeData
qlonglong nodePtr;
stream >> nodePtr;
TreeNode *node = reinterpret_cast<TreeNode *>(nodePtr);
// Adjust destination row for the case of moving an item
// within the same parent, to a position further down.
// Its own removal will reduce the final row number by one.
if (node->row() < row && parentNode == node->parentNode())
--row;
// Remove from old position
removeNode(node);
// Insert at new position
//qDebug() << "Inserting into" << parent << row;
beginInsertRows(parent, row, row);
parentNode->insertChild(row, node);
endInsertRows();
++row;
}
return true;
}
Qt::DropActions TreeModel::supportedDragActions() const
{
return Qt::MoveAction;
}
QVariant TreeModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (role != Qt::DisplayRole)
return QVariant();
TreeNode *node = nodeForIndex(index);
return node->data(index.column());
}
Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::ItemIsDropEnabled;
return QAbstractItemModel::flags(index) | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
}
QVariant TreeModel::headerData(int section, Qt::Orientation orientation,
int role) const
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
return m_rootNode->data(section);
return QVariant();
}
//returns a pointer to the "index"
TreeNode * TreeModel::nodeForIndex(const QModelIndex &index) const
{
if (!index.isValid())
return m_rootNode;
else
return static_cast<TreeNode*>(index.internalPointer());
}
void TreeModel::removeNode(TreeNode *node)
{
const int row = node->row();
QModelIndex idx = createIndex(row, 0, node);
beginRemoveRows(idx.parent(), row, row);
node->parentNode()->removeChild(row);
endRemoveRows();
}
QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent) const
{
if (!hasIndex(row, column, parent))
return QModelIndex();
TreeNode *parentNode = nodeForIndex(parent);
TreeNode *childNode = parentNode->child(row);
if (childNode)
return createIndex(row, column, childNode);
else
return QModelIndex();
}
QModelIndex TreeModel::parent(const QModelIndex &index) const
{
TreeNode *childNode = nodeForIndex(index);
if (childNode == m_rootNode)
return QModelIndex();
TreeNode *parentNode = childNode->parentNode();
if (parentNode == m_rootNode)
return QModelIndex();
return createIndex(parentNode->row(), 0, parentNode);
}
int TreeModel::rowCount(const QModelIndex &parent) const
{
if (parent.column() > 0)
return 0;
TreeNode *parentNode = nodeForIndex(parent);
return parentNode->childCount();
}
Qt::DropActions TreeModel::supportedDropActions() const
{
return Qt::MoveAction;
}
void TreeModel::setupModelData(const QStringList &data, TreeNode * parent)
{
for (int i = 0; i < 3; ++i){
TreeNode * node1 = new TreeNode("Parent-" + QString::number(i), parent);
for (int j = 0; j < 3; ++j){
TreeNode * node2 = new TreeNode("P-"+ QString::number(i) +"- Child - "+ QString::number(j), node1);
node1->appendChild(node2);
}
parent->insertChild(i, node1);
}
}
TreeNode.cpp
#include <QStringList>
TreeNode::TreeNode(const QString &data, TreeNode *parent)
: m_nodeData(data), m_parentNode(parent)
{
}
TreeNode::~TreeNode()
{
qDeleteAll(m_childNodes);
}
void TreeNode::appendChild(TreeNode *node)
{
m_childNodes.append(node);
}
void TreeNode::removeChild(int row)
{
m_childNodes.removeAt(row);
}
TreeNode *TreeNode::child(int row) const
{
return m_childNodes.value(row);
}
int TreeNode::childCount() const
{
return m_childNodes.count();
}
int TreeNode::columnCount() const
{
return m_nodeData.count();
}
QVariant TreeNode::data(int column) const
{
return m_nodeData.value(column);
}
TreeNode *TreeNode::parentNode() const
{
return m_parentNode;
}
int TreeNode::row() const
{
if (m_parentNode)
return m_parentNode->m_childNodes.indexOf(const_cast<TreeNode*>(this));
return 0;
}
void TreeNode::insertChild(int pos, TreeNode *child)
{
m_childNodes.insert(pos, child);
child->m_parentNode = this;
}
TreeView.h
#define TREEVIEW_H
#include "TreeModel.h"
#include <QObject>
#include <QTreeView>
class TreeView : public QTreeView
{
public:
TreeView(TreeModel *model);
TreeView();
void setModel(TreeModel *model);
};
#endif // TREEVIEW_H
TreeView.cpp
#include "TreeModel.h"
#include <QHeaderView>
TreeView::TreeView(TreeModel *model)
{
setModel(model);
TreeView();
}
TreeView::TreeView()
{
connect(this, &QTreeView::pressed, this, &QTreeView::expandAll);
setDragEnabled(true);
setAcceptDrops(true);
resizeColumnToContents(0);
resize(400, 500);
setSelectionMode(QAbstractItemView::SingleSelection);
expandAll();
connect(this, &QTreeView::pressed, this, &QTreeView::expandAll);
header()->setVisible(false);
setIndentation(5);
setEditTriggers(QAbstractItemView::EditTriggers());
}
void TreeView::setModel(TreeModel* model)
{
QItemSelectionModel* m = selectionModel();
delete m;
QTreeView::setModel(model);
}
main.cpp (This is just to run the project, just ignore the QStringList data)
#include "TreeView.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QStringList data;
data.push_back("Item 1");
TreeModel model(data);
TreeView view;
view.setModel(&model);
view.setWindowTitle(QObject::tr("Tree Model"));
view.show();
return a.exec();
}
I use this post as a reference to implement drag and drop.
https://forum.qt.io/topic/76708/full-drag-and-drop-support-in-qtreeview
I start to lern MVC pattern in Qt.
I have the simple data structure, like this:
class Stage
{
public:
QString name;
int number;
};
class Doc
{
public:
Doc();
~Doc();
QString name;
int number;
QList<Stage *> *stages;
};
I want to view this structure in TreeView. For this i create model class:
class Model: public QAbstractItemModel
{
Q_OBJECT
public:
Model(QObject *parent);
~Model();
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
QModelIndex index(int row, int column, const QModelIndex &parent) const;
QModelIndex parent(const QModelIndex &index) const;
int rowCount(const QModelIndex &parent = QModelIndex()) const;
int columnCount(const QModelIndex &parent = QModelIndex()) const;
Qt::ItemFlags flags(const QModelIndex &index) const;
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const;
private:
QList<Doc*> *docs;
If I have only one level of data (doc without stage), everything works fine, but I can't understand how to add second tree level to the model(stage)?
Code example (one level)
Model::Model(QObject *parent) : QAbstractItemModel(parent)
{
docs = new QList<Doc*>();
for (int i = 0; i < 3; ++i) {
Doc *d = new Doc();
d->name = "name_" + QString::number(i);
d->number = i;
docs->append(d);
}
}
Model::~Model()
{
delete docs;
}
QModelIndex Model::index(int row, int column, const QModelIndex &parent) const
{
if (!parent.isValid()) {
Doc *d = docs->at(row);
return createIndex(row, column, d);
}
return QModelIndex();
}
QModelIndex Model::parent(const QModelIndex &index) const
{
return QModelIndex();
}
int Model::rowCount(const QModelIndex &parent) const
{
int cnt = 0;
if (!parent.isValid()) {
cnt = docs->size();
}
return cnt;
}
int Model::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return 3;
}
Qt::ItemFlags Model::flags(const QModelIndex &index) const
{
if (!index.isValid())
return nullptr;
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
QVariant Model::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role != Qt::DisplayRole)
return QVariant();
if (orientation == Qt::Horizontal) {
switch (section) {
case 0:
return tr("Number");
case 1:
return tr("Code");
case 2:
return tr("Stages count");
default:
return QVariant();
}
}
return QVariant();
}
QVariant Model::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (role != Qt::DisplayRole)
return QVariant();
{
Doc *d = static_cast<Doc*>(index.internalPointer());
switch (index.column()) {
case 0:
return d->name;
case 1:
return d->number;
case 2:
return d->stages->size();
default:
break;
}
}
return QVariant();
}
i think its will look like this:
Model::Model(QObject *parent) : QAbstractItemModel(parent)
{
docs = new QList<Doc*>();
for (int i = 0; i < 3; ++i) {
Doc *d = new Doc();
d->name = "name_" + QString::number(i);
d->number = i;
for (int j = 5; j < 9; ++j) {
Stage *s = new Stage();
s->name = "name_" + QString::number(j);
s->number = j;
d->stages->append(s);
}
docs->append(d);
}
}
QModelIndex Model::index(int row, int column, const QModelIndex &parent) const
{
if (!parent.isValid()) {
Doc *d = docs->at(row);
return createIndex(row, column, d);
} else {
Stage *s = static_cast<Doc*>(parent.internalPointer())->stages->at(row);
return createIndex(row, column, s);
}
return QModelIndex();
}
QModelIndex Model::parent(const QModelIndex &index) const
{
if (!index.isValid())
return QModelIndex();
else if (index.parent().isValid())
return createIndex(index.parent().row(), 0, index.internalPointer());
return QModelIndex();
}
int Model::rowCount(const QModelIndex &parent) const
{
int cnt = 0;
if (!parent.isValid()) {
cnt = docs->size();
} else {
cnt = static_cast<Doc*>(parent.parent().internalPointer())->stages->size();
}
return cnt;
}
but its wrong code...
I am using QAbstractTableModel and ComboBoxItemDelegate which inherits QStyledItemDelegate. I am able to populate my data in combo boxes in table view but by default nothing is displayed but after clicking correct data is visible in dropdown. How can i set first item as a default item and display it?
My Combox delegate:
class ComboBoxItemDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
ComboBoxItemDelegate(QObject* parent = 0);
~ComboBoxItemDelegate();
virtual QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const;
virtual void setEditorData(QWidget* editor, const QModelIndex& index) const;
virtual void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const;
virtual void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const;
};
Create editor function:
QWidget* ComboBoxItemDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
QComboBox* cb = new QComboBox(parent);
QStringList destList = SFilterEditorData::instance().getDestinationList();
cb->addItem(QString("All"));
for (int i = 0; i < destList.size(); i++)
{
cb->addItem(destList.at(i));
}
cb->setEditable(true);
return cb;
}
set Editor function:
void ComboBoxItemDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const
{
if (QComboBox* cb = qobject_cast<QComboBox*>(editor)) {
//QString currentText = index.data(Qt::EditRole).toString();
QString currentText = index.model()->data(index, Qt::DisplayRole).toString();
int cbIndex = cb->findText(currentText);
// if it is valid, adjust the combobox
if (cbIndex >= 0)
cb->setCurrentIndex(cbIndex);
}
else {
QStyledItemDelegate::setEditorData(editor, index);
}
}
Inserting rows in my model:
bool STableModel::insertRows(int position, int rows, const QModelIndex &parent)
{
int columns = columnCount();
beginInsertRows(parent, position, position + rows - 1);
for (int row = 0; row < rows; ++row) {
QStringList items;
for (int column = 0; column < columns; ++column)
{
items.append(""); // This might be the reason for this issue.
}
rowList.insert(position, items);
}
endInsertRows();
return true;
}
Setting data in model:
QVariant STableModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (role == Qt::DisplayRole)
return rowList[index.row()][index.column()];
else if (role == Qt::CheckStateRole && index.column() == 0)
{
int status = SFilterEditorData::instance().getStatus(index.row());
if (status)
return Qt::Checked;
else
return Qt::Unchecked;
}
else
return QVariant();
}
bool STableModel::setData(const QModelIndex &index,
const QVariant &value, int role)
{
if (!index.isValid() /*|| role != Qt::EditRole*/)
return false;
if (role == Qt::CheckStateRole)
{
if ((Qt::CheckState)value.toInt() == Qt::Checked)
{
SFilterEditorData::instance().setStatus(index.row(),1);
return true;
}
else
{
SFilterEditorData::instance().setStatus(index.row(), 0);
return true;
}
}
i have referred this link: http://www.qtcentre.org/threads/39194-setEditorData()-for-QComboBox-Delegate
I'm attempting to create a QTreeView and use a custom model for it. I have placed qDebug() statements at various places, and I have determined that data() is never being called. How can I fix this problem?
The model's code is below
#include "ModelItemNeural.h"
ModelItemNeural::ModelItemNeural(QObject *parent, NeuralNode *rootNode)
: QAbstractItemModel(parent)
{
this->rootNode = 0;
}
QModelIndex ModelItemNeural::index(int row, int column, const QModelIndex &parent) const
{
// Out of bounds and null rootNode check.
if (rootNode == 0 || row < 0 || column < 0)
{
return QModelIndex();
}
NeuralNode* parentNode = nodeFromIndex(parent);
NeuralNode* childNode = parentNode->getInputs().value(row);
if (childNode == 0)
{
return QModelIndex();
}
return createIndex(row, column, childNode);
}
QModelIndex ModelItemNeural::parent(const QModelIndex &child) const
{
NeuralNode* node = nodeFromIndex(child);
if (node == 0)
{
return QModelIndex();
}
NeuralNode* parentNode = node->getParent();
if (parentNode == 0)
{
return QModelIndex();
}
NeuralNode* grandParentNode = parentNode->getParent();
if (grandParentNode == 0)
{
return QModelIndex();
}
int row = grandParentNode->getInputs().indexOf(parentNode);
return createIndex(row, 0, parentNode);
}
int ModelItemNeural::rowCount(const QModelIndex& parent) const
{
if (parent.isValid() == false)
{
return 0;
}
if (parent.column() > 0)
{
return 0;
}
NeuralNode* parentNode = nodeFromIndex(parent);
if (parentNode == 0)
{
return 0;
}
return parentNode->getInputs().length();
}
int ModelItemNeural::columnCount(const QModelIndex &parent) const
{
return 2;
}
QVariant ModelItemNeural::data(const QModelIndex &index, int role) const
{
qDebug() << "Data";
if (index.isValid() == false)
{
return QVariant();
}
if (role != Qt::DisplayRole)
{
return QVariant();
}
NeuralNode* node = nodeFromIndex(index);
if (node == 0)
{
return QVariant();
}
switch (index.column())
{
case 0:
{
// Stripping the name of the NeuralNode type.
QString name = typeid(node).name();
int index = name.indexOf(" ");
if (index >= 0)
{
name = name.remove(0, index + 1);
}
qDebug() << "Name Column";
return "Test";
return name;
}
case 1:
{
qDebug() << "Value Column";
return node->getWeight();
}
}
return QVariant();
}
QVariant ModelItemNeural::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
{
switch (section)
{
case 0:
{
return "Node";
}
case 1:
{
return "Weight";
}
}
}
return QVariant();
}
NeuralNode * ModelItemNeural::nodeFromIndex(const QModelIndex &index) const
{
if (index.isValid() == true)
{
//return (NeuralNode*)(index.internalPointer());
return static_cast<NeuralNode *>(index.internalPointer());
}
else
{
return rootNode;
}
}
void ModelItemNeural::setRootNode(NeuralNode *rootNode)
{
delete this->rootNode;
this->rootNode = rootNode;
reset();
}
The code from the MainWindow where the view is located is below.
#include "MainWindow.h"
#include "ui_MainWindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
NeuralNetwork* network = new NeuralNetwork();
modelNeural = new ModelItemNeural();
modelNeural->setRootNode(network);
ui->treeView->setModel(modelNeural);
update();
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_actionNew_triggered()
{
NeuralNetwork* network = new NeuralNetwork();
modelNeural->setRootNode(network);
ui->treeView->update();
}
I should mention that the header does display for this model. However, even when I set an item, nothing is displayed in the widget save the header.
Oh and NeuralNetwork is a sub of NeuralNode.
The problem is this fragment:
int ModelItemNeural::rowCount(const QModelIndex& parent) const
{
if (parent.isValid() == false)
{
return 0;
}
You're basically saying that the root node (indicated by invalid parent index) has zero children i.e. the model has zero top-level rows. So the view queries no further.
Just drop this check and it should work. nodeFromIndex seems to handle root node correctly.
Did you add the model (not the item) to the treeview control?
Did you create items of your derived type and add them to the model?
data() should be called if your model is being accessed.
You have to override the following method in your QAbstractItemModel inherited class:
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
Then write this within:
return createIndex(row, column, nullptr);
Something like this:
QModelIndex index(int row, int column, const QModelIndex &parent) const {
return createIndex(row, column, nullptr);
}