Qt快速入门(MV架构之TableView + QStandardItemModel + 自定义代理小案例)

Qt快速入门(MV架构之TableView + QStandardItemModel + 自定义代理小案例)

关于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。

  1. 创建项目,选择QMainWindow作为主窗口的基类。主窗口名为:TableWindow。

  2. 为项目添加一个资源文件,将项目用到的图标引用到资源文件中。

  3. 双击ui文件,移除菜单栏,添加工具栏,保留状态栏。添加数个QAction,如下图:

    在这里插入图片描述

  4. 界面设计:

    控件名bjectnameframeShapeframeShadowreadOnly
    QTableViewm_tablewinpanelsunken\
    QPlainTextEditm_editwinpanelsunken
  5. 系统信号和系统槽的连接:

    在这里插入图片描述

  6. 系统信号和自定义槽的连接(鼠标右击创建的QAction,选择转到槽):

    QAction要连接的信号
    加粗triggered(bool)
    其余的QAction(除退出外)triggered()

整体效果如下:

在这里插入图片描述

功能实现

添加一个自定义代理类,类名为ComboBoxDelegate,基类为:QStyledItemDelegate

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()
{

}

/*
 *  1.创建一个编辑框
 *  参数:
 *      参数1 - 新创建的编辑器的父部件
 *      参数2 - 渲染视图的样式选项
 *      参数3 - 要编辑位置的模型索引
*/
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;
}

/*
 *  1.将模型中的数据放到编辑框中
 *      参数1:编辑组件
 *      参数2:需要设置数据的模型索引
 *
 *  创建的组件是QComboBox - 给的是QWidget
 */
void ComboBoxDelegate::setEditorData(QWidget *editor,
                   const QModelIndex &index) const
{
    static_cast<QComboBox*>(editor)->setCurrentText(
                index.model()->data(index, Qt::EditRole).toString());
}

/*
    1.将编辑框中的数据给到模型
*/
void ComboBoxDelegate::setModelData(QWidget *editor,
                  QAbstractItemModel *model,
                  const QModelIndex &index) const
{
    // index - 将数据放到模型的哪个位置
    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信号的槽函数。定义如下:

class TableWindow : public QMainWindow
{
    Q_OBJECT

private slots:
/*
    ...
*/
    // item选择发生了改变
    void on_m_selection_currentChanged(const QModelIndex &current,
                                        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的构造函数实现如下:

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); // view和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);
}

加粗的实现:

// 加粗Action的槽函数
void TableWindow::on_m_actBold_triggered(bool checked)
{
    // selectedIndexes - 获取所有的选中的项的索引
    for(QModelIndex const& index :
        m_selection->selectedIndexes()){
        QStandardItem* item = m_model->itemFromIndex(index);
        QFont font = item->font();// 获取该item的字体
        font.setBold(checked);
        item->setFont(font);
    }
}

打开文件的实现:

// 打开Action的槽函数
void TableWindow::on_m_actOpen_triggered()
{
    // 1.获取文件的路径
    QString path = QFileDialog::getOpenFileName(this, "打开",
                       QCoreApplication::applicationDirPath(),
                        "逗号分隔符文件(*.csv);;所有文件(*.*)");
    if(path.isEmpty())
        return;
    //2.打开文件
    // 构造一个QFile对象表示的就是path所指向的文件
    QFile file(path);
    // 打开file指向的文件 - 以只读和文本模式打开该文件
    if(!file.open(QIODevice::ReadOnly | QIODevice::Text))
        return;
    // 3.读取文件
    QTextStream stream(&file);// 构建文本流对象, 关联已经打开的文件
    //  stream.setCodec("utf-8");

    QStringList strings;
    // readLine - 读一行  atEnd - 文件末尾
    // 将数据读出来后 - 放到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();

    // 1.strings.at(0)   获取的是strings字符串列表中的第一个字符串
    // 设置列的标题 - 参数1:使用逗号将数据分割, 参数2:忽略空字符串
    // 将分割后的字符串作为列的标题
    m_model->setHorizontalHeaderLabels(
                    strings.at(0).split(",", Qt::SkipEmptyParts));

    // 2.填充数据 - 填充n-1次
    // values = 1001 张飞 1996-09-12 男 13512345678
    // 2.3.根据列数获取要循环放入数据的次数
    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);
}

保存的实现:

