I'm trying to create my own model based on QAbstractItemModel. It seems to work fine. It passes modeltest assertions.
I've this strange problem when I remove a row. Removal operation works ok. But then other rows become unselectable (not all of them). Have You ever come across such behaviour ?
In which conditions QTreeView could decide that row can not be selected ?
Any ideas ? If needed I can provide the whole model implementation.
EDIT: As an alternative I'm looking for an example of 100% working QAbstractItemModel + QtSql + QTreeView implementation. Model should provide add and remove methods and it has to pass modeltest. This also would answer my question :-)
EDIT: Below is my source code. Compacted a little to make it smaller
ps I see now that there is a bug in parent() implementation. After removing a row values in nodeParams[*].row contain incorrect positions. How do You solve this issue without loading the whole tree into memory ?
class TasksModel : public QAbstractItemModel
{
Q_OBJECT
public:
explicit TasksModel(QObject *parent = 0);
virtual QVariant data ( const QModelIndex & index, int role = Qt::DisplayRole ) const;
virtual Qt::ItemFlags flags ( const QModelIndex & index ) const;
virtual int columnCount ( const QModelIndex & parent = QModelIndex() ) const;
virtual QVariant headerData ( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const;
virtual int rowCount (const QModelIndex & parent = QModelIndex() ) const;
virtual bool hasChildren ( const QModelIndex & parent = QModelIndex() ) const;
virtual void sort ( int column, Qt::SortOrder order = Qt::AscendingOrder );
virtual QModelIndex index ( int row, int column, const QModelIndex & parent = QModelIndex() ) const;
virtual QModelIndex parent ( const QModelIndex & index ) const;
virtual bool setData ( const QModelIndex & index, const QVariant & value, int role = Qt::EditRole );
virtual bool setHeaderData ( int section, Qt::Orientation orientation, const QVariant & value, int role = Qt::EditRole );
int selectedId;
QModelIndex indexForId(int id);
// add,remove..
int addTask(QMap<QString,QVariant> params);
void removeTask(int id, bool children);
private:
int nrOfColumns;
QSqlDatabase* dbh;
mutable QMap<qint64, QSqlQuery*> subQueries;
mutable QMap<qint64, int> rowsCount;
mutable QSqlQuery topQuery;
mutable int topRowsCount;
mutable bool topQueryReady;
QSqlQuery* verifyAndPrepareQuery (const QModelIndex& index) const;
int totalCount(const qint64 id, bool force=false) const;
void recountTotalCount(const qint64 id) const;
struct NodeParams {
int row;
int parentId;
};
mutable QMap<qint64, NodeParams> nodeParams;
signals:
public slots:
};
// ------------------ implementation ---------------------------
TasksModel::TasksModel(QObject *parent) : QAbstractItemModel(parent)
{
nrOfColumns = 2;
topQueryReady = false;
topRowsFetched = 0;
topRowsCount = 0;
selectedId = 0;
// db connection
dbh = Config::connection();
}
QVariant TasksModel::data ( const QModelIndex & index, int role ) const
{
if (!index.isValid()) return QVariant();
int column = index.column();
if (role == Qt::DisplayRole || role == Qt::EditRole)
{
QSqlQuery* query = verifyAndPrepareQuery(index.parent());
if (!query->seek(index.row())) return QVariant("x");
switch (column)
{
case 0: return query->value(2).toString();
case 1: return query->value(4).toString() +"%";
}
}
else if (role == Qt::CheckStateRole) {
// set status of checkbox in 2nd column
if (column == 1) {
QSqlQuery* query = verifyAndPrepareQuery(index.parent());
if (!query->seek(index.row())) return QVariant();
if (query->value(3).toInt() > 0)
return Qt::Checked;
else
return Qt::Unchecked;
}
}
else if (role == Qt::TextAlignmentRole) {
switch (column)
{
case 0: return Qt::AlignLeft + Qt::AlignVCenter;
case 1: return Qt::AlignRight + Qt::AlignVCenter;
}
}
return QVariant();
}
Qt::ItemFlags TasksModel::flags ( const QModelIndex & index ) const
{
if (!index.isValid()) return 0;
Qt::ItemFlags result = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
if (index.column()==0) {
result |= Qt::ItemIsEditable;
}
else if (index.column()==1) {
result |= Qt::ItemIsUserCheckable;
}
return result;
}
QVariant TasksModel::headerData ( int section, Qt::Orientation orientation, int role) const
{
return QVariant();
}
int TasksModel::columnCount ( const QModelIndex & parent ) const
{
return nrOfColumns;
}
int TasksModel::rowCount (const QModelIndex & parent) const
{
if (parent.isValid() && parent.column() != 0)
return 0;
int id;
if (parent.isValid())
id = parent.internalId();
else
id = 0;
return totalCount(id);
}
bool TasksModel::hasChildren ( const QModelIndex & parent) const
{
if (parent.isValid()) {
if (totalCount(parent.internalId()) > 0) return true;
} else {
if (totalCount(0) > 0) return true;
}
return false;
}
void TasksModel::sort ( int column, Qt::SortOrder order )
{
}
// TreeView methods
QModelIndex TasksModel::index ( int row, int column, const QModelIndex& parent ) const
{
if (row < 0 || column < 0 || column >= nrOfColumns)
// || (parent.isValid() && parent.column() != 0))
return QModelIndex();
QSqlQuery* query = verifyAndPrepareQuery(parent);
if (!query->seek(row)) return QModelIndex();
int id = query->value(0).toInt();
if (!nodeParams.contains(id)) {
NodeParams params;
params.parentId = (int)query->value(1).toInt();
params.row = row;
nodeParams.insert(id, params);
}
return QAbstractItemModel::createIndex(row, column, id);
}
QModelIndex TasksModel::parent ( const QModelIndex & index ) const
{
return QModelIndex();
if (!index.isValid()) { return QModelIndex(); }
if (!nodeParams.contains(index.internalId())) { qDebug("b"); return QModelIndex();}
NodeParams itemParams = nodeParams.value(index.internalId());
if (itemParams.parentId == 0) return QModelIndex();
if (!nodeParams.contains(itemParams.parentId)) { qDebug("d"); return QModelIndex(); }
NodeParams parentParams = nodeParams.value(itemParams.parentId);
int parentId = itemParams.parentId;
int parentRow = parentParams.row;
return QAbstractItemModel::createIndex(parentRow, 0, parentId);
}
// Edit methods
bool TasksModel::setData ( const QModelIndex & index, const QVariant & value, int role )
{
return false;
}
bool TasksModel::setHeaderData ( int section, Qt::Orientation orientation, const QVariant & value, int role )
{
return false;
}
// Build and return query object for current index parent
QSqlQuery* TasksModel::verifyAndPrepareQuery (const QModelIndex& index) const
{
if (!index.isValid()) {
// prepare query for root
if (!topQueryReady) {
QString sql = "SELECT id,id_parent,title,complete,completion_rate,priority,date_start,date_deadline,date_preferred FROM tasks WHERE id_parent = 0";
topQuery = QSqlQuery(sql, *dbh);
topRowsFetched = 0;
topRowsCount = 0;
topQueryReady = true;
}
return &topQuery;
} else {
// prepare queries for subitems (queries stored in subQueries QMap)
qint64 id = index.internalId();
if (!subQueries.contains(id)) {
QString sql = "SELECT id,id_parent,title,complete,completion_rate,priority,date_start,date_deadline,date_preferred FROM tasks WHERE id_parent = "+ QString::number(id);
QSqlQuery* querySub = new QSqlQuery(sql, *dbh);
subQueries.insert(id, querySub);
rowsFetched.insert(id, 0);
return querySub;
}
return subQueries.value(id);
}
}
int TasksModel::totalCount(const qint64 id, bool force) const
{
force = true; // temporary setting, to force recalculation in each request, to be optimized
if (id > 0) {
if (!rowsCount.contains(id) || force) {
QString sql = "SELECT COUNT(*) FROM tasks WHERE id_parent = "+ QString::number(id);
QSqlQuery countQuery(sql, *dbh);
countQuery.next();
int count = count开发者_JS百科Query.value(0).toInt();
rowsCount[id] = count;
return count;
}
return rowsCount.value(id);
} else {
if (topRowsCount == 0 || force) {
QString sql = "SELECT COUNT(*) FROM tasks WHERE id_parent = 0 ";
QSqlQuery countQuery(sql, *dbh);
countQuery.next();
topRowsCount = countQuery.value(0).toInt();
}
return topRowsCount;
}
}
void TasksModel::recountTotalCount(const qint64 id) const
{
// reset variables related to rowsCount and data functions. Called after new child is created or removed
if (id > 0) {
rowsCount.remove(id);
subQueries.remove(id);
}
else {
topRowsCount = 0;
topQueryReady = false;
}
totalCount(id);
}
QModelIndex TasksModel::indexForId(int id)
{
// convert id to index based on data stored in nodeParams
if (id == 0) return QModelIndex();
if (!nodeParams.contains(id)) { qDebug() << "z"; return QModelIndex(); }
NodeParams params = nodeParams.value(id);
return QAbstractItemModel::createIndex(params.row, 0, id);
}
// CRUD
int TasksModel::addTask(QMap<QString,QVariant> params)
{
// create record
QString sql;
if (params.value("complete").toInt() == 1)
params["completion_rate"] = 100;
// Add task
QSqlQuery query(*dbh);
sql = "INSERT INTO tasks (id_parent,id_sibling,position,title,description,complete,completion_rate,priority,date_start,date_deadline,date_preferred) VALUES (?,?,?,?,?,?,?,?,?,?,?)";
query.prepare(sql);
query.addBindValue(params.value("id_parent", 0));
query.addBindValue(params.value("id_sibling", 0));
query.addBindValue(params.value("position", 0));
query.addBindValue(params.value("title", ""));
query.addBindValue(params.value("description", ""));
query.addBindValue(params.value("complete", 0));
query.addBindValue(params.value("completion_rate", 0));
query.addBindValue(params.value("priority", 0));
query.addBindValue(params.value("date_start", 0));
query.addBindValue(params.value("date_deadline", 0));
query.addBindValue(params.value("date_preferred", 0));
// begin insert
int parentId = params.value("id_parent").toInt();
int count = totalCount(parentId);
beginInsertRows(indexForId(parentId), count, count);
query.exec();
int taskId = query.lastInsertId().toInt();
// update nodeParams map
NodeParams subNodeParams;
subNodeParams.row = count;
subNodeParams.parentId = parentId;
nodeParams[taskId] = subNodeParams;
recountTotalCount(parentId);
verifyAndPrepareQuery(indexForId(parentId));
endInsertRows();
// insert finished
return taskId;
}
// method recursively removes task and its children
void TasksModel::removeTask(int id, bool children)
{
if (!nodeParams.contains(id)) return;
NodeParams taskParams = nodeParams.value(id);
QString sql;
QSqlQuery query(*dbh);
// remove children
if (children) {
sql = "SELECT id FROM tasks WHERE id_parent = "+ QString::number(id);
QSqlQuery query2(sql, *dbh);
while (query2.next()) {
removeTask(query2.value(0).toInt(), true);
}
}
// remove task (tasks)
beginRemoveRows(indexForId(taskParams.parentId), taskParams.row, taskParams.row);
sql = "DELETE FROM tasks WHERE id = "+ QString::number(id);
query.exec(sql);
// update ui
recountTotalCount(taskParams.parentId);
endRemoveRows();
nodeParams.remove(id);
// remove task (tasks_parents)
sql = "DELETE FROM tasks_parents WHERE id_task = "+ QString::number(id) +" AND id_parent = "+ QString::number(taskParams.parentId);
query.exec(sql);
verifyAndPrepareQuery(indexForId(taskParams.parentId));
}
looking at the model's source code would be helpful,
without this I would start from checking what QAbstractItemModel::flags method is returning for items which you can't select
Your nodeParams always has to stay up to date. This means that after each add and remove you have to reload entries of all children of a parent that has been affected.
Wouldn't it be better to simply lazy load tree as items ? Create additional class eg. TreeItem and store children inside QList children.
精彩评论