You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

QTableView结合QSortFilterProxyModel仅显示名称列问题求助

问题:QTableView中年龄列无数据显示

我实现了一个存储姓名和年龄数据的QAbstractListModel,需要在QTableView中展示数据并基于年龄过滤,因此使用了QSortFilterProxyModel。但目前只有名称列能正常填充数据,年龄列没有内容,求解决建议。

QTableView仅显示名称列


代码文件

main_window.h

#ifndef MAIN_WINDOW_H
#define MAIN_WINDOW_H

#include <QMainWindow>
#include <QAbstractListModel>
#include <QSortFilterProxyModel>

namespace Ui
{
class MainWindow;
} // namespace Ui

class MainWindow : public QMainWindow
{
    Q_OBJECT

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

private:
    Ui::MainWindow* ui;
};

class Data
{
public:

    Data(const QString& name, int age);

    QString name() const;
    void setName(const QString& name);

    int age() const;
    void setAge(int age);

private:
    QString m_name;
    int     m_age;
};


class ListModel : public QAbstractListModel
{
    Q_OBJECT

public:

    enum Role {
        NameRole = Qt::UserRole + 1,
        AgeRole
    };
    Q_ENUM(Role)

    explicit ListModel(QObject* prnt = nullptr);
    int rowCount(const QModelIndex& parent) const override;
    QVariant data(const QModelIndex& index, int role) const override;

private:
    QList<Data> m_data;
};


class FilterModel : public QSortFilterProxyModel
{
    Q_OBJECT

public:

    explicit FilterModel(QObject* prnt = nullptr);

    int columnCount(const QModelIndex& parent) const override;
    QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
    QVariant data(const QModelIndex& index, int role) const override;

    // QSortFilterProxyModel interface
protected:
    bool filterAcceptsRow(int srcRow, const QModelIndex& srcPrnt) const override;
};

#endif // MAIN_WINDOW_H

main_window.cpp

#include "main_window.h"
#include "ui_main_window.h"

#include <QDebug>

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

    auto filterModel = new FilterModel(this);
    ui->tableView->setModel(filterModel);

    auto listModel = new ListModel(this);
    filterModel->setSourceModel(listModel);
}

MainWindow::~MainWindow()
{
    delete ui;
}

// ===================================== LIST MODEL ========================================================
ListModel::ListModel(QObject* prnt)
    : QAbstractListModel (prnt),
      m_data({{"Bob", 30}, {"Carl", 25}, {"Foo", 20}, {"Mike", 33}, {"Lisa", 35}})
{

}

int ListModel::rowCount(const QModelIndex& parent) const
{
    return m_data.count();
}

QVariant ListModel::data(const QModelIndex& index, int role) const
{
    if (index.row() < rowCount({}) && role > Qt::UserRole) {
        const auto& item = m_data.at(index.row());

        switch (role) {
            case NameRole:
                return item.name();

            case AgeRole:
                return item.age();
        }
    }

    return {};
}

// ===================================== FILTER MODEL ========================================================
FilterModel::FilterModel(QObject* prnt)
    : QSortFilterProxyModel (prnt)
{

}

int FilterModel::columnCount(const QModelIndex& parent) const
{
    return 2;
}

QVariant FilterModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
        return section == 0 ? "Name" : "Age";
    }

    return QSortFilterProxyModel::headerData(section, orientation, role);
}

QVariant FilterModel::data(const QModelIndex& index, int role) const
{
    if (role == Qt::DisplayRole) {
        auto srcIdx = mapToSource(index);
        qInfo() << index.row() << index.column() << index << srcIdx;
        auto role = index.column() == 0 ? ListModel::NameRole : ListModel::AgeRole;
        return sourceModel()->data(srcIdx, role);
    }

    return QSortFilterProxyModel::data(index, role);
}

bool FilterModel::filterAcceptsRow(int srcRow, const QModelIndex& srcPrnt) const
{
    Q_UNUSED(srcPrnt);
    const QModelIndex modelIdx(createIndex(srcRow, 0));

    if (modelIdx.isValid()) {
        QVariant srcData(sourceModel()->data(modelIdx, ListModel::AgeRole));
        qInfo() << "srcRow:" << srcRow << ", srcPrnt:" << srcPrnt << srcData;

        if (srcData.isValid()) {
            auto age = srcData.toInt();
            return age >= 30 && age <= 39;
        }
    }

    return true;
}