//  保存Action对应的槽函数
void TableWindow::on_m_actSave_triggered()
{
    // 1.获取保存文件路径
    QString path = QFileDialog::getSaveFileName(this, "保存",
                       QCoreApplication::applicationDirPath(),
                        "逗号分隔符文件(*.csv);;所有文件(*.*)");
    if(path.isEmpty())
        return;
    // 2.打开文件
    // 构造一个QFile对象表示的就是path所指向的文件
    QFile file(path);
    // 打开file指向的文件 - 以可读可写和文本模式  + 清空原文件打开该文件
    if(!file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate))
        return;
    // 3.写入文件
    QTextStream stream(&file);// 构建文本流对象, 关联已经打开的文件
    //  stream.setCodec("utf-8");

    // 4.写入列标题
    // 学号  姓名  出生日期  性别  电话
    // 循环遍历模型的列 逐列写入标题到文件中
    int columnCount = m_model->columnCount();
    for(int col = 0; col < columnCount; col++){
        stream << m_model->horizontalHeaderItem(col)->text() <<
                  (col == columnCount - 1 ? "\n" : ",");
    }

    // 5.写入数据行
    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" : ",");
        }
    }

    // 6.关闭文件
    file.close();
}

预览的实现:

//预览Action对应的槽函数
void TableWindow::on_m_actPreview_triggered()
{
    // 1.清空QPlainTextEdit内容
    ui->m_edit->clear();

    // 2.显示列标题
    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);


    // 3.显示数据行
    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);
    }
}

添加的实现:

// 添加Action对应的槽函数
void TableWindow::on_m_actAppend_triggered()
{
    // 1.先获取有多少列
    int columnCount = m_model->columnCount();
    if(!columnCount)
        return;

    // 2.创建空数据项
    // 创建新的空数据行 - 需要columnCount个数据项
    QList<QStandardItem*> items;// 用于存储空的数据项
    for(int col = 0; col < columnCount; col++)
        items << new QStandardItem;
    // 3.插入新行
    m_model->insertRow(m_model->rowCount(), items);

    // 4.设置当前选择
    m_selection->clearSelection();
    m_selection->setCurrentIndex(
                m_model->index(m_model->rowCount() -1, 0), QItemSelectionModel::Select);
}

插入的实现:

// 插入Action对应的槽函数
void TableWindow::on_m_actInsert_triggered()
{
    // 1.先获取有多少列
    int columnCount = m_model->columnCount();
    if(!columnCount)
        return;

    // 2.创建空数据项
    // 创建新的空数据行 - 需要columnCount个数据项
    QList<QStandardItem*> items;// 用于存储空的数据项
    for(int col = 0; col < columnCount; col++)
        items << new QStandardItem;

    // 3.获取当前的模型索引
    QModelIndex current = m_selection->currentIndex();
    m_model->insertRow(current.row(), items);

    // 4.设置当前选择
    m_selection->clearSelection();
    m_selection->setCurrentIndex(current, QItemSelectionModel::Select);
}

删除的实现:

// 删除Action对应的槽函数
void TableWindow::on_m_actDelete_triggered()
{
    // 1.先获取当前位置的索引
    // 1.1.获取上一行,同一列的位置(上一行, 同一列)
    // 1.2.判断当前行是否是最后一行
    QModelIndex current = m_selection->currentIndex();
    QModelIndex above = m_model->index( current.row() - 1, current.column());
    bool last = current.row() == m_model->rowCount() - 1;

    // 2.移除选中行
    m_model->removeRow(current.row());
    m_selection->setCurrentIndex(last ? above : current,
                                 QItemSelectionModel::Select);
}

对齐的实现:

// 左对齐Action对应的槽函数
void TableWindow::on_m_actLeft_triggered()
{
    // 1.遍历选中的索引
    for(QModelIndex const& index : m_selection->selectedIndexes())
    {
        m_model->itemFromIndex(index)->setTextAlignment(
                    Qt::AlignLeft | Qt::AlignVCenter);
    }
}
// 中对齐Action对应的槽函数
void TableWindow::on_m_actCenter_triggered()
{
    // 1.遍历选中的索引
    for(QModelIndex const& index : m_selection->selectedIndexes())
    {
        m_model->itemFromIndex(index)->setTextAlignment(
                    Qt::AlignHCenter | Qt::AlignVCenter);
    }
}
// 右对齐Action对应的槽函数
void TableWindow::on_m_actRight_triggered()
{
    // 1.遍历选中的索引
    for(QModelIndex const& index : m_selection->selectedIndexes())
    {
        m_model->itemFromIndex(index)->setTextAlignment(
                    Qt::AlignRight | Qt::AlignVCenter);
    }
}

