首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

Qt表格入门

编程知识
2024年09月21日 19:20

摘要

    表格作为数据展示的界面,会在很多场景下使用。Qt为我们提供了使用简单方便和扩展性强的表格视图,这里做一个简单的入门整理。
    个人能力有限,有错误欢迎留言指正,如果你有更好的方法,也欢迎分享讨论。

关键词

    Qt、表格、过滤、筛选、自定义单元格、排序、委托、代理

主要类

    QTableWidget、QTableView、QStandardItemModel、QStyledItemDelegate、QSortFilterProxyModel

〇、准备数据

  • Qt 5.14.2
  • 数据类
// 学生类
class Student
{
public:
    Student(const QString &id, const QString &name, int age, int score, int sex);
    ~Student();

    QString mId;   // 学号
    QString mName; // 名字
    int mAge;      // 年龄
    int mScore;    // 分数
    int mSex;      // 性别
};
// 初始化数据
void MainWindow::initStudent()
{
    // QStringList mHeader;
    mHeader << "学号" << "姓名" << "年龄" << "分数" << "性别";
    // QList<Student *> mStudents;
    mStudents << new Student("501", "小明", 20, 85, 0)
              << new Student("402", "小红", 29, 19, 1)
              << new Student("311", "小刚", 25, 79, 1)
              << new Student("813", "小李", 27, 33, 1)
              << new Student("514", "小赵", 23, 21, 0)
              << new Student("425", "小王", 24, 50, 0)
              << new Student("326", "小张", 26, 44, 1)
              << new Student("28", "小淘", 28, 93, 1)
              << new Student("30", "小杨", 21, 77, 1);
}

在这段代码中,

  • 定义学生类,学生类主要包括学生的学号、姓名、年龄、分数、性别;

  • 定义了表格的表头;

  • 同时创建了几个学生,并将学生存储到QList里。


一、显示数据(QTableWidget)

void MainWindow::initTableWidget()
{
    ui->tableWidget->setRowCount(mStudents.size());
    ui->tableWidget->setColumnCount(5);
    ui->tableWidget->setHorizontalHeaderLabels(mHeader);
    ui->tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
    for (int i = 0; i < mStudents.size(); ++i) {
        Student *s = mStudents.at(i);
        QTableWidgetItem *item0 = new QTableWidgetItem;
        item0->setData(Qt::DisplayRole, s->mId);
        QTableWidgetItem *item1 = new QTableWidgetItem;
        item1->setData(Qt::DisplayRole, s->mName);
        QTableWidgetItem *item2 = new QTableWidgetItem;
        item2->setData(Qt::DisplayRole, s->mAge);
        QTableWidgetItem *item3 = new QTableWidgetItem;
        item3->setData(Qt::DisplayRole, s->mScore);
        QTableWidgetItem *item4 = new QTableWidgetItem;
        item4->setData(Qt::DisplayRole, s->mSex);

        ui->tableWidget->setItem(i, 0, item0);
        ui->tableWidget->setItem(i, 1, item1);
        ui->tableWidget->setItem(i, 2, item2);
        ui->tableWidget->setItem(i, 3, item3);
        ui->tableWidget->setItem(i, 4, item4);
    }
}

在这段代码中,使用QTableWidget显示数据。

  • 首先设置了行数和列数;

  • 然后设置QTableWidget的水平表头的列名,同时设置为平铺拉伸模式;

  • 再然后遍历数据对表格进行了填充,使用的是QTableWidgetItem,同时使用setData和Qt::DisplayRole可以方便以后对数字列进行排序。

  • 运行效果如下:
    image


二、显示数据(QTableView和QStandardItemModel)

void MainWindow::initTableView()
{
    // QStandardItemModel *mTableViewModel;
    mTableViewModel = new QStandardItemModel(this);
    mTableViewModel->setRowCount(mStudents.size());
    mTableViewModel->setColumnCount(5);
    mTableViewModel->setHorizontalHeaderLabels(mHeader);
    for (int i = 0; i < mStudents.size(); ++i) {
        Student *s = mStudents.at(i);
        QStandardItem *item0 = new QStandardItem;
        item0->setData(s->mId, Qt::DisplayRole);
        QStandardItem *item1 = new QStandardItem;
        item1->setData(s->mName, Qt::DisplayRole);
        QStandardItem *item2 = new QStandardItem;
        item2->setData(s->mAge, Qt::DisplayRole);
        QStandardItem *item3 = new QStandardItem;
        item3->setData(s->mScore, Qt::DisplayRole);
        QStandardItem *item4 = new QStandardItem;
        item4->setData(s->mSex, Qt::DisplayRole);

        mTableViewModel->setItem(i, 0, item0);
        mTableViewModel->setItem(i, 1, item1);
        mTableViewModel->setItem(i, 2, item2);
        mTableViewModel->setItem(i, 3, item3);
        mTableViewModel->setItem(i, 4, item4);
    }
    ui->tableView->setModel(mTableViewModel);
    ui->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
}

在这段代码中,使用了QStandardItemModel和QTableView来显示数据。通过view和model分离的模式,可以创建更多的高级功能。后面将使用QTableView来进行高级功能的演示。

  • 首先,创建了model,设置行数、列数和表头;

  • 然后,创建单元格,填充model;需要注意的是QTableWidgetItem和QStandardItem的setData函数的两个参数顺序是相反的;

  • 最后,给view设置model,设置表头平铺拉伸显示。

  • 运行效果如下:
    image


三、数据代理(委托)(QStyledItemDelegate)

