摘要

    表格作为数据展示的界面,会在很多场景下使用。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可以方便以后对数字列进行排序。

  • 运行效果如下:

二、显示数据(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,设置表头平铺拉伸显示。

  • 运行效果如下:

三、数据代理(委托)(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的性别列设置了代理对象;

  • 运行效果如下:





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





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、运行效果如下:

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));
}
}

运行效果如下:

四、筛选过滤、排序(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选项变化,过滤性别;

  • 运行效果如下:

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)函数实现,这里不做过多演示。

运行效果如下:

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:



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

sexChangedByQStandardItem:



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

结论:使用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

Qt表格入门的更多相关文章

  1. Qt快速入门系列教程目录

    Qt快速入门系列教程目录

  2. Qt快速入门学习笔记(基础篇)

    本文基于Qter开源社区论坛版主yafeilinux编写的<Qt快速入门系列教程目录>,网址:http://bbs.qter.org/forum.php?mod=viewthread&am ...

  3. 第15.10节 PyQt(Python+Qt)入门学习:Qt Designer可视化设计界面组件与QWidget类相关的组件属性详解

    PyQt学习有阵子了,对章节的骨架基本考虑好了,准备本节就写组件的属性的,结果一是日常工作繁忙,经常晚上还要加班,二是Qt的组件属性很多,只能逐一学习.研究和整理,花的时间有点长,不过终于将可视化设计 ...

  4. QT从入门到入土(二)——对象模型(对象树)和窗口坐标体系

    摘要 我们使用的标准 C++,其设计的对象模型虽然已经提供了非常高效的 RTTI 支持,但是在某些方面还是不够灵活.比如在 GUI 编程方面,既需要高效的运行效率也需要强大的灵活性,诸如删除某窗口时可 ...

  5. QT从入门到入土(三)——信号和槽机制

    摘要 信号槽是 Qt 框架引以为豪的机制之一.所谓信号槽,实际就是观察者模式.当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号 (signal).这种发出是没有目的的,类似广播 ...

  6. Qt Creator 入门

    Qt 的入门我觉得可以直接从窗口开始,而不是什么"Hello World!".因为Qt 是一个基于图形界面的编程软件,图形界面编程是其核心所在.很久以前,那时候还是Shell编程, ...

  7. QT从入门到入土(四)——多线程(QtConcurrent::run())

    引言 在前面对Qt多线程(QThread)做了详细的分析:QT从入门到入土(四)--多线程(QThread) - 唯有自己强大 - 博客园 (cnblogs.com) 但是最近在做项目时候,要将一个函 ...

  8. QT快速入门

    QT快速入门 本文档将介绍QT工程的创建.UI界面布局,并以计数器为例了解QT中多线程的用法,最终完成一个基础的QT项目. 1 创建QT工程文件 在安装好QT之后,能够在其安装组件中找到Qt Crea ...

  9. qt编程入门

    面对qt编程,必须先知道qt中常用的类: QPushButton按钮类.QLabel标签类.QMessageBox对话框类.QCheckBox.QAction.QMenu.QStatusBar.QTo ...

  10. Qt 表格的使用

    参考 http://doc.qt.io/qt-5/qtablewidget.html http://doc.qt.io/qt-5/qtablewidgetitem.html https://blog. ...

随机推荐

  1. Prometheus 使用Python推送指标数据到Pushgateway

    使用Python推送指标数据到Pushgateway 需求描述 实践环境 Python 3.6.5 Django 3.0.6 prometheus-client 0.11.0 代码实现 !/usr/b ...

  2. php8.3开启jit技术

    查看是否开启:$jitEnabled = ini_get('jit.enabled'); echo "JIT Enabled: " . ($jitEnabled == '1' ? ...

  3. Linux网络问题排查

    Linux网络问题排查 用于排查Linux系统的网络故障. 网络排查一般是有一定的思路和顺序的,其实排查的思路就是根据具体的问题逐段排除故障可能发生的地方,最终确定问题. 所以首先要问一问,网络问题是 ...

  4. ambari+ bigtop 编译、打包、部署步骤总览

    1 ambari + bigtop 构建大数据基础平台 1.1 参考: 1.2 参考 amabri bigtop 打包部署 2 ambari+bigtop编译.打包.部署 2.0 基础环境准备 2.1 ...

  5. 【perl】01

    1.环境搭建 -- 解释器 / 编译器 Perl 在 Window 平台上有 ActiveStatePerl 和 Strawberry Perl 编译器. ActiveState Perl和 Stra ...

  6. 在计算机论文中suppose suggest assume 用法上的区别

    ChatGPT3.5的答案: 在计算机论文中,"suppose," "suggest," 和 "assume" 有不同的用法和含义.它们在表 ...

  7. MindSpore 计算框架 模型参数 和 优化器 参数的重新载入

    本文主要内容源于: https://www.mindspore.cn/tutorial/training/zh-CN/master/use/load_model_for_inference_and_t ...

  8. 【转载】 Parallel Computing in Python using mpi4py

    原地址: https://research.computing.yale.edu/sites/default/files/files/mpi4py.pdf ====================== ...

  9. 新版的Django Docker部署方案,多阶段构建、自动处理前端依赖

    前言 前几天的文章中,我们已经把使用 pdm 的项目用 docker 搞定了,那么下一步就是把完整的 DjangoStarter v3 版本用 docker 部署. 现在不像之前那么简单直接一把梭了, ...

  10. SMU Summer 2023 Contest Round 7

    SMU Summer 2023 Contest Round 7 A. Two Rival Students 答案不能大于 \(n-1\): 如果竞争对手之间的当前距离小于 \(n - 1\) ,我们总 ...