Qt - 模型/视图(Model/View)
注意:本教程假设读者已经具备基本的Qt知识,包括信号和槽的使用。
-
模型/视图架构简介
模型/视图架构是一种用于将数据存储和用户界面分离的设计模式。在这种架构中,模型负责管理数据,视图负责显示数据,而委托负责处理如何渲染和编辑数据项。
这种分离使得我们可以使用不同的视图来显示同一模型的数据,而无需改变模型本身。 -
为什么使用模型/视图架构
-
分离数据存储和显示,使得代码更清晰、更易于维护。
-
同一个模型可以用于多个视图,保持数据的一致性。
-
标准化的接口使得自定义显示和编辑数据变得容易。
-
模型(Model)的角色
模型是数据的抽象,它提供了视图和委托访问数据的接口。所有的模型都继承自QAbstractItemModel。模型中的数据被组织成层次结构,但最常见的列表和表格结构分别由QAbstractListModel和QAbstractTableModel提供。 -
视图(View)的角色
视图是显示数据的组件,例如QListView、QTableView和QTreeView。它们从模型获取数据并显示给用户。 -
委托(Delegate)的角色
委托负责绘制数据项,并在需要编辑时提供编辑器。默认的委托是QStyledItemDelegate,它可以处理常见数据类型的显示和编辑。 -
一个简单的示例:使用QStringListModel和QListView
我们将创建一个简单的字符串列表,并使用QListView来显示。
步骤:
a. 创建一个QStringListModel,并设置字符串列表数据。
b. 创建一个QListView,并设置模型。
c. 显示视图。
-
一个更复杂的示例:自定义模型
我们将创建一个自定义的表格模型,继承自QAbstractTableModel,并实现必要的虚函数。 -
总结
下面我们将通过代码来演示这两个示例。
注意:为了简洁,示例代码将不包括完整的项目设置,只展示关键部分。我们将使用C++和Qt5。
首先,我们来看第一个示例:使用QStringListModel和QListView。
一、模型/视图架构概述
1.1 什么是模型/视图架构
Qt的模型/视图架构是一种用于分离数据存储和数据显示的设计模式,它解决了传统部件(如QListWidget、QTableWidget)中数据与显示耦合的问题。
核心组件:
-
模型(Model):管理数据,提供数据访问接口
-
视图(View):显示数据,处理用户交互
-
委托(Delegate):控制数据项的渲染和编辑方式
1.2 模型/视图与传统部件的对比
| 特性 | 传统部件(QListWidget等) | 模型/视图架构 |
|---|---|---|
| 数据存储 | 部件内部存储 | 外部模型管理 |
| 大数据集 | 性能差 | 性能优异 |
| 多视图同步 | 困难 | 简单(共享模型) |
| 自定义数据 | 有限 | 高度灵活 |
二、基础模型类
2.1 标准模型类
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QStringListModel>
#include <QStandardItemModel>
#include <QListView>
#include <QTableView>
#include <QVBoxLayout>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
void setupUI(); // 创建UI
void setupModels(); // 设置模型
QListView *listView;
QTableView *tableView;
QStringListModel *listModel;// 1. QStringListModel - 列表数据模型
QStandardItemModel *standardModel;// 2. QStandardItemModel - 通用模型
QVBoxLayout *mainLayout;
};
#endif // WIDGET_H
#include "widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, listView(new QListView(this))
, tableView(new QTableView(this))
, listModel(new QStringListModel(this))
, standardModel(new QStandardItemModel(this))
, mainLayout(new QVBoxLayout(this))
{
setupUI();
setupModels();
}
Widget::~Widget()
{
// Qt的对象树会自动管理内存,通常不需要手动删除
}
void Widget::setupUI()
{
// 设置主布局
setLayout(mainLayout);
// 添加视图到布局
mainLayout->addWidget(listView);
mainLayout->addWidget(tableView);
// 设置窗口属性
setWindowTitle("Model/View Example");
resize(600, 400);
}
void Widget::setupModels()
{
// 1. 设置列表数据
QStringList listData;
listData << "Apple" << "Banana" << "Cherry" << "Date"
<< "Elderberry" << "Fig" << "Grape" << "Honeydew";
listModel->setStringList(listData);
listView->setModel(listModel);
// 2. 设置表格数据
// 设置表头
standardModel->setHorizontalHeaderLabels({"Name", "Age", "Department", "Salary"});
// 添加数据
QList<QStandardItem*> row1;
row1 << new QStandardItem("John Doe")
<< new QStandardItem("28")
<< new QStandardItem("Engineering")
<< new QStandardItem("$75,000");
standardModel->appendRow(row1);
QList<QStandardItem*> row2;
row2 << new QStandardItem("Jane Smith")
<< new QStandardItem("32")
<< new QStandardItem("Marketing")
<< new QStandardItem("$65,000");
standardModel->appendRow(row2);
// 更多数据...
tableView->setModel(standardModel);
// 设置表格属性
tableView->setAlternatingRowColors(true);
//tableView->horizontalHeader()->setStretchLastSection(true);
}
运行效果:

