QAbstractItemModel + QTreeView 什么会导致项目变得不可选择?
我正在尝试基于 QAbstractItemModel 创建自己的模型。看起来效果很好。它通过了模型测试断言。
当我删除一行时,我遇到了这个奇怪的问题。删除操作正常。 但随后其他行变得不可选择(并非全部)。您遇到过这样的行为吗?
在什么情况下QTreeView可以决定该行不能被选择?
有什么想法吗?如果需要,我可以提供整个模型的实现。
编辑:作为替代方案,我正在寻找 100% 工作 QAbstractItemModel + QtSql + QTreeView 实现的示例。模型应提供添加和删除方法,并且必须通过模型测试。 这也可以回答我的问题:-)
编辑:下面是我的源代码。压缩一点以使其更小
ps 我现在看到parent() 实现中有一个错误。删除一行后 nodeParams[*].row 中的值包含不正确的位置。如何在不将整个树加载到内存中的情况下解决这个问题?
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 = countQuery.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));
}
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 = countQuery.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));
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
查看模型的源代码会很有帮助,
如果没有这个,我将从检查 QAbstractItemModel 开始::flags 方法正在返回您无法选择的项目
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
您的 nodeParams 必须始终保持最新。这意味着每次添加和删除后,您必须重新加载受影响父项的所有子项的条目。
简单地延迟加载树作为项目不是更好吗?创建额外的类,例如。 TreeItem 并将子项存储在 QList 子项中。
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.