QStyledItemDelegate可以让表格拥有更高级的显示效果和编辑功能。

1、使用QComboBox代理显示性别

// 代理类
class ComboxDelegate : public QStyledItemDelegate
{
    Q_OBJECT

public:
    ComboxDelegate(QObject *parent = nullptr);
    ~ComboxDelegate();

protected:
    QWidget * createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
    void setEditorData(QWidget *editor, const QModelIndex &index) const override;
    void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
    void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
};

在这段代码中,继承了QStyledItemDelegate类,来实现一个代理类。需要实现四个函数。

  • createEditor:在点击表格视图进入编辑状态时,该函数会创建一个QWidget,在此函数中实现QComboBox的生成;

  • setEditorData:该函数会对QComboBox设置值;

  • setModelData:在编辑完成后,该函数应该对model中的数据进行修改;

  • updateEditorGeometry:该函数应该设置编辑区域的大小;

ComboxDelegate::ComboxDelegate(QObject *parent)
{

}

ComboxDelegate::~ComboxDelegate()
{

}

QWidget *ComboxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    QComboBox *combox = new QComboBox(parent);
    combox->addItem("女");
    combox->addItem("男");
    return combox;
}

void ComboxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
    int sex = index.data(Qt::EditRole).toInt();
    QComboBox *combox = qobject_cast<QComboBox *>(editor);
    combox->setCurrentIndex(sex);
}

void ComboxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
    QComboBox *combox = qobject_cast<QComboBox *>(editor);
    int sex = combox->currentIndex();
    model->setData(index, sex, Qt::EditRole);
}

void ComboxDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    editor->setGeometry(option.rect);
}

在这段代码中,实现四个函数的具体功能。

  • createEditor:创建了一个QComboBox,并设置了两个选项;

  • setEditorData:从QModelIndex中获取数据,转换QWidget为QComboBox,并给其设置值;

  • setModelData:获取编辑后的值,将编辑后的值设置给model;

  • updateEditorGeometry:设置几何大小;

void MainWindow::initSource()
{
    mSourceModel = new QStandardItemModel(this);
    mSourceModel->setRowCount(mStudents.size());
    mSourceModel->setColumnCount(5);
    mSourceModel->setHorizontalHeaderLabels(mHeader);
    for (int i = 0; i < mStudents.size(); ++i) {
        Student *s = mStudents.at(i);
        QStandardItem *item0 = new QStandardItem;
        item0->setData(s->mId, Qt::DisplayRole);
        QStandardItem *item1 = new QStandardItem;
        item1->setData(s->mName, Qt::DisplayRole);
        QStandardItem *item2 = new QStandardItem;
        item2->setData(s->mAge, Qt::DisplayRole);
        QStandardItem *item3 = new QStandardItem;
        item3->setData(s->mScore, Qt::DisplayRole);
        QStandardItem *item4 = new QStandardItem;
        item4->setData(s->mSex, Qt::DisplayRole);

        mSourceModel->setItem(i, 0, item0);
        mSourceModel->setItem(i, 1, item1);
        mSourceModel->setItem(i, 2, item2);
        mSourceModel->setItem(i, 3, item3);
        mSourceModel->setItem(i, 4, item4);
    }
    ui->tableView_Source->setModel(mSourceModel);
    ui->tableView_Source->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);

    ComboxDelegate *d = new ComboxDelegate(this);
    ui->tableView_Source->setItemDelegateForColumn(4, d);
}

在这段代码中,

  • 首先,给model填充数据;

  • 然后,创建了ComboxDelegate代理对象;

  • 最后,给QTableView的性别列设置了代理对象;

  • 运行效果如下:
    image


    更多的例子可以参考Qt自带的例子,在「示例」中搜索「Color Editor Factory Example」、「Spin Box Delegate Example」、「Star Delegate Example」
    image
    image
    image


2、使用自定义的窗口代理显示性别

除了自带的控件,如果想将自定义的控件或者窗口插入到表格的单元格中应该怎么做呢?
与刚才的例子类似,只不过把QComboBox换成自定义的窗口类就可以了。

class RadioWidget : public QWidget
{
    Q_OBJECT
public:
    explicit RadioWidget(const QModelIndex &index, QWidget *parent = nullptr);
    ~RadioWidget();

    void setSex(int sex);
    int getSex() const;

    QStandardItem *getItem() const;
    void setItem(QStandardItem *item);

signals:
    void sexChangedByQModelIndex(int row, int sex);
    void sexChangedByQStandardItem(int row, int sex);


private:
    QRadioButton *mRadioMale;
    QRadioButton *mRadioFemale;

    int mSex;

    QStandardItem *mItem = nullptr;
    QModelIndex mIndex;

    void changeState(QAbstractButton *button, bool checked);
};

在这段代码中,定义了一个自定义的窗口:

  • 它包含两个QRadioButton和性别;

  • 对应单元格的QStandardItem和QModelIndex;

  • 当性别变化时发出的sexChangedByQModelIndex和sexChangedByQStandardItem信号,发送的参数是单元格所在行号和当前性别;

需要注意的是,为了定位单元格,使用了QStandardItem和QModelIndex———这个会在后面进行演示,添加这两变量是为了:

  • 1、由于代理的存在,自定义窗口和模型单元格之间隔了一层,需要将定位信息(主要是为了行号)存在自定义窗口中;

  • 2、比较QStandardItem和QModelIndex,结论就是应该使用QStandardItem;

