PDA

View Full Version : Viewing QSqlTableModel in QTreeView through a proxy model



kazaak
7th January 2018, 22:04
I'm trying to use a proxy model to take a 'parentId' column in a QSqlTableModel (using SQLITE) and view the hierarchy with a QTreeView. To make this work without needed a treeItem-type class I'm using internalId() from QModelIndex to store the 'parentId'. It works 99% of the time and I can't nail down where I'm messing up. I can view the parents/children and even expand/collapse them but I cannot select the children of a parent item. The selectionModel's selectionChanged() signal and the proxy models index() return the expected QModelIndex and so I don't know what is making the view upset. Here is the code that produces the issue. Thanks for any insight or suggestions

mainwindow.h


#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QSqlError>

class QTreeView;
class QSqlTableModel;
class sqlTreeModelTest;

class MainWindow : public QMainWindow
{
Q_OBJECT

public:
explicit MainWindow(QWidget *parent = 0);
QSqlError initdb();
private:
QTreeView *treeView;
void setupModel();
};

#endif // MAINWINDOW_H


mainwindow.cpp


#include "mainwindow.h"
#include "sqltreemodeltest.h"
#include "proxytreemodeltest.h"

#include <QTreeView>
#include <QVBoxLayout>
#include <QSqlQuery>

MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
QWidget *centralWidget = new QWidget(this);
QVBoxLayout *verticalLayout = new QVBoxLayout(centralWidget);
treeView = new QTreeView(centralWidget);
verticalLayout->addWidget(treeView);
setCentralWidget(centralWidget);

QSqlError error = initdb();
if(error.isValid())
qDebug() << error.text();
setupModel();
}

QSqlError MainWindow::initdb() {

QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");

db.setDatabaseName(":memory:");
if(!db.open())
return db.lastError();

QSqlQuery q;
QStringList tables = db.tables();
if(tables.contains("test", Qt::CaseInsensitive))
if(!q.exec("DROP TABLE test"))
return q.lastError();

if(!q.exec(QLatin1String("CREATE TABLE test(id INTEGER PRIMARY KEY, title VARCHAR, type INTEGER, parentId INTEGER DEFAULT 0)")))
return q.lastError();
if(!q.exec(QLatin1String("INSERT INTO test (title, type, parentId)"
"VALUES ('row0', 0, 0),"
"('row0a', 0, 1)")))
return q.lastError();
return QSqlError();
}

void MainWindow::setupModel() {

sqlTreeModelTest *testModel = new sqlTreeModelTest(this);
testModel->setTable("test");
testModel->select();

proxyTreeModelTest *treeProxy = new proxyTreeModelTest(this);
treeProxy->setSourceModel(testModel);

treeView->setModel(treeProxy);
treeView->setSelectionBehavior(QAbstractItemView::SelectRows );
treeView->setSelectionMode(QAbstractItemView::SingleSelectio n);
}


sqltreemodeltest.h


#ifndef SQLTREEMODELTEST_H
#define SQLTREEMODELTEST_H

#include <QSqlTableModel>

class sqlTreeModelTest : public QSqlTableModel
{
Q_OBJECT

public:
sqlTreeModelTest(QObject *parent=0);
virtual Qt::ItemFlags flags(const QModelIndex &index) const;
};

#endif // SQLTREEMODELTEST_H



sqltreemodeltest.cpp


#include "sqltreemodeltest.h"

sqlTreeModelTest::sqlTreeModelTest(QObject *parent) : QSqlTableModel(parent) {}

Qt::ItemFlags sqlTreeModelTest::flags(const QModelIndex &index) const {
//QSqlTableModel doesn't allow children so use these flags
if(index.isValid())
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
else
return 0;
}



proxytreemodeltest.h


#ifndef PROXYTREEMODELTEST_H
#define PROXYTREEMODELTEST_H

#include <QDebug>
#include <QAbstractProxyModel>

class QSqlQuery;

class proxyTreeModelTest : public QAbstractProxyModel
{
Q_OBJECT

public:
proxyTreeModelTest(QObject *parent=0);
virtual QModelIndex mapFromSource(const QModelIndex &sourceIndex) const;
virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const;
virtual QModelIndex parent(const QModelIndex &child) const;

virtual QModelIndex index(int row, int column, const QModelIndex &parent) const;
virtual int rowCount(const QModelIndex &parent) const;
virtual int columnCount(const QModelIndex &parent) const { return sourceModel()->columnCount(parent); }

virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const { return sourceModel()->headerData(section,orientation,role); }
virtual bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) { return sourceModel()->setHeaderData(section,orientation,value,role); }

