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 sqlTreeModelTest;
{
Q_OBJECT
public:
explicit MainWindow
(QWidget *parent
= 0);
private:
void setupModel();
};
#endif // 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
To copy to clipboard, switch view to plain text mode
mainwindow.cpp
#include "mainwindow.h"
#include "sqltreemodeltest.h"
#include "proxytreemodeltest.h"
#include <QTreeView>
#include <QVBoxLayout>
#include <QSqlQuery>
MainWindow
::MainWindow(QWidget *parent
) :{
verticalLayout->addWidget(treeView);
setCentralWidget(centralWidget);
if(error.isValid())
qDebug() << error.text();
setupModel();
}
db.setDatabaseName(":memory:");
if(!db.open())
return db.lastError();
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();
}
void MainWindow::setupModel() {
sqlTreeModelTest *testModel = new sqlTreeModelTest(this);
testModel->setTable("test");
testModel->select();
proxyTreeModelTest *treeProxy = new proxyTreeModelTest(this);
treeProxy->setSourceModel(testModel);
treeView->setModel(treeProxy);
}
#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::SingleSelection);
}
To copy to clipboard, switch view to plain text mode
sqltreemodeltest.h
#ifndef SQLTREEMODELTEST_H
#define SQLTREEMODELTEST_H
#include <QSqlTableModel>
{
Q_OBJECT
public:
sqlTreeModelTest
(QObject *parent
=0);
virtual Qt
::ItemFlags flags
(const QModelIndex &index
) const;
};
#endif // 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
To copy to clipboard, switch view to plain text mode
sqltreemodeltest.cpp
#include "sqltreemodeltest.h"
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;
}
#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;
}
To copy to clipboard, switch view to plain text mode
proxytreemodeltest.h
#ifndef PROXYTREEMODELTEST_H
#define PROXYTREEMODELTEST_H
#include <QDebug>
#include <QAbstractProxyModel>
{
Q_OBJECT
public:
proxyTreeModelTest
(QObject *parent
=0);
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;
};
#endif // 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
To copy to clipboard, switch view to plain text mode
proxytreemodeltest.cpp
#include "proxytreemodeltest.h"
#include <QSqlQuery>
#include <QSqlTableModel>
#include <QSqlError>
#include <QDebug>
if(!sourceIndex.isValid())
int id = (sourceIndex.column() == 0) ? sourceIndex.data().toInt() : sourceIndex.sibling(sourceIndex.row(),0).data().toInt();
int row = -1;
while(q->next()) {
row++;
if(q->value(0).toInt() == id)
break;
}
delete q;
return createIndex(row, sourceIndex.column(), id);
}
if(!proxyIndex.isValid())
int id = proxyIndex.internalId();
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 {
q.prepare("SELECT COUNT(*) FROM test WHERE parentId=?");
q.addBindValue(parent.internalId());
q.exec();
q.first();
return q.value(0).toInt() > 0;
}
int childId = childIndex.internalId();
int parentId = getParentId(childId);
if(parentId == 0)
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);
}
if(row < 0 || column < 0)
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 {
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 { q->prepare("SELECT id FROM test WHERE parentId=?");
q->addBindValue(parentId);
q->exec();
return q;
}
#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().toInt();
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;
}
To copy to clipboard, switch view to plain text mode
main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
MainWindow w;
w.show();
return a.exec();
}
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
To copy to clipboard, switch view to plain text mode
Bookmarks