Qt 模型(model)/视图(view)详解

基于Qt6.2.0

  以下所有代码均可在Qt安装目录:Qt安装目录/examples中找到

一.模型/视图介绍

  在没有使用模型/视图的应用程序中,一个标准的表格组件是一个用户可以修改的数据元素的二维数组。表格组件能够通过读写表格组件提供的数据元素来集成到程序中。这种方法在大多数应用中都很直观而且很有用,但是当显示和编辑数据库的时候标准组件可能就有问题了。数据的两个副本必须协调:一个在组件外部,一个在组件内部。同步这两个副本的数据是开发者的职责。除了这个,显示和数据的紧密结合使得写单元测试变得更难。
而模型/视图是在处理数据集合的窗口组件中用来把数据从视图中分离出来的一种技术。MVC 把图形界面分为三个部分

  • 模型(Model):用于管理数据,注意,数据不一定需要位于模型之中。

  • 视图(View):就是呈现在用户面前的界面外观,视图负责把模型中的数据显示给用户

  • 控制器(Controller):处理用户在界面的交互数据操作

可以通过 view(可能有多个)来修改 data,当 data 改变了之后要通知所有的 view 修改自己的显示!

二.Qt中的模型/视图控件介绍

  表、列表和树控件是GUI中经常使用的组件。这些控件可以通过两种不同的方式访问其数据。传统的方式涉及用于存储数据的内部容器控件。这种方法非常直观,但在许多应用程序中,它会导致数据同步问题。第二种方法是模型/视图编程,其中控件不维护内部数据容器。他们通过标准化接口访问外部数据,因此避免了数据重复。

WidgetStandard Widget
(an item based convenience class)
Model/View View Class
(for use with external data)
QListWidgetQListView
QTableWidgetQTableView
QTreeWidgetQTreeView
QColumnView shows a tree as a hierarchy of lists
QComboBox can work as both a view class and also as a traditional widget

Qt中模型类的继承关系:
在这里插入图片描述

不同模型(的结构):
在这里插入图片描述

Qt中预定义好的模型类:

Model类说明
QStringListModel存储字符串列表
QStandardItemModel存储任意分层项目
QFileSystemModel封装本地文件系统
QSqlQueryModel封装SQL结果集
QSqlTableModel封装SQL表
QSqlRelationalTableModel用外键封装SQL表
QSortFilterProxyModel排序和/或筛选其他模型

Qt中视图类的继承关系:
在这里插入图片描述

1. 在模型/视图控件中显示数据

视图(view)通过调用模型(model)的rowCount()calCount()函数获取显示的行数和列数,对于每个项中要显示什么,则是通过调用模型(model)的data()函数。data()函数的参数为项的索引以及项的角色(进行什么操作),通常,我们只需要根据项的索引编写相对应的逻辑,再判断需要对项进行什么操作即可。以下例程中,我们只需要显示数据即可,所以,每当data()函数被调用并且操作角色为Qt::DisplayRole显示数据时,我们就对显示的内容进行操作。

// main.cpp
#include <QApplication>
#include <QTableView>
#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 <QAbstractTableModel>

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中的Qt::ItemDataRole种类

Qt中模型/视图中的项的角色(enum Qt::ItemDataRole),有以下几种:

通用角色(以及相关类型)

Qt::ItemDataRole描述
Qt::DisplayRole0以文本形式呈现的关键数据。(QString)
Qt::DecorationRole1以图标形式呈现为装饰的数据。(QColor, QIcon or QPixmap)
Qt::EditRole2适合在编辑器中编辑的格式中的数据。(QString)
Qt::ToolTipRole项目工具提示中显示的数据。(QString)
Qt::StatusTipRole4状态栏中显示的数据。(QString)
Qt::WhatsThisRole5以“这是什么?”模式显示的项目数据。(QString)
Qt::SizeHintRole13将提供给视图的项的大小提示。(QSize)

描述外观和元数据的角色

Qt::ItemDataRole描述
Qt::FontRole6使用默认委托呈现的项目所使用的字体。(QFont)
Qt::TextAlignmentRole7使用默认委托呈现的项的文本对齐方式。 (Qt::Alignment)
Qt::BackgroundRole8用于使用默认代理渲染的项目的背景画笔。(QBrush)
Qt::ForegroundRole9用于使用默认代理渲染的项目的前景画笔(通常为文本颜色)。(QBrush)
Qt::CheckStateRole10此角色用于获取项目的选中状态。(Qt::CheckState)
Qt::InitialSortOrderRole14此角色用于获取标题视图节的初始排序顺序。(Qt::SortOrder)。

辅助功能角色

Qt::ItemDataRole描述
Qt::AccessibleTextRole11可访问性扩展和插件(如屏幕阅读器)使用的文本。(QString)
Qt::AccessibleDescriptionRole12出于可访问性目的对项目的描述。(QString)

用户角色

Qt::ItemDataRole描述
Qt::UserRole0x0100可用于特定应用程序目的的第一个角色。

2. 更改模型/视图控件中显示数据的格式

想要更改视图中每个项的显示格式,只需要在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();
}

在这里插入图片描述

3. 显示动态数据

为了进行动态的数据更新,我们需要先设定一个时间定时器:

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();
}

在这里插入图片描述

4. 设置标题栏

要显示标题栏,只需要重新实现模型(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();
}

在这里插入图片描述

5. 添加可编辑的视图

/ mymodel.h
#include <QAbstractTableModel>
#include <QString>

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);
}

6. 获取鼠标选中的项

利用QItemSelectionModel *selectionModel = tableView->selectionModel()可以获取到视图中选中的项,其中selectionModel 有一个信号&QItemSelectionModel::selectionChanged,即当被选中项发生改变时触发信号,利用该信号可以获取到所需要的选中项

//mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <iostream>

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 <QTableView>

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 <QAbstractTableModel>
#include <QString>

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);
}
  • 6
    点赞
  • 80
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AoDeLuo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值