由QTableView/QTableWidget显示进度条和按钮,理解qt代理delegate用法

背景:

我的最初应用场景,就是要在表格上用进度条显示数据,以及放一个按钮。

qt-creator中有自带的delegate示例可以参考,但终归自己动手还是需要理解细节,否则不能随心所欲。

自认没那个天赋,于是记录下来以便日后参考。

qt自带示例:

打开qt-creator首页,点击左侧示例,搜索那里输入delegate就列出来几个,常见的也就是spinbox输入数值。

83772f912d4b4bacb90cc3581da309aa.png

上图中的第一个例子是个意外,我觉得挺好玩的,关于QItemEditorFactory和QItemEditorCreatorBase的用法,可以设置item的编辑方式,和代理有异曲同工之妙。也许本质上有某种联系,但我没有深究,本次只想记录代理的用法。

用法宗旨:

按照资料以及网上的说法,无非就是继承QStyledItemDelegate,重写几个函数,然后设置为QTableView/QTableWidget的代理即可。但关键是,QStyledItemDelegate咋用。

我认为还是始终贯彻MVC模式的应用,比如一个view绑定了一个model,则model的数据是和view同步的。前后端分离的本质是:view是躯壳,model是灵魂。肉体感受可以影响灵魂,灵魂变化也会反应到肉体。打住!再分析就可以感悟人性了。

主要是继承QStyledItemDelegate之后,重写那几个函数的意义。我认为常用的几个:paint,createEditor,editorEvent,setEditorData,setModelData,updateEditorGeometry。看名字就知道,凡是带editor的都是需要编辑view时才用。

paint函数:

绘制代理区域,如果希望表格一显示,马上就要看到代理控件的时候用,比如我要显示进度条或者按钮,我希望界面一出来它就有。而不是编辑这个单元格的时候才出来。

至于paint调用的时机,如果要代码控制,就控制model好了,view同步显示数据时,会重绘界面。

延伸题外话:

我试过其它方式修改数据,然后总想别的方法来调用主动调用paint函数,没戏的。比如this->repaint(),不行的。除非把界面最小化再恢复,或者点一下单元格。反正这个paint别想着自己控制它,就用model的item间接更新就行了。所以,都说tableview比widget优化显示速度之类的说法,实际上是mvc内部优化的意思。

很早以前,大约十多年了,我用vs做了一个超大的电子表格,因为单元格太多,业务上又必须这样,所以ui更新效率很低。最后也是让grid绑定了datatable,然后通过更新datatable来间接更新grid才解决。其实所谓的mvc别说多先进,早期vb6.0时代就有这样的雏形了。隐约记得,那时候叫控件绑定数据源,道理类似。

另外,现在的扁平化风格,有时候看起来特别别扭,直接绘制出来的控件就是死的,例如按钮,看不到按下去的感觉。也许通过style能实现,但我没尝试。

通常paint函数重写的内容主要是定义QStyleOption和drawControl。比如:

void Debug_Delegate_Button::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{Q_UNUSED(index);QStyleOptionButton btnStyle;btnStyle.text = "exec";btnStyle.rect = option.rect;btnStyle.state = QStyle::State_Active;QPushButton btn;btn.setEnabled(m_bEnabled);btn.style()->drawControl(QStyle::CE_PushButton, &btnStyle, painter, &btn);
}

我亲测的感受是,QStyleOption是用于定义显示效果的,drawControl只是画出来。

上面的代码定义了一个按钮,下面我写了个setEnable,但我感觉意义不大,主要看上面的Style。

createEditor函数:

qt手册说是返回用于编辑item的控件指针。也就是编辑单元格时显示的控件实例,需要在这个函数里new出来并return。亦即,你得告诉代理,要用什么控件来编辑。

我的理解是,其它凡是带有editor入参的函数,都依赖这个函数,否则editor就是空指针,会报错的。这一点手册里我没看到哪里有提到,但亲测就是如此。

editorEvent函数:

相当于一个eventFilter。被代理的那个区域,如果发生事件,从这里写响应。

比如,我做了一个发送命令的界面:

a77dbe18af4e470dae07384da3f80d90.png

