QML 中的时间选择器

Time Picker in QML

我需要让用户能够 select QML 应用程序中的日期和时间。对于 selection 日期,QtQuick 控件中有 Calendar。 我还没有找到类似的控件来让用户 select 一天中的时间。

互联网上有几个例子,例如 Grog or Harmattan。 然而,我假设它们不像其他 QtQuick 控件那样与本机外观和感觉集成。

是否有我不知道的标准方法、我没有遇到过的好的替代方法或有关选择的建议?

从 Qt 5.5 开始,所谓的 Qt Quick Enterprise Controls will be available also in the community edition of Qt under the name Qt Quick Extras. Among the others, the Tumbler 似乎是满足您要求的可行解决方案:您可以轻松设置两列,一列用于小时,一列用于分钟。

如果您仍然对循环 selection 感兴趣(或者想实现您自己的不倒翁),您可以采用不同的方法,例如创建您自己的继承自 QQuickItem or QQuickPaintedItem or exploiting a custom view with PathView 的组件。后者是我将在这个答案中介绍的情况。有关自定义组件创建的示例,请参阅提供的链接。

引用 PathView 的文档:

The view has a model, which defines the data to be displayed, and a delegate, which defines how the data should be displayed. The delegate is instantiated for each item on the path. The items may be flicked to move them along the path.

因此,路径定义了项目在屏幕上的布局方式,即使是以圆形方式。路径可以通过 Path type, i.e. a sequence of path segments of different kind. PathArc 构建,这是我们感兴趣的路径,因为它提供了所需的圆形。

以下示例使用这些元素来定义循环时间选择器。每个路径都是通过利用委托的 currentIndex 构造的:一个整数用作 PathViews 的模型 - 12 用于小时视图,6 用于分钟视图, 分别。代表的文本是通过利用 index 附加的 属性 并对其进行操作以生成小时和 10 分钟的间隔值来生成的(请参阅代表 Text 项目)。最后,当前元素的文本(即currentItem)绑定到window中心的时间标签:随着currentIndexcurrentItem的变化,也标签得到更新。

整体组件如下所示:

highlight组件(蓝色和绿色圆圈)用于图形表示时间的编辑:可见时可以编辑时间,即路径的另一个Item可以是select编辑。单击中心的时间标签可在正常模式和编辑模式之间切换。

在编辑模式下,用户只需将不同的 hours/minutes 值悬停在 select 上即可。如果单击新 selected hour/minute,则针对该特定 PathView 的编辑将被禁用,相应的突出显示圆圈将消失。

这段代码显然只是一个玩具示例,目的是让您了解 PathView 的用途。可以做一些改进,例如动画,更好的数字定位,详细的分钟表示,漂亮的背景等等。但是它们超出了范围 w.r.t。这个问题并没有被考虑。

import QtQuick 2.4
import QtQuick.Window 2.2
import QtQuick.Controls.Styles 1.3
import QtQuick.Layouts 1.1

