Qml组件化编程6-进度条定制



  • 简介

    本文是《Qml组件化编程》系列文章的第六篇,涛哥将教大家,进度条组件的定制。


    顺便说一下,涛哥的TaoQuick项目正式开源了, 系列文章中的所有功能,包括动态换皮肤、切换多语言等等,都集成在了TaoQuick中,

    同时涛哥也在TaoQuick中使用了持续集成(CI)技术,目前已经能够自动编译、发布Windows和 Macos平台的软件包,可以在github的Release界面下载体验。

    互联网行业很流行的DevOps理念,在TaoQuick项目中得到了最佳的实践。

    (linux平台的发布工具linuxdeployqt暂时还有点问题,涛哥后续会搞定的)

    地址在这https://github.com/jaredtao/TaoQuick, 赶快去star吧。


    注:文章主要发布在涛哥的博客知乎专栏-涛哥的Qt进阶之路

    先看预览图

    预览

    新的渐变效果

    Qt 5.12 加入了新的渐变效果,一共180种,效果来自这个网站https://webgradients.com

    按照帮助文档的介绍,可以通过下面这两种方式使用

      Rectangle {
          y: 0; width: 80; height: 80
          gradient: Gradient.NightFade
      }
    
      Rectangle {
          y: 0; width: 80; height: 80
          gradient: "NightFade"
      }
    

    涛哥立即想到了,枚举不就是数字嘛

      Rectangle {
          y: 0; width: 80; height: 80
          gradient: 1
      }
      Rectangle {
          y: 0; width: 80; height: 80
          gradient: 2
      }
      Rectangle {
          y: 0; width: 80; height: 80
          gradient: 3
      }
    

    试了一下,这样也是可以啊,哈哈。

    于是涛哥就把180种渐变效果都拉出来看看。

    预览

    Qt只支持水平和垂直的渐变,其中有小部分是不能用的,所以只有165个能用。

    看一下展示全部渐变的Qml代码:

    import QtQuick 2.9
    import QtQuick.Controls 2.5
    
    Item {
        anchors.fill: parent
    
        GridView {
            id: g
            anchors.fill: parent
            anchors.margins: 20
            cellWidth: 160
            cellHeight: 160
            model: 180          //这里的数据Model直接给个数字180
            clip: true
            property var invalidList: [27, 39, 40, 45, 71, 74, 105, 111, 119, 130, 135, 141]    //这几个是不能用的,看过运行报错后手动列出来的。
            delegate: Item{
                width: 160
                height: 160
                Rectangle{
                    width: 150
                    height: 150
                    anchors.centerIn: parent
                    color: "white"
                    radius: 10
                    Text {
                        anchors.horizontalCenter: parent.horizontalCenter
                        anchors.top: parent.top
                        anchors.topMargin: 2
                        text: index + 1
                    }
                    Rectangle {
                        width: 100
                        height: width
                        radius:  width / 2
                        //编号在列表里的,直接渐变赋值为null,就不会在Qml运行时报警告了
                        gradient: g.invalidList.indexOf(modelData + 1) < 0 ? modelData + 1 : null
                        anchors.centerIn: parent
                        anchors.verticalCenterOffset: 10
                    }
                }
            }
        }
    }
    

    条形进度条

    普通进度条的原理,就是有一个比较长的矩形做背景,在上面放一个颜色不同的矩形,其宽度跟着百分比变化,

    100%时宽度与背景一致。

    可以写一个很简要的进度条。

    Rectangle {
        id: back
        width: 300
        height: 50
        radius: height / 2
        color: "white"  
        Rectangle {
            id: front
            //宽度是 背景宽度 * 百分比
            width: percent / 100 * parent.width  
            height: parent.height
            radius: parent.radius
            color: "red"
        }
    }
    

    再添加一点元素,在右侧放一个文本,表示百分比,或者放图片。甚至给进度条加个闪光特效。

    经过一系列的加工,封装成一个综合的组件,最终结果如下:

    //NormalProgressBar.qml
    
    import QtQuick 2.12
    import QtQuick.Controls 2.12
    Item {
        id: r
        property int percent: 0
        implicitWidth: 200
        implicitHeight: 16
        //枚举, 表示右侧Bar的类型
        enum BarType {  
            Text,               //右侧放文本
            SucceedOrFailed,    //右侧放图片表示成功和失败,没有100%就是失败
            NoBar               //右侧不放东西
        }
        //只读属性,内置一些颜色
        readonly property color __backColor: "#f5f5f5"
        readonly property color __blueColor: "#1890ff"
        readonly property color __succeedColor: "#52c41a"
        readonly property color __failedColor: "#f5222d"
    
        //背景色,默认值
        property color backgroundColor: __backColor
        //前景色
        property color frontColor: {
            switch (barType) {
            case TNormalProgress.BarType.SucceedOrFailed:
                return percent === 100 ? __succeedColor : __failedColor
            default:
                return __blueColor
            }
        }
        //文字
        property string text: String("%1%").arg(percent)
        //渐变 0-180 除掉不能用的,165种渐变任你选
        property int gradientIndex: -1
        //闪烁特效
        property bool flicker: false
        //右侧Bar类型
        property var barType: TNormalProgress.BarType.Text
        Text {
            id: t
            enabled: barType === TNormalProgress.BarType.Text
            visible: enabled
            text: r.text
            anchors.verticalCenter: parent.verticalCenter
            anchors.right: parent.right
        }
        Image {
            id: image
            source: percent === 100 ? "qrc:/Core/Image/ProgressBar/ok_circle.png" : "qrc:/Core/Image/ProgressBar/fail_circle.png"
            height: parent.height
            width: height
            enabled: barType === TNormalProgress.BarType.SucceedOrFailed
            visible: enabled
            anchors.right: parent.right
        }
    
        property var __right: {
            switch (barType) {
            case TNormalProgress.BarType.Text:
                return t.left
            case TNormalProgress.BarType.SucceedOrFailed:
                return image.left
            default:
                return r.right
            }
        }
        Rectangle {                             //背景
            id: back
            anchors.left: parent.left
            anchors.right: __right
            anchors.rightMargin: 4
            height: parent.height
            radius: height / 2
            color: backgroundColor
            Rectangle {                         //前景
                id: front
                width: percent / 100 * parent.width
                height: parent.height
                radius: parent.radius
                color: frontColor
                gradient: gradientIndex === -1 ? null : gradientIndex
                Rectangle {                     //前景上的闪光特效
                    id: flick
                    height: parent.height
                    width: 0
                    radius: parent.radius
                    color: Qt.lighter(parent.color, 1.2)
                    enabled: flicker
                    visible: enabled
                    NumberAnimation on width {
                        running: visible
                        from: 0
                        to: front.width
                        duration: 1000
                        loops: Animation.Infinite;
                    }
                }
            }
        }
    }
    
    

    圆形进度条

    将一个Rectangle做成圆形: 宽高相等,半径为宽度一半。

    再把 颜色设置为透明,边框不透明,边框加粗一点,就是一个圆环了。

    Rectangle {
        id: back
        width: 120
        height: width
        radius: width / 2
        color: "transparent"
        border.width: 10
        border.color: "white"
    }
    

    接下来给圆环贴上一个圆形渐变色,渐变按照百分比来做。

    import QtGraphicalEffects 1.12
    Rectangle {
        id: back
        width: 120
        height: width
        radius: width / 2
        color: "transparent"
        border.width: 10
        border.color: "white"
    
        ConicalGradient {
            anchors.fill: back
            source: back
            gradient: Gradient {
                GradientStop { position: 0.0; color: "white" }
                GradientStop { position: percent / 100 ; color: "red" }
                GradientStop { position: percent / 100 + 0.001; color: "white" }
                GradientStop { position: 1.0; color: "white" }
            }
        }
    }
    

    渐变从0 到 percent处都是有渐变颜色的, 再从percent + 0.001 到1.0处,都是背景色,这样就是一个简易的圆形进度条了。

    不过这里percent为100的情况,圆形渐变处理不了,我们可以特殊处理,直接让背景圆环变成前景色就行了。(既然都100%了,背景肯定是全部被遮住了,那就让背景做前景,藏掉真正的前景)

    ```qml
    import QtGraphicalEffects 1.12
    Rectangle {
        id: back
        width: 120
        height: width
        radius: width / 2
        color: "transparent"
        border.width: 10
        border.color: percent === 100 ? "red" : "white"     //百分比为100时显示为前景,否则显示为背景
    
        ConicalGradient {
            anchors.fill: back
            source: back
            enabled: percent != 100     //百分比不为100时有效
            visible: enabled            //百分比不为100时有效
            gradient: Gradient {
                GradientStop { position: 0.0; color: "white" }
                GradientStop { position: percent / 100 ; color: "red" }
                GradientStop { position: percent / 100 + 0.001; color: "white" }
                GradientStop { position: 1.0; color: "white" }
            }
        }
    }
    

    再加点料,封装成组件

    //CircleProgressBar.qml
    import QtQuick 2.12
    import QtQuick.Controls 2.12
    import QtGraphicalEffects 1.12
    Item {
        id: r
        property int percent: 0
    
        enum BarType {
            Text,
            SucceedOrFailed,
            NoBar
        }
        readonly property color __backColor: "#f5f5f5"
        readonly property color __blueColor: "#1890ff"
        readonly property color __succeedColor: "#52c41a"
        readonly property color __failedColor: "#f5222d"
        property color backgroundColor: __backColor
        property color frontColor: {
            switch (barType) {
            case TNormalProgress.BarType.SucceedOrFailed:
                return percent === 100 ? __succeedColor : __failedColor
            default:
                return __blueColor
            }
        }
        property string text: String("%1%").arg(percent)
        property var barType: TNormalProgress.BarType.Text
        Rectangle {
            id: back
            color: "transparent"
            anchors.fill: parent
            border.color: percent === 100 ? frontColor : backgroundColor
            border.width: 10
            radius: width / 2
        }
        Text {
            id: t
            enabled: barType === TNormalProgress.BarType.Text
            visible: enabled
            text: r.text
            anchors.centerIn: parent
            verticalAlignment: Text.AlignVCenter
            horizontalAlignment: Text.AlignHCenter
        }
        Image {
            id: image
            source: percent === 100 ? "qrc:/Core/Image/ProgressBar/ok.png" : "qrc:/Core/Image/ProgressBar/fail.png"
            enabled: barType === TNormalProgress.BarType.SucceedOrFailed
            visible: enabled
            scale: 2
            anchors.centerIn: parent
        }
        ConicalGradient {
            anchors.fill: back
            source: back
            enabled: percent != 100
            visible: enabled
            smooth: true
            antialiasing: true
            gradient: Gradient {
                GradientStop { position: 0.0; color: frontColor }
                GradientStop { position: percent / 100 ; color: frontColor }
                GradientStop { position: percent / 100 + 0.001; color: backgroundColor }
                GradientStop { position: 1.0; color: backgroundColor }
            }
        }
    }
    
    

    最后,来个合影

    Item {
        id: r
        anchors.fill: parent
        Grid {
            id: g
            anchors.fill: parent
            anchors.margins: 10
            columns: 2
            spacing: 10
            Column {
                width: g.width / 2 - 10
                height: g.height /2 - 10
                spacing: 10
                TNormalProgress {
                    width: parent.width
                    backgroundColor: gConfig.reserverColor
                    NumberAnimation on percent { from: 0; to: 100; duration: 5000; running: true; loops: Animation.Infinite}
                }
                TNormalProgress {
                    width: parent.width
                    backgroundColor: gConfig.reserverColor
                    flicker: true
                    percent: 50
                }
                TNormalProgress {
                    width: parent.width
                    backgroundColor: gConfig.reserverColor
                    barType: TNormalProgress.BarType.SucceedOrFailed
                    percent: 70
                }
                TNormalProgress {
                    width: parent.width
                    backgroundColor: gConfig.reserverColor
                    barType: TNormalProgress.BarType.SucceedOrFailed
                    percent: 100
                }
                TNormalProgress {
                    width: parent.width
                    backgroundColor: gConfig.reserverColor
                    barType: TNormalProgress.BarType.NoBar
                    percent: 50
                    gradientIndex: 12
                }
            }
            Row {
                width: g.width / 2 - 10
                height: g.height /2 - 10
                spacing: 10
    
                TCircleProgress {
                    width: 120
                    height: 120
                    backgroundColor: gConfig.reserverColor
                    NumberAnimation on percent { from: 0; to: 100; duration: 5000; running: true; loops: Animation.Infinite}
                }
                TCircleProgress {
                    width: 120
                    height: 120
                    backgroundColor: gConfig.reserverColor
                    barType: TNormalProgress.BarType.SucceedOrFailed
                    percent: 75
                }
                TCircleProgress {
                    width: 120
                    height: 120
                    backgroundColor: gConfig.reserverColor
                    barType: TNormalProgress.BarType.SucceedOrFailed
                    percent: 100
                }
            }
            Row {
                width: g.width / 2 - 10
                height: g.height /2 - 10
                spacing: 10
    
                TCircleProgress {
                    width: 120
                    height: 120
                    backgroundColor: gConfig.reserverColor
                    text: String("%1天").arg(percent)
                    NumberAnimation on percent { from: 0; to: 100; duration: 5000; running: true; loops: Animation.Infinite}
                }
                TCircleProgress {
                    id: ppppp
                    width: 120
                    height: 120
                    backgroundColor: gConfig.reserverColor
                    barType: TNormalProgress.BarType.SucceedOrFailed
                    SequentialAnimation {
                        running: true
                        loops: Animation.Infinite
                        NumberAnimation {
                            target: ppppp
                            property: "percent"
                            from: 0
                            to: 100
                            duration: 3000
                        }
                        PauseAnimation {
                            duration: 500
                        }
                    }
                }
                TCircleProgress {
                    width: 120
                    height: 120
                    backgroundColor: gConfig.reserverColor
                    percent: 100
                }
            }
            Column {
                width: g.width / 2 - 10
                height: g.height /2 - 10
                spacing: 10
            }
            Column {
                width: g.width / 2 - 10
                height: g.height /2 - 10
                spacing: 10
            }
        }
    
    }
    

    效果如下:

    预览


Log in to reply
 

走马观花

最近的回复

  • C

    Qt for MCU需要商业授权的

    read more
  • 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

关注我们

微博
QQ群