RadioWidget::RadioWidget(const QModelIndex &index, QWidget *parent) :
    QWidget(parent)
{
    mIndex = index;

    QHBoxLayout *layout = new QHBoxLayout();
    mRadioMale = new QRadioButton(this);
    mRadioMale->setText("男");
    mRadioFemale = new QRadioButton(this);
    mRadioFemale->setText("女");

    layout->addWidget(mRadioMale, Qt::AlignCenter);
    layout->addWidget(mRadioFemale, Qt::AlignCenter);

    layout->setMargin(0);
    this->setLayout(layout);

    mSex = 1;
    mRadioMale->setChecked(true);
    mRadioFemale->setChecked(false);

    QButtonGroup *button_group = new QButtonGroup(this);
    button_group->addButton(mRadioMale);
    button_group->addButton(mRadioFemale);

    connect(button_group, QOverload<QAbstractButton *, bool>::of(&QButtonGroup::buttonToggled),
            this, &RadioWidget::changeState);
}

RadioWidget::~RadioWidget()
{

}

void RadioWidget::setSex(int sex)
{
    mSex = sex;
    if (mSex == 1) {
        mRadioMale->setChecked(true);
        mRadioFemale->setChecked(false);
    } else {
        mRadioMale->setChecked(false);
        mRadioFemale->setChecked(true);
    }
}

QStandardItem *RadioWidget::getItem() const
{
    return mItem;
}

void RadioWidget::setItem(QStandardItem *item)
{
    mItem = item;
}

int RadioWidget::getSex() const
{
    return mSex;
}

void RadioWidget::changeState(QAbstractButton *button, bool checked)
{
    if (checked == false) {
        return ;
    }

    if (button == mRadioMale) {
        mSex = 1;
    } else if (button == mRadioFemale) {
        mSex = 0;
    }
    emit sexChangedByQModelIndex(mIndex.row(), mSex);
    if (mItem) {
        emit sexChangedByQStandardItem(mItem->row(), mSex);
    }
}

在这段代码中,具体实现了各个函数的功能:

  • 在构造函数中,创建了两个性别的QRadioButton和按钮组,并创建了窗口的布局;

  • 在setSex函数中,设置了对应的按钮状态;

  • 在changeState函数中,当性别变化时,发出对应的两个信号;


class RadioDelegate : public QStyledItemDelegate
{
   Q_OBJECT
public:
   RadioDelegate(QObject *parent = nullptr);
   ~RadioDelegate();

   void setSourceModel(QStandardItemModel *model);

protected:
   QWidget * createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
   void setEditorData(QWidget *editor, const QModelIndex &index) const override;
   void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
   void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;

private:
   QStandardItemModel *mSourceModel = nullptr;
};

RadioDelegate::RadioDelegate(QObject *parent) : QStyledItemDelegate(parent)
{

}

RadioDelegate::~RadioDelegate()
{

}

QWidget *RadioDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
   RadioWidget *rw = new RadioWidget(index, parent);

   QStandardItem *item = mSourceModel->itemFromIndex(index);
   rw->setItem(item);
   rw->setSex(item->data(Qt::DisplayRole).toInt());

   return rw;
}

void RadioDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
   int sex = index.model()->data(index).toInt();
   RadioWidget *rw = static_cast<RadioWidget *>(editor);
   rw->setSex(sex);
}

void RadioDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
   editor->setGeometry(option.rect);
}

void RadioDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
   RadioWidget *rw = static_cast<RadioWidget *>(editor);
   int sex = rw->getSex();
   model->setData(index, sex);
}

void RadioDelegate::setSourceModel(QStandardItemModel *model)
{
   mSourceModel = model;
}

在这段代码中,实现了自定义窗口对应的代理,和上面的代理类差不多,需要注意的是:

  • 1、要将上面代码中的QComboBox替换成自定义窗口RadioWidget;

  • 2、在createEditor函数中,设置了RadioWidget的QModelIndex和QStandardItem,以及性别;

  • 3、在调用时使用代码:

    RadioDelegate *d = new RadioDelegate(this);
    d->setSourceModel(mSourceModel);
    ui->tableView_Source->setItemDelegateForColumn(4, d);
  • 4、运行效果如下:
    image

3、如何让代理始终显示呢

从上图的效果可以看出,需要双击才可以显示出单元格的自定义窗口,但是需要始终显示的时候应该怎么做呢?

只需要调用void QAbstractItemView::openPersistentEditor(const QModelIndex &index)函数就可以了。

修改后的代码如下:

void MainWindow::initSource()
{
    mSourceModel = new QStandardItemModel(this);
    mSourceModel->setRowCount(mStudents.size());
    mSourceModel->setColumnCount(5);
    mSourceModel->setHorizontalHeaderLabels(mHeader);
    for (int i = 0; i < mStudents.size(); ++i) {
        Student *s = mStudents.at(i);
        QStandardItem *item0 = new QStandardItem;
        item0->setData(s->mId, Qt::DisplayRole);
        QStandardItem *item1 = new QStandardItem;
        item1->setData(s->mName, Qt::DisplayRole);
        QStandardItem *item2 = new QStandardItem;
        item2->setData(s->mAge, Qt::DisplayRole);
        QStandardItem *item3 = new QStandardItem;
        item3->setData(s->mScore, Qt::DisplayRole);
        QStandardItem *item4 = new QStandardItem;
        item4->setData(s->mSex, Qt::DisplayRole);

        mSourceModel->setItem(i, 0, item0);
        mSourceModel->setItem(i, 1, item1);
        mSourceModel->setItem(i, 2, item2);
        mSourceModel->setItem(i, 3, item3);
        mSourceModel->setItem(i, 4, item4);
    }
    ui->tableView_Source->setModel(mSourceModel);
    ui->tableView_Source->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);