Window {
    visible: true
    width: 280; height: 280

    RowLayout {             // centre time label
        anchors.centerIn: parent
        Text {
            id: h
            font.pixelSize: 30
            font.bold: true
            text: outer.currentItem.text
        }
        Text {
            id: div
            font.pixelSize: 30
            font.bold: true
            text: qsTr(":")
        }
        Text {
            id: m
            font.pixelSize: 30
            font.bold: true
            text: inner.currentItem.text
        }

        MouseArea {
            anchors.fill: parent
            onClicked: outer.choiceActive = inner.choiceActive = !outer.choiceActive
        }
    }


    PathView {          // hours path
        id: outer
        property bool pressed: false
        model: 12

        interactive: false
        highlightRangeMode:  PathView.NoHighlightRange
        property bool choiceActive: false

        highlight: Rectangle {
            id: rect
            width: 30 * 1.5
            height: width
            radius: width / 2
            border.color: "darkgray"
            color: "steelblue"
            visible: outer.choiceActive
        }

        delegate: Item {
            id: del
            width: 30
            height: 30
            property bool currentItem: PathView.view.currentIndex == index
            property alias text : textHou.text
            Text {
                id: textHou
                anchors.centerIn: parent
                font.pixelSize: 24
                font.bold: currentItem
                text: index + 1
                color: currentItem ? "black" : "gray"
            }

            MouseArea {
                anchors.fill: parent
                enabled: outer.choiceActive
                onClicked: outer.choiceActive = false
                hoverEnabled: true
                onEntered: outer.currentIndex = index
            }
        }

        path: Path {
            startX: 200; startY: 40
            PathArc {
                x: 80; y: 240
                radiusX: 110; radiusY: 110
                useLargeArc: false
            }
            PathArc {
                x: 200; y: 40
                radiusX: 110; radiusY: 110
                useLargeArc: false
            }
        }
    }

    PathView {          // minutes path
        id: inner
        property bool pressed: false
        model: 6
        interactive: false
        highlightRangeMode:  PathView.NoHighlightRange
        property bool choiceActive: false

        highlight: Rectangle {
            width: 30 * 1.5
            height: width
            radius: width / 2
            border.color: "darkgray"
            color: "lightgreen"
            visible: inner.choiceActive
        }

        delegate: Item {
            width: 30
            height: 30
            property bool currentItem: PathView.view.currentIndex == index
            property alias text : textMin.text
            Text {
                id: textMin
                anchors.centerIn: parent
                font.pixelSize: 24
                font.bold: currentItem
                text: index * 10
                color: currentItem ? "black" : "gray"
            }

            MouseArea {
                anchors.fill: parent
                enabled: inner.choiceActive
                onClicked: inner.choiceActive = false
                hoverEnabled: true
                onEntered: inner.currentIndex = index
            }
        }

        path: Path {
            startX: 140; startY: 60
            PathArc {
                x: 140; y: 220
                radiusX: 40; radiusY: 40
                useLargeArc: false
            }
            PathArc {
                x: 140; y: 60
                radiusX: 40; radiusY: 40
                useLargeArc: false
            }
        }
    }

    // to set current time!
    onVisibleChanged: {
        var d = new Date();
        outer.currentIndex = d.getUTCHours() % 12
        inner.currentIndex = d.getMinutes() / 10
    }
}

我认为我自己的时间选择器很好,你可以根据自己的喜好扩展它的波斯语方向你需要稍微交换一下东西或使用一些布局镜像:
UButton.qml

import QtQuick 2.4
import QtQuick.Controls 2.4
import QtQuick.Controls.Universal 2.4
Button {
    id:root
    Universal.accent: Universal.Cobalt
    Universal.foreground: "white"
    highlighted: true
    font.family: "B Nazanin"
    font.pointSize: 12
}

UCard.qml

import QtQuick 2.4
import QtQuick.Controls 2.4
import QtQuick.Controls.Universal 2.4
import QtGraphicalEffects 1.0
Item{
    property alias radius : morakhasiRect.radius
    property alias color : morakhasiRect.color
    implicitWidth: 150
    implicitHeight: 150

    Rectangle{
        anchors.rightMargin: 1
        anchors.leftMargin: 1
        anchors.bottomMargin: 1
        anchors.topMargin: 1
        id:morakhasiRect
        anchors.fill: parent
        color: "#f5f5f5"
    }
    DropShadow {
        anchors.fill: morakhasiRect
        radius: 9.0
        samples: 17
        color: "#80000000"
        source: morakhasiRect
    }
}

URect.qml

Rectangle{
    color: "transparent"
    border.color: Universal.color(Universal.Cobalt)
    border.width: 1
}

UTumbler.qml

