关于MV架构的简单介绍
在Qt框架中,代理(Delegate)、模型(Model)和视图(View)之间的关系构成了MVVM(Model-View-ViewModel)架构的一部分,尽管Qt通常使用Model-View架构。这三者之间的关系可以这样理解:
1. Model(模型)
Model是数据的核心代表,它负责存储和管理应用程序的数据。Model提供了数据的接口,允许View查询和修改数据。Model与View的交互是通过信号和槽机制来完成的,当Model中的数据发生变化时,它会发出信号通知View进行更新。
2. View(视图)
View是Model数据的展示层,它负责将数据以用户友好的形式展示出来,并接收用户的交互操作。在Qt中,View通常是通过一些控件来实现的,比如QListView、QTableView、QTreeView等。View不处理数据的逻辑,它只是简单地展示Model提供的数据。
3. Delegate(代理)
Delegate位于Model和View之间,充当了一个中介的角色。它允许开发者为View中的每个项创建自定义的编辑器或显示组件。代理的作用是处理View中的项的创建、显示和编辑。当用户与View交互时,代理负责将用户的输入转换为对Model的修改,同时也负责将Model的数据转换为View中的显示形式。
代理、模型和视图之间的关系
Model与View:Model和View之间通过数据接口进行交互。Model提供数据,View展示数据。Model通过信号通知View数据的变化,View通过槽来响应这些信号并更新显示。
Model与Delegate:Model提供了数据的接口,而Delegate负责将这些数据以特定方式显示在View中。Delegate从Model获取数据,并将其转换为用户可以理解的形式。
View与Delegate:View使用Delegate来创建和管理每个项的显示和编辑。Delegate为View中的项提供自定义的外观和行为,使得View可以展示复杂的数据项。
Delegate作为中介:Delegate作为Model和View之间的中介,它处理用户的输入并将这些输入转换为对Model的操作。同时,它也负责将Model的数据格式化并展示在View中。
需要注意的是,MV架构中,默认的代理使用的是单行文本框!

UI设计
本博客需要的资源链接。提取码:14ml。
创建项目,选择QMainWindow作为主窗口的基类。主窗口名为:TableWindow。
为项目添加一个资源文件,将项目用到的图标引用到资源文件中。
双击ui文件,移除菜单栏,添加工具栏,保留状态栏。添加数个QAction,如下图:

界面设计:
控件名 |
bjectname |
frameShape |
frameShadow |
readOnly |
QTableView |
m_table |
winpanel |
sunken |
\ |
QPlainTextEdit |
m_edit |
winpanel |
sunken |
√ |
系统信号和系统槽的连接:

系统信号和自定义槽的连接(鼠标右击创建的QAction,选择转到槽):
QAction |
要连接的信号 |
加粗 |
triggered(bool) |
其余的QAction(除退出外) |
triggered() |
整体效果如下:

