Qt 模型/视图架构 #

架构概述 #

text
┌─────────────────────────────────────────────────────────────┐
│                    Model-View-Delegate 架构                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   ┌─────────────┐     ┌─────────────┐     ┌─────────────┐ │
│   │    Model    │────>│    View     │<────│  Delegate   │ │
│   │   (数据)    │     │   (显示)    │     │  (编辑)     │ │
│   └─────────────┘     └─────────────┘     └─────────────┘ │
│         │                   │                   │         │
│         │                   │                   │         │
│         ▼                   ▼                   ▼         │
│   ┌─────────────────────────────────────────────────────┐ │
│   │                    数据流                            │ │
│   │  Model 提供数据 → View 显示数据 → Delegate 编辑数据  │ │
│   └─────────────────────────────────────────────────────┘ │
│                                                             │
│   优势:                                                    │
│   ✅ 数据与显示分离                                         │
│   ✅ 同一数据多种视图                                       │
│   ✅ 自定义显示和编辑                                       │
│                                                             │
└─────────────────────────────────────────────────────────────┘

预定义模型 #

QStringListModel #

cpp
#include <QStringListModel>
#include <QListView>

QStringList data = {"Item 1", "Item 2", "Item 3", "Item 4"};

QStringListModel *model = new QStringListModel(data, this);

QListView *listView = new QListView(this);
listView->setModel(model);

// 修改数据
model->setStringList({"New 1", "New 2", "New 3"});

// 添加数据
model->insertRow(model->rowCount());
QModelIndex index = model->index(model->rowCount() - 1);
model->setData(index, "New Item");

QFileSystemModel #

cpp
#include <QFileSystemModel>
#include <QTreeView>

QFileSystemModel *model = new QFileSystemModel(this);
model->setRootPath(QDir::rootPath());

QTreeView *treeView = new QTreeView(this);
treeView->setModel(model);
treeView->setRootIndex(model->index(QDir::homePath()));

// 设置过滤器
model->setFilter(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot);

// 设置名称过滤器
model->setNameFilters(QStringList() << "*.txt" << "*.cpp");
model->setNameFilterDisables(false);  // 隐藏不匹配的文件

QStandardItemModel #

cpp
#include <QStandardItemModel>
#include <QTableView>

QStandardItemModel *model = new QStandardItemModel(4, 3, this);
model->setHorizontalHeaderLabels({"Name", "Age", "City"});

// 设置数据
model->setItem(0, 0, new QStandardItem("Alice"));
model->setItem(0, 1, new QStandardItem("25"));
model->setItem(0, 2, new QStandardItem("Beijing"));

model->setItem(1, 0, new QStandardItem("Bob"));
model->setItem(1, 1, new QStandardItem("30"));
model->setItem(1, 2, new QStandardItem("Shanghai"));

QTableView *tableView = new QTableView(this);
tableView->setModel(model);

// 添加行
QList<QStandardItem*> rowItems;
rowItems << new QStandardItem("Charlie")
         << new QStandardItem("28")
         << new QStandardItem("Guangzhou");
model->appendRow(rowItems);

// 设置项属性
QStandardItem *item = model->item(0, 0);
item->setEditable(true);
item->setCheckable(true);
item->setCheckState(Qt::Checked);
item->setIcon(QIcon(":/icon.png"));
item->setForeground(Qt::red);

自定义模型 #

只读模型 #

cpp
class MyModel : public QAbstractTableModel
{
    Q_OBJECT
public:
    MyModel(QObject *parent = nullptr) : QAbstractTableModel(parent) {}
    
    // 必须实现的虚函数
    int rowCount(const QModelIndex &parent = QModelIndex()) const override
    {
        if (parent.isValid()) return 0;
        return m_data.size();
    }
    
    int columnCount(const QModelIndex &parent = QModelIndex()) const override
    {
        if (parent.isValid()) return 0;
        return 3;  // 3 列
    }
    
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
    {
        if (!index.isValid() || index.row() >= m_data.size()) {
            return QVariant();
        }
        
        if (role == Qt::DisplayRole || role == Qt::EditRole) {
            const auto &row = m_data.at(index.row());
            switch (index.column()) {
            case 0: return row.name;
            case 1: return row.age;
            case 2: return row.city;
            }
        }
        
        if (role == Qt::TextAlignmentRole) {
            if (index.column() == 1) {
                return Qt::AlignCenter;
            }
        }
        
        return QVariant();
    }
    
    // 表头
    QVariant headerData(int section, Qt::Orientation orientation, 
                        int role = Qt::DisplayRole) const override
    {
        if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
            switch (section) {
            case 0: return "Name";
            case 1: return "Age";
            case 2: return "City";
            }
        }
        return QVariant();
    }

private:
    struct DataRow {
        QString name;
        int age;
        QString city;
    };
    QList<DataRow> m_data;
};

可编辑模型 #

cpp
class EditableModel : public QAbstractTableModel
{
    Q_OBJECT
public:
    using QAbstractTableModel::QAbstractTableModel;
    
    Qt::ItemFlags flags(const QModelIndex &index) const override
    {
        if (!index.isValid()) {
            return Qt::NoItemFlags;
        }
        return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
    }
    