import QtQuick 2.0
import QtQuick.Controls.Universal 2.4
import QtQuick.Controls 2.4
Tumbler{
    id:hourSpin
    wrap: false
    delegate: Text{

        font.pointSize: 12
        text: modelData
        horizontalAlignment: Text.AlignHCenter
        verticalAlignment: Text.AlignVCenter
        opacity: 1.0 - Math.abs(Tumbler.displacement) / (hourSpin.visibleItemCount / 2)
    }
    Rectangle {
        anchors.horizontalCenter: hourSpin.horizontalCenter
        y: hourSpin.height * 0.4
        width: 40
        height: 1
        color: Universal.color(Universal.Cobalt)
    }

    Rectangle {
        anchors.horizontalCenter: hourSpin.horizontalCenter
        y: hourSpin.height * 0.6
        width: 40
        height: 1
        color: Universal.color(Universal.Cobalt)
    }
}

UTimeDialog

import QtQuick 2.0
import QtQuick.Controls 2.4
import QtQuick.Controls.Universal 2.4
Item{
    id:root
    property alias hour : hourSpin.currentIndex
    property alias minute : minuteSpin.currentIndex
    signal open
    signal close
    signal accepted
    signal rejected
    visible: element.opened
    onOpen: element.open()
    onClose: element.close()
    implicitWidth: 200
    implicitHeight: 200
    Dialog {
        id: element
        modal: true
        width: parent.width
        height: parent.height
        padding: 5
        margins: 5
        background: Item{

        }

        onAccepted: {
            root.accepted()
        }
        onRejected: {
            root.rejected()
        }
        contentItem: UCard{
            anchors.fill: parent
            radius: 10
        }

        Column{
            id: column
            spacing: 30
            anchors.centerIn: parent
            Row{
                id: row
                spacing: 20
                anchors.horizontalCenter: parent.horizontalCenter
                Column{
                    id: column1
                    spacing: 15
                    height: 80
                    width: 50
                    clip:true

                    UTumbler{
                        id:hourSpin
                        anchors.horizontalCenter: parent.horizontalCenter
                        anchors.verticalCenter: parent.verticalCenter
                        model: 24
                    }

                }
                Text{
                    text: ":"
                    font.pointSize: 12
                    anchors.verticalCenter: parent.verticalCenter
                    font.family: "B Nazanin"
                }
                Column{
                    id: column2
                    spacing: 15
                    height: 80
                    width: 50
                    clip:true

                    UTumbler{
                        id:minuteSpin
                        anchors.horizontalCenter: parent.horizontalCenter
                        anchors.verticalCenter: parent.verticalCenter
                        model: 60
                    }

                }

            }
            Row{
                anchors.horizontalCenter: parent.horizontalCenter
                spacing: 40
                UButton{
                    text:"select"

                    onClicked: {
                        element.reject()
                    }
                }
                UButton{
                    text: "cancel"
                    onClicked: {
                        element.accept()
                    }
                }
            }
        }

    }
}

UIcoButton.qml