状态栏文本动态变化的实现:

// 选择项改变对应的槽函数
void TableWindow::on_m_selection_currentChanged(const QModelIndex &current,
                                                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());

    // 检查并更新粗体显示状态
    // item->font().bold() - 判断选中的item的字体是否为 粗体
    ui->m_actBold->setChecked(item->font().bold());
}

运行效果如下:

在这里插入图片描述


本章完结

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/543797.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

jvisualVM分析jvm内存使用快照dump

服务发生内存溢出&#xff0c;就需要查看服务器上Java服务的jvm堆内存使用情况&#xff0c;可以使用dump命令生成dump文件&#xff0c;然后下载到本地&#xff0c;然后使用jvisualVM工具打开&#xff0c;即可实现可视化分析。 生成dump文件常用的两种方式&#xff1a; 第一种…

linux fixmap分析

本文基于Linux-4.19.125&#xff0c; ARM V7&#xff0c;dual core, MMU采用2级页表&#xff08;未开启LPAE&#xff09;。 1 为什么需要fixmap Linux内核启动过程中&#xff0c;经过汇编阶段后&#xff0c;mmu功能已经开启&#xff0c;后续只能通过虚拟地址来访问DDR&#x…

RAG应用开发实战02-相似性检索的关键 - Embedding

1 文本Embedding 将整个文本转化为实数向量的技术。 Embedding优点是可将离散的词语或句子转化为连续的向量&#xff0c;就可用数学方法来处理词语或句子&#xff0c;捕捉到文本的语义信息&#xff0c;文本和文本的关系信息。 ◉ 优质的Embedding通常会让语义相似的文本在空…

Linux 添加启动服务--Service

1&#xff0c;服务配置service文件 Service 服务的实际作用是开启后自动启动服务&#xff0c;运行一些不须要登录的程序&#xff0c;任务。 实例1、上电自动连接WIFI热点 1.1 新建.service文件 /etc/systemd/system/wificonnect.service [Unit] DescriptionService [wifico…

记录linux从0部署java项目(宝塔)

目录 一、安装宝塔可视化界面 二、部署前端 三、部署后端 1、配置并连接Mysql数据库 2、配置并连接redis 3、安装jdk 这里先记录一个安装后遇到的问题 安装openJDK 四、检查 一、安装宝塔可视化界面 宝塔面板下载&#xff0c;免费全能的服务器运维软件 运行安装脚本 安…

MySQL 社区版 安装总结

很早就安装过MySQL&#xff0c;没有遇到过什么问题&#xff0c;直接next就行了&#xff0c;这次在新电脑上安装却遇到了一些问题&#xff0c;记录一下。 安装的是MySQL社区版&#xff0c;下载地址是www.mysql.com&#xff0c;进入后选择DOWNLOAD页面&#xff0c;选择MySQL Com…

基本的数据类型在16位、32位和64位机上所占的字节大小

1、目前常用的机器都是32位和64位的&#xff0c;但是有时候会考虑16位机。总结一下在三种位数下常用的数据类型所占的字节大小。 数据类型16位(byte)32位(byte)64位(byte)取值范围char111-128 ~ 127unsigned char1110 ~ 255short int / short222-32768~32767unsigned short222…

HarmonyOS实战开发-自定义通知角标、如何设定应用的桌面图标角标的功能。

介绍 本示例主要展示了设定应用的桌面图标角标的功能&#xff0c;使用ohos.notificationManager 接口&#xff0c;进行桌面角标的设置&#xff0c;通知的发送&#xff0c;获取等。 效果预览 使用说明 在使用本应用时&#xff0c;需安装并启动仿桌面应用&#xff1b;在主界面…

LeetCode 57—— 插入区间

阅读目录 1. 题目2. 解题思路3. 代码实现 1. 题目 2. 解题思路 第一步&#xff0c;我们先寻找新区间和原始区间列表的重叠部分。 假设新区间为 [ x 1 , x 2 ] [x_1, x_2] [x1​,x2​]&#xff0c;原始区间列表中的其中一个区间为 [ y 1 , y 2 ] [y_1, y_2] [y1​,y2​]&…

PostgreSQL入门到实战-第三十弹

PostgreSQL入门到实战 PostgreSQL教程网站官网地址PostgreSQL概述更新计划 PostgreSQL教程网站 https://www.postgresqltutorial.com/ 官网地址 声明: 由于操作系统, 版本更新等原因, 文章所列内容不一定100%复现, 还要以官方信息为准 https://www.postgresql.org/PostgreS…

【数据工具】ArcGIS批量出图工具箱

工具下载链接&#xff1a;数据下载链接 我们在使用Arcgis制图的过程中&#xff0c;经常会遇到需要大量出图的情况&#xff0c;如何将做好的图批量导出jpg是一件令人头疼的问题。 今天小编就给大家分享俩个ArcGIS批量出图的工具箱&#xff0c;一个可以批量导出图层为jpg&#…

运筹说 第112期 | M/M/s等待制排队模型

通过上期学习&#xff0c;大家已经了解了排队论中的一些基本概念&#xff0c;以及生灭过程和Poisson过程。 那么本期小编将基于这些基本原理&#xff0c;为大家介绍M/M/s混合制排队模型&#xff0c;包括单服务台模型和多服务台模型&#xff0c;介绍模型的概念以及推导过程等内容…

PoE 技术

1 PoE 技术产生背景 随着 WLAN 、 VoIP 、网络视频监控等新业务的飞速发展,大量的无线 LAN 访问点、 IP 电话、 IP 网络摄像头等基于 IP 的终端出现在工业现场。这些设备通常数量众多、位置特殊 、 布线复杂、设备取电困难,其实施部署不仅消耗大量人力物力,…

【数据结构与算法】搜索算法(深度优先搜索 DFS和广度优先搜索 BFS)以及典型算法例题

目录 搜索算法&#xff08;深度优先搜索DFS和广度优先搜索BFS&#xff09;以及典型算法例题深度优先搜索 &#xff08;Depth First Search 简称 DFS&#xff09;DFS 的设计步骤深度优先搜索&#xff08;DFS&#xff09;算法例题例题一&#xff1a;N皇后问题例题二&#xff1a;路…

FreeRTOS学习 -- FreeRTOSConfig.h介绍

一、FreeRTOSConfig.h文件 FreeRTOS 的系统配置文件为 FreeRTOSConfig.h&#xff0c;在此配置文件中可以完成 FreeRTOS 的裁剪和配置。 FreeRTOS 的配置基本是通过在 FreeRTOSConfig.h 中使用“#define”这样的语句来定义宏定义实现的。在 FreeRTOS 的官方 demo 中&#xff0…

CentOS7离线升级OpenSSH_8.8p1

一、环境 centos7.9,升级openssh到8.8p1最新版本 二、下载升级包 # openssl和zlib为相关依赖 wget https://cdn.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-8.8p1.tar.gz wget https://www.openssl.org/source/openssl-1.1.1j.tar.gz wget http://www.zlib.net/zl…

mysql dll文件的缺失和Can‘t connect to MySQL server on ‘localhost‘ (10061)

个人笔记&#xff08;整理不易&#xff0c;有帮助&#xff0c;收藏点赞评论&#xff0c;爱你们&#xff01;&#xff01;&#xff01;你的支持是我写作的动力&#xff09; 笔记目录&#xff1a;学习笔记目录_pytest和unittest、airtest_weixin_42717928的博客-CSDN博客 个人随笔…

数学:人工智能学习之路上的“拦路虎”及其背后的奥秘

在人工智能的浪潮席卷全球的今天&#xff0c;越来越多的人开始涉足这一领域&#xff0c;以期掌握其核心技术&#xff0c;为未来的科技发展贡献力量。然而&#xff0c;在学习的道路上&#xff0c;许多人却遇到了一个不小的挑战——数学。为何数学会成为学习人工智能的“拦路虎”…

js学习总结

这里写目录标题 前情提要JavaScript书写位置1. 内部javaScript (不常用)2. 外部javaScript (常用)3.内联javaScript (常用) js中的输入和输出输出语法1. document.write()2. alert()3. console.log() 输入语法prompt() 前情提要 1. 在javaScript中的 分号 是可以省略的JavaScr…

rocketmq面试

broker主从复制机制 同步复制&#xff1a; 等Master和Slave均写成功后&#xff0c;才反馈给客户端写成功状态&#xff1b; 如果Master出故障&#xff0c; Slave上有全部的备份数据&#xff0c;容易恢复&#xff0c;但是同步复制会增大数据写入延迟&#xff0c;降低系统吞吐量。…