//    ComboxDelegate *d = new ComboxDelegate(this);
//    ui->tableView_Source->setItemDelegateForColumn(4, d);
    RadioDelegate *d = new RadioDelegate(this);
    d->setSourceModel(mSourceModel);
    ui->tableView_Source->setItemDelegateForColumn(4, d);
    // 让代理自定义窗口始终显示
    for (int i = 0, size = mSourceModel->rowCount(); i < size; ++i) {
        ui->tableView_Source->openPersistentEditor(mSourceModel->index(i, 4));
    }
}

运行效果如下:
image


四、筛选过滤、排序(QSortFilterProxyModel)

QSortFilterProxyModel可以让表格实现筛选过滤和排序的功能。

1、实现筛选过滤

在这个例子中实现了名字和性别的筛选过滤。

class SortFilterProxyModel : public QSortFilterProxyModel
{
public:
    SortFilterProxyModel(QObject *parent = nullptr);

    void setSex(int sex);

protected:
    bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;

private:
    int mSex = -1;
};

在这段代码中,定义了一个自定义的排序筛选代理模型:

  • 新增了性别变量,当设置性别setSex时,需要调用invalidateFilter()重新筛选;

  • 在filterAcceptsRow函数中,获取到源数据,返回比较的结果布尔值;


void MainWindow::initProxy()
{
    mProxyModel = new SortFilterProxyModel(this);
    mProxyModel->setSourceModel(mSourceModel);
    ui->tableView_Proxy->setModel(mProxyModel);
    ui->tableView_Proxy->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);

    // 过滤筛选
    connect(ui->lineEdit, &QLineEdit::textChanged, this, [=](const QString &text) {
        mProxyModel->setFilterRegExp(text);
    });

    ui->comboBox->addItem("全部", -1);
    ui->comboBox->addItem("女", 0);
    ui->comboBox->addItem("男", 1);
    connect(ui->comboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [=](int index) {
        int sex = ui->comboBox->itemData(index).toInt();
        mProxyModel->setSex(sex);
    });
}

在这段代码中,

  • 创建了SortFilterProxyModel对象,设置了一些基础属性:设置源模型,显示到view上,设置表头;

  • 通过检测lineEdit文本变化,过滤名字;

  • 通过检测comboBox选项变化,过滤性别;

  • 运行效果如下:
    image

2、实现排序

在上面基础上添加以下代码:

    // 排序
    connect(ui->tableView_Proxy->horizontalHeader(), &QHeaderView::sortIndicatorChanged,
            this, [=](int logicalIndex, Qt::SortOrder order)
    {
        ui->tableView_Proxy->model()->sort(logicalIndex, order);
    });

对于排序的实现,可以自定义void QSortFilterProxyModel::sort(int column, Qt::SortOrder order = Qt::AscendingOrder)函数实现,这里不做过多演示。

运行效果如下:
image

3、显示代理

需要对上面的RadioDelegate做一些修改,添加以下内容:


void setProxyModel(QSortFilterProxyModel *sortModel);
QSortFilterProxyModel *mProxyModel = nullptr;


void RadioDelegate::setProxyModel(QSortFilterProxyModel *sortModel)
{
    mProxyModel = sortModel;
    QAbstractItemModel *source_model = mProxyModel->sourceModel();
    mSourceModel = static_cast<QStandardItemModel *>(source_model);
}

QWidget *RadioDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    RadioWidget *rw = new RadioWidget(index, parent);

//    QStandardItem *item = mSourceModel->itemFromIndex(index);
//    rw->setItem(item);
//    rw->setSex(item->data(Qt::DisplayRole).toInt());

    if (mProxyModel) {
        QStandardItem *item = mSourceModel->itemFromIndex(mProxyModel->mapToSource(index));
        rw->setItem(item);
        rw->setSex(item->data(Qt::DisplayRole).toInt());
    } else {
        QStandardItem *item = mSourceModel->itemFromIndex(index);
        rw->setItem(item);
        rw->setSex(item->data(Qt::DisplayRole).toInt());
    }


    connect(rw, &RadioWidget::sexChangedByQModelIndex, this, [=](int row, int sex) {
        if (mSourceModel) {
//            qDebug() << "sexChangedByQModelIndex" << row << mSourceModel->item(row, 0)->text();
            mSourceModel->item(row, 4)->setData(sex, Qt::DisplayRole);
        }
    });
    connect(rw, &RadioWidget::sexChangedByQStandardItem, this, [=](int row, int sex) {
        if (mSourceModel) {
//            qDebug() << "sexChangedByQStandardItem" << row << mSourceModel->item(row, 0)->text();
            mSourceModel->item(row, 4)->setData(sex, Qt::DisplayRole);
        }
    });

    return rw;
}

在这段代码中,

  • 添加了QSortFilterProxyModel成员变量;

  • 为了方便(偷懒),在createEditor函数中判断后,再setItem;

  • 连接了两个信号;


