首页 > 其他分享 >Qt表格入门

Qt表格入门

时间:2024-09-21 20:25:05浏览次数:10  
标签:index const Qt 表格 int void ui QStandardItem 入门


摘要

    表格作为数据展示的界面,会在很多场景下使用。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


标签:index,const,Qt,表格,int,void,ui,QStandardItem,入门
From: https://www.cnblogs.com/wsry/p/18350462

相关文章

  • 【JavaWeb从入门到精通系列】 - JavaSE基础篇(1) - 抽象,静态,单例设计模式
    一、抽象1、存在意义似是而非的,像却又不是;具有某种特征,但不完整。Animal仅是一种会吃会睡的对象,再无其他行为,不够具体,不够完整。程序是用来模拟现实世界,解决实际问题的,现实世界中存在的都是动物具体的子类对象,并不存在动物对象,所以,Animal不应该被独立创建成对象。如何......
  • 【JavaWeb从入门到精通系列】 - JavaSE基础篇(1) -面向对象
    一、封装1、存在意义public对属性的设置或者修改没有任何限制隐藏该隐藏的,暴露该暴露的。封装之后设置set和get方法2、封装//1、将需要封装的属性修饰符设置为private(私有的,在外界无法访问)privateintage;//年龄//正常情况下通过创建对象可以访问属性Students......
  • CSP-J 2024 入门组初赛第一轮初赛试题及答案解析
    CSP-J2024入门组初赛第一轮初赛试题及答案解析一、单项选择题(共15题,每题2分,共计30分:每题有且仅有一个正确选项)132位int类型的存储范围是()A-2147483647~+2147483647B-2147483647~+2147483648C-2147483648~+2147483647D-2147483648~+2147483648答案C......
  • 使用`xlsx-js-style`插件导出表格,并添加样式导出
    安装npminstallxlsx-js-style--save使用引入importXLSXfrom'xlsx-js-style'//1.创建一个工作簿constwb=XLSX.utils.book_new()//表格标题//这里设置标题是怕数据没有,返回空表,连标题都没有//这里的权重,没有数据tableData里的高,果如ta......
  • 适合新手入门的靶场 TryHackMe
    对于不少喜欢网络安全的小伙伴,一直在很焦虑。如何学习网络安全,但由于各类平台和自身硬件等方面的限制。学习起来很吃力,故而本文为搭建分享一款在线靶场环境TryHackMe推荐理由对自身硬件没有太高的要求,有个浏览器就行了。支持THMAttackBox无需安装kali等系统,在浏览器直......
  • AI绘画实操 Stable Diffusion 到底怎么玩儿,新手必看的AI绘画入门安装使用教程
    大家好,我是灵魂画师向阳2024年,是AI绘画技术飞速发展的一年,各种AI绘画工具层出不穷,为了让大家在了解和学习AI绘画的过程中少走弯路,今天我将详细介绍目前世界上使用用户最多,社区最大,生态最丰富的免费图像生成模型——StableDiffusion,并为你提供详细的安装教程,让你轻松踏入AI......
  • 一文通Maven :入门配置详解与最佳实践、进阶技巧、项目案例分析、常用依赖
    Maven是我们开发中的基础工具之一,尤为重要。它不仅仅是构建工具,还是项目管理、依赖管理、插件管理的强大平台。本文将通过对Maven配置进行详尽分析,并结合实际项目案例,讨论如何有效配置和优化Maven,提升项目的管理和开发效率。一、Maven基础概念与配置结构Maven的核心......
  • go语言基础入门(一)
    变量声明:批量声明变量:变量赋值:声明变量同时为变量赋值可以在变量声明时为其赋值go中赋值时的编译器会自动根据等号右侧的数据类型自动推导变量的类型使用:=进行赋值匿名变量常量常量计数器iota1.使用场景2.基本用法3.简化语法4.自定义增量5.复杂使用go的类似枚......
  • Java Stream流编程入门
    流式编程stream流式编程分为首先转化为stream中间函数的链接最后的终结函数怎么转化为stream单列集合List<String>list=newArrayList<String>();Collections.addAll(list,"1","2","3","4","5","6","7","8"......
  • Java入门:09.Java中三大特性(封装、继承、多态)02
    2继承需要两个类才能实现继承的效果。比如:类A继承类BA类称为子类,衍生类,派生类B类称为父类,基类,超类继承的作用子类自动的拥有父类的所有属性和方法(父类编写,子类不需要再编写)。代码复用目前私有的属性和方法无法访问。多态的基础。继承语法先定义父......