简体   繁体   中英

How to create a Progress Bar in Qml with slant lines?

在此处输入图像描述

Hello,

I am looking for some solution to create a progress bar in qml having slant lines.

I dont having any snippet to share here, as I am not aware how to do so.

Can anyone please guide??

The indeterminate Fusion style ProgressBar does something similar, where it has moving slanted lines.

Here's the code from that adjusted to show the lines for determinate bars as well, using only public APIs:

import QtQuick
import QtQuick.Controls

ApplicationWindow {
    width: 640
    height: 480
    visible: true

    ProgressBar {
        id: control
        value: 0.5

        contentItem: Item {
            implicitWidth: 120
            implicitHeight: 24

            Rectangle {
                height: parent.height
                width: control.position * parent.width

                radius: 2
                border.color: "steelblue"
                gradient: Gradient {
                    GradientStop {
                        position: 0
                        color: Qt.lighter("steelblue", 1.2)
                    }
                    GradientStop {
                        position: 1
                        color: Qt.darker("steelblue", 1.2)
                    }
                }
            }

            Item {
                x: 1
                y: 1
                width: parent.width - 2
                height: parent.height - 2
                clip: true

                Image {
                    width: Math.ceil(parent.width / implicitWidth + 1) * implicitWidth
                    height: parent.height

                    mirror: control.mirrored
                    fillMode: Image.TileHorizontally
                    source: "qrc:/qt-project.org/imports/QtQuick/Controls/Fusion/images/progressmask.png"
                    opacity: 0.25

                    NumberAnimation on x {
                        running: control.visible
                        from: -31 // progressmask.png width
                        to: 0
                        loops: Animation.Infinite
                        duration: 750
                    }
                }
            }
        }
    }
}

If you don't want the animation, just remove the NumberAnimation.

Note that since this example uses an image from the Fusion style (for convenience), it must be run with the Fusion style.

If you want to run it with another style, replace the image's URL with another, and make sure you're using a non-native style :

Note: The macOS and Windows styles are not suitable for customizing. It is instead recommended to always base a customized control on top of a single style that is available on all platforms, eg Basic Style, Fusion Style, Imagine Style, Material Style, Universal Style. By doing so, you are guaranteed that it will always look the same, regardless of which style the application is run with. For example:

I am going to solve the problem in stages. This is reflective of how I go about solving QML problems myself. The first goal is to create a 200x32 ProgressBar and to set it to 25% progress. First, I start with a blank Rectangle for my custom ProgressBar:

ProgressBar {
  implicitWidth: 200
  implicitHeight: 32
  contentItem: Rectangle {
    border.color: "black"
  }
}

进度条空白矩形

Next, I decide to craft a 32x32 SVG of a diagonal line and I repeat this line at 4 pixel intervals covering the whole width (ie 200 / 4 === 50).

ProgressBar {
  implicitWidth: 200
  implicitHeight: 32
  contentItem: Rectangle {
    border.color: "black"
    Repeater {
      model: 200 / 4
      Image {
        x: index * 4
        y: 0
        source: `data:image/svg+xml;utf8,
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
        <path fill="none" stroke="black" d="M 0 0 L 32 32" />
        </svg>`
      }
    }
  }
}

进度条-try-slant-1

I observe two problems with the above:

  1. The triangle gap at the left
  2. The overshoot at the right

To fix these problems, (1) shift the lines 32 pixels to the left to cover the gap, (2) I extend the lines to be (200 + 32) / 4 === 58, (3) use clip to crop clean the diagonals that are outside my Rectangle.

ProgressBar {
  implicitWidth: 200
  implicitHeight: 32
  contentItem: Rectangle {
    border.color: "black"
    clip: true
    Repeater {
      model: (200 + 32) / 4
      Image {
        x: index * 4 - 32
        y: 0
        source: `data:image/svg+xml;utf8,
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
        <path fill="none" stroke="black" d="M 0 0 L 32 32" />
        </svg>`
      }
    }
  }
}

进度条清洁倾斜

Next, I want to represent 25%. I do this by drawing a 75% white Rectangle covering the slant lines on the right.

ProgressBar {
  implicitWidth: 200
  implicitHeight: 32
  value: 0.25
  contentItem: Rectangle {
    border.color: "black"
    clip: true
    Repeater {
      model: (200 + 32) / 4
      Image {
        x: index * 4 - 32
        y: 0
        source: `data:image/svg+xml;utf8,
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
        <path fill="none" stroke="black" d="M 0 0 L 32 32" />
        </svg>`
      }
    }
    Rectangle {
      anchors.top: parent.top
      anchors.right: parent.right
      anchors.bottom: parent.bottom
      implicitWidth: parent.width * 0.75
      implicitHeight: parent.height
      border.color: "black"
      color: "white"
    }
  }
}

进度条-25-%

Finally, we try to generalize our solution:

ProgressBar {
  id: progressBar
  implicitWidth: 200
  implicitHeight: 32
  contentItem: Rectangle {
    border.color: "black"
    clip: true
    Repeater {
      model: (parent.width + parent.height) / 4
      Image {
        x: index * 4 - parent.height
        y: 0
        source: `data:image/svg+xml;utf8,
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${parent.height} ${parent.height}">
        <path fill="none" stroke="black" d="M 0 0 L ${parent.height} ${parent.height}" />
        </svg>`
      }
    }
    Rectangle {
      anchors.top: parent.top
      anchors.right: parent.right
      anchors.bottom: parent.bottom
      implicitWidth: parent.width * (1 - parent.parent.value)
      implicitHeight: parent.height
      border.color: "black"
      color: "white"
    }
  }
}