// ===================================== DATA ========================================================
Data::Data(const QString& name, int id)
    : m_name(name),
      m_age(id)
{

}

QString Data::name() const
{
    return m_name;
}

void Data::setName(const QString& name)
{
    m_name = name;
}

int Data::age() const
{
    return m_age;
}

void Data::setAge(int id)
{
    m_age = id;
}

main_window.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>860</width>
    <height>490</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralWidget">
   <widget class="QTableView" name="tableView">
    <property name="geometry">
     <rect>
      <x>10</x>
      <y>10</y>
      <width>841</width>
      <height>411</height>
     </rect>
    </property>
    <property name="selectionMode">
     <enum>QAbstractItemView::SingleSelection</enum>
    </property>
    <property name="selectionBehavior">
     <enum>QAbstractItemView::SelectRows</enum>
    </property>
    <attribute name="verticalHeaderVisible">
     <bool>false</bool>
    </attribute>
   </widget>
  </widget>
  <widget class="QMenuBar" name="menuBar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>860</width>
     <height>24</height>
    </rect>
   </property>
  </widget>
  <widget class="QToolBar" name="mainToolBar">
   <attribute name="toolBarArea">
    <enum>TopToolBarArea</enum>
   </attribute>
   <attribute name="toolBarBreak">
    <bool>false</bool>
   </attribute>
  </widget>
  <widget class="QStatusBar" name="statusBar"/>
 </widget>
 <layoutdefault spacing="6" margin="11"/>
 <resources/>
 <connections/>
</ui>

问题原因及修复方案

1. 源索引映射错误

源模型是QAbstractListModel,仅支持1列(列0)。当代理模型处理年龄列(列1)时,调用mapToSource(index)会得到无效的源索引,因为源模型不存在列1,导致无法获取年龄数据。

修复代码:
修改FilterModel::data方法,直接通过源模型创建列0的索引:

QVariant FilterModel::data(const QModelIndex& index, int role) const
{
    if (role == Qt::DisplayRole) {
        // 源模型是列表模型,固定使用列0创建索引
        QModelIndex srcIdx = sourceModel()->index(index.row(), 0);
        auto targetRole = index.column() == 0 ? ListModel::NameRole : ListModel::AgeRole;
        return sourceModel()->data(srcIdx, targetRole);
    }

    return QSortFilterProxyModel::data(index, role);
}

2. 过滤方法中索引创建错误

filterAcceptsRow中调用createIndex是创建代理模型的索引,而非源模型的索引,导致无法正确获取源数据。

修复代码:
修改FilterModel::filterAcceptsRow方法,通过源模型创建索引:

bool FilterModel::filterAcceptsRow(int srcRow, const QModelIndex& srcPrnt) const
{
    Q_UNUSED(srcPrnt);
    // 通过源模型创建正确的索引
    const QModelIndex modelIdx = sourceModel()->index(srcRow, 0);

    if (modelIdx.isValid()) {
        QVariant srcData = sourceModel()->data(modelIdx, ListModel::AgeRole);
        qInfo() << "srcRow:" << srcRow << ", srcPrnt:" << srcPrnt << srcData;

        if (srcData.isValid()) {
            auto age = srcData.toInt();
            return age >= 30 && age <= 39;
        }
    }

    return true;
}

3. 可选优化:重写roleNames

为了让模型在更多场景(如Qt Quick)下兼容,建议在ListModel中添加roleNames方法:

// ListModel类的public声明
QHash<int, QByteArray> roleNames() const override;

// 实现部分
QHash<int, QByteArray> ListModel::roleNames() const
{
    QHash<int, QByteArray> roles;
    roles[NameRole] = "name";
    roles[AgeRole] = "age";
    return roles;
}

内容的提问来源于stack exchange,提问作者ramtheconqueror

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 07:05:34