功能实现
添加一个自定义代理类,类名为ComboBoxDelegate,基类为:QStyledItemDelegate
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
| class ComboBoxDelegate : public QStyledItemDelegate { public: ComboBoxDelegate();
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; };
ComboBoxDelegate::ComboBoxDelegate() {
}
QWidget * ComboBoxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(option); Q_UNUSED(index);
QComboBox* editor = new QComboBox(parent ); editor->addItem("男"); editor->addItem("女");
return editor; }
void ComboBoxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { static_cast<QComboBox*>(editor)->setCurrentText( index.model()->data(index, Qt::EditRole).toString()); }
void ComboBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { model->setData(index, static_cast<QComboBox*>(editor)->currentText(), Qt::EditRole); }
void ComboBoxDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(index);
editor->setGeometry(option.rect); }
|
在TableWindow类中,添加Model、和标签,此外手动添加一个m_selection成员的currentChanged信号的槽函数。定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class TableWindow : public QMainWindow { Q_OBJECT
private slots:
void on_m_selection_currentChanged(const QModelIndex ¤t, const QModelIndex &previous); private: void initModel(QStringList const& strings);
Ui::TableWindow *ui;
QLabel* m_labCurFile; QLabel* m_labCellPos; QLabel* m_labCellText;
QStandardItemModel* m_model; QItemSelectionModel* m_selection;
};
|
TableWindow的构造函数实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| TableWindow::TableWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::TableWindow) , m_labCurFile(new QLabel("当前文件: ")) , m_labCellPos(new QLabel("单元格位置: ")) , m_labCellText(new QLabel("单元格内容: ")) , m_model(new QStandardItemModel(this)) , m_selection(new QItemSelectionModel(m_model)) { ui->setupUi(this);
m_labCurFile->setMinimumWidth(420); ui->statusBar->addWidget(m_labCurFile); m_labCellPos->setMinimumWidth(190); ui->statusBar->addWidget(m_labCellPos); m_labCellText->setMinimumWidth(190); ui->statusBar->addWidget(m_labCellText);
ui->m_table->setModel(m_model); ui->m_table->setSelectionModel(m_selection);
connect(m_selection,SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(on_m_selection_currentChanged(QModelIndex,QModelIndex))); ui->m_table->setItemDelegateForColumn(3, new ComboBoxDelegate); }
|
加粗的实现:
1 2 3 4 5 6 7 8 9 10 11 12
| void TableWindow::on_m_actBold_triggered(bool checked) { for(QModelIndex const& index : m_selection->selectedIndexes()){ QStandardItem* item = m_model->itemFromIndex(index); QFont font = item->font(); font.setBold(checked); item->setFont(font); } }
|
打开文件的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| void TableWindow::on_m_actOpen_triggered() { QString path = QFileDialog::getOpenFileName(this, "打开", QCoreApplication::applicationDirPath(), "逗号分隔符文件(*.csv);;所有文件(*.*)"); if(path.isEmpty()) return; QFile file(path); if(!file.open(QIODevice::ReadOnly | QIODevice::Text)) return; QTextStream stream(&file);
QStringList strings; while(!stream.atEnd()) strings.append(stream.readLine());
file.close(); initModel(strings);
m_labCurFile->setText("当前文件: " + path); }
void TableWindow::initModel(QStringList const& strings) { m_model->clear();
m_model->setHorizontalHeaderLabels( strings.at(0).split(",", Qt::SkipEmptyParts));
int rowCount = strings.count() - 1; for(int row = 0; row < rowCount; row++){ QStringList values = strings.at(row + 1).split(",", Qt::SkipEmptyParts); int columnCount = values.size(); for(int column = 0; column < columnCount; column++){ m_model->setItem(row, column, new QStandardItem(values.at(column))); } }
m_selection->setCurrentIndex( m_model->index(0,0), QItemSelectionModel::Select); }
|
保存的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| void TableWindow::on_m_actSave_triggered() { QString path = QFileDialog::getSaveFileName(this, "保存", QCoreApplication::applicationDirPath(), "逗号分隔符文件(*.csv);;所有文件(*.*)"); if(path.isEmpty()) return; QFile file(path); if(!file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate)) return; QTextStream stream(&file);
int columnCount = m_model->columnCount(); for(int col = 0; col < columnCount; col++){ stream << m_model->horizontalHeaderItem(col)->text() << (col == columnCount - 1 ? "\n" : ","); }
int rowCount = m_model->rowCount(); for(int row = 0; row < rowCount; row++) { for(int col = 0; col < columnCount; col++) { stream << m_model->item(row, col)->text() << (col == columnCount - 1 ? "\n" : ","); } }
file.close(); }
|
预览的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| void TableWindow::on_m_actPreview_triggered() { ui->m_edit->clear();
QString text; int columnCount = m_model->columnCount(); for(int col = 0; col < columnCount; col++) { text += m_model->horizontalHeaderItem(col)->text() + (col == columnCount - 1 ? "" : ","); } ui->m_edit->appendPlainText(text);
int rowCount = m_model->rowCount(); for(int row = 0; row < rowCount; row++) { QString text; for(int col = 0; col < columnCount; col++) { text += m_model->item(row, col)->text() + (col == columnCount - 1 ? "" : ","); } ui->m_edit->appendPlainText(text); } }
|
添加的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| void TableWindow::on_m_actAppend_triggered() { int columnCount = m_model->columnCount(); if(!columnCount) return;
QList<QStandardItem*> items; for(int col = 0; col < columnCount; col++) items << new QStandardItem; m_model->insertRow(m_model->rowCount(), items);
m_selection->clearSelection(); m_selection->setCurrentIndex( m_model->index(m_model->rowCount() -1, 0), QItemSelectionModel::Select); }
|
插入的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| void TableWindow::on_m_actInsert_triggered() { int columnCount = m_model->columnCount(); if(!columnCount) return;
QList<QStandardItem*> items; for(int col = 0; col < columnCount; col++) items << new QStandardItem;
QModelIndex current = m_selection->currentIndex(); m_model->insertRow(current.row(), items);
m_selection->clearSelection(); m_selection->setCurrentIndex(current, QItemSelectionModel::Select); }
|
删除的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void TableWindow::on_m_actDelete_triggered() { QModelIndex current = m_selection->currentIndex(); QModelIndex above = m_model->index( current.row() - 1, current.column()); bool last = current.row() == m_model->rowCount() - 1;
m_model->removeRow(current.row()); m_selection->setCurrentIndex(last ? above : current, QItemSelectionModel::Select); }
|
对齐的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| void TableWindow::on_m_actLeft_triggered() { for(QModelIndex const& index : m_selection->selectedIndexes()) { m_model->itemFromIndex(index)->setTextAlignment( Qt::AlignLeft | Qt::AlignVCenter); } }
void TableWindow::on_m_actCenter_triggered() { for(QModelIndex const& index : m_selection->selectedIndexes()) { m_model->itemFromIndex(index)->setTextAlignment( Qt::AlignHCenter | Qt::AlignVCenter); } }
void TableWindow::on_m_actRight_triggered() { for(QModelIndex const& index : m_selection->selectedIndexes()) { m_model->itemFromIndex(index)->setTextAlignment( Qt::AlignRight | Qt::AlignVCenter); } }
|
状态栏文本动态变化的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| void TableWindow::on_m_selection_currentChanged(const QModelIndex ¤t, const QModelIndex &previous){ Q_UNUSED(previous);
if(!current.isValid()) return;
m_labCellPos->setText( QString(" 单元格位置:第%1行, 第%2列 "). arg(current.row() + 1). arg(current.column() + 1)); QStandardItem* item = m_model->itemFromIndex(current); m_labCellText->setText(" 单元格内容: " + item->text());
ui->m_actBold->setChecked(item->font().bold()); }
|
运行效果如下:

本章完结