QT----基于QML的计时器

news2024/11/23 21:34:02

赶上了实习的末班车,现在在做QML开发,第一天的学习成果,一个计时器.逻辑挺简单的,纯QML实现,代码在仓库QT-Timer
在这里插入图片描述

学习使用c++的listmodel

学习使用了如何用c++的listmodel来存储数据.

新建一个TImeListModel类继承自QAbstractListModel

class TimeListModel : public QAbstractListModel
{
    Q_OBJECT
public:
    explicit TimeListModel(QObject *parent = nullptr);

创建一个结构体存储数据对,在用一个列表存储所有的数据,这个datalist就是listview

private:
    // 定义一个结构体来存储列表项的数据,包括 idnumber 和 timeStr
    struct Data{
        QString m_idnumber; // 存储编号
        QString m_timeStr;  // 存储时间字符串
    };

    // 使用 QList 来存储所有 Data 结构体的数据
    QList<Data> m_datalist;

然后重写三个函数,这三个函数是必须重写的,直接复制就行,都不用改.在定义一个枚举以便 QML 通过这些角色从模型中获取数据。具体来说,NumberRoleTimerRole 代表不同的数据属性,能够让 ListView 或其他基于模型的视图组件根据这些角色来访问对应的字段。


// 返回模型中数据的总数,用于 ListView 获取到数据的数量
    // 这个函数是 QAbstractListModel 的纯虚函数,必须实现
    int rowCount(const QModelIndex &parent = QModelIndex()) const override;

    // 返回模型的角色名称,用于在 QML 中根据角色访问数据
    // 例如,NumberRole 对应的别名可以被 ListView 使用
    virtual QHash<int, QByteArray> roleNames() const override;

    // 获取指定行和角色的数据,这个函数会在 ListView 渲染数据时被调用
    // 数据是从 QList 中的 Data 结构体获取的
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;

// 枚举角色,用于指定 QML 中访问数据的键值
    // NumberRole 和 TimerRole 允许 QML 中根据不同的键来访问数据
    enum DataRoles{
        NumberRole = Qt::UserRole + 1, // 用户自定义的角色从 Qt::UserRole 开始
        TimerRole,                     // 时间角色
    };

重写这三个函数的实现,根据你的数据直接抄就行,数据的个数与结构体和枚举对应

rowCount获取列表里元素的个数

roleNames设置角色与对应的名称,这样可以在qml使用model.m_idnumber来访问列表元素的idnumber

data用于获取索引和角色的数据

// 返回列表项的数量,用于 ListView 知道有多少项要显示
int TimeListModel::rowCount(const QModelIndex &parent) const
{
    // 返回数据列表的大小
    return m_datalist.size();
}

// 定义数据角色的名称映射,用于在 QML 中使用这些角色名称来访问数据
QHash<int, QByteArray> TimeListModel::roleNames() const
{
    // 设置角色与对应的名称,这样可以在 QML 中通过 "m_idnumber" 和 "m_timeStr" 获取对应的数据
    QHash<int,QByteArray> roles;
    roles[NumberRole] = "m_idnumber"; // 角色 NumberRole 对应 m_idnumber
    roles[TimerRole] = "m_timeStr";   // 角色 TimerRole 对应 m_timeStr
    return roles;
}

// 获取指定索引行和角色的数据,用于 ListView 显示数据
QVariant TimeListModel::data(const QModelIndex &index, int role) const
{
    int row = index.row(); // 获取当前索引的行号

    // 检查索引是否有效,避免访问越界
    if(row < 0 || row >= m_datalist.count()) {
        return QVariant(); // 返回无效数据
    }

    // 获取当前行对应的数据
    const Data &data = m_datalist[row];

    // 根据传入的角色返回不同的数据
    switch(role) {
    case NumberRole:
        return data.m_idnumber;  // 返回编号数据
    case TimerRole:
        return data.m_timeStr;   // 返回时间字符串数据
    default:
        return QVariant();       // 返回空值
    }
}

除了三个必须重写的函数,一般我们会添加一个增加数据 append和删除数据的函数,我这边直接清空就行使用 clear.将这两个函数暴露给QML,使用 Q_INVOKABLE添加上这个后,这两个函数可以在qml中调用

// Q_INVOKABLE 使得这些方法可以从 QML 中被调用
    // append 函数用于向模型中添加数据,接收 idnumber 和 timeStr 两个字符串
    Q_INVOKABLE void append(const QString &idnumber , const QString &timeStr);