import QtQuick 2.4
import QtQuick.Controls 2.4
import QtQuick.Controls.Universal 2.4
import QtGraphicalEffects 1.0
FocusScope{
    id:focusScope
    signal clicked
    property alias font : icoText.font.family
    property alias icon : icoText.text
    property alias size : icoText.font.pixelSize
    property alias caption : captionTxt.text
    property alias spacing : row.spacing
    property string colorEnter :Universal.color(Universal.Cobalt)
    property string colorExit :"#00171f"
    property alias state: root.state
    implicitWidth: captionTxt.text!= "" ? 100 : 35
    implicitHeight: 40
    Rectangle {
        id: root
        radius: 0
        anchors.fill: parent
        color: colorExit
        state: "default"
        focus: true

        onFocusChanged: {
            if(focus){
                root.border.width = 1
                root.border.color = Universal.color( Universal.Cobalt)
            }
            else{
                root.border.width = 0
                root.border.color = "transparent"
            }
        }

        Row{
            id: row
            anchors.rightMargin: 5
            anchors.leftMargin: 5
            anchors.bottomMargin: 5
            anchors.topMargin: 5
            anchors.fill: parent
            layoutDirection: Qt.RightToLeft
            spacing: 15
            Text {
                id: icoText
                text: ""
                anchors.verticalCenter: parent.verticalCenter
                font.pixelSize: 25
                font.family: "fontawesome"
                color: "white"
            }
            Text{
                id:captionTxt
                text: ""
                anchors.verticalCenter: parent.verticalCenter
                font.pixelSize: icoText.font.pixelSize * 55 /100
                font.family: "B Nazanin"
                color: "white"
                visible: text!= ""
            }

        }
        InnerShadow {
            id:shadow
            anchors.fill: row
            radius: 1.0
            samples: 17
            horizontalOffset: 1
            color: colorExit
            source: row
            visible: false
        }
        //    Glow {
        //        id:shadow
        //        anchors.fill: row
        //        radius: 6
        //        samples: 25
        //        color: "white"
        //        source: row
        //        visible: false
        //    }

        MouseArea{
            id: mouseArea
            anchors.fill: parent
            hoverEnabled: true
            onEntered: {
                if(root.state == "default")
                    root.color = colorEnter
                else{
                    icoText.color = colorEnter
                    captionTxt.color = colorEnter
                }
            }

            onExited: {
                if(root.state == "default")
                    root.color = colorExit
                else{
                    icoText.color = colorExit
                    captionTxt.color = colorExit
                }
            }

            onPressed: {
                shadow.visible = true
            }

            onReleased: {
                shadow.visible = false
            }

            onClicked: {
                focusScope.clicked()
            }
        }
        states: [
            State {
                name: "transparent"
                PropertyChanges {
                    target: root
                    color:"transparent"
                }
                PropertyChanges {
                    target: icoText
                    color:colorExit
                }
                PropertyChanges {
                    target: captionTxt
                    color:colorExit
                }
            },
            State{
                name: "default"
                PropertyChanges {
                    target: root
                    color:"#00171f"
                }
                PropertyChanges {
                    target: icoText
                    color:"white"
                }
                PropertyChanges {
                    target: captionTxt
                    color:"white"
                }
            }

        ]
    }
}

UTimePicker