    bool setData(const QModelIndex &index, const QVariant &value, 
                 int role = Qt::EditRole) override
    {
        if (!index.isValid() || role != Qt::EditRole) {
            return false;
        }
        
        // 更新数据
        // m_data[index.row()][index.column()] = value;
        
        emit dataChanged(index, index, {role});
        return true;
    }
    
    bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override
    {
        if (parent.isValid()) return false;
        
        beginInsertRows(parent, row, row + count - 1);
        // 插入数据
        for (int i = 0; i < count; ++i) {
            // m_data.insert(row, DataRow{});
        }
        endInsertRows();
        return true;
    }
    
    bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override
    {
        if (parent.isValid()) return false;
        
        beginRemoveRows(parent, row, row + count - 1);
        // 删除数据
        for (int i = 0; i < count; ++i) {
            // m_data.removeAt(row);
        }
        endRemoveRows();
        return true;
    }
};

视图类 #

QListView #

cpp
QListView *listView = new QListView(this);
listView->setModel(model);

// 显示模式
listView->setViewMode(QListView::ListMode);    // 列表模式
listView->setViewMode(QListView::IconMode);    // 图标模式

// 选择模式
listView->setSelectionMode(QAbstractItemView::SingleSelection);
listView->setSelectionMode(QAbstractItemView::ExtendedSelection);

// 编辑触发
listView->setEditTriggers(QAbstractItemView::DoubleClicked);

// 信号
connect(listView, &QListView::clicked, [](const QModelIndex &index) {
    qDebug() << "Clicked:" << index.row();
});

connect(listView->selectionModel(), &QItemSelectionModel::selectionChanged,
        [](const QItemSelection &selected, const QItemSelection &deselected) {
    qDebug() << "Selection changed";
});

QTableView #

cpp
QTableView *tableView = new QTableView(this);
tableView->setModel(model);

// 列宽设置
tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive);
tableView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
tableView->setColumnWidth(1, 100);

// 行高设置
tableView->verticalHeader()->setDefaultSectionSize(30);

// 隐藏表头
tableView->horizontalHeader()->hide();
tableView->verticalHeader()->hide();

// 交替行颜色
tableView->setAlternatingRowColors(true);

// 排序
tableView->setSortingEnabled(true);

// 选择行为
tableView->setSelectionBehavior(QAbstractItemView::SelectRows);

QTreeView #

cpp
QTreeView *treeView = new QTreeView(this);
treeView->setModel(model);

// 展开/折叠
treeView->expandAll();
treeView->collapseAll();
treeView->expandToDepth(2);

// 设置展开
treeView->setExpanded(index, true);

// 信号
connect(treeView, &QTreeView::expanded, [](const QModelIndex &index) {
    qDebug() << "Expanded:" << index;
});

connect(treeView, &QTreeView::collapsed, [](const QModelIndex &index) {
    qDebug() << "Collapsed:" << index;
});

代理类 (Delegate) #

自定义代理 #

cpp
class SpinBoxDelegate : public QStyledItemDelegate
{
    Q_OBJECT
public:
    SpinBoxDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent) {}
    
    QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &,
                          const QModelIndex &) const override
    {
        QSpinBox *editor = new QSpinBox(parent);
        editor->setFrame(false);
        editor->setMinimum(0);
        editor->setMaximum(100);
        return editor;
    }
    
    void setEditorData(QWidget *editor, const QModelIndex &index) const override
    {
        int value = index.model()->data(index, Qt::EditRole).toInt();
        QSpinBox *spinBox = qobject_cast<QSpinBox*>(editor);
        spinBox->setValue(value);
    }
    
    void setModelData(QWidget *editor, QAbstractItemModel *model,
                      const QModelIndex &index) const override
    {
        QSpinBox *spinBox = qobject_cast<QSpinBox*>(editor);
        spinBox->interpretText();
        int value = spinBox->value();
        model->setData(index, value, Qt::EditRole);
    }
    
    void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,
                              const QModelIndex &) const override
    {
        editor->setGeometry(option.rect);
    }
};

// 使用代理
tableView->setItemDelegateForColumn(1, new SpinBoxDelegate(this));

选择模型 #

cpp
// 获取选择模型
QItemSelectionModel *selectionModel = tableView->selectionModel();

// 获取选中的索引
QModelIndexList selected = selectionModel->selectedIndexes();
QModelIndexList selectedRows = selectionModel->selectedRows();
QModelIndexList selectedColumns = selectionModel->selectedColumns();

// 设置选中
selectionModel->select(index, QItemSelectionModel::Select);
selectionModel->select(index, QItemSelectionModel::Toggle);
selectionModel->select(index, QItemSelectionModel::ClearAndSelect);

// 清除选择
selectionModel->clearSelection();

// 设置当前索引
selectionModel->setCurrentIndex(index, QItemSelectionModel::SelectCurrent);

// 信号
connect(selectionModel, &QItemSelectionModel::selectionChanged,
        [](const QItemSelection &selected, const QItemSelection &deselected) {
    for (const QModelIndex &index : selected.indexes()) {
        qDebug() << "Selected:" << index;
    }
});

下一步 #

现在你已经掌握了模型/视图架构,接下来学习 图形视图框架,了解 Qt 的 2D 图形编程!

最后更新:2026-03-29