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

运行效果如下:

在这里插入图片描述


本章完结

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

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

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

Java-Scanner类进阶+题目

Scanner进阶 接收整数数据时&#xff1a; 接收小数数据时&#xff1a; 例子&#xff1a; 可以先这样弄出scanner的框架&#xff1a; 未完待续... ...

科技论文和会议录制高质量Presentation Video视频方法

一、背景 机器人领域&#xff0c;许多高质量的期刊和会议&#xff08;如IEEE旗下的TRO&#xff0c;RAL&#xff0c;IROS&#xff0c;ICRA等&#xff09;在你的论文收录后&#xff0c;需要上传一个Presentation Video材料&#xff0c;且对设备兼容性和视频质量有较高要求&#…

2024年DTC的回顾与思考

刚结束了2024的数据库技术嘉年华 这是我从2017年开始就参加的技术大会。中途因为疫情的耽误。正常来说我是连续的。知道我的朋友都知道我习惯炫耀一下。 按照惯例&#xff0c;此时此刻群友都在写大会回顾。只是有几个不讲武德的人已经发送了。下面有主观和客观的分析。 主观上…

手机银行客户端框架之TMF框架介绍

腾讯移动开发平台&#xff08;Tencent Mobile Framework&#xff09;整合了腾讯在移动产品中开发、测试、发布和运营的技术能力&#xff0c;为企业提供一站式、覆盖全生命周期的移动端技术平台。核心服务包括移动客户端开发组件、H5容器、灰度发布、热更新、离线包、网关服务、…

Vol.41 SEO基本术语解释

1.TDK TDK&#xff1a;即标题、描述、关键词&#xff1b;TDK是网站的基本属性&#xff0c;对SEO非常重要&#xff0c;可以帮助搜索引擎了解你的网站&#xff1b; T&#xff1a;title 谷歌建议不超过70个字符 D&#xff1a;description 谷歌建议不超过150个字符 K&#xff1…

element-ui中el-radio-group组件绑定点击事件触发多次的解决办法

1、需求 电商首页需求&#xff0c;需要做个单选框&#xff0c;然后点击选中切换图标方向及更换价格升倒序&#xff0c;如下图&#xff1a; 从官网文档看&#xff0c;单选框支持change event&#xff0c;使用click加载按钮处不会触发选中 但是使用 click.native 事件不做处理…

ACID模型是什么

ACID模型是什么 ACID模型是数据库管理系统中保证事务处理安全性的一组特性。ACID是原子性&#xff08;Atomicity&#xff09;、一致性&#xff08;Consistency&#xff09;、隔离性&#xff08;Isolation&#xff09;和持久性&#xff08;Durability&#xff09;四个英文单词的…

开源博客项目Blog .NET Core源码学习(15:App.Hosting项目结构分析-3)

本文学习并分析App.Hosting项目中前台页面的关于本站页面和点点滴滴页面。 关于本站页面 关于本站页面相对而言布局简单&#xff0c;与后台控制器类的交互也不算复杂。整个页面主要使用了layui中的面包屑导航、选项卡、模版、流加载等样式或模块。   面包屑导航。使用layui…

初探vercel托管项目

文章目录 第一步、注册与登录第二步、本地部署 在个人网站部署的助手vercel&#xff0c;支持 Github部署&#xff0c;只需简单操作&#xff0c;即可发布&#xff0c;方便快捷&#xff01; 第一步、注册与登录 进入vercel【官网】&#xff0c;在右上角 login on&#xff0c;可登…

WXML模板语法-条件与列表渲染

wx:if 在小程序中&#xff0c;使用wx:if"{{condition}}"来判断是否需要渲染该代码 也可以用wx:elif和wx:else来添加else判断 <!--pages/ifIndex/ifindex.wxml--> <view wx:if"{{type 1}}">男</view> <view wx:elif"{{type …

【Linux】磁盘与文件系统管理

目录 一、 磁盘结构 1. 数据结构 2. 物理结构 3. 硬盘的接口类型 二、 如何使用Linux中的磁盘 三、 文件系统 四、 磁盘分区 1. MBR分区 2. 分区的优缺点 3. 磁盘及分区的管理工具 五、格式化与挂载 1. 格式化 2. 挂载 六、实例演示 1. 演示分区格式化挂载 2. …

24年大一4月14日训练(东北林业大学)

前言&#xff1a; 今晚的5道题。 正文&#xff1a; Problem:A宋哥猜拳(2)&#xff1a; #include<bits/stdc.h> using namespace std; int main(){int t;cin>>t;while(t--){string x;cin>>x;int a0,b0,c0,lx.length();// cout<<l;for(int i0;i<l;i…

反射与动态代理

一、反射 什么是反射? 反射允许对成员变量&#xff0c;成员方法和构造方法的信息进行编程访问 1.获取class对象的三种方式 Class这个类里面的静态方法forName&#xff08;“全类名”&#xff09;&#xff08;最常用&#xff09; 通过class属性获取 通过对象获取字节码文件对…

20240414,类的嵌套,分文件实现

笑死&#xff0c;和宝哥同时生病了 一&#xff0c;封装-案例 1.0 立方体类 #include<iostream>//分别用全局函数和成员函数判定立方体是否相等 using namespace std;class Cube { public:int m_area;int m_vol;int geth(){return m_h;}int getl() { return m_l; }int…

C# Window form 自定义控件的结构和设计(三)

C# Window form 自定义控件的结构和设计(三) 一、前面介绍了如何来创建第一个自定义的控件&#xff0c;以及一个测试程序。下面我们来看下如何在自定义控件中添加属性。 C#和其他.NET语言支持属性作为语言的第一类成员。把属性作为语言的基础属性有两点主要的有点&#xff1a…

Flutter第八弹 构建拥有不同项的列表

目标&#xff1a;1&#xff09;项目中&#xff0c;数据源可能涉及不同的模版&#xff0c;显示不同类型的子项&#xff0c;类似RecycleView的itemType, 有多种类型&#xff0c;列表怎么显示&#xff1f; 2&#xff09;不同的数据源构建列表 一、创建不同的数据源 采用类似Rec…

【设计模式】六大设计原则

设计原则 研究 23 种设计模式是困难的&#xff0c;甚至是没必要的六大设计原则零、单一职责原则开闭原则里氏代换原则依赖倒置原则接口隔离原则迪米特法则合成复用原则 研究 23 种设计模式是困难的&#xff0c;甚至是没必要的 设计模式有23种&#xff0c;我认为对普通人来说想…

快速寻找可以构建出网通信隧道的计算机

点击星标&#xff0c;即时接收最新推文 本文选自《内网安全攻防&#xff1a;红队之路》 扫描二维码五折购书 为加强内网的安全防范&#xff0c;安全管理员往往会限制内网计算机访问互联网&#xff0c;当然不同机构的限制策略是不一样的&#xff0c;有的完全阻断了内网计算机访问…

如何发布自己的Python库?

Python包发布 1、背景概述2、操作指南 1、背景概述 为什么我们要发布自己的Python库&#xff1f;如果你想让你的Python代码&#xff0c;通过pip install xxx的方式供所有人下载&#xff0c;那就需要将代码上传到PyPi上&#xff0c;这样才能让所有人使用 那么&#xff0c;如何发…