    // 清空模型中的所有数据
    Q_INVOKABLE void clear();

实现这两个函数,添加数据时需要发送信号,添加时发送,添加完成发送,这样listView会在界面上实时刷新,如果想插入到末尾,发送信号位置是开始位置m_datalist.lastIndexOf(),结束位置也是

// 向数据列表中添加一项新数据
void TimeListModel::append(const QString &idnumber, const QString &timeStr)
{
    // 通知视图模型即将插入一行新数据,索引 0 表示新数据会插入到列表的最前面
    emit beginInsertRows(QModelIndex(), 0, 0);

    // 使用 prepend 将新的 Data 结构体添加到列表的开头
    m_datalist.prepend({idnumber, timeStr});

    // 通知视图模型插入操作完成,ListView 会根据此信号刷新显示
    emit endInsertRows();
}

// 清除所有数据
void TimeListModel::clear()
{
    int row = m_datalist.count(); // 获取当前数据的行数

    // 只有当数据列表不为空时才执行清除操作
    if(row > 0) {
        // 通知视图模型即将移除所有行,范围从 0 到最后一行
        emit beginRemoveRows(QModelIndex(), 0, m_datalist.size() - 1);

        // 清空数据列表
        m_datalist.clear();

        // 通知视图模型移除操作完成
        emit endRemoveRows();
    } else {
        return; // 如果没有数据,直接返回
    }
}

随后在main.cpp中注册这个listmodel,引入头文件,实例化model,这样在qml中可以使用m_TimeListModel这个对象.第二种方法需要在qml中实例化对象

//把写好的list模型注册到qml中
    TimeListModel listmodel;
    engine.rootContext()->setContextProperty("m_TimeListModel",&listmodel);

    //第二种方法
    qmlRegisterType<TimeListModel>("com.timeListModel",1,0,"TimeListModel");

    //在qml中import导入
    import com.timeListModel 1.0
    
    TimeListModel{
            id:m_TimeListModel
        }

现在可以把qml中ListView中原来的model替换为m_TimeListModel,在原来的逻辑中使用append和clear

ListView{
            id:list1
            anchors.fill:parent
            anchors.margins: 20 //让元素离listview有边界
            clip: true
            model:m_TimeListModel
            delegate: recordlist
            spacing: 5
        }

m_TimeListModel.append(index.toString(),totaltime);
TimeListModel.clear()

多线程优化

在使用的过程中发现自己的计时器时间会慢,并且一直点击记录的话时间1s可以走10s,排查发现是在计时器的间隔取得太小了,取了1太过于消耗资源,改成10的话能解决这个问题.同时也想尝试使用线程来解决.

新建TimerThread 类继承自QObject,只有这样才能使用线程.我们需要发送时间和运行的状态,因此使用信号和QML文件通信

#ifndef TIMERTHREAD_H
#define TIMERTHREAD_H

#include <QObject>
#include <QTimer>
#include <QThread>
#include <cmath>

/******************************************************************************
 *
 * @file       timerthread.h
 * @brief      把计时放入线程
 *
 * @author     纯真丁一郎
 * @date       2024/09/18
 * @Blog       https://www.relxdingyilang.cn/
 * @history
 *****************************************************************************/

class TimerThread : public QObject
{
    Q_OBJECT
public:
    explicit TimerThread(QObject *parent = nullptr);

    //判断运行状态
    bool isRunning = false;

    QString caculateTime(int totaltime);

signals:
    void timeUpdated(QString totaltimestr); //发送时间给主界面
    void sig_isRunning(bool isRunning);//发送状态

public slots:
    void start();
    void stop();
    void pause();
    void onTimeout();

private:
    int m_totaltime; //总时间
    QTimer *timer;
};

#endif // TIMERTHREAD_H

cpp里实现计时的功能启动计时器,计算时间格式.使用定时器的timeout信号,让我们的时间增加

#include "timerthread.h"

TimerThread::TimerThread(QObject *parent)
    : QObject{parent}
{
    m_totaltime = 0;
    timer = new QTimer(this);
    connect(timer,&QTimer::timeout,this,&TimerThread::onTimeout);

}

void TimerThread::start(){
    timer->start(1);
    isRunning = true;
    emit sig_isRunning(isRunning);
}

void TimerThread::pause(){
    timer->stop();
    isRunning = false;
    emit sig_isRunning(isRunning);
}

void TimerThread::stop(){
    timer->stop();
    isRunning = false;
    m_totaltime = 0;
    emit sig_isRunning(isRunning);
}

void TimerThread::onTimeout(){
    //计时
    m_totaltime += 1;
    emit timeUpdated(caculateTime(m_totaltime));
}

QString TimerThread::caculateTime(int totaltime){
    //格式化字符串
    int millisecond =totaltime % 1000;
    millisecond = std::floor(millisecond/10);
    int second = int(std::floor(totaltime /1000) )% 60;
    int minute = int(std::floor(totaltime/1000 /60)) % 60;

    QString result = (minute<10 ? "0":"") + QString::number(minute)+":"+
                     (second<10 ? "0":"") + QString::number(second) + ":"+
                     (millisecond<10 ? "0":"")+QString::number(millisecond);

    return result;
}

在main.cpp里实现多线程,实例化timerThread类,在实例化一个工作线程,把我们自己的类放入工作线程,启动工作线程即可.
同时我们需要qmlRegisterType来注册我们的类,这样才能让QML文件知道要与这个文件通信

 //注册计时线程,并将计时线程移动到工作线程
    TimerThread timerThread;
    QThread workerThread;
    timerThread.moveToThread(&workerThread);

    //启动工作线程
    workerThread.start();

    QQmlApplicationEngine engine;

    qmlRegisterType<TimerThread>("com.timerthread",1,0,"TimerThread");

main.qml的修改,首先使用import倒入我们的timerThread类,这样我们就可以在qml中实例化,可以加上idimport com.timerthread 1.0

定义两个变量,接受我们信号发送的参数.发送的参数的作用域只在Connection里,所以需要外部变量来接收,方便我们的使用,

 property bool isrunning: false
 property string totaltime: ""
 
 TimerThread{
        id:timerThread
    }
    Connections{
        target:timerThread
        // 使用传递过来的 totaltime 参数,信号传递出来的参数在connect内部可以直接使用,在外部不行
        onTimeUpdated:{
            timerDisplay.text = totaltimestr
            totaltime = totaltimestr
            //console.log(totaltime)
        }
        onSig_isRunning:{
            isrunning = isRunning
            console.log(isRunning)
        }
    }

后边就将原来的一些变量替换为新接收的变量就行.

使用多线程的方式,定时器间隔取1也能精确计时
实际上 创建的timerThread并没有被使用,在qml中又实例化了另一个TimerThread对象,这两是不同的实例,放入线程的并没有被使用
file

点击访问博客查看更多内容

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

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

相关文章

AIGC基础工具-科学计算和数据处理的重要库NumPy(Numerical Python)简介

文章目录 1. NumPy 的核心概念1.1 ndarray&#xff1a;多维数组对象示例代码 2. NumPy 的数据类型 (dtype)示例代码 3. NumPy 的数组创建方法3.1 使用 array() 创建数组3.2 使用 zeros() 和 ones()3.3 使用 arange() 和 linspace()3.4 使用 random 模块生成随机数组 4. NumPy 数…

AOT源码解析4.3-model主体解析

1.添加参考图像&#xff08;add_reference_frame&#xff09; 1.1 生成位置编码和ID编码 具体操作见详情。 图1&#xff1a;如图所示&#xff0c;显示的是参考图像的位置编码和id编码的生成过程。对于id编码&#xff0c;将mask图像输入进conv2d卷积网络后&#xff0c;进行结…

容器化安装Jenkins部署devops

基础环境介绍 系统使用的是centos7.9 内核使用的是5.16.13-1.el7.elrepo.x86_64 容器使用的是26.1.4 docker-compose使用的是 v2.29.0 链路图 devops 配置git环境插件 部署好jenkins后开始配置 jenkins连接git&#xff0c;这里需要jenkins有连接git的插件。在已安装的插件…

【SD教程】图片也能开口说话?别惊讶!用SadTalker插件,一键生成自己的数字人,本地部署,免费使用!(附资料)

最近数字人越来越火&#xff0c;连互联网大佬都纷纷下场&#xff0c;比如360的周鸿祎&#xff0c;京东的刘强东等等。小伙伴可能也想拥有自己的数字人如果想用最简单的方式&#xff0c;那么可以用第三方的网站&#xff0c;例如 HeyGen平台、腾讯的智影等等。可这些网站都是收费…

HFSS中看TDR波形详细设置以及相关的解释

时域反射测量&#xff08;TDR&#xff09;中心思想就是用阶跃函数作为激励&#xff0c;应用在模型上&#xff0c;并检查反射随时间的变化。在检查时域之前&#xff0c;必须对driven solution&#xff08;Modal、Terminal或Transient&#xff09;执行插值扫描。然后&#xff0c;…

vite分目录打包以及去掉默认的.gz 文件

1.vite打包情况介绍&#xff1a; 1.1vite在不进行任何配置的情况下&#xff0c;会将除开public的所有引用到资源打包编译添加哈希值至assets文件夹中&#xff08;非引用文件以及行内样式图片未被打包编译资源会被treeSharp直接忽略不打包&#xff09;&#xff0c;     1.2w…

阿里云函数计算 x NVIDIA 加速企业 AI 应用落地

作者&#xff1a;付宇轩 前言 阿里云函数计算&#xff08;Function Compute, FC&#xff09;是一种无服务器&#xff08;Serverless&#xff09;计算服务&#xff0c;允许用户在无需管理底层基础设施的情况下&#xff0c;直接运行代码。与传统的计算架构相比&#xff0c;函数…

极星Polestar EDI 项目案例

近期国内汽车行业供应商J公司收到了极星Polestar的邀请&#xff0c;需要通过EDI与其国内工厂传输业务数据。本案例将为大家介绍对接过程以及实施方案。 梳理需求文档 极星Polestar的EDI需求与Volvo一样&#xff0c;传输协议选择 OFTP&#xff0c;报文标准为EDIFACT&#xff0…

Swing模拟银行柜台系统

> 这是一个基于JavaSwing实现的模拟银行柜台系统。 > 具有管理员、柜员、客户三种登录角色。 > 支持开户、注册、存取款、转账、汇款、账单查询等功能。 > 本项目适合JAVA初学者作为入门学习项目。 一、部分界面演示 二、基础依赖 技术/框架版本描述Java11编…

Vue前端浏览器指纹获取:数字世界的身份密码

程序员必备宝典https://tmxkj.top/#/一个开源的JavaScript库&#xff0c;它通过收集用户浏览器的多种属性&#xff08;如屏幕分辨率、浏览器插件、字体、Canvas和WebGL等&#xff09;来生成一个独特的浏览器指纹&#xff0c;用于识别和追踪用户。 #Github地址 GitHub - finger…

Uniapp时间戳转时间显示/时间格式

使用uview2 time 时间格式 | uView 2.0 - 全面兼容 nvue 的 uni-app 生态框架 - uni-app UI 框架 <text class"cell-tit clamp1">{{item.create_time}} --- {{ $u.timeFormat(item.create_time, yyyy-mm-dd hh:MM:ss)}} </text>

apply、call和bind的作用和区别

apply与call 首先介绍一下apply与call&#xff0c;因为这两个方法的功能和使用方式都差不多&#xff0c;只是传参的方式不同。call和apply的作用都是改变函数运行时的上下文&#xff08;context&#xff09; 语法 fun.call(thisArg, arg1, arg2, ...)fun.apply(thisArg, arg…

类的难疑点

一、知识点 1、类的属性和对象属性&#xff08;实例属性&#xff09; shuxing"123" self.shuxing"123" 2、类的对象 self.loginMyclass() loginMyclass() 3、访问类属性和方法的操作 通过“类名.属性”访问&#xff1a;Myclass.shuxing 通…

详解常见排序

目录 ​编辑 插入排序 希尔排序&#xff08;缩小增量排序&#xff09; 选择排序 冒泡排序 堆排序 快速排序 hoare版 挖坑法 前后指针法 非递归版 归并排序 递归版 非递归版 计数排序 声明&#xff1a;以下排序代码由Java实现&#xff01;&#xff01;&#xff01…

【研赛D题成品论文】24华为杯数学建模研赛D题成品论文(第一问)+可运行代码丨免费分享

2024华为杯研究生数学建模竞赛D题精品成品论文已出&#xff01; D题 大数据驱动的地理综合问题 一、问题分析 问题一&#xff1a;目标&#xff1a;利用1990-2020年的数据&#xff0c;针对降水量和土地利用的时空演化特征进行描述。数据&#xff1a;两个核心变量&#xff0c;一…

电商效果图渲染神器:轻松高效出图

在这个电商行业飞速发展的今天&#xff0c;离不开商品图的效果。而电商效果图同样离不开渲染&#xff0c;而大量的渲染需求有需要大量的机器&#xff0c;还要追求更快的渲染速度和更稳定的性能。毕竟&#xff0c;谁不想快点完成项目又省心呢&#xff1f; 而云渲染服务是个很好…

C++之STL—deque容器

双端数组 区别于 vector (单端数组)&#xff0c; 构造函数 注意&#xff1a;读取数据时&#xff0c;const修饰保证函数内只能读取&#xff0c;不能修改数据 void print(const deque<int>& deq) {for (deque<int>::const iterator it deq.begin(); it ! deq.e…

使用 Nuxt Kit 的构建器 API 来扩展配置

title: 使用 Nuxt Kit 的构建器 API 来扩展配置 date: 2024/9/24 updated: 2024/9/24 author: cmdragon excerpt: 摘要:本文详细介绍了如何使用 Nuxt Kit 的构建器 API 来扩展和定制 Nuxt 3 项目的 webpack 和 Vite 构建配置,包括扩展Webpack和Vite配置、添加自定义插件、…

正向科技|格雷母线定位系统的设备接线安装示范

格雷母线安装规范又来了&#xff0c;这次是设备接线步骤 格雷母线是格雷母线定位系统的核心部件&#xff0c;沿着移动机车轨道方向上铺设&#xff0c;格雷母线以相互靠近的扁平状电缆与天线箱电磁偶合来进行信号传递&#xff0c;从而检测得到天线箱在格雷母线长度方向上的位置。…

OpenLayers 开源的Web GIS引擎 - 添加地图控件地图控件

中心点按钮、地图放大缩小滑块、全图和比例尺控件 直接上代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.…