首页 > 用户笔记 > wf的笔记 阅读:25

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 的情况;由此问题彻底得到解决。

总结

相关文章