void MainWindow::initProxy()
{
    mProxyModel = new SortFilterProxyModel(this);
    mProxyModel->setSourceModel(mSourceModel);
    ui->tableView_Proxy->setModel(mProxyModel);
    ui->tableView_Proxy->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);

    // 代理
    RadioDelegate *rd = new RadioDelegate(ui->tableView_Proxy);
    rd->setProxyModel(mProxyModel);
    ui->tableView_Proxy->setItemDelegateForColumn(4, rd);


    for (int i = 0, size = mProxyModel->rowCount(); i < size; ++i) {
        ui->tableView_Proxy->openPersistentEditor(mProxyModel->index(i, 4));
    }

    // 过滤筛选
    connect(ui->lineEdit, &QLineEdit::textChanged, this, [=](const QString &text) {
        mProxyModel->setFilterRegExp(text);
        for (int i = 0, size = mProxyModel->rowCount(); i < size; ++i) {
            ui->tableView_Proxy->openPersistentEditor(mProxyModel->index(i, 4));
        }
    });

    ui->comboBox->addItem("全部", -1);
    ui->comboBox->addItem("女", 0);
    ui->comboBox->addItem("男", 1);
    connect(ui->comboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [=](int index) {
        int sex = ui->comboBox->itemData(index).toInt();
        mProxyModel->setSex(sex);
        for (int i = 0, size = mProxyModel->rowCount(); i < size; ++i) {
            ui->tableView_Proxy->openPersistentEditor(mProxyModel->index(i, 4));
        }
    });

    // 排序
    connect(ui->tableView_Proxy->horizontalHeader(), &QHeaderView::sortIndicatorChanged,
            this, [=](int logicalIndex, Qt::SortOrder order)
    {
        ui->tableView_Proxy->model()->sort(logicalIndex, order);
        for (int i = 0, size = mProxyModel->rowCount(); i < size; ++i) {
            ui->tableView_Proxy->openPersistentEditor(mProxyModel->index(i, 4));
        }
    });
}

在这段代码中,添加了以下内容:

  • 创建代理,给表格的性别列设置代理,让代理始终显示;

  • 在过滤筛选的两个槽函数中,重新设置代理始终显示;

  • 在排序的槽函数中,重新设置代理始终显示;

4、运行效果

对于RadioDelegate::createEditor中的连接的两个信号,可以注释掉其中一个运行一下效果:

使用sexChangedByQModelIndex:
image
可以看到在多次的筛选排序后,出现了问题。

sexChangedByQStandardItem:
image
可以看到在多次筛选排序后,都没有出现问题。

结论:使用QStandardItem的row()函数。


附录一:参考文献

附录二:完整代码

  • mainwindow.h
点击折叠或展开代码
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QtCore>
#include <QtWidgets>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

// 学生类
class Student
{
public:
   Student(const QString &id, const QString &name, int age, int score, int sex);
   ~Student();

   QString mId;   // 学号
   QString mName; // 名字
   int mAge;      // 年龄
   int mScore;    // 分数
   int mSex;      // 性别
};
// 代理类
class ComboxDelegate : public QStyledItemDelegate
{
   Q_OBJECT

public:
   ComboxDelegate(QObject *parent = nullptr);
   ~ComboxDelegate();

protected:
   QWidget * createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
   void setEditorData(QWidget *editor, const QModelIndex &index) const override;
   void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
   void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
};

// 自定义窗口
class RadioWidget : public QWidget
{
   Q_OBJECT
public:
   explicit RadioWidget(const QModelIndex &index, QWidget *parent = nullptr);
   ~RadioWidget();

   void setSex(int sex);
   int getSex() const;

   QStandardItem *getItem() const;
   void setItem(QStandardItem *item);

signals:
   void sexChangedByQModelIndex(int row, int sex);
   void sexChangedByQStandardItem(int row, int sex);


private:
   QRadioButton *mRadioMale;
   QRadioButton *mRadioFemale;

   int mSex;

   QStandardItem *mItem = nullptr;
   QModelIndex mIndex;

   void changeState(QAbstractButton *button, bool checked);
};
// 自定义窗口对应的代理类
class RadioDelegate : public QStyledItemDelegate
{
   Q_OBJECT
public:
   RadioDelegate(QObject *parent = nullptr);
   ~RadioDelegate();

   void setSourceModel(QStandardItemModel *model);

   void setProxyModel(QSortFilterProxyModel *sortModel);

protected:
   QWidget * createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
   void setEditorData(QWidget *editor, const QModelIndex &index) const override;
   void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
   void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;

private:
   QStandardItemModel *mSourceModel = nullptr;
   QSortFilterProxyModel *mProxyModel = nullptr;
};

class SortFilterProxyModel : public QSortFilterProxyModel
{
public:
   SortFilterProxyModel(QObject *parent = nullptr);

   void setSex(int sex);

protected:
   bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;

private:
   int mSex = -1;
};

class MainWindow : public QMainWindow
{
   Q_OBJECT

public:
   MainWindow(QWidget *parent = nullptr);
   ~MainWindow();

private:
   Ui::MainWindow *ui;

   void initStudent();
   void initTableWidget();
   void initTableView();
   void initSource();
   void initProxy();

   QStringList mHeader;
   QList<Student *> mStudents;
   QStandardItemModel *mTableViewModel;

   QStandardItemModel *mSourceModel;
   SortFilterProxyModel *mProxyModel;
};
#endif // MAINWINDOW_H


  • mainwindow.cpp
点击折叠或展开代码
#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
   : QMainWindow(parent)
   , ui(new Ui::MainWindow)
{
   ui->setupUi(this);

   initStudent();

   initTableWidget();
   initTableView();
   initSource();
   initProxy();
}

MainWindow::~MainWindow()
{
   delete ui;
}
// 初始化数据
void MainWindow::initStudent()
{
   // QStringList mHeader;
   mHeader << "学号" << "姓名" << "年龄" << "分数" << "性别";
   // QList<Student *> mStudents;
   mStudents << new Student("501", "小明", 20, 85, 0)
             << new Student("402", "小红", 29, 19, 1)
             << new Student("311", "小刚", 25, 79, 1)
             << new Student("813", "小李", 27, 33, 1)
             << new Student("514", "小赵", 23, 21, 0)
             << new Student("425", "小王", 24, 50, 0)
             << new Student("326", "小张", 26, 44, 1)
             << new Student("28", "小淘", 28, 93, 1)
             << new Student("30", "小杨", 21, 77, 1);
}

void MainWindow::initTableWidget()
{
   ui->tableWidget->setRowCount(mStudents.size());
   ui->tableWidget->setColumnCount(5);
   ui->tableWidget->setHorizontalHeaderLabels(mHeader);
   ui->tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
   for (int i = 0; i < mStudents.size(); ++i) {
       Student *s = mStudents.at(i);
       QTableWidgetItem *item0 = new QTableWidgetItem;
       item0->setData(Qt::DisplayRole, s->mId);
       QTableWidgetItem *item1 = new QTableWidgetItem;
       item1->setData(Qt::DisplayRole, s->mName);
       QTableWidgetItem *item2 = new QTableWidgetItem;
       item2->setData(Qt::DisplayRole, s->mAge);
       QTableWidgetItem *item3 = new QTableWidgetItem;
       item3->setData(Qt::DisplayRole, s->mScore);
       QTableWidgetItem *item4 = new QTableWidgetItem;
       item4->setData(Qt::DisplayRole, s->mSex);

       ui->tableWidget->setItem(i, 0, item0);
       ui->tableWidget->setItem(i, 1, item1);
       ui->tableWidget->setItem(i, 2, item2);
       ui->tableWidget->setItem(i, 3, item3);
       ui->tableWidget->setItem(i, 4, item4);
   }
}

void MainWindow::initTableView()
{
   // QStandardItemModel *mTableViewModel;
   mTableViewModel = new QStandardItemModel(this);
   mTableViewModel->setRowCount(mStudents.size());
   mTableViewModel->setColumnCount(5);
   mTableViewModel->setHorizontalHeaderLabels(mHeader);
   for (int i = 0; i < mStudents.size(); ++i) {
       Student *s = mStudents.at(i);
       QStandardItem *item0 = new QStandardItem;
       item0->setData(s->mId, Qt::DisplayRole);
       QStandardItem *item1 = new QStandardItem;
       item1->setData(s->mName, Qt::DisplayRole);
       QStandardItem *item2 = new QStandardItem;
       item2->setData(s->mAge, Qt::DisplayRole);
       QStandardItem *item3 = new QStandardItem;
       item3->setData(s->mScore, Qt::DisplayRole);
       QStandardItem *item4 = new QStandardItem;
       item4->setData(s->mSex, Qt::DisplayRole);

       mTableViewModel->setItem(i, 0, item0);
       mTableViewModel->setItem(i, 1, item1);
       mTableViewModel->setItem(i, 2, item2);
       mTableViewModel->setItem(i, 3, item3);
       mTableViewModel->setItem(i, 4, item4);
   }
   ui->tableView->setModel(mTableViewModel);
   ui->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
}

void MainWindow::initSource()
{
   mSourceModel = new QStandardItemModel(this);
   mSourceModel->setRowCount(mStudents.size());
   mSourceModel->setColumnCount(5);
   mSourceModel->setHorizontalHeaderLabels(mHeader);
   for (int i = 0; i < mStudents.size(); ++i) {
       Student *s = mStudents.at(i);
       QStandardItem *item0 = new QStandardItem;
       item0->setData(s->mId, Qt::DisplayRole);
       QStandardItem *item1 = new QStandardItem;
       item1->setData(s->mName, Qt::DisplayRole);
       QStandardItem *item2 = new QStandardItem;
       item2->setData(s->mAge, Qt::DisplayRole);
       QStandardItem *item3 = new QStandardItem;
       item3->setData(s->mScore, Qt::DisplayRole);
       QStandardItem *item4 = new QStandardItem;
       item4->setData(s->mSex, Qt::DisplayRole);

       mSourceModel->setItem(i, 0, item0);
       mSourceModel->setItem(i, 1, item1);
       mSourceModel->setItem(i, 2, item2);
       mSourceModel->setItem(i, 3, item3);
       mSourceModel->setItem(i, 4, item4);
   }
   ui->tableView_Source->setModel(mSourceModel);
   ui->tableView_Source->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);

//    ComboxDelegate *d = new ComboxDelegate(this);
//    ui->tableView_Source->setItemDelegateForColumn(4, d);
   RadioDelegate *d = new RadioDelegate(this);
   d->setSourceModel(mSourceModel);
   ui->tableView_Source->setItemDelegateForColumn(4, d);
   for (int i = 0, size = mSourceModel->rowCount(); i < size; ++i) {
       ui->tableView_Source->openPersistentEditor(mSourceModel->index(i, 4));
   }
}