进度条结果

There are assumptions that we made:

  • ProgressBar is 200x32
  • Color is white bar with a black outline with black diagonal lines
  • The bar to the right is filled white
  • A SVG image is used

To generalize further, we can consider (1) replacing the SVG Image with a more efficient equivalent, (2) allow the colours to be configurable.

Yet another solution.

在此处输入图像描述

ProgressBar {
    id: control

    property color contentItemColor: "white"
    property color backgroundColor: "yellow"
    property color borderColor: "red"
    property int borderWidth: 1
    property color stripColor: "green"
    property int stripGap: 10
    property int stripWidth: 2
    property color indicatorColor: "red"
    property int indicatorWidth: 2
    property int indicatorRadius: 0
    property int indicatorExtend: 0

    value: 0.5
    width: 600
    height: 80

    background: Rectangle {
        color: control.backgroundColor
        border.width: control.borderWidth
        border.color: control.borderColor

        Canvas {
            x: control.borderWidth
            y: control.borderWidth
            width: control.width - (2 * control.borderWidth)
            height: control.height - (2 * control.borderWidth)

            onPaint: {
                var ctx = getContext("2d");
                ctx.strokeStyle = control.stripColor
                ctx.lineWidth = control.stripWidth
                ctx.lineCap = "square"

                var p1, p2

                let n = Math.ceil((control.width + control.height) / (control.stripGap)) + 1

                for (let i = 0; i != n; ++i) {
                    let p = i * control.stripGap
                    p1 = Qt.vector2d(p - control.height, 0)
                    p2 = Qt.vector2d(p, control.height)

                    ctx.beginPath()
                    ctx.moveTo(p1.x, p1.y)
                    ctx.lineTo(p2.x, p2.y)
                    ctx.closePath()
                    ctx.stroke()
                }
            }
        }
    }

    contentItem: Item {
        Rectangle {
            anchors.fill: parent
            anchors.margins: control.borderWidth
            anchors.leftMargin: Math.max(control.borderWidth,
                                         control.visualPosition * control.width)

            color: control.contentItemColor
        }

        Rectangle {
            x: control.visualPosition * control.width - (control.indicatorWidth / 2)
            y: -control.indicatorExtend
            width: control.indicatorWidth
            height: control.height + (2 * control.indicatorExtend)
            color: control.indicatorColor
            radius: control.indicatorRadius
            visible: control.visualPosition > 0.0 && control.visualPosition < 1.0
        }
    }
}

I recommend using ShaderEffect to create a more efficient result.
So, as everyone else implemented their own code, I did as well.
The results are shown below.

SlantLines.qml

// SlantLines.qml
import QtQuick 2.12

ShaderEffect {
    id: effect
    property real seed: 0 // To animate the lines' x axis.
    property real num: width / 6 // The number of lines changes with width and may even be fixed.
    property color color: '#000' // Color of lines (background is transparent)
    property vector2d ratio: {
        const max = Math.max(width, height);
        return Qt.vector2d(width/max, height/max);
    }

    fragmentShader: "
        uniform lowp float qt_Opacity;
        uniform lowp float seed;
        uniform lowp float num;
        uniform highp vec2 ratio;
        uniform highp vec4 color;
        varying highp vec2 qt_TexCoord0;
        void main() {
            vec2 uv = qt_TexCoord0*ratio/ ratio.x;
            // Slope can be changed by multiplying 'y' by any number.
            uv.x -= uv.y + seed/num; 
            vec2 grid = fract(uv * vec2(num,1.));
            // You may also change the line width from 0.0 to 1.0. (change 0.5)
            gl_FragColor = color * smoothstep(0.0,ratio.x*1e-1,grid.x - 0.5) * qt_Opacity;
        }"
}

Preview

An example of how it works.

板条线预览
component SlantLinesColumn: Column {
    property real xSeed: 0
    spacing: 5
    Repeater {
        model: 12
        SlantLines {
            height: 10
            width: (index + 1) * 20
            color: Qt.hsva(index/13,1,1,1)
            seed: xSeed
        }
    }
}

SlantLinesColumn { }
SlantLinesColumn {
    y: 190
    Timer {
        running: true; repeat: true
        interval: 50 // Almost 30Hz.
        onTriggered: parent.xSeed -= 0.1
    }
}

ProgressBar

SlantLines can also be embedded in a progressbar.
This template is available from the Qt source code .

import QtQuick 2.15
import QtQuick.Templates 2.15  as T

T.ProgressBar {
    id: control
    implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, implicitContentWidth + leftPadding + rightPadding)
    implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, implicitContentHeight + topPadding + bottomPadding)
    
    property int orientation: Qt.Vertical
    readonly property bool vertical: orientation == Qt.Vertical
    
    contentItem: Item {
        SlantLines {
            y: control.vertical ? parent.height - height : 0
            width: !control.vertical && !control.indeterminate ? 
                        parent.width * control.position : parent.width 
            height: control.vertical && !control.indeterminate ? 
                        parent.height * control.position : parent.height
            color: control.palette.dark
            
            Timer {
                running: control.visible && control.indeterminate
                repeat: true; interval: 50
                onTriggered: parent.seed -= 0.1
            }
        }
    }

    background: Rectangle {
        implicitWidth: !control.vertical ? 200 : 18
        implicitHeight: control.vertical ? 200 : 18
        color: control.palette.midlight            
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM