使用QML仿火绒安全软件



  • 火绒安全软件

    基于Qt 5.11,以QML为主开发的火绒安全软件界面。

    该Demo作为我最近学习QML的一个综合考验,尽量多的还原火绒的界面和操作方式,以及呈现的效果。

    选择做火绒安全软件的理由

    火绒目前是我电脑里的常驻软件,自从发现火绒这款很不错的软件后,就替代了360作为电脑里主力的安全软件。最近闲着,于是重新学学QML。之前虽然有接触,但是大多写了一个Hello World以后就扔掉了,没有什么实际价值。很久很久以前,我看到Qt吧有大佬用Qt做了360的界面,于是产生了用QML做火绒的界面的想法,作为对自己的一个小小的挑战。

    火绒安全软件界面分析

    火绒的界面整体上非常干净和清爽,界面的风格也是符合现在的潮流。

    整体界面分析

    image

    image

    整体上可以看作Header, Navigation, Content 三个部分组成

    • Header

    Header主要展示火绒的logo,当前界面提示,还有右边3个跟窗口相关的按钮:菜单,最小化窗口,关闭窗口。

    • Navigation

    Navigation主要展示病毒查杀,防护中心,家长控制,扩展工具四个部分,类似TabBar。

    • Content

    病毒查杀

    image

    image

    image

    这个界面难在快速查杀那个地方的一个动画,非常不好调试,很难和火绒调得一样顺畅。

    防护中心

    image

    image

    家长控制

    image

    image

    扩展工具

    image

    image

    image

    最终呈现的效果

    v0.1.0

    image

    思考与总结

    目前的这4个界面,我做了3天,其中画了不少时间找图标,和测量控件大小。为了图标这个事情,我还特地做了一个字体图标的组件,它可以根据名称直接显示图标,就好像在web中使用一样,非常有用。目前这个控件支持了Material Icons和Font Awesome两种字体。如果需要支持新的字体的话,只需要添加一个名称到Unicode码的映射即可。在做这个界面的时候,我看到一个视频——QML + Sketch,在这个演示视频中,做界面的速度非常快,自己和他比,别人是火箭,我就是乌龟。在这个视频里,我了解到了一个设计图的重要意义,尤其是非常精密和详细的图纸,对于快速实现,帮助巨大。

    QML做界面有一个特点就是它的容器不是撑开的,之前做web的时候,每次使用div都是靠内容撑开,在QML中这样不行,需要自己去计算每一项的大小。这一点让我很不适应,常常忘记去计算,导致布局出现各种问题。QML会强制我去思考把界面(ui.qml)和逻辑(普通的.qml)分离,考虑如何抽象一个公共的组件,如何让组件重用。后来每次造新组件,我总会同时建立这两个文件,一个方便在Qt Quick Designer中预览和拖拽控件,另一个写一些简单的逻辑,完成界面展示的一些其他需求。

    在制作过程中,我自己造了很多组件,比如IconButton,Switch。它们中有的有着共同的逻辑,只是展示不一样,我想把这些共同的东西抽象出来,代码更简洁。在所有的界面中,我觉得最困难的就是那个导航的组件。导航组件不仅仅是展示,还需要动态切换右边的内容。我一直弄不清楚,如何去实现这样的一个功能。QML的example中有一个例子是用TabBar+SwipeView实现的,目前我用很丑陋的方式实现了类似的功能,非常麻烦,后面我希望能写一个更优秀的组件代替目前的。

    Qt Quick Control 2是一个很难说的东西,我不熟悉,在使用过程中,大部分的控件都是自己从头做的,很少使用Qt Quick Control 2提供的,一方面是我对这些控件的特性不了解,另一方面是原本提供的控件都很丑,难以完成我的任务。事实上,它提供的控件也很少,可直接修改的东西不多,还得自己去造。对于Qt Quick Control 2这组控件,将是我下一步需要了解的,并对现有的组件进行重构。不过,那是以后的事情了。现在还是专注于把所有的界面做好吧。

    调试在QML中是一个比较麻烦的事情,我不能像web那样,在运行时获取每个组件的大小。所以,我写了一个Debug组件,可以看到组件的范围和大小,实现的原理也很简单。如果QML也能像web一样,在运行的时候,可以看到组件的范围和大小,会方便很多。按理说,同是树的结构,应该是有相关的调试工具的,可能我没有找到吧!

    实际的开发中,总会遇到这样那样的问题,我把那些东西写在了一个叫TRICKS.md的文件中,比较的乱。要是有相似的经历的话,看到乱糟糟的这些东西,也会有些感触的。

    总的来说,第一个版本的火绒就到此为止。QML相对于用Qt/C++去写界面,的的确确要快许多,自由度也大一些,相对也简单一些。倒是我认为最重要的是,首先得有一份漂亮并精细的设计图。有了设计图,无论用什么去实现,都会有了非常明确的目标和方向,也就不会看重用什么去实现,也不会陷于到某些坑中。

    未来展望

    用QML做界面是非常棒的,是有效率的。想要发挥QML的巨大威力,还有很长的路要走。

    在下一版中,我的计划是

    • 最重要的

    把火绒剩下的界面做完

    • 持续要做的

    组件抽象和封装

    • 具体可能做的事项
    1. Navigation重构
    2. 最顶层的窗口加阴影
    3. Navigation左边的动画,当点击其他的项时,需要一个过渡动画,不能直接过去
    4. 模拟运行的C++后端

    GitHub地址: https://github.com/PikachuHy/qml-demo/tree/huorong/huorong


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群