Qt Model/View 架构
为什么需要 Model/View
传统做法(QTableWidget):数据直接存在控件里 → 数据一变就要手动更新控件 → 数据和 UI 耦合。
Model/View 把数据管理和显示分离:
QAbstractItemModel QAbstractItemView
(数据) ← 问答协议 → (显示)
View 不碰数据,只通过标准接口问 Model:
- rowCount() — 几行?
- columnCount() — 几列?
- data(index, role) — 第(r,c)格显示什么?
- headerData(section, orientation, role) — 表头是什么?
内置 Model
// QStringListModel — 简单字符串列表
QStringListModel model;
model.setStringList({"Alice", "Bob", "Charlie"});
QListView view;
view.setModel(&model); // View 绑定 Model
// QStandardItemModel — 通用表格/树
QStandardItemModel model(3, 2); // 3 行 2 列
model.setHeaderData(0, Qt::Horizontal, "Name");
model.setHeaderData(1, Qt::Horizontal, "Age");
model.setItem(0, 0, new QStandardItem("Alice"));
model.setItem(0, 1, new QStandardItem("25"));
QTableView view;
view.setModel(&model);
// QFileSystemModel — 文件系统
QFileSystemModel model;
model.setRootPath(QDir::homePath());
QTreeView view;
view.setModel(&model);
view.setRootIndex(model.index(QDir::homePath()));
自定义 Model
class MyTableModel : public QAbstractTableModel {
QVector<QVector<QVariant>> m_data;
public:
// 必须实现的 3 个虚函数
int rowCount(const QModelIndex &parent = QModelIndex()) const override {
return parent.isValid() ? 0 : m_data.size();
}
int columnCount(const QModelIndex &parent = QModelIndex()) const override {
return parent.isValid() ? 0 : 3; // 3 列
}
QVariant data(const QModelIndex &index, int role) const override {
if (!index.isValid()) return {};
if (role == Qt::DisplayRole)
return m_data[index.row()][index.column()];
if (role == Qt::TextAlignmentRole)
return int(Qt::AlignCenter);
return {};
}
// 可选:表头
QVariant headerData(int section, Qt::Orientation o, int role) const override {
if (role != Qt::DisplayRole) return {};
if (o == Qt::Horizontal)
return QStringList{"Name", "Age", "Email"}[section];
return section + 1; // 行号
}
// 通知 View 数据变了
void appendRow(const QVector<QVariant> &row) {
beginInsertRows(QModelIndex(), m_data.size(), m_data.size());
m_data.append(row);
endInsertRows();
}
};
关键规则
// ⚠️ 修改数据前后必须调用 begin/end 方法
beginInsertRows(parent, first, last);
// 修改 m_data ...
endInsertRows();
beginRemoveRows(parent, first, last);
// 修改 m_data ...
endRemoveRows();
beginResetModel();
// 全量替换数据 ...
endResetModel();
// 单个单元格变化
dataChanged(topLeft, bottomRight);
Data Role — data() 的核心参数
QVariant data(const QModelIndex &index, int role) const override {
switch (role) {
case Qt::DisplayRole: return m_data[index.row()].name;
case Qt::ToolTipRole: return m_data[index.row()].description;
case Qt::DecorationRole: return QIcon(":/icon.png");
case Qt::ForegroundRole: return QColor(Qt::red); // 文字颜色
case Qt::BackgroundRole: return QColor(Qt::lightGray);
case Qt::FontRole: return QFont("Consolas", 12);
case Qt::TextAlignmentRole: return int(Qt::AlignCenter);
case Qt::UserRole: return m_data[index.row()].id; // 自定义数据
default: return {};
}
}
| Role | 作用 |
|---|---|
DisplayRole | 显示文本 |
DecorationRole | 图标 |
ToolTipRole | 鼠标悬停提示 |
ForegroundRole | 文字颜色 |
BackgroundRole | 背景色 |
FontRole | 字体 |
UserRole | 自定义(隐藏数据,如 ID) |
Delegate — 自定义渲染和编辑
class ColorDelegate : public QStyledItemDelegate {
void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const override {
// 自定义绘制
painter->save();
if (index.data(Qt::UserRole).toBool()) {
painter->fillRect(option.rect, QColor("#e8f5e9"));
}
// 调用父类画文字
QStyledItemDelegate::paint(painter, option, index);
painter->restore();
}
};
view->setItemDelegate(new ColorDelegate);
View 常用设置
QTableView *view = new QTableView;
view->setModel(&model);
// 外观
view->setSelectionBehavior(QAbstractItemView::SelectRows); // 整行选中
view->setSelectionMode(QAbstractItemView::SingleSelection);
view->setAlternatingRowColors(true); // 交替行颜色
view->verticalHeader()->setVisible(false);
view->setSortingEnabled(true); // 点击表头排序
// 列宽
view->horizontalHeader()->setStretchLastSection(true);
view->setColumnWidth(0, 150);
// 隐藏列
view->setColumnHidden(2, true);
// 滚动到底部(数据追加后)
view->scrollToBottom();