qt桌面应用-闹钟提醒



  • 前言

    作为非专业业余程序员,天天做电脑面前很累。五一劳动节看了一篇帖子说,劳动时间越长,越放弃思考,为了放松自我,写一个桌面小程序,按时提醒自己休息

    设计功能

    启动程序开始计时,每50分钟提醒休息10分钟,这样一天8小时,能休息80分钟,想想就开心。

    搭建程序框架

    • 新建项目,使用widget作为主窗口
    • 在widget里嵌入quickWidget作为主界面
    • 创建资源文件,main.qml文件
    • 设置quickWidget的source为main.qml

    0_1525653976357_0f62fc29-645d-45f6-9354-d4c6fba283b3-image.png

    配置程序图标

    在pro里写 RC_FILE += myapp.rc
    myapp.rc文件内容

    IDI_ICON1               ICON    DISCARDABLE     "logo.ico"
    

    0_1525655211549_logo.ico

    背景透明

    setWindowFlags(this->windowFlags() | Qt::WindowStaysOnTopHint | Qt::SubWindow | Qt::FramelessWindowHint);
    
        //背景透明
        setAttribute(Qt::WA_TranslucentBackground);
    
        //填充背景
        setAutoFillBackground(true);
    
        //画刷
        QPalette palette;
        QPixmap pixmap("qrc:/toumingBg.png");
        palette.setBrush(QPalette::Window, QBrush(pixmap));
        setPalette(palette);
    
        //qml透明
        ui->quickWidget->setClearColor(QColor(Qt::transparent));
    

    效果,注意红边框

    0_1525656018096_6c0390bf-7149-479c-883a-0f39ffeedda7-image.png

    系统托盘

    因为程序没有右上角关闭按钮,任务栏图标也去掉了,所以无法关闭程序。因此在系统托盘加个图标,象qq那样,很不错的主意

    trayIcon = new QSystemTrayIcon();
        trayIcon->setIcon(QIcon(":/logo.ico"));
    
        QMenu *menu = new QMenu();
        QAction *action = menu->addAction("退出");
        connect(action, &QAction::triggered, this, &Widget::systemExit);
    
        trayIcon->setContextMenu(menu);
        trayIcon->show();
    

    0_1525656816896_4e28b28e-df21-4a58-892c-6b0a05f017d9-image.png

    0_1525656838166_7c5b8040-475f-4fe1-a89a-d3fd282a126f-image.png

    闪烁

    正常窗口第一次显示时候会闪现出来,有背景存在所以不会察觉,但是我们的界面是透明,所以第一次闪烁非常非常明显,我没有找到好的方法解决这个问题,投机取巧了一下,效果出奇的好

    setFixedSize(1,1);
    QTimer::singleShot(1000, this, &Widget::init);
    
    void Widget::init()
    {
        setWindowState(Qt::WindowMaximized);
    }
    

    全屏黑屏

    设置窗口全屏后黑屏,进行下面设置发现不黑屏,原因不明,全屏有毒。。。

    void Widget::init()
    {
        setWindowState(Qt::WindowMaximized);
        setGeometry(0,0,QApplication::desktop()->width(),QApplication::desktop()->height()+1);
    }
    

    接下来终于到了实现业务逻辑的部分了,真不容易。

    • timer实现计时,达到50分钟后显示窗口,黑布下滑遮盖桌面,
    • 动画结束后,timerMin计时10分钟内时间流逝
    • 10分钟过去后,黑布上拉,重新开始timer计时
    • 动画结束,窗口隐藏
       Timer{
            id: timer
            interval: 1000
            repeat: true
            running: true
            onTriggered: {
                var currentDate = new Date
    
                //秒到达50*60
                if(currentDate-lastDate > workTime*1000){
                    widget.show()
                    animDown.restart()
                }
            }
        }
    
       ParallelAnimation{
            id: animDown
            PropertyAnimation{ target: rect; property: "y"; from: -Screen.height; to: 0; easing.type: Easing.Linear; 
            duration: 500}
    
            onStopped: {
                lastMinDate = new Date
    
                myTime = myTimeText
                timerMin.restart()
                timer.stop()
            }
        }
    
        Timer{
            id: timerMin
            interval: 500
            repeat: true
            onTriggered: {
                var currentDate = new Date
    
                //流逝的时间
                var timeLapse = Math.floor(currentDate-lastMinDate) / 1000
    
                if(restTime-timeLapse>=0){
                    var left = (restTime-timeLapse)
    
                    var min = Number(left/60).toFixed(0)
                    var sec = Number(left%60).toFixed(0)
    
                    myTime = addZero(min) + ":" + addZero(sec)
    
                }
                else{
                    lastDate = currentDate
                    timerMin.stop()
                    animUp.restart()
                    timer.restart()
                }
            }
        }
    
        ParallelAnimation{
            id: animUp
            PropertyAnimation{ target: rect; property: "y"; from: 0; to: -Screen.height; easing.type: Easing.Linear; 
        duration: 500}
    
            onStopped: {
                widget.hide()
            }
        }
    
    

    0_1525669493539_f80232ed-59be-4ade-b467-f67b1a9a8d23-image.png

    注册开机启动

    花了一下午时间,经历了N多坑

    • QSettings设置注册表好简单啊——然并软
    • Win Api有啥方法——RegOpenKeyEx,RegSetValueEx,RegCloseKey
    • 为啥编译失败——该api需要添加lib
    • 为啥windows.h不好用——可能需要qt_windows.h
    • QString咋转LPCWSTR——TEXT()
    • 都写入成功了,为啥看不到——需要添加管理员权限
    • 普通注册表为啥写入乱码啊,unicode编码转成ANSI编码再转LPBYTE
    • 值终于对了,应用名怎么只剩一个字母了——QString转LPCSTR
    • 开机启动写成功了,注册表怎么还没有——开机启动项有就行了

    0_1525694527486_QQ截图20180507183633.png

    福利

    源代码下载
    程序包下载,把bin文件夹下载下来哦

    第二天

    注册开机启动,QSettings其实好用,

    void Widget::autoRun(bool run)
    {
        QString path = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run";
    
        QString exePath = QApplication::applicationFilePath();
        exePath = exePath.replace("/", "\\");
    
        QSettings *reg = new QSettings(path, QSettings::NativeFormat);
    
        if(run){
            reg->setValue("rest", exePath);
        }
        else{
            reg->setValue("rest", "");
        }
    }
    

    0_1525738087123_f7d2d4fb-f454-4eb4-84a3-3bae1b400a45-image.png

    总结

    qt下修改注册表,推荐用QSettings,很方便

    • 64位系统有重定向问题,真正的默认开机启动项在
      HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Run

    0_1525738661800_33a3c92d-0121-419c-9cb3-169705f82bba-image.png



  • 可否到时候整合到我们的萌梦套件里面?这应该不难。



  • @jiangcaiyang 可以整合,等做完了,你可以评估一下



  • @青山白云 如果是自启动的功能嘛。可能真需要平台相关的API。不知道Qt有没有已经封装好的操作,这样可以尽量减少我们的工作量。☺ 😊 😊



  • @jiangcaiyang QSetting可以,但是不起作用

    //设置开机启动
    void Widget::autoRun(bool run)
    {
        QString path = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run";
    
        QString exePath = QApplication::applicationFilePath();
        exePath = exePath.replace("/", "\\");
    
        QSettings *reg = new QSettings(path, QSettings::NativeFormat);
    
        if(run){
            reg->setValue("rest", exePath);
        }
        else{
            reg->setValue("rest", "");
        }
    }
    


  • @jiangcaiyang QSettings我刚又试了一下,好用!!!



  • @青山白云 写入注册表,QSettings类也可以做。而且写入设置的,除非你将QSettings的导出格式设置为ini,否则的话,各种设置都会写入注册表的。



  • 可以弄个番茄工作法



  • 有开GitHub仓库么?我想读一下完整的代码




Log in to reply
 

走马观花

最近的回复

  • Qt for MCUs

    搭建Qt for MCUs PC端开发环境。qt for mcus提供了一个完整的图形框架和工具包,包含了在MCUs上设计、开发和部署gui所需的一切。它允许您在裸机或实时操作系统上运行应用程序。

    先决条件

    开发主机环境支持仅限于Windows 10

    MSVC compiler v19.16 (Visual Studio 2017 15.9.9 or newer) x64

    CMake v3.13 or newer (you can install it using the Qt Online installer) x64

    使用Qt联机安装程序安装Qt for MCUs,该安装程序可通过Qt帐户下载

    安装Qt 5.14和Qt Creator 4.11 or higher

    安装链接

    › Qt: https://account.qt.io/downloads
    › CMake: https://cmake.org/download/
    › Python 2.7 32-bit: https://www.python.org/downloads/release/python-2716/
    › Arm GCC: https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnutoolchain/gnu-rm/downloads
    › J-Link Software Pack: https://www.segger.com/downloads/jlink/JLink_Windows.exe
    › J-Link OpenSDA Firmware: https://www.segger.com/downloads/jlink/OpenSDA_MIMXRT1050-EVKHyperflash
    › STM32CubeProgrammer: https://www.st.com/en/development-tools/stm32cubeprog.html
    › STM32 ST-LINK Utility: https://www.st.com/en/development-tools/stsw-link004.html​​​​​​​

    Qt Creator设置 启用Qt Creator插件 选择“帮助>关于插件”,然后从列表中选择“MCU支持(实验性)”插件,重新启动Qt Creator以应用更改
    替代文字 为MCU创建Qt工具包

    选择工具>选项>设备>MCU

    选择Qt for MCUs-Desktop 32bpp作为目标

    如果尚未设置,请提供Qt for MCUs安装目录的路径。

    单击Apply应用。

    替代文字

    替代文字
    替代文字

    注意:

    编译器要选X64,Qt版本要选64bit,CMake Tool选x64

    打开恒温器项目demo

    选择文件>打开文件或项目。。。

    打开CMakefiles.txt文件来自thermo文件夹的文件。

    选择Qt作为MCU-桌面32bpp套件。

    单击“配置项目”以完成。

    替代文字

    问题

    开发主机环境支持仅限于Windows 10

    C++编译失败,文本大字体.pixelSize.

    文本类型无法正确呈现需要复杂文本布局的unicode序列。对复杂文本使用StaticText

    read more
  • H

    hi 有问题请教你,方便加个联系方式吗

    read more
  • boost.asio是一个很棒的网络库,这回儿我也开始系统地学习起来了。想想当年接触boost,也有八年多了。这次开始接触boost,觉得既熟悉又陌生。熟悉的是小写字母+下划线的命名方式、晦涩的模板、很慢的编译速度以及较大的程序体积,陌生的是asio的各种概念:io服务、接收器、套接字等等:我之前对网络编程不是非常了解。

    于是根据我的理解,参考《Boost.Asio C++网络编程》实现了这样一个简单的客户端和服务端通信的例子,例子非常简单,还不完善,但是幸运的是,可以在本机上互通了。
    下面是客户端的代码:

    #include <iostream> #include <boost/asio.hpp> #include <boost/proto/detail/ignore_unused.hpp> using namespace std; using namespace boost::asio; using namespace boost::system; using namespace boost::proto::detail;// 提供ignore_unused方法 void writeHandler( const boost::system::error_code& ec, size_t bytesTransferred ) { if ( ec ) { cout << "Write data error, code: " << ec.value( ) << "transferred: " << bytesTransferred << endl; } else { cout << "OK! " << bytesTransferred << "bytes written. " << endl; } } int main(int argc, char *argv[]) { ignore_unused( argc ); ignore_unused( argv ); io_service service; ip::tcp::socket sock( service ); ip::tcp::endpoint ep( ip::address::from_string( "127.0.0.1" ), 6545 ); boost::system::error_code ec; sock.connect( ep, ec ); if ( ec ) { cout << "Connect error, code: " << ec.value( ) << ", We will exit." << endl; return ec.value( ); } else { char buf[1024] = "Hello world!"; sock.async_write_some( buffer( buf ), writeHandler ); sock.close( ); } return service.run( ); }

    下面是服务端的代码:

    #include <iostream> #include <boost/asio.hpp> #include <boost/proto/detail/ignore_unused.hpp> using namespace std; using namespace boost::asio; using namespace boost::system; using namespace boost::proto::detail;// 提供ignore_unused方法 void acceptHandle( const boost::system::error_code& code ) { cout << "Accepted." << endl; } int main(int argc, char *argv[]) { ignore_unused( argc ); ignore_unused( argv ); io_service service; ip::tcp::endpoint ep( ip::address::from_string( "127.0.0.1" ), 6545 ); boost::system::error_code ec; ip::tcp::socket sock( service ); ip::tcp::acceptor acceptor( service, ep ); acceptor.async_accept( sock, acceptHandle ); if ( ec ) { cout << "There is an error in server. code: " << ec.value( ) << endl; } return service.run( );// 阻塞运行 }

    运行结果是这样的:
    78448d7b-b3ae-42fc-9e2e-4dd2fbdac2c2-image.png

    我对boost.asio中几个概念的理解:

    io_service,这就是一个类似事件循环的东西,它为io设备提供服务,故名。不管是套接字、文件还是串口设备,都要使用它的服务。它的run()函数相当于启动了一个事件循环。一旦有消息了,即进行响应。这也是实现异步编程的重要基础。 socket,这个类则是套接字,可以处理TCP或者是UDP请求。有同步以及异步的处理方式,也有带异常以及不带异常的处理方式。 acceptor,接收器,仅仅是服务端使用。相当于其余框架中的listener,作接收用的。

    比较浅显,如果有不当之处,敬请指正。

    read more
  • 843143141.jpg
    闲下来了,我又开始大规模地学习了。
    最近开始学习内存模型和无锁结构。因为这个是和操作系统密切相关的,懂得这些对于编写C++服务端应用程序
    有着非常好的帮助。之前我对内存模型以及无锁结构几乎没有什么了解,我就询问群里的大佬看看有没有可以参考的资料。
    大佬很高兴,并且推荐了我一本名为《Memory Model》的电子书。这本电子书虽然页数不多,但是从起源到发展,
    从源码到汇编,都给我们详细地介绍了。看了一遍,不是非常理解,但是依然尝试将自己的理解写下来,以便日后翻阅。
    首先因为多核处理器成为主流,多线程的程序已经非常常见,因此我们不可避免地要处理多线程程序的同步问题。
    然后,因为编译器默认都对源码进行了优化,在单核处理器中这通常不是什么问题,但是在多核处理器中,就会因为编译器
    对其进行了乱序处理而导致程序出现问题。由此深入地探讨内存模型。
    内存模型主要分为:
    载-载 顺序(load-load order)
    载-存 顺序(load-store order)
    存-载 顺序(store-load order)
    存-存 顺序(store-store order)
    依赖载入顺序(dependent loads order)

    通过内存栅栏(memory barrier)能够避免编译器对指令的乱序。Linux中有

    READ_ONCE( x, value ) WRITE_ONCE( x )

    避免这些读写被编译器乱序或者是优化掉。

    这里谈到volatile关键字。在另外一篇博客上说,volatile具有“易变性、不可优化性、顺序性”。简单说,由于
    被volatile声明的变量,指令须从内存读取,并且不能被编译器乱序以及优化。在Java(语言扩展)和MSVC(系统兼容)上,
    还附带了Accquire()和Release()语义,因此可部分用于多线程环境。但多数情况下,还是慎用volatile,
    因为不同架构的处理器,它的内存模型是千变万化的,不能一而概之。

    至于C++11,它提供了std::atomic<T>这个模板类,相当于提供了很多方式来实现不同内存模型的原子操作。
    它的load()和store()方法,第二个参数有以下几个选项:

    std::memory_order_relaxed std::memory_order_seq_cst std::memory_order_acq_rel std::memory_order_acquire std::memory_order_release std::memory_order_consume

    我们最常用来实现RCpc(Release Consistency、Processor Consistency)是使用

    std::memory_order_acquire std::memory_order_release

    这两对。

    作为例子,在实现自旋锁时使用std::atomic<T>是这样的:

    struct SpinLock2 { void lock( ) { for ( ; ; ) { while ( lock_.load( std::memory_order_relaxed ) ); if ( !lock_.exchange( true, std::memory_order_acquire ) ) break; } } void unlock( ) { lock_.store( false, std::memory_order_release ); } std::atomic<bool> lock_ = { false }; };

    read more

关注我们

微博
QQ群