2.2 自定义模型基础
#include <QAbstractTableModel>
#include <QVector>
class CustomTableModel : public QAbstractTableModel {
Q_OBJECT
public:
explicit CustomTableModel(QObject *parent = nullptr)
: QAbstractTableModel(parent) {
// 初始化示例数据
m_data = {
{"Alice", "25", "Sales"},
{"Bob", "30", "Marketing"},
{"Charlie", "35", "Engineering"}
};
}
// 必须实现的纯虚函数
int rowCount(const QModelIndex &parent = QModelIndex()) const override {
Q_UNUSED(parent);
return m_data.size();
}
int columnCount(const QModelIndex &parent = QModelIndex()) const override {
Q_UNUSED(parent);
return m_data.isEmpty() ? 0 : m_data[0].size();
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override {
if (!index.isValid() ||
index.row() >= m_data.size() ||
index.column() >= m_data[0].size()) {
return QVariant();
}
if (role == Qt::DisplayRole || role == Qt::EditRole) {
return m_data[index.row()][index.column()];
}
if (role == Qt::TextAlignmentRole) {
return Qt::AlignCenter;
}
return QVariant();
}
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const override {
if (role != Qt::DisplayRole) return QVariant();
if (orientation == Qt::Horizontal) {
QStringList headers = {"Name", "Age", "Department"};
return section < headers.size() ? headers[section] : QVariant();
}
return section + 1; // 行号从1开始
}
// 可选:实现可编辑模型
Qt::ItemFlags flags(const QModelIndex &index) const override {
Qt::ItemFlags flags = QAbstractTableModel::flags(index);
if (index.isValid()) {
flags |= Qt::ItemIsEditable;
}
return flags;
}
bool setData(const QModelIndex &index, const QVariant &value,
int role = Qt::EditRole) override {
if (!index.isValid() || role != Qt::EditRole) {
return false;
}
if (index.row() < m_data.size() && index.column() < m_data[0].size()) {
m_data[index.row()][index.column()] = value.toString();
emit dataChanged(index, index, {role});
return true;
}
return false;
}
// 添加数据方法
void addData(const QStringList &rowData)
{
if (rowData.size() < 3) {
//qWarning() << "Invalid row data, expected 3 elements but got" << rowData.size();
return;
}
beginInsertRows(QModelIndex(), m_data.size(), m_data.size());
// 根据你的数据结构进行适配
// 假设 m_data 是 QVector<QVector<QString>> 类型
QVector<QString> newRow;
newRow.append(rowData[0]); // Name
newRow.append(rowData[1]); // Age
newRow.append(rowData[2]); // Department
m_data.append(newRow);
endInsertRows();
}
// 添加员工方法
void addEmployee(const QString &name, const QString &age, const QString &department)
{
QStringList rowData;
rowData << name << age << department;
addData(rowData);
}
// 删除行
void removeRow(int row)
{
if (row < 0 || row >= m_data.size()) {
//qWarning() << "Invalid row index:" << row;
return;
}
beginRemoveRows(QModelIndex(), row, row);
m_data.remove(row);
endRemoveRows();
}
// 删除员工(别名方法)
void removeEmployee(int row)
{
removeRow(row);
}
// 清空所有数据
void clearAll()
{
if (m_data.isEmpty()) return;
beginRemoveRows(QModelIndex(), 0, m_data.size() - 1);
m_data.clear();
endRemoveRows();
}
private:
QVector<QVector<QString>> m_data;
};
使用自定义模型:
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QStringListModel>
#include <QStandardItemModel>
#include <QListView>
#include <QTableView>
#include <QVBoxLayout>
#include <QPushButton>
#include <QLabel>
#include "CustomTableModel.h"
// Widget类
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = nullptr);
~Widget();
private slots:
void onAddButtonClicked();
void onRemoveButtonClicked();
void onClearButtonClicked();
void onTableViewDoubleClicked(const QModelIndex &index);
private:
void setupUI();
void setupModels();
void setupConnections();
// 界面控件
QTableView *tableView;
QPushButton *addButton;
QPushButton *removeButton;
QPushButton *clearButton;
QLabel *statusLabel;
QVBoxLayout *mainLayout;
// 模型
CustomTableModel *customModel;
};
#endif // WIDGET_H
#include "widget.h"
#include <QMessageBox>
#include <QInputDialog>
#include <QHeaderView>
#include <QHBoxLayout>
// Widget的实现
Widget::Widget(QWidget *parent)
: QWidget(parent)
, tableView(new QTableView(this))
, addButton(new QPushButton("Add Employee", this))
, removeButton(new QPushButton("Remove Selected", this))
, clearButton(new QPushButton("Clear All", this))
, statusLabel(new QLabel("Ready", this))
, mainLayout(new QVBoxLayout(this))
, customModel(new CustomTableModel(this))
{
setupUI();
setupModels();
setupConnections();
}
Widget::~Widget()
{
// 使用Qt对象树管理,不需要手动删除
}
void Widget::setupUI()
{
// 设置窗口标题和大小
setWindowTitle("Custom Table Model Demo");
resize(800, 600);
// 创建按钮布局
QHBoxLayout *buttonLayout = new QHBoxLayout();
buttonLayout->addWidget(addButton);
buttonLayout->addWidget(removeButton);
buttonLayout->addWidget(clearButton);
buttonLayout->addStretch(); // 添加弹性空间
// 设置状态标签
statusLabel->setAlignment(Qt::AlignCenter);
statusLabel->setStyleSheet("QLabel { padding: 5px; background-color: #f0f0f0; }");
// 将控件添加到主布局
mainLayout->addLayout(buttonLayout);
mainLayout->addWidget(tableView);
mainLayout->addWidget(statusLabel);
// 设置主窗口布局
setLayout(mainLayout);
}
void Widget::setupModels()
{
// 设置自定义模型到QTableView
tableView->setModel(customModel);
// 设置表格属性
tableView->setAlternatingRowColors(true);
tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
tableView->setSelectionMode(QAbstractItemView::SingleSelection);
// 设置列宽
tableView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
tableView->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
tableView->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch);
// 设置行高
tableView->verticalHeader()->setDefaultSectionSize(30);
// 启用排序
tableView->setSortingEnabled(true);
// 设置表格样式
tableView->setStyleSheet(
"QTableView {"
" gridline-color: #d0d0d0;"
" selection-background-color: #4da6ff;"
"}"
"QHeaderView::section {"
" background-color: #e0e0e0;"
" padding: 4px;"
" border: 1px solid #d0d0d0;"
" font-weight: bold;"
"}"
);
}
void Widget::setupConnections()
{
// 连接按钮信号
connect(addButton, &QPushButton::clicked, this, &Widget::onAddButtonClicked);
connect(removeButton, &QPushButton::clicked, this, &Widget::onRemoveButtonClicked);
connect(clearButton, &QPushButton::clicked, this, &Widget::onClearButtonClicked);
// 连接表格双击信号
connect(tableView, &QTableView::doubleClicked, this, &Widget::onTableViewDoubleClicked);
}
void Widget::onAddButtonClicked()
{
bool ok;
QString name = QInputDialog::getText(this, "Add Employee",
"Enter name:", QLineEdit::Normal, "", &ok);
if (!ok || name.isEmpty()) return;
QString age = QInputDialog::getText(this, "Add Employee",
"Enter age:", QLineEdit::Normal, "", &ok);
if (!ok || age.isEmpty()) return;
QString department = QInputDialog::getText(this, "Add Employee",
"Enter department:", QLineEdit::Normal, "", &ok);
if (!ok || department.isEmpty()) return;
// 添加数据到模型
QStringList newRow = {name, age, department};
// 这里需要确保CustomTableModel有addData方法
// 如果没有,可能需要修改CustomTableModel或使用其他方法
customModel->addData(newRow);
statusLabel->setText(QString("Added: %1, %2, %3").arg(name).arg(age).arg(department));
}
void Widget::onRemoveButtonClicked()
{
QModelIndex currentIndex = tableView->currentIndex();
if (!currentIndex.isValid()) {
QMessageBox::warning(this, "Warning", "Please select a row to remove.");
return;
}
int row = currentIndex.row();
QString name = customModel->data(customModel->index(row, 0)).toString();
QMessageBox::StandardButton reply;
reply = QMessageBox::question(this, "Confirm Removal",
QString("Remove %1?").arg(name),
QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::Yes) {
// 这里需要确保CustomTableModel有removeRow方法
customModel->removeRow(row);
statusLabel->setText(QString("Removed: %1").arg(name));
}
}
void Widget::onClearButtonClicked()
{
if (customModel->rowCount() == 0) return;
QMessageBox::StandardButton reply;
reply = QMessageBox::question(this, "Confirm Clear",
"Clear all data?",
QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::Yes) {
// 这里需要确保CustomTableModel有clearAll方法
customModel->clearAll();
statusLabel->setText("All data cleared.");
}
}
void Widget::onTableViewDoubleClicked(const QModelIndex &index)
{
if (!index.isValid()) return;
int row = index.row();
int col = index.column();
QString name = customModel->data(customModel->index(row, 0)).toString();
QString age = customModel->data(customModel->index(row, 1)).toString();
QString dept = customModel->data(customModel->index(row, 2)).toString();
QString message = QString("Double clicked on:\n"
"Row: %1, Column: %2\n"
"Name: %3\n"
"Age: %4\n"
"Department: %5")
.arg(row + 1).arg(col + 1).arg(name).arg(age).arg(dept);
QMessageBox::information(this, "Item Details", message);
}
运行效果:

三、视图组件详解
3.1 常用视图部件
void viewComponentsDemo() {
// 创建数据模型
QStandardItemModel model(4, 3);
for (int row = 0; row < 4; ++row) {
for (int col = 0; col < 3; ++col) {
QStandardItem *item = new QStandardItem(
QString("Row %1, Col %2").arg(row).arg(col));
model.setItem(row, col, item);
}
}
// 1. QListView - 列表视图
QListView listView;
listView.setModel(&model);
listView.setViewMode(QListView::IconMode); // 图标模式
listView.setDragEnabled(true); // 启用拖放
listView.show();
// 2. QTableView - 表格视图
QTableView tableView;
tableView.setModel(&model);
tableView.setSortingEnabled(true); // 启用排序
tableView.horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
tableView.show();
// 3. QTreeView - 树形视图
QStandardItemModel treeModel;
QStandardItem *root = new QStandardItem("Root");
for (int i = 0; i < 3; ++i) {
QStandardItem *child = new QStandardItem(QString("Child %1").arg(i));
for (int j = 0; j < 2; ++j) {
QStandardItem *grandChild = new QStandardItem(
QString("Grandchild %1-%2").arg(i).arg(j));
child->appendRow(grandChild);
}
root->appendRow(child);
}
treeModel.appendRow(root);
QTreeView treeView;
treeView.setModel(&treeModel);
treeView.expandAll(); // 展开所有节点
treeView.show();
}
3.2 视图选择和导航
void viewSelectionDemo() {
QStandardItemModel model(5, 3);
// 填充数据
for (int row = 0; row < 5; ++row) {
for (int col = 0; col < 3; ++col) {
model.setData(model.index(row, col),
QString("Item %1-%2").arg(row).arg(col));
}
}
QTableView view;
view.setModel(&model);
// 设置选择模式
view.setSelectionMode(QAbstractItemView::ExtendedSelection); // 多选
view.setSelectionBehavior(QAbstractItemView::SelectRows); // 整行选择
// 获取选择模型
QItemSelectionModel *selectionModel = view.selectionModel();
// 连接选择变化的信号
QObject::connect(selectionModel, &QItemSelectionModel::selectionChanged,
[](const QItemSelection &selected, const QItemSelection &deselected) {
qDebug() << "Selection changed";
qDebug() << "Selected items:" << selected.indexes().size();
});
view.show();
}
四、委托(Delegate)系统
委托就是在视图组件上为编辑数据提供编辑器,如在表格组件中编辑一个单元格的数据时,缺省是使用一个 QLineEdit 编辑框。代理负责从数据模型获取相应的数据,然后显示在编辑器里,修改数据后,又将其保存到数据模型中。
QAbstractltemDelegate 是所有代理类的基类,作为抽象类,它不能直接使用。它的一个子类 QStyledltemDelegate,是 Qt 的视图组件缺省使用的代理类。
对于一些特殊的数据编辑需求,例如只允许输入整型数,使用一个 QSpinBox 作为代理组件更恰当,从列表中选择数据时使用一个 QComboBox 作为代理组件更好。这时,就可以从 QStyledltemDelegate 继承创建自定义代理类。
4.1 自定义委托
#ifndef CUSTOMDELEGATE_H
#define CUSTOMDELEGATE_H
#include <QStyledItemDelegate>
#include <QSpinBox>
#include <QComboBox>
#include <QPainter>
class CustomDelegate : public QStyledItemDelegate {
Q_OBJECT
public:
explicit CustomDelegate(QObject *parent = nullptr)
: QStyledItemDelegate(parent) {}
// 创建编辑器
QWidget *createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const override {
// 根据列返回不同的编辑器
if (index.column() == 1) { // 第二列使用SpinBox
QSpinBox *editor = new QSpinBox(parent);
editor->setRange(0, 100);
editor->setSingleStep(1);
return editor;
} else if (index.column() == 2) { // 第三列使用ComboBox
QComboBox *editor = new QComboBox(parent);
editor->addItems({"Engineering", "Sales", "Marketing", "HR"});
return editor;
}
// 其他列使用默认编辑器
return QStyledItemDelegate::createEditor(parent, option, index);
}
// 设置编辑器数据
void setEditorData(QWidget *editor,
const QModelIndex &index) const override {
if (index.column() == 1) {
QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
int value = index.model()->data(index, Qt::EditRole).toInt();
spinBox->setValue(value);
} else if (index.column() == 2) {
QComboBox *comboBox = static_cast<QComboBox*>(editor);
QString value = index.model()->data(index, Qt::EditRole).toString();
comboBox->setCurrentText(value);
} else {
QStyledItemDelegate::setEditorData(editor, index);
}
}
// 将编辑器数据保存到模型
void setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const override {
if (index.column() == 1) {
QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
model->setData(index, spinBox->value(), Qt::EditRole);
} else if (index.column() == 2) {
QComboBox *comboBox = static_cast<QComboBox*>(editor);
model->setData(index, comboBox->currentText(), Qt::EditRole);
} else {
QStyledItemDelegate::setModelData(editor, model, index);
}
}
// 自定义绘制
void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const override
{
// 高亮特定条件的单元格
if (index.column() == 1)
{
int age = index.data().toInt();
if (age > 60)
{
painter->fillRect(option.rect, QColor(255, 200, 200));
}
}
QStyledItemDelegate::paint(painter, option, index);
}
};
#endif // CUSTOMDELEGATE_H
4.2 使用自定义委托
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QStringListModel>
#include <QStandardItemModel>
#include <QListView>
#include <QTableView>
#include <QVBoxLayout>
#include <QPushButton>
#include <QLabel>
#include "CustomTableModel.h"
#include "CustomDelegate.h" // 添加委托类头文件
// Widget类
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = nullptr);
~Widget();
private slots:
void onAddButtonClicked();
void onRemoveButtonClicked();
void onClearButtonClicked();
void onTableViewDoubleClicked(const QModelIndex &index);
private:
void setupUI();
void setupModels();
void setupConnections();
void setupDelegate(); // 新增:设置委托的方法
// 界面控件
QTableView *tableView;
QPushButton *addButton;
QPushButton *removeButton;
QPushButton *clearButton;
QLabel *statusLabel;
QVBoxLayout *mainLayout;
// 模型和委托
CustomTableModel *customModel;
CustomDelegate *customDelegate; // 自定义委托对象
};
#endif // WIDGET_H
#include "widget.h"
#include <QMessageBox>
#include <QInputDialog>
#include <QHeaderView>
#include <QHBoxLayout>
#include <QApplication>
#include <QDesktopWidget>
// Widget的实现
Widget::Widget(QWidget *parent)
: QWidget(parent)
, tableView(new QTableView(this))
, addButton(new QPushButton("Add Employee", this))
, removeButton(new QPushButton("Remove Selected", this))
, clearButton(new QPushButton("Clear All", this))
, statusLabel(new QLabel("Ready", this))
, mainLayout(new QVBoxLayout(this))
, customModel(new CustomTableModel(this))
, customDelegate(new CustomDelegate(this)) // 初始化委托对象
{
setupUI();
setupModels();
setupDelegate(); // 设置委托
setupConnections();
}
Widget::~Widget()
{
// 使用Qt对象树管理,不需要手动删除
}
void Widget::setupUI()
{
// 设置窗口标题和大小
setWindowTitle("Custom Table Model with Delegate Demo");
// 根据屏幕大小调整窗口尺寸
QRect screenGeometry = QApplication::desktop()->screenGeometry();
int width = screenGeometry.width() * 2 / 3;
int height = screenGeometry.height() * 2 / 3;
resize(width, height);
// 创建按钮布局
QHBoxLayout *buttonLayout = new QHBoxLayout();
buttonLayout->addWidget(addButton);
buttonLayout->addWidget(removeButton);
buttonLayout->addWidget(clearButton);
buttonLayout->addStretch(); // 添加弹性空间
// 添加功能说明标签
QLabel *hintLabel = new QLabel("提示:双击单元格可编辑,年龄列使用SpinBox,部门列使用下拉框", this);
hintLabel->setStyleSheet("QLabel { color: #666666; font-style: italic; padding: 5px; }");
// 设置状态标签
statusLabel->setAlignment(Qt::AlignCenter);
statusLabel->setStyleSheet("QLabel { padding: 8px; background-color: #f5f5f5; border: 1px solid #ddd; }");
// 将控件添加到主布局
mainLayout->addLayout(buttonLayout);
mainLayout->addWidget(hintLabel);
mainLayout->addWidget(tableView);
mainLayout->addWidget(statusLabel);
// 设置主窗口布局
setLayout(mainLayout);
}
void Widget::setupModels()
{
// 设置自定义模型到QTableView
tableView->setModel(customModel);
// 设置表格属性
tableView->setAlternatingRowColors(true);
tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
tableView->setSelectionMode(QAbstractItemView::ExtendedSelection); // 允许多选
// 设置列宽
tableView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Interactive);
tableView->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
tableView->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch);
// 设置默认列宽
tableView->setColumnWidth(0, 200); // 姓名列宽
tableView->setColumnWidth(1, 100); // 年龄列宽
// 设置行高
tableView->verticalHeader()->setDefaultSectionSize(35);
tableView->verticalHeader()->setVisible(true); // 显示行号
// 启用排序
tableView->setSortingEnabled(true);
// 设置表格样式
tableView->setStyleSheet(
"QTableView {"
" background-color: white;"
" gridline-color: #e0e0e0;"
" selection-background-color: #b3d9ff;"
" selection-color: black;"
"}"
"QTableView::item {"
" padding: 5px;"
" border-bottom: 1px solid #f0f0f0;"
"}"
"QTableView::item:selected {"
" background-color: #b3d9ff;"
"}"
"QHeaderView::section {"
" background-color: #4a86e8;"
" color: white;"
" padding: 8px;"
" border: 1px solid #357ae8;"
" font-weight: bold;"
"}"
"QHeaderView::section:checked {"
" background-color: #3c78d8;"
"}"
);
}
void Widget::setupDelegate()
{
// 为整个表格设置自定义委托
tableView->setItemDelegate(customDelegate);
// 或者可以为特定列设置不同的委托(如果需要)
// tableView->setItemDelegateForColumn(1, customDelegate); // 只对年龄列设置
// tableView->setItemDelegateForColumn(2, customDelegate); // 只对部门列设置
// 启用编辑触发方式
tableView->setEditTriggers(
QAbstractItemView::DoubleClicked | // 双击编辑
QAbstractItemView::SelectedClicked | // 选中后单击编辑
QAbstractItemView::EditKeyPressed | // 按F2键编辑
QAbstractItemView::AnyKeyPressed // 输入任意字符开始编辑
);
statusLabel->setText("自定义委托已启用:年龄列使用SpinBox,部门列使用下拉框");
}
void Widget::setupConnections()
{
// 连接按钮信号
connect(addButton, &QPushButton::clicked, this, &Widget::onAddButtonClicked);
connect(removeButton, &QPushButton::clicked, this, &Widget::onRemoveButtonClicked);
connect(clearButton, &QPushButton::clicked, this, &Widget::onClearButtonClicked);
// 连接表格双击信号
connect(tableView, &QTableView::doubleClicked, this, &Widget::onTableViewDoubleClicked);
// 连接模型数据变化信号
connect(customModel, &CustomTableModel::dataChanged, this, [this]() {
statusLabel->setText(QString("数据已更新,当前行数:%1").arg(customModel->rowCount()));
});
}
void Widget::onAddButtonClicked()
{
bool ok;
QString name = QInputDialog::getText(this, "Add Employee",
"Enter name:", QLineEdit::Normal, "", &ok);
if (!ok || name.isEmpty()) return;
// 使用SpinBox输入年龄
int age = QInputDialog::getInt(this, "Add Employee",
"Enter age:", 25, 18, 65, 1, &ok);
if (!ok) return;
// 使用ComboBox选择部门
QStringList departments = {"Engineering", "Sales", "Marketing", "HR", "Finance", "Operations"};
QString department = QInputDialog::getItem(this, "Add Employee",
"Select department:",
departments, 0, false, &ok);
if (!ok || department.isEmpty()) return;
// 添加数据到模型
// 根据你的CustomTableModel的实际方法名调用
// 如果方法名是addEmployee,则:
customModel->addEmployee(name, QString::number(age), department);
// 或者如果方法名是addData,则:
// customModel->addData(QStringList() << name << QString::number(age) << department);
statusLabel->setText(QString("Added: %1, %2, %3").arg(name).arg(age).arg(department));
}
void Widget::onRemoveButtonClicked()
{
QModelIndexList selectedIndexes = tableView->selectionModel()->selectedRows();
if (selectedIndexes.isEmpty()) {
QMessageBox::warning(this, "Warning", "Please select row(s) to remove.");
return;
}
QStringList names;
QList<int> rows;
for (const QModelIndex &index : selectedIndexes) {
int row = index.row();
QString name = customModel->data(customModel->index(row, 0)).toString();
names.append(name);
rows.append(row);
}
QMessageBox::StandardButton reply;
reply = QMessageBox::question(this, "Confirm Removal",
QString("Remove %1 selected employee(s)?").arg(names.size()),
QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::Yes) {
// 从后往前删除,避免索引变化
std::sort(rows.begin(), rows.end(), std::greater<int>());
for (int row : rows) {
customModel->removeRow(row);
}
statusLabel->setText(QString("Removed %1 employee(s)").arg(rows.size()));
}
}
void Widget::onClearButtonClicked()
{
if (customModel->rowCount() == 0) return;
QMessageBox::StandardButton reply;
reply = QMessageBox::question(this, "Confirm Clear",
"Clear all data?",
QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::Yes) {
customModel->clearAll();
statusLabel->setText("All data cleared.");
}
}
void Widget::onTableViewDoubleClicked(const QModelIndex &index)
{
if (!index.isValid()) return;
int row = index.row();
int col = index.column();
QString name = customModel->data(customModel->index(row, 0)).toString();
QString age = customModel->data(customModel->index(row, 1)).toString();
QString dept = customModel->data(customModel->index(row, 2)).toString();
QString columnName;
switch (col) {
case 0: columnName = "Name"; break;
case 1: columnName = "Age"; break;
case 2: columnName = "Department"; break;
default: columnName = "Unknown"; break;
}
QString message = QString("Double clicked on:\n"
"Row: %1, Column: %2 (%3)\n"
"Name: %4\n"
"Age: %5\n"
"Department: %6")
.arg(row + 1)
.arg(col + 1)
.arg(columnName)
.arg(name)
.arg(age)
.arg(dept);
QMessageBox::information(this, "Item Details", message);
}
运行效果:

五、实战案例:联系人管理系统
5.1 完整示例代码
#include <QApplication>
#include <QMainWindow>
#include <QTableView>
#include <QHeaderView>
#include <QMenuBar>
#include <QToolBar>
#include <QStatusBar>
#include <QMessageBox>
#include <QFileDialog>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
class ContactModel : public QAbstractTableModel {
Q_OBJECT
public:
enum Columns {
NameColumn = 0,
PhoneColumn,
EmailColumn,
DepartmentColumn,
ColumnCount
};
explicit ContactModel(QObject *parent = nullptr)
: QAbstractTableModel(parent) {}
int rowCount(const QModelIndex &parent = QModelIndex()) const override {
return parent.isValid() ? 0 : m_contacts.size();
}
int columnCount(const QModelIndex &parent = QModelIndex()) const override {
return parent.isValid() ? 0 : ColumnCount;
}
QVariant data(const QModelIndex &index, int role) const override {
if (!index.isValid() || index.row() >= m_contacts.size()) {
return QVariant();
}
const Contact &contact = m_contacts.at(index.row());
switch (role) {
case Qt::DisplayRole:
case Qt::EditRole:
switch (index.column()) {
case NameColumn: return contact.name;
case PhoneColumn: return contact.phone;
case EmailColumn: return contact.email;
case DepartmentColumn: return contact.department;
}
break;
case Qt::TextAlignmentRole:
return Qt::AlignLeft + Qt::AlignVCenter;
case Qt::ToolTipRole:
return QString("Phone: %1\nEmail: %2").arg(contact.phone).arg(contact.email);
}
return QVariant();
}
QVariant headerData(int section, Qt::Orientation orientation,
int role) const override {
if (role != Qt::DisplayRole) return QVariant();
if (orientation == Qt::Horizontal) {
switch (section) {
case NameColumn: return "Name";
case PhoneColumn: return "Phone";
case EmailColumn: return "Email";
case DepartmentColumn: return "Department";
}
}
return section + 1;
}
bool setData(const QModelIndex &index, const QVariant &value,
int role = Qt::EditRole) override {
if (!index.isValid() || role != Qt::EditRole) {
return false;
}
if (index.row() < m_contacts.size()) {
Contact &contact = m_contacts[index.row()];
switch (index.column()) {
case NameColumn: contact.name = value.toString(); break;
case PhoneColumn: contact.phone = value.toString(); break;
case EmailColumn: contact.email = value.toString(); break;
case DepartmentColumn: contact.department = value.toString(); break;
default: return false;
}
emit dataChanged(index, index, {role});
return true;
}
return false;
}
Qt::ItemFlags flags(const QModelIndex &index) const override {
Qt::ItemFlags flags = QAbstractTableModel::flags(index);
if (index.isValid()) {
flags |= Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled;
}
return flags;
}
bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override {
if (row < 0 || row > m_contacts.size()) return false;
beginInsertRows(parent, row, row + count - 1);
for (int i = 0; i < count; ++i) {
m_contacts.insert(row + i, Contact());
}
endInsertRows();
return true;
}
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override {
if (row < 0 || row + count > m_contacts.size()) return false;
beginRemoveRows(parent, row, row + count - 1);
for (int i = 0; i < count; ++i) {
m_contacts.removeAt(row);
}
endRemoveRows();
return true;
}
// 添加联系人
void addContact(const QString &name, const QString &phone,
const QString &email, const QString &department) {
int row = m_contacts.size();
beginInsertRows(QModelIndex(), row, row);
m_contacts.append(Contact{name, phone, email, department});
endInsertRows();
}
// 保存到文件
bool saveToFile(const QString &filename) {
QJsonArray contactsArray;
for (const Contact &contact : m_contacts) {
QJsonObject obj;
obj["name"] = contact.name;
obj["phone"] = contact.phone;
obj["email"] = contact.email;
obj["department"] = contact.department;
contactsArray.append(obj);
}
QJsonDocument doc(contactsArray);
QFile file(filename);
if (!file.open(QIODevice::WriteOnly)) {
return false;
}
file.write(doc.toJson());
file.close();
return true;
}
// 从文件加载
bool loadFromFile(const QString &filename) {
QFile file(filename);
if (!file.open(QIODevice::ReadOnly)) {
return false;
}
QByteArray data = file.readAll();
file.close();
QJsonDocument doc = QJsonDocument::fromJson(data);
if (!doc.isArray()) {
return false;
}
beginResetModel();
m_contacts.clear();
QJsonArray array = doc.array();
for (const QJsonValue &value : array) {
QJsonObject obj = value.toObject();
m_contacts.append(Contact{
obj["name"].toString(),
obj["phone"].toString(),
obj["email"].toString(),
obj["department"].toString()
});
}
endResetModel();
return true;
}
private:
struct Contact {
QString name;
QString phone;
QString email;
QString department;
};
QVector<Contact> m_contacts;
};
class MainWindow : public QMainWindow {
Q_OBJECT
public:
MainWindow() {
setupUI();
setupConnections();
// 添加示例数据
m_model.addContact("John Doe", "123-456-7890",
"john@example.com", "Engineering");
m_model.addContact("Jane Smith", "987-654-3210",
"jane@example.com", "Marketing");
}
private:
void setupUI() {
// 创建中央部件
QWidget *centralWidget = new QWidget(this);
QVBoxLayout *layout = new QVBoxLayout(centralWidget);
// 创建表格视图
m_tableView = new QTableView;
m_tableView->setModel(&m_model);
m_tableView->setSelectionMode(QAbstractItemView::SingleSelection);
m_tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
m_tableView->horizontalHeader()->setStretchLastSection(true);
m_tableView->setAlternatingRowColors(true);
layout->addWidget(m_tableView);
setCentralWidget(centralWidget);
// 创建菜单
QMenu *fileMenu = menuBar()->addMenu("File");
fileMenu->addAction("Add Contact", this, &MainWindow::addContact);
fileMenu->addAction("Remove Contact", this, &MainWindow::removeContact);
fileMenu->addSeparator();
fileMenu->addAction("Save", this, &MainWindow::saveContacts);
fileMenu->addAction("Load", this, &MainWindow::loadContacts);
fileMenu->addSeparator();
fileMenu->addAction("Exit", qApp, &QApplication::quit);
// 创建工具栏
QToolBar *toolBar = addToolBar("Tools");
toolBar->addAction("Add Contact", this, &MainWindow::addContact);
toolBar->addAction("Remove Contact", this, &MainWindow::removeContact);
// 状态栏
statusBar()->showMessage("Ready");
setWindowTitle("Contact Manager");
resize(800, 600);
}
void setupConnections() {
connect(m_tableView->selectionModel(), &QItemSelectionModel::selectionChanged,
this, &MainWindow::updateStatusBar);
}
private slots:
void addContact() {
int row = m_model.rowCount();
if (m_model.insertRow(row)) {
QModelIndex index = m_model.index(row, 0);
m_tableView->setCurrentIndex(index);
m_tableView->edit(index);
}
}
void removeContact() {
QModelIndexList selected = m_tableView->selectionModel()->selectedRows();
if (selected.isEmpty()) {
QMessageBox::warning(this, "Warning", "Please select a contact to remove");
return;
}
int row = selected.first().row();
m_model.removeRow(row);
}
void saveContacts() {
QString filename = QFileDialog::getSaveFileName(this, "Save Contacts",
"", "JSON Files (*.json)");
if (!filename.isEmpty()) {
if (m_model.saveToFile(filename)) {
statusBar()->showMessage("Contacts saved successfully", 3000);
} else {
QMessageBox::critical(this, "Error", "Failed to save contacts");
}
}
}
void loadContacts() {
QString filename = QFileDialog::getOpenFileName(this, "Load Contacts",
"", "JSON Files (*.json)");
if (!filename.isEmpty()) {
if (m_model.loadFromFile(filename)) {
statusBar()->showMessage("Contacts loaded successfully", 3000);
} else {
QMessageBox::critical(this, "Error", "Failed to load contacts");
}
}
}
void updateStatusBar() {
int selectedCount = m_tableView->selectionModel()->selectedRows().size();
int totalCount = m_model.rowCount();
statusBar()->showMessage(
QString("Total: %1 | Selected: %2").arg(totalCount).arg(selectedCount));
}
private:
ContactModel m_model;
QTableView *m_tableView;
};
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
MainWindow window;
window.show();
return app.exec();
}
运行效果:

浙公网安备 33010602011771号