void MainWindow::initProxy()
{
   mProxyModel = new SortFilterProxyModel(this);
   mProxyModel->setSourceModel(mSourceModel);
   ui->tableView_Proxy->setModel(mProxyModel);
   ui->tableView_Proxy->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);

   // 代理
   RadioDelegate *rd = new RadioDelegate(ui->tableView_Proxy);
   rd->setProxyModel(mProxyModel);
   ui->tableView_Proxy->setItemDelegateForColumn(4, rd);


   for (int i = 0, size = mProxyModel->rowCount(); i < size; ++i) {
       ui->tableView_Proxy->openPersistentEditor(mProxyModel->index(i, 4));
   }

   // 过滤筛选
   connect(ui->lineEdit, &QLineEdit::textChanged, this, [=](const QString &text) {
       mProxyModel->setFilterRegExp(text);
       for (int i = 0, size = mProxyModel->rowCount(); i < size; ++i) {
           ui->tableView_Proxy->openPersistentEditor(mProxyModel->index(i, 4));
       }
   });

   ui->comboBox->addItem("全部", -1);
   ui->comboBox->addItem("女", 0);
   ui->comboBox->addItem("男", 1);
   connect(ui->comboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [=](int index) {
       int sex = ui->comboBox->itemData(index).toInt();
       mProxyModel->setSex(sex);
       for (int i = 0, size = mProxyModel->rowCount(); i < size; ++i) {
           ui->tableView_Proxy->openPersistentEditor(mProxyModel->index(i, 4));
       }
   });

   // 排序
   connect(ui->tableView_Proxy->horizontalHeader(), &QHeaderView::sortIndicatorChanged,
           this, [=](int logicalIndex, Qt::SortOrder order)
   {
       ui->tableView_Proxy->model()->sort(logicalIndex, order);
       for (int i = 0, size = mProxyModel->rowCount(); i < size; ++i) {
           ui->tableView_Proxy->openPersistentEditor(mProxyModel->index(i, 4));
       }
   });
}


Student::Student(const QString &id, const QString &name, int age, int score, int sex)
{
   mId = id;
   mName = name;
   mAge = age;
   mScore = score;
   mSex = sex;
}

Student::~Student()
{

}

ComboxDelegate::ComboxDelegate(QObject *parent) :
   QStyledItemDelegate(parent)
{

}

ComboxDelegate::~ComboxDelegate()
{

}

QWidget *ComboxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
   QComboBox *combox = new QComboBox(parent);
   combox->addItem("女");
   combox->addItem("男");
   return combox;
}

void ComboxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
   int sex = index.data(Qt::EditRole).toInt();
   QComboBox *combox = qobject_cast<QComboBox *>(editor);
   combox->setCurrentIndex(sex);
}

void ComboxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
   QComboBox *combox = qobject_cast<QComboBox *>(editor);
   int sex = combox->currentIndex();
   model->setData(index, sex, Qt::EditRole);
}

void ComboxDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
   editor->setGeometry(option.rect);
}

RadioWidget::RadioWidget(const QModelIndex &index, QWidget *parent) :
   QWidget(parent)
{
   mIndex = index;

   QHBoxLayout *layout = new QHBoxLayout();
   mRadioMale = new QRadioButton(this);
   mRadioMale->setText("男");
   mRadioFemale = new QRadioButton(this);
   mRadioFemale->setText("女");

   layout->addWidget(mRadioMale, Qt::AlignCenter);
   layout->addWidget(mRadioFemale, Qt::AlignCenter);

   layout->setMargin(0);
   this->setLayout(layout);

   mSex = 1;
   mRadioMale->setChecked(true);
   mRadioFemale->setChecked(false);

   QButtonGroup *button_group = new QButtonGroup(this);
   button_group->addButton(mRadioMale);
   button_group->addButton(mRadioFemale);

   connect(button_group, QOverload<QAbstractButton *, bool>::of(&QButtonGroup::buttonToggled),
           this, &RadioWidget::changeState);
}

RadioWidget::~RadioWidget()
{

}

void RadioWidget::setSex(int sex)
{
   mSex = sex;
   if (mSex == 1) {
       mRadioMale->setChecked(true);
       mRadioFemale->setChecked(false);
   } else {
       mRadioMale->setChecked(false);
       mRadioFemale->setChecked(true);
   }
}

QStandardItem *RadioWidget::getItem() const
{
   return mItem;
}

void RadioWidget::setItem(QStandardItem *item)
{
   mItem = item;
}

int RadioWidget::getSex() const
{
   return mSex;
}

void RadioWidget::changeState(QAbstractButton *button, bool checked)
{
   if (checked == false) {
       return ;
   }

   if (button == mRadioMale) {
       mSex = 1;
   } else if (button == mRadioFemale) {
       mSex = 0;
   }
   emit sexChangedByQModelIndex(mIndex.row(), mSex);
   if (mItem) {
       emit sexChangedByQStandardItem(mItem->row(), mSex);
   }
}

RadioDelegate::RadioDelegate(QObject *parent) : QStyledItemDelegate(parent)
{

}

RadioDelegate::~RadioDelegate()
{

}

QWidget *RadioDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
   RadioWidget *rw = new RadioWidget(index, parent);

//    QStandardItem *item = mSourceModel->itemFromIndex(index);
//    rw->setItem(item);
//    rw->setSex(item->data(Qt::DisplayRole).toInt());

   if (mProxyModel) {
       QStandardItem *item = mSourceModel->itemFromIndex(mProxyModel->mapToSource(index));
       rw->setItem(item);
       rw->setSex(item->data(Qt::DisplayRole).toInt());
   } else {
       QStandardItem *item = mSourceModel->itemFromIndex(index);
       rw->setItem(item);
       rw->setSex(item->data(Qt::DisplayRole).toInt());
   }


//    connect(rw, &RadioWidget::sexChangedByQModelIndex, this, [=](int row, int sex) {
//        if (mSourceModel) {
////            qDebug() << "sexChangedByQModelIndex" << row << mSourceModel->item(row, 0)->text();
//            mSourceModel->item(row, 4)->setData(sex, Qt::DisplayRole);
//        }
//    });
   connect(rw, &RadioWidget::sexChangedByQStandardItem, this, [=](int row, int sex) {
       if (mSourceModel) {
//            qDebug() << "sexChangedByQStandardItem" << row << mSourceModel->item(row, 0)->text();
           mSourceModel->item(row, 4)->setData(sex, Qt::DisplayRole);
       }
   });

   return rw;
}

void RadioDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
   int sex = index.model()->data(index).toInt();
   RadioWidget *rw = static_cast<RadioWidget *>(editor);
   rw->setSex(sex);
}

void RadioDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
   editor->setGeometry(option.rect);
}

void RadioDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
   RadioWidget *rw = static_cast<RadioWidget *>(editor);
   int sex = rw->getSex();
   model->setData(index, sex);
}

void RadioDelegate::setProxyModel(QSortFilterProxyModel *sortModel)
{
   mProxyModel = sortModel;
   QAbstractItemModel *source_model = mProxyModel->sourceModel();
   mSourceModel = static_cast<QStandardItemModel *>(source_model);
}

void RadioDelegate::setSourceModel(QStandardItemModel *model)
{
   mSourceModel = model;
}

SortFilterProxyModel::SortFilterProxyModel(QObject *parent)
   : QSortFilterProxyModel(parent)
{

}

void SortFilterProxyModel::setSex(int sex)
{
   mSex = sex;
   invalidateFilter();
}

bool SortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
   QModelIndex index1 = sourceModel()->index(source_row, 1);
   QString name = sourceModel()->data(index1).toString();

   QModelIndex index4 = sourceModel()->index(source_row, 4);
   int sex = sourceModel()->data(index4).toInt();

   bool sex_flag = (mSex == -1) ? true : (sex == mSex);
   return sex_flag && name.contains(filterRegExp());
}


  • ui_mainwindow.ui

image


From:https://www.cnblogs.com/wsry/p/18350462
本文地址: http://shuzixingkong.net/article/2188
0评论
提交 加载更多评论
其他文章 数字产品护照 (DPP) 解决方案:利用 Blazor 和区块链实现产品全生命周期追踪
数字产品护照 (DPP) 解决方案:利用 Blazor 和区块链实现产品全生命周期追踪 随着全球对可持续发展和产品透明度的关注日益增加,企业需要一种可靠的方法来跟踪和管理产品生命周期中的关键数据。我们的数字产品护照(Digital Product Passport,DPP)系统正是为此而生,提供了一
数字产品护照 (DPP) 解决方案:利用 Blazor 和区块链实现产品全生命周期追踪 数字产品护照 (DPP) 解决方案:利用 Blazor 和区块链实现产品全生命周期追踪 数字产品护照 (DPP) 解决方案:利用 Blazor 和区块链实现产品全生命周期追踪
Maven 使用方法
Maven Maven是一个项目管理工具,它包含了一个项目对象模型(POM:Project Object Model),其表现于一个XML文件(pom.xml),其中包含了项目的基本学习,依赖关系,插件配置,构建路径等等 为什么使用Maven 导入第三方jar包更便捷:之前我们在使用第三方框架时我们
Maven 使用方法 Maven 使用方法 Maven 使用方法
适用于 VitePress 的公告插件开发实记
开发了一个适用于 VitePress 站点的公告插件 vitepress-plugin-announcement
适用于 VitePress 的公告插件开发实记 适用于 VitePress 的公告插件开发实记 适用于 VitePress 的公告插件开发实记
BFS 颜色填涂———洛谷p1162
填涂颜色 题目描述 由数字 \(0\) 组成的方阵中,有一任意形状的由数字 \(1\) 构成的闭合圈。现要求把闭合圈内的所有空间都填写成 \(2\)。例如:\(6\times 6\) 的方阵(\(n=6\)),涂色前和涂色后的方阵如下: 如果从某个 \(0\) 出发,只向上下左右 \(4\) 个方向
C#|.net core 基础 - 深拷贝的五大类N种实现方式
C#深拷贝复杂,文中介绍了五大类N种深拷贝方法,包括简单引用类型、手动方式、序列化方式、第三方库方式和扩展视野方式,并对比了性能。建议使用AutoMapper和DeepCloner等成熟库或根据性能需求选择表达式树和Emit。
C#|.net core 基础 - 深拷贝的五大类N种实现方式 C#|.net core 基础 - 深拷贝的五大类N种实现方式
IT男如何走上的自由职业之路。
前言 在博客园的一篇文章《40岁大龄失业程序猿,未来该何去何从》,在下面留言,目前自己在做自由职业,很多人加好友咨询自由职业的事情,对IT行业自由职业比较感兴趣,问怎么能走上这条路,所以才有了今天这篇文章。把自己走上自由职业这条路说下情况。 正文 现状:我84年的,自由18年开始自由职业,已有6年有
IT男如何走上的自由职业之路。 IT男如何走上的自由职业之路。 IT男如何走上的自由职业之路。
web架构-nginx负载均衡
nginx的负载均衡 Nginx 是一个广泛使用的反向代理服务器,能够高效地实现负载均衡。负载均衡的核心作用是将来自客户端的请求分发到多个后端服务器上,从而平衡每台服务器的压力。通过Nginx,我们可以实现多种负载均衡算法,如轮询、IP哈希等。 vi /etc/nginx/nginx.conf 插入
web架构-nginx负载均衡 web架构-nginx负载均衡 web架构-nginx负载均衡
java基础 -网络编程笔记
666,InetAddress package com.hspedu.api; import java.net.InetAddress; import java.net.UnknownHostException; public class API { public static void main(
java基础 -网络编程笔记 java基础 -网络编程笔记 java基础 -网络编程笔记