Qt QSqlTableModel加入QComboBox控件代理后的显示问题
背景
我在 widget 上写了一个 QTableView--QSqlTableModel--mysql 关联关系的 UI。需求
mysql 表中的一个字段类型为 int,然而我在 QTableView 上想显示映射的文本。开发过程如下所示:1)我增加了一个自定义代理类 UserTypeDelegate: public QStyledItemDelegate,在指定字段所在列加入代理,
tableView->setItemDelegateForColumn(index, &comboxDelegate);其中代理类中重写了 createEditor,setEditorData,setModelData,updateEditorGeometry 四个虚函数,代码如下:
QWidget *UserTypeDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { QComboBox *combox = new QComboBox(parent); combox->addItems(m_typeList); combox->setEnabled(false); return combox; } void UserTypeDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { QString str = index.model()->data(index, Qt::EditRole).toString(); QComboBox *combox = static_cast<QComboBox*>(editor); int i = combox->findText(str); combox->setCurrentIndex(i); } void UserTypeDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { QComboBox *combox = static_cast<QComboBox*>(editor); model->setData(index, combox->currentIndex(), Qt::EditRole); } void UserTypeDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const { editor->setGeometry(option.rect); }2)运行程序,程序段错误,直接退出,经过排查发现问题原因是我在定义代理类的时候是定义在局部变量,当 setItemDelegateForColumn 调用的时候,变量内存回收,导致段错误,解决办法为设置代理变量为成员变量。
3)再次运行程序,然后查看 tableView 上显示的仍然是数字,但是双击单元格后,显示的是文本,结果如下:

4)经过查阅和请教,得知 tableView 上显示的显示状态,单元格双击的是编辑状态,所以显示的是 QSqlTableModel 保存的数据,而编辑状态的是我添加的代理 combox;编辑状态的现在没有问题,有问题的是显示,而显示又与 QSqlTableModel 相关;因此我重写了 QSqlTableModel 的 data 函数,在 data 函数中映射了数字和文本,代码如下:
QVariant CustomSqlTableModel::data(const QModelIndex &index, int role) const { int column = index.column(); if (column == 2) { //2为指定列,需要映射文本的那一列 int value = QSqlTableModel::data(index, role).toInt(); if (value == 0) { return QString("管理员"); } else if (value == 1) { return QString("普通用户"); } } return QSqlTableModel::data(index, role); }5)再次运行程序,这次发现了一个严重问题,显示界面倒是显示文本了,但是文本前面有了复选框,而且我来回双击单元格后,单元格内的内容发生了变化,两个单元格内的内容变成一样的了。
6)经过研究,发现是 data 函数重写了,但是 setData 函数没有重写,所以 setData 的时候,本应该将数字作为 value 进行存储,但是现在是将文本作为 value 存储了,解决办法就是重写 setData 函数,在获取文本后,将对应的数字存储在 QSqlTableModel 中,setData 重写代码如下:
bool CustomSqlTableModel::setData(const QModelIndex &index, const QVariant &value, int role) { QString type = value.toString(); int column = index.column(); if (column == 2) { //2的含义同上 if (type == "管理员") { QSqlTableModel::setData(index, 0, role); } else if (type == "普通用户") { QSqlTableModel::setData(index, 1, role); } return true; } QSqlTableModel::setData(index, value, role); return true; }7)再次运行程序,发现显示为文本了,来回双击单元格也不会发生内容发生变化的问题了,但是文本前面的复选框还是存在的,结果如下:

8)经过研究研究再研究,终于发现问题原因,这个文本前的复选框根本就不是复选框,而是 icon(图标)的默认框,当 role 这个参数没有被限制时,恰巧我的数字值又是 0,当 role 为 Qt::DecorationRole 时,QSqlTableModel 认为进来的是一个 icon,此时又没有真正的 icon 设置,所以显示的是 icon 的一个默认空白框。
9)修改问题,在 data 函数和 setData 函数中加入 if(index.isValid() && (role == Qt::DisplayRole || role == Qt::EditRole)) 这么一行 if 语句,就把其他形式的 role 过滤掉了,代码如下:
QVariant CustomSqlTableModel::data(const QModelIndex &index, int role) const { int column = index.column(); if(index.isValid() && (role == Qt::DisplayRole || role == Qt::EditRole)) { if (column == 2) { int value = QSqlTableModel::data(index, role).toInt(); if (value == 0) { return QString("管理员"); } else if (value == 1) { return QString("普通用户"); } } } return QSqlTableModel::data(index, role); } bool CustomSqlTableModel::setData(const QModelIndex &index, const QVariant &value, int role) { if(index.isValid() && (role == Qt::DisplayRole || role == Qt::EditRole)) { QString type = value.toString(); int column = index.column(); if (column == 2) { if (type == "管理员") { QSqlTableModel::setData(index, 0, role); } else if (type == "普通用户") { QSqlTableModel::setData(index, 1, role); } return true; } } QSqlTableModel::setData(index, value, role); return true; }10)最后运行程序,文本显示成功,结果如下:

11)最后,验证一下这个是不是真的是由于 icon 导致的文本上的复选框显示问题;我在 if 语句加上 role == Qt::DecorationRole,并打印 role 的值,果然出现大量 role = Qt::DecorationRole 的情况;由此问题彻底得到解决。
总结
- 添加 QcomboxBox 代理,实质上只是在 QTableModel 中添加了编辑状态的 comboBox,或者说只是修改了实际 value,但是 UI 显示的并未改变,还是要通过重写 QSqlTableModel 中的 data 和 setData 方法来修改显示。
- QSqlTableModel 的 data 和 setData 总是成对重写的。
- 进行 data 和 setData 数据重写是注意过滤 role。
- 参数为指针的函数,入参坚决不能为局部变量。