import QtQuick 2.4
import QtQuick.Controls 2.4
import QtQuick.Controls.Universal 2.4
Item {
    id: scope
    clip: true
    QtObject{
        id:variables
        property var time: ({hour: 0, minute: 0})
        onTimeChanged: {
            refreshDialogTime()

        }
    }
    signal changed
    property alias caption : captionTxt.text
    property size size : Qt.size(30,70)
    property string splitter : ":"
    property alias spacing : row.spacing
    Component.onCompleted: {
        var q = new Date()
        var curtime = q.toLocaleTimeString().substring(0,5);
        if(splitter != ":"){
            curtime.replace(':',splitter)
        }
        var vars = curtime.split(':')
        setTime(vars[0],vars[1])
        refreshDialogTime()
    }
    function refreshDialogTime(){
        dialog.hour = variables.time.hour
        dialog.minute = variables.time.minute
    }

    function getTime(){
        return variables.time;
    }

    function setTimeString(time){
        textArea.text= time
    }

    function setTime(hour,minute){
        var _hour = hour
        if(_hour<10){
            _hour = "0"+hour.toString()
        }
        else{
            _hour = hour.toString()
        }
        var _minute = minute
        if(_minute <10){
            _minute = "0"+minute.toString()
        }
        else{
            _minute = minute.toString()
        }

        var time = _hour+":"+_minute
        textArea.text = time
    }
    implicitHeight: 50
    implicitWidth: 200
    Row{
        id: row
        width: parent.width
        height: parent.height
        spacing: 25
        layoutDirection: Qt.RightToLeft
        Text{
            font.bold: true
            id: captionTxt
            font.pointSize: 12
            horizontalAlignment: Text.AlignRight
            anchors.verticalCenter: parent.verticalCenter
            width: scope.size.width * scope.width /100 - scope.spacing/2
            verticalAlignment: Text.AlignVCenter
            font.family: "B Nazanin"

        }
        Item{
            id: element
            anchors.verticalCenter: parent.verticalCenter
            height: parent.height
            width: scope.size.height * scope.width /100 - scope.spacing/2
            Rectangle{
                id:backrec
                height: parent.height
                anchors.verticalCenter: parent.verticalCenter
                width: parent.width
                border.width: 1
                border.color: "black"
                TextField{
                    id:textArea
                    selectByMouse: true

                    anchors.verticalCenter: parent.verticalCenter

                    height: parent.height
                    rightPadding: 5
                    bottomPadding: 5
                    topPadding: 5
                    padding: 5
                    verticalAlignment: Text.AlignVCenter
                    onFocusChanged: {
                        if(focus){
                            captionTxt.color = Universal.color( Universal.Cobalt)
                            backrec.border.color = Universal.color( Universal.Cobalt)

                        }
                        else{
                            captionTxt.color = "black"
                            backrec.border.color = "black"
                        }
                    }
                    background: URect{

                        color: "transparent"
                        border.color: "black"
                        border.width: 0
                    }
                    onTextChanged: {
                        var _temp = text.split(splitter)
                        if(_temp.length>0){

                            variables.time.hour =_temp[0]==""?0:  _temp[0]
                            variables.time.minute = _temp[1]==""?0:_temp[1]
                        }
                        changed()
                    }


                    placeholderText : "HH:mm"
                    anchors.right: parent.right
                    anchors.left: iconBtn.right
                    font.family: "B Nazanin"
                    font.pointSize: 12
                    inputMask:  "99:99"
                    validator: RegExpValidator { regExp: /^([0-1\s]?[0-9\s]|2[0-3\s]):([0-5\s][0-9\s])$ / }
                }
                IcoButton{
                    anchors.verticalCenter: parent.verticalCenter
                    anchors.left: parent.left
                    anchors.leftMargin: 2
                    id:iconBtn
                    caption: ""
                    size: 30
                    icon: "\uf017"
                    state: "transparent"
                    onClicked: {
                        textArea.focus = true
                        dialog.open()
                    }
                }
            }


        }
    }
    UTimeDialog{
        id:dialog
        x:iconBtn.x
        y:iconBtn.y+ scope.height
        onAccepted: {
            setTime(hour,minute)
        }
    }
}

例子

UTimePicker{
    x: 285
    width: 200
    spacing: 15
    size: Qt.size(35,65)
    caption: "time"
    onChanged: {
        var i =  getTime()
        console.log(i.hour)
        console.log(i.minute)
    }
}

看起来像这样:

用于镜像:

    LayoutMirroring.enabled: true
    LayoutMirroring.childrenInherit: true

如果有人感兴趣,我可以为此共享图书馆

这是iphone选时风格

   Rectangle {
    id:clockid
    width: frame.implicitWidth + 10
    height: frame.implicitHeight + 10
    anchors.centerIn: parent
    color: "cornsilk"
    function formatText(count, modelData) {
        var data = count === 12 ? modelData + 1 : modelData;
        return data.toString().length < 2 ? "0" + data : data;
    }
    FontMetrics {
        id: fontMetrics
        font.pixelSize: 10
    }

    Component {
        id: delegateComponent

        Label {
            text: clockid.formatText(Tumbler.tumbler.count, modelData)
            opacity: 1.0 - Math.abs(Tumbler.displacement) / (Tumbler.tumbler.visibleItemCount / 2)
            horizontalAlignment: Text.AlignHCenter
            verticalAlignment: Text.AlignVCenter
            font.pixelSize: fontMetrics.font.pixelSize * 1.25
        }

    }

    Frame {
        id: frame
        padding: 0
        anchors.centerIn: parent



        Row {
            id: row_clock

            Tumbler {
                id: hoursTumbler
                model: 12
                delegate: delegateComponent
                visibleItemCount: 5
            }

            Tumbler {
                id: minutesTumbler
                model: 60
                delegate: delegateComponent
                visibleItemCount: 5
            }

            Tumbler {
                id: amPmTumbler
                model: ["AM", "PM"]
                delegate: delegateComponent
            }
        }
    }
}