首先那个exec按钮是使用paint函数画上去的,但就是死的一匹。然后使用editorEvent函数捕获了鼠标点击事件。cpp代码如下:


void Debug_Delegate_Button::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{Q_UNUSED(index);QStyleOptionButton btnStyle;btnStyle.text = "exec";btnStyle.rect = option.rect;btnStyle.state = QStyle::State_Active;QPushButton btn;btn.setEnabled(m_bEnabled);btn.style()->drawControl(QStyle::CE_PushButton, &btnStyle, painter, &btn);
}
bool Debug_Delegate_Button::editorEvent(QEvent *event, QAbstractItemModel *model,const QStyleOptionViewItem &option, const QModelIndex &index)
{Q_UNUSED(model);QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);if(option.rect.contains(mouseEvent->pos())){//Two signals will be send. Select one of them.//event->type() == QEvent::MouseButtonPress//event->type() == QEvent::MouseButtonReleaseif (event->type() == QEvent::MouseButtonPress && m_bEnabled){emit sigClicked(index.row());}}return true;
}

当捕获鼠标点击事件时,发送一个信号给外界,外界再处理这个信号,就知道是点击了哪一行的按钮。

setEditorData函数:

还是要提MVC模式的应用,比如一个view绑定了一个model,则model的数据是和view同步的。当编辑某个view对应的item时,如果使用了代理控件编辑它,控件就叫editor,用户操作editor,editor会把数据写入item。反之,用户更新item,editor也会同步更新。

所以,这个函数就是通过item更新editor。因为有editor入参,需要配合重写createEditor函数并返回editor。

setModelData函数:

通过操作editor更新item。因为有editor入参,需要配合重写createEditor函数并返回editor。

updateEditorGeometry函数:

手册提到:

Updates the geometry of the editor for the item with the given index, according to the rectangle specified in the option. If the item has an internal layout, the editor will be laid out accordingly. Note that the index contains information about the model being used.

我认为就是字面意思,用于更新区域显示布局效果,通常写一句editor->setGeometry(option.rect);即可。

官方实例spinbox:

如果要通过编辑view来更新model,也就是示例中spinbox用法,则看一段官方实例:

delegate.h

#ifndef DELEGATE_H
#define DELEGATE_H#include <QStyledItemDelegate>//! [0]
class SpinBoxDelegate : public QStyledItemDelegate
{Q_OBJECTpublic:SpinBoxDelegate(QObject *parent = nullptr);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;
};
//! [0]

delegate.cpp

#include "delegate.h"#include <QSpinBox>//! [0]
SpinBoxDelegate::SpinBoxDelegate(QObject *parent): QStyledItemDelegate(parent)
{
}
//! [0]//! [1]
QWidget *SpinBoxDelegate::createEditor(QWidget *parent,const QStyleOptionViewItem &/* option */,const QModelIndex &/* index */) const
{QSpinBox *editor = new QSpinBox(parent);editor->setFrame(false);editor->setMinimum(0);editor->setMaximum(100);return editor;
}
//! [1]//! [2]
void SpinBoxDelegate::setEditorData(QWidget *editor,const QModelIndex &index) const
{int value = index.model()->data(index, Qt::EditRole).toInt();QSpinBox *spinBox = static_cast<QSpinBox*>(editor);spinBox->setValue(value);
}
//! [2]//! [3]
void SpinBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,const QModelIndex &index) const
{QSpinBox *spinBox = static_cast<QSpinBox*>(editor);spinBox->interpretText();int value = spinBox->value();model->setData(index, value, Qt::EditRole);
}
//! [3]//! [4]
void SpinBoxDelegate::updateEditorGeometry(QWidget *editor,const QStyleOptionViewItem &option,const QModelIndex &/* index */) const
{editor->setGeometry(option.rect);
}
//! [4]

createEditor函数定义了用于编辑单元格的spinbox控件,并返回editor控件指针,因为其它函数用到。

setEditorData函数把model中的item数据更新到spinbox。

setModelData函数把spinbox的操作结果更新到model的item。

updateEditorGeometry函数负责更新显示区域。

实践:

上面提到过放置button并可以响应点击的用法,只用到了paint和editorEvent函数。下面再记录进度条的用法。

因为进度条用于显示而不是编辑,需要界面一打开就能看到进度条,所以需要paint函数。而进度条又不像按钮那样需要用户操作,然后就没有然后了。

delegate_progressbar.h

#ifndef DELEGATE_PROGRESSBAR_H
#define DELEGATE_PROGRESSBAR_H#include <QStyledItemDelegate>
#include <QProgressBar>class Delegate_ProgressBar : public QStyledItemDelegate
{Q_OBJECT
public:Delegate_ProgressBar(QObject *parent = nullptr);enum EItemValue { eIV_Maxinum, eIV_Value };void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
};#endif // DELEGATE_PROGRESSBAR_H

delegate_progressbar.cpp

#include "delegate_progressbar.h"Delegate_ProgressBar::Delegate_ProgressBar(QObject *parent): QStyledItemDelegate(parent)
{}
void Delegate_ProgressBar::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{//这里控制显示效果QStyleOptionProgressBar pbarStyle;pbarStyle.rect = option.rect;pbarStyle.state = QStyle::State_Active;pbarStyle.maximum = index.data(Qt::UserRole + eIV_Maxinum).toInt();//设置进度条最大值pbarStyle.progress = index.data(Qt::UserRole + eIV_Value).toInt();//设置当前进度//这里把它绘制出来QProgressBar pbar;pbar.style()->drawControl(QStyle::CE_ProgressBar, &pbarStyle, painter, &pbar);
}

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>
#include <QStandardItemModel>
#include <QTimer>
#include "delegate_progressbar.h"namespace Ui {
class MainWindow;
}class MainWindow : public QMainWindow
{Q_OBJECTpublic:explicit MainWindow(QWidget *parent = nullptr);~MainWindow();private slots:void on_pushButton_clicked();void onTimerOut();private:Ui::MainWindow *ui;QStandardItemModel *m_model = nullptr;Delegate_ProgressBar *m_delegate = nullptr;QTimer *m_timer = nullptr;int m_i = 0;
};#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow)
{ui->setupUi(this);m_model = new QStandardItemModel(2, 3, this);ui->tableView->setModel(m_model);m_delegate = new Delegate_ProgressBar;ui->tableView->setItemDelegateForColumn(2, m_delegate);QStandardItem *item = new QStandardItem;item->setData(100, Qt::UserRole + Delegate_ProgressBar::eIV_Maxinum);//这里给进度条设置最大值,相当于setMaximum。m_model->setItem(0, 2, item);//这是model的另一种用法,与直接操作item一样。
//    QModelIndex index = m_model->index(0, 2, QModelIndex());
//    m_model->setData(index, QVariant(50));m_timer = new QTimer(this);m_timer->setInterval(100);connect(m_timer, &QTimer::timeout, this, &MainWindow::onTimerOut);
}MainWindow::~MainWindow()
{delete ui;
}
void MainWindow::onTimerOut()
{m_model->item(0, 2)->setData(m_i++, Qt::UserRole + Delegate_ProgressBar::eIV_Value);//这里给进度条设置当前值,相当于setValue。if (m_i == 100){m_timer->stop();}
}
void MainWindow::on_pushButton_clicked()
{m_timer->start();
}

再提MVC模式。这里用到的tableview,当绑定model之后,即使model没有任何item,也可以设置行数和列数,运行效果依然能看到表格有行有列,但只能看不能操作。

给model添加item之后,每个item对应一个单元格,才能实现数据同步显示。

我这里需要单元格显示进度条,而进度条本质是max和value两个int数据,所以只要用代码控制响应item,让它存储的数据发生变化,则进度条就会响应变化了。

妥善起见,显示进度条的单元格设置为readonly。

item是个好东西,就像结构体一样,可以按角色字段划分,存储很多数据。而这些数据是QVariant类型,也就是任意类型,我还用它存过对象指针,基本上可以无限扩展,非常好用。

所以,如果要显示进度条的那个单元格能保存max和value两个int数据,非常简单,setData时,指定Qt:UserRole即可。比如:

item->setData(100, Qt:UserRole);

item->setData(50, Qt:UserRole + 1);

Qt:UserRole是枚举,后面加几都可以,那就根据需要定义成枚举,见名知意,比如:

item->setData(100, Qt:UserRole + eEnum_Max);

item->setData(50, Qt:UserRole + eEnum + Value);

就可以随便怎么玩了。

本文完。

 

 

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

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

相关文章

判断一个数字是否是奇数

思路&#xff1a; 用scanf读取一个数字num后&#xff0c;如果它不能被2整除&#xff0c;那么它是奇数&#xff0c;否则就是偶数。 #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> //引用头文件 int main() {int num;printf("请输入一个数字 &#xff1a;&qu…

正向代理和反向代理与负载均衡

自存用 什么是反向代理&#xff0c;反向代理与正向代理的区别 一文帮你梳理清楚「正向代理和反向代理的区别与联系」 什么是反向代理服务器 正向代理为用户服务&#xff0c;给用户换个ip使其能访问其他网站 反向代理为服务器服务&#xff0c;使用户访问特定网站服务器。反向代…

JAVA 实现PDF转图片(spire.pdf.free版)

1.引入jar包 导入方法1&#xff1a; 手动引入。将Free Spire.PDF for Java下载到本地&#xff0c;解压&#xff0c;找到lib文件夹下的Spire.PDF.jar文件。在IDEA中打开如下界面&#xff0c;将本地路径中的jar文件引入Java程序&#xff1a; 导入方法2&#xff1a;如果您想通过…

Java设计模式之命令模式

目录 定义 结构 案例 优点 缺点 使用场景 JDK源码解析 Thread中start与run方法的区别 定义 将一个请求封装为一个对象&#xff0c;使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通&#xff0c;这样方便将命令对象进行存储、传递、调用、增…

Java调用HTTPS接口,绕过SSL认证

1&#xff1a;说明 网络编程中&#xff0c;HTTPS&#xff08;Hypertext Transfer Protocol Secure&#xff09;是一种通过加密的方式在计算机网络上进行安全通信的协议。网络传输协议&#xff0c;跟http相比更安全&#xff0c;因为他加上了SSL/TLS协议来加密通信内容。 Java调…

Thinkphp6项目在虚拟机无法指向pulic的目录访问的方法

以阿里云虚拟主机为例&#xff0c;服务器环境为 LAMP&#xff0c;Apache2.4 php7.2 mysql5.7 1.根目录新建 index.php 文件&#xff0c;将以下内容放入文件中 <?php include ./public/index.php;2.将 public 目录下的 admin.php、backend 文件夹、static 文件夹、tinymc…

Linux--线程--互斥锁

1.互斥量 a&#xff09;互斥量&#xff08;mutex&#xff09;从本质上来说是一把锁&#xff0c;一般在主线程中定义一个互斥量&#xff0c;就是定义一把锁。然后根据我们的需求来对线程操作这把锁。 b&#xff09;如果给所有的线程都加上锁了&#xff0c;线程们会去争取内存空…

Python 中的 Schedule

本篇文章将介绍 Python 中的 Schedule 包&#xff0c;以在特定时间间隔后定期安排作业。 Schedule是Python中的一个轻量级进程调度程序库&#xff0c;用于安排任务以指定的时间间隔定期运行。 我们可以使用人类友好的语法调用函数或任何可调用对象来自动执行任务&#xff0c;…

基于深度学习的语音识别算法的设计与实现

收藏和点赞&#xff0c;您的关注是我创作的动力 文章目录 概要 一、课题内容二、需求分析2.1 算法需求分析2.2 语音录制2.3 声学模型2.4 语言模型2.5 训练集和测试集2.6 深度神经网络 三 算法设计原理3.1 语音识别系统3.1.1 声学模型3.1.2 语言模型3.1.3 发音词典 四 简单问答…

全新二开游戏支付通道/话费/电网、紫水晶带云端源码

源码修复可用&#xff0c;YY业务都可用 本店所售程序只供测试研究&#xff0c;不得使用于非法用途&#xff0c;不得违反国家法律&#xff0c;不得用于进行违法行为&#xff0c;否则后果自负&#xff01;购买以后用作他用附带的一切法律责任后果都由购买者承担于本店无任何关…

