以下所有代码均可在Qt安装目录:Qt安装目录/examples中找到
在没有使用模型/视图的应用程序中,一个标准的表格组件是一个用户可以修改的数据元素的二维数组。表格组件能够通过读写表格组件提供的数据元素来集成到程序中。这种方法在大多数应用中都很直观而且很有用,但是当显示和编辑数据库的时候标准组件可能就有问题了。数据的两个副本必须协调:一个在组件外部,一个在组件内部。同步这两个副本的数据是开发者的职责。除了这个,显示和数据的紧密结合使得写单元测试变得更难。
而模型/视图是在处理数据集合的窗口组件中用来把数据从视图中分离出来的一种技术。MVC 把图形界面分为三个部分
模型(Model):用于管理数据,注意,数据不一定需要位于模型之中。
视图(View):就是呈现在用户面前的界面外观,视图负责把模型中的数据显示给用户
控制器(Controller):处理用户在界面的交互数据操作
可以通过 view(可能有多个)来修改 data,当 data 改变了之后要通知所有的 view 修改自己的显示!
表、列表和树控件是GUI中经常使用的组件。这些控件可以通过两种不同的方式访问其数据。传统的方式涉及用于存储数据的内部容器控件。这种方法非常直观,但在许多应用程序中,它会导致数据同步问题。第二种方法是模型/视图编程,其中控件不维护内部数据容器。他们通过标准化接口访问外部数据,因此避免了数据重复。
Qt中预定义好的模型类:
Model类 | 说明 |
---|---|
QStringListModel | 存储字符串列表 |
QStandardItemModel | 存储任意分层项目 |
QFileSystemModel | 封装本地文件系统 |
QSqlQueryModel | 封装SQL结果集 |
QSqlTableModel | 封装SQL表 |
QSqlRelationalTableModel | 用外键封装SQL表 |
QSortFilterProxyModel | 排序和/或筛选其他模型 |
视图(view)通过调用模型(model)的rowCount()
和calCount()
函数获取显示的行数和列数,对于每个项中要显示什么,则是通过调用模型(model)的data()
函数。data()
函数的参数为项的索引以及项的角色(进行什么操作),通常,我们只需要根据项的索引编写相对应的逻辑,再判断需要对项进行什么操作即可。以下例程中,我们只需要显示数据即可,所以,每当data()
函数被调用并且操作角色为Qt::DisplayRole
显示数据时,我们就对显示的内容进行操作。
// main.cpp
#include
#include
#include "mymodel.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTableView tableView; //定义一个视图控件
MyModel myModel; //定义一个模型
tableView.setModel(&myModel); //将模型与视图进行绑定
tableView.show(); //显示视图
return a.exec();
}
// mymodel.h
#include
class MyModel : public QAbstractTableModel
{
Q_OBJECT
public:
MyModel(QObject *parent = nullptr); //模型的构造函数
int rowCount(const QModelIndex &parent = QModelIndex()) const override; //返回模型中的行数
int columnCount(const QModelIndex &parent = QModelIndex()) const override; //返回模型中的列数
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; //模型中数据的显示处理
};
// mymodel.cpp
#include "mymodel.h"
MyModel::MyModel(QObject *parent)
: QAbstractTableModel(parent)
{
}
int MyModel::rowCount(const QModelIndex & /*parent*/) const
{
return 2;
}
int MyModel::columnCount(const QModelIndex & /*parent*/) const
{
return 3;
}
QVariant MyModel::data(const QModelIndex &index, int role) const
{
if (role == Qt::DisplayRole)
return QString("Row%1, Column%2")
.arg(index.row() + 1)
.arg(index.column() +1);
return QVariant();
}
Qt中模型/视图中的项的角色(enum Qt::ItemDataRole),有以下几种:
通用角色(以及相关类型)
Qt::ItemDataRole | 值 | 描述 |
---|---|---|
Qt::DisplayRole |
0 |
以文本形式呈现的关键数据。(QString) |
Qt::DecorationRole |
1 |
以图标形式呈现为装饰的数据。(QColor, QIcon or QPixmap) |
Qt::EditRole |
2 |
适合在编辑器中编辑的格式中的数据。(QString) |
Qt::ToolTipRole |
三 |
项目工具提示中显示的数据。(QString) |
Qt::StatusTipRole |
4 |
状态栏中显示的数据。(QString) |
Qt::WhatsThisRole |
5 |
以“这是什么?”模式显示的项目数据。(QString) |
Qt::SizeHintRole |
13 |
将提供给视图的项的大小提示。(QSize) |
描述外观和元数据的角色
Qt::ItemDataRole | 值 | 描述 |
---|---|---|
Qt::FontRole |
6 |
使用默认委托呈现的项目所使用的字体。(QFont) |
Qt::TextAlignmentRole |
7 |
使用默认委托呈现的项的文本对齐方式。 (Qt::Alignment) |
Qt::BackgroundRole |
8 |
用于使用默认代理渲染的项目的背景画笔。(QBrush) |
Qt::ForegroundRole |
9 |
用于使用默认代理渲染的项目的前景画笔(通常为文本颜色)。(QBrush) |
Qt::CheckStateRole |
10 |
此角色用于获取项目的选中状态。(Qt::CheckState) |
Qt::InitialSortOrderRole |
14 |
此角色用于获取标题视图节的初始排序顺序。(Qt::SortOrder)。 |
辅助功能角色
Qt::ItemDataRole | 值 | 描述 |
---|---|---|
Qt::AccessibleTextRole |
11 |
可访问性扩展和插件(如屏幕阅读器)使用的文本。(QString) |
Qt::AccessibleDescriptionRole |
12 |
出于可访问性目的对项目的描述。(QString) |
用户角色
Qt::ItemDataRole | 值 | 描述 |
---|---|---|
Qt::UserRole |
0x0100 |
可用于特定应用程序目的的第一个角色。 |
想要更改视图中每个项的显示格式,只需要在data()
中参数为对应的项以及操作角色时,返回对应的对象即可
// mymodel.cpp
QVariant MyModel::data(const QModelIndex &index, int role) const
{
int row = index.row();
int col = index.column();
// generate a log message when this method gets called
qDebug() << QString("row %1, col%2, role %3").arg(row).arg(col).arg(role);
switch (role) {
case Qt::DisplayRole:
if (row == 0 && col == 1) return QString("<--left");
if (row == 1 && col == 1) return QString("right-->");
return QString("Row%1, Column%2").arg(row + 1).arg(col +1);
case Qt::FontRole:
if (row == 0 && col == 0) { //改变cell(0,0)的字体
QFont boldFont;
boldFont.setBold(true);
return boldFont;
}
break;
case Qt::BackgroundRole:
if (row == 1 && col == 2) //改变cell(1,2)的背景
return QBrush(Qt::red);
break;
case Qt::TextAlignmentRole:
if (row == 1 && col == 1) //改变cell(1,1)的对齐方式
return int(Qt::AlignRight | Qt::AlignVCenter);
break;
case Qt::CheckStateRole:
if (row == 1 && col == 0) //为cell(1,0)添加一个勾选框
return Qt::Checked;
break;
}
return QVariant();
}
为了进行动态的数据更新,我们需要先设定一个时间定时器:
MyModel::MyModel(QObject *parent)
: QAbstractTableModel(parent)
, timer(new QTimer(this))
{
timer->setInterval(1000); //设置1000毫秒的定时器
connect(timer, &QTimer::timeout , this, &MyModel::timerHit); //绑定定时器的到点触发函数
timer->start(); //开启定时器
}
定时器触发函数中进行数据的更新,数据更新后需要释放一个dataChanged()
信号,信号的参数为要更新项的索引和所需操作
void MyModel::timerHit()
{
QModelIndex topLeft = createIndex(0,0); //要更新的数据位置
emit dataChanged(topLeft, topLeft, {Qt::DisplayRole}); //释放一个信号,让视图去更新数据
}
dataChanged()
信号释放后,则会调用data()
函数对数据进行更新
QVariant MyModel::data(const QModelIndex &index, int role) const
{
int row = index.row();
int col = index.column();
if (role == Qt::DisplayRole && row == 0 && col == 0)
return QTime::currentTime().toString();
return QVariant();
}
要显示标题栏,只需要重新实现模型(model)的headerData()函数
视图中可以通过tableView->verticalHeader()->hide()函数进行标题栏的显示与设置
QVariant MyModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
switch (section) {
case 0:
return QString("first");
case 1:
return QString("second");
case 2:
return QString("third");
}
}
return QVariant();
}
/ mymodel.h
#include
#include
const int COLS= 3;
const int ROWS= 2;
class MyModel : public QAbstractTableModel
{
Q_OBJECT
public:
MyModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; //重新实现此函数,以实现对被编辑的单元格的操作
Qt::ItemFlags flags(const QModelIndex &index) const override;
private:
QString m_gridData[ROWS][COLS]; //holds text entered into QTableView
signals:
void editCompleted(const QString &);
};
模型(model)中的data()
函数直接将m_gridData
中的数据显示到视图中
QVariant MyModel::data(const QModelIndex &index, int role) const
{
if (role == Qt::DisplayRole && checkIndex(index))
return m_gridData[index.row()][index.column()];
return QVariant();
}
要实现对于视图中项的的可编辑性,需要重新实现模型(model)中的setData()函数。每当视图中的某个项被编辑时,都会调用setData()函数,其中,参数index
告诉函数那个项被编辑了,value
代表被编辑的内容,role
代表操作角色。如果要想设置勾选框,则操作角色就为Qt::CheckStateRole.
bool MyModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (role == Qt::EditRole) {
if (!checkIndex(index))
return false;
//将编辑的数据保存到m_gridData中
m_gridData[index.row()][index.column()] = value.toString();
//用于演示,将编辑的数据保存到一个字符串中
QString result;
for (int row = 0; row < ROWS; row++) {
for (int col= 0; col < COLS; col++)
result += m_gridData[row][col] + ' ';
}
//输出结果
std::cout << result std::endl;
return true;
}
return false;
}
Qt::ItemFlags MyModel::flags(const QModelIndex &index) const
{
return Qt::ItemIsEditable | QAbstractTableModel::flags(index);
}
利用QItemSelectionModel *selectionModel = tableView->selectionModel()
可以获取到视图中选中的项,其中selectionModel
有一个信号&QItemSelectionModel::selectionChanged
,即当被选中项发生改变时触发信号,利用该信号可以获取到所需要的选中项
//mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include
#include
QT_BEGIN_NAMESPACE
class QTableView; //forward declaration
class QItemSelection;
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
private:
QTableView *tableView;
public:
MainWindow(QWidget *parent = nullptr);
public slots:
void showWindowTitle(const QString &title);
private slots:
void selectionChangedSlot(const QItemSelection &newSelection, const QItemSelection &oldSelection);
};
#endif // MAINWINDOW_H
//mainwindow.cpp
#include "mainwindow.h"
#include "mymodel.h"
#include
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, tableView(new QTableView(this))
{
setCentralWidget(tableView);
MyModel *myModel = new MyModel(this);
tableView->setModel(myModel);
//将项的编辑完成信号与处理函数进行绑定
connect(myModel, &MyModel::editCompleted, this, &MainWindow::showWindowTitle);
//将项的选中焦点变化与处理函数进行绑定
QItemSelectionModel *selectionModel = tableView->selectionModel(); //选中对象
connect(selectionModel, &QItemSelectionModel::selectionChanged, this, &MainWindow::selectionChangedSlot);
}
void MainWindow::showWindowTitle(const QString &title)
{
setWindowTitle(title);
}
void MainWindow::selectionChangedSlot(const QItemSelection & /*newSelection*/, const QItemSelection & /*oldSelection*/)
{
//获取选中项中的内容
const QModelIndex index = tableView->selectionModel()->currentIndex();
QString selectedText = index.data(Qt::DisplayRole).toString();
//打印选中项的位置和内容
QString showString = QString("%1, %2, %3").arg(index.row()).arg(index.column()).arg(selectedText);
std::cout << showString.toStdString() << std::endl;
}
// mymodel.h
#include
#include
const int COLS= 3;
const int ROWS= 2;
class MyModel : public QAbstractTableModel
{
Q_OBJECT
public:
MyModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; //重新实现此函数,以实现对被编辑的单元格的操作
Qt::ItemFlags flags(const QModelIndex &index) const override;
private:
QString m_gridData[ROWS][COLS]; //holds text entered into QTableView
signals:
void editCompleted(const QString &);
};
//mymodel.cpp
#include "mymodel.h"
MyModel::MyModel(QObject *parent)
: QAbstractTableModel(parent)
{
}
int MyModel::rowCount(const QModelIndex & /*parent*/) const
{
return ROWS;
}
int MyModel::columnCount(const QModelIndex & /*parent*/) const
{
return COLS;
}
QVariant MyModel::data(const QModelIndex &index, int role) const
{
if (role == Qt::DisplayRole && checkIndex(index))
return m_gridData[index.row()][index.column()];
return QVariant();
}
bool MyModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (role == Qt::EditRole) {
if (!checkIndex(index))
return false;
//将数据保存到m_gridData
m_gridData[index.row()][index.column()] = value.toString();
//将所有项中的内容添加到result中
QString result;
for (int row = 0; row < ROWS; row++) {
for (int col= 0; col < COLS; col++)
result += m_gridData[row][col] + ' ';
}
emit editCompleted(result);
return true;
}
return false;
}
Qt::ItemFlags MyModel::flags(const QModelIndex &index) const
{
return Qt::ItemIsEditable | QAbstractTableModel::flags(index);
}