virtual bool hasChildren(const QModelIndex &parent) const;

private:

int getParentId(int childId) const;
QSqlQuery* getChildren(int parentId) const;
};

#endif // PROXYTREEMODELTEST_H



proxytreemodeltest.cpp


#include "proxytreemodeltest.h"

#include <QSqlQuery>
#include <QSqlTableModel>
#include <QSqlError>

#include <QDebug>

proxyTreeModelTest::proxyTreeModelTest(QObject *parent) : QAbstractProxyModel(parent) {}

QModelIndex proxyTreeModelTest::mapFromSource(const QModelIndex &sourceIndex) const {
if(!sourceIndex.isValid())
return QModelIndex();

int id = (sourceIndex.column() == 0) ? sourceIndex.data().toInt() : sourceIndex.sibling(sourceIndex.row(),0).data().to Int();

int row = -1;
QSqlQuery* q = getChildren(getParentId(id));
while(q->next()) {
row++;
if(q->value(0).toInt() == id)
break;
}
delete q;
return createIndex(row, sourceIndex.column(), id);
}

QModelIndex proxyTreeModelTest::mapToSource(const QModelIndex &proxyIndex) const {
if(!proxyIndex.isValid())
return QModelIndex();

int id = proxyIndex.internalId();

QSqlQuery q;
q.exec("SELECT id FROM test");
int row = -1;
while(q.next()) {
row++;
if(q.value(0).toInt() == id)
break;
}
return sourceModel()->index(row, proxyIndex.column());
}

bool proxyTreeModelTest::hasChildren(const QModelIndex &parent) const {

QSqlQuery q;
q.prepare("SELECT COUNT(*) FROM test WHERE parentId=?");
q.addBindValue(parent.internalId());
q.exec();
q.first();
return q.value(0).toInt() > 0;
}

QModelIndex proxyTreeModelTest::parent(const QModelIndex &childIndex) const {

int childId = childIndex.internalId();
int parentId = getParentId(childId);
if(parentId == 0)
return QModelIndex();

int parentRow = -1;
QSqlQuery* q = getChildren(getParentId(parentId));
while(q->next()) {
parentRow++;
if(q->value(0).toInt() == parentId)
break;
}
delete q;
return createIndex(parentRow, childIndex.row(), parentId);
}

QModelIndex proxyTreeModelTest::index(int row, int column, const QModelIndex &parent) const {

if(row < 0 || column < 0)
return QModelIndex();

QSqlQuery* q = getChildren(parent.internalId());
q->seek(row);
int id = q->value(0).toInt();
delete q;
return createIndex(row, column, id);
}

int proxyTreeModelTest::rowCount(const QModelIndex &parent) const {

QSqlQuery* q = getChildren(parent.internalId());

//use last() and at() since SQLite does not support query size calls
q->last();
int size = q->at() + 1;
delete q;
return size;
}

int proxyTreeModelTest::getParentId(int childId) const {
QSqlQuery q;
q.prepare("SELECT parentId FROM test WHERE id=?");
q.addBindValue(childId);
q.exec();
q.first();
return q.value(0).toInt();
}

QSqlQuery* proxyTreeModelTest::getChildren(int parentId) const {
QSqlQuery* q = new QSqlQuery;
q->prepare("SELECT id FROM test WHERE parentId=?");
q->addBindValue(parentId);
q->exec();
return q;
}



main.cpp


#include "mainwindow.h"
#include <QApplication>

int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();

return a.exec();
}

LinJH
14th March 2018, 23:22
In sqltreemodeltest.cpp.

return Qt::ItemIsEnabled | Qt::ItemIsSelectable|Qt::ItemIsEditable;

You need add a flag Qt::ItemIsEditable;

Added after 19 minutes:

Oh, sorry, I misunderstand the question.

dogustokat
28th May 2022, 12:38
i want to say;
change
"treeView->setSelectionBehavior(QAbstractItemView::SelectRows );"
to
"treeView->setSelectionBehavior(QAbstractItemView::SelectItem s);"
in mainwindow.cpp

thats all

but, i think you are not a beginner and this solution is not for you :) isn,t it?

and how we add and delete nodes, any idea?
(i am a beginner)

d_stranz
28th May 2022, 17:37
and how we add and delete nodes, any idea?

The way you add or delete nodes is to change the QAbstractItemModel-based model that the QTreeView uses to build itself. The QTreeView is simply a way to display the model, and does not contain any data itself beyond what is needed to show the model on screen.

You can find information on tree views and item models in the Qt documentation, tutorials, and examples.