jQuery中ajax如何使用

jQuery中ajax如何使用及代码详解 1. 引言 在现代Web开发中&#xff0c;使用Ajax进行异步数据交互变得非常普遍。而在jQuery中&#xff0c;提供了便捷的方法来实现Ajax请求&#xff0c;简化了开发过程。本文将介绍jQuery中如何使用Ajax以及通过代码详解其使用方法。 2. Ajax简介…

有效的数独

题目链接 有效的数独 题目描述 注意点 board.length 9board[i].length 9board[i][j] 是一位数字&#xff08;1-9&#xff09;或者 ‘.’ 解答思路 首先判断行是否满足数独条件&#xff0c;再判断列是否满足数独条件&#xff0c;最后再判断划分的3x3方格是否满足数独条件…

Tuna: Instruction Tuning using Feedback from Large Language Models

本文是LLM系列文章&#xff0c;针对《Tuna: Instruction Tuning using Feedback from Large Language Models》的翻译。 Tuna:使用来自大型语言模型的反馈的指令调优 摘要1 引言2 方法3 实验4 相关工作5 结论局限性 摘要 使用更强大的LLM&#xff08;如Instruction GPT和GPT-…

PCI9054入门1:硬件引脚定义、时序、FPGA端驱动源码

文章目录 1&#xff1a;PCI9054的FPGA侧&#xff08;local侧引脚定义&#xff09;2&#xff1a;PCI9054的C模式下的读写时序3&#xff1a;FPGA代码部分具体代码&#xff1a; 1&#xff1a;PCI9054的FPGA侧&#xff08;local侧引脚定义&#xff09; 而PCI9054的本地总线端的主要…

小程序day01

简介: 小程序项目的基本结构 页面的组成部分 一个页面对应一个文件夹&#xff0c;所有有关的内容都放在一起。 JSON配置文件 2.app.json文件 3.project.config.json文件 4.sitemap.json文件 5.页面的.json配置文件 6. 新建小程序页面 7.修改项目首页 小程序代码构成 小程序的宿…

zabbix6.4监控centos

1、关闭防火墙 setenforce 0 #关闭SELinux sed -i "s/SELINUX=enforcing/SELINUX=disabled/g" /etc/selinux/config #设置永久关闭SELinux systemctl stop firewalld.service #关闭防火墙 systemctl disable firewalld.service …

某汽车金融企业:搭建SDLC安全体系,打造智慧金融服务样本

某汽车金融企业是国内头部汽车金融公司&#xff0c;已经为超过数百万名客户提供专业的汽车金融服务。该公司通过近几年的数字化创新&#xff0c;在提升客户体验、提高管理效率、降低经营成本等方面已具备很强的服务能力&#xff0c;让客户获得更方便、更快捷、更灵活的金融服务…

VScode clangd 插件浏览 linux 源码

文章目录 VScode clangd 插件浏览 linux 源码clangd 安装与配置VScode 插件安装clangd 安装方法一方法二 clangd 配置 cmake 生成bear 生成 compile_commands.json触发 clangd linux 内核脚本生成 compile_commands.json 文件三种方式对比 VScode clangd 插件浏览 linux 源码 …

【计算机网络笔记】传输层——可靠数据传输之流水线机制与滑动窗口协议

系列文章目录 什么是计算机网络&#xff1f; 什么是网络协议&#xff1f; 计算机网络的结构 数据交换之电路交换 数据交换之报文交换和分组交换 分组交换 vs 电路交换 计算机网络性能&#xff08;1&#xff09;——速率、带宽、延迟 计算机网络性能&#xff08;2&#xff09;…

Kotlin this和it的使用区别

在 Kotlin 中&#xff0c;this 和 it 是两个关键字&#xff0c;用于引用不同的对象。 this 关键字&#xff1a; 在类或对象中&#xff0c;this 关键字引用当前对象本身。 在 Lambda 表达式中&#xff0c;this 关键字引用包含该 Lambda 的类实例。 class MyClass {private val…