编程入门Qt Quick
Qt Quick 基于报警应用程序的教程。
本教程介绍如何开发一个简单的闹钟应用程序,作为Qt Quick 和Qt Quick Controls 的入门。
该应用程序类似于通常在 Android 手机上使用的闹钟应用程序。其功能可让您输入、编辑或删除闹钟。闹钟可以在指定日期触发,也可以设置在随后的一系列日子重复触发。
主屏幕显示已保存的闹钟列表:
详细界面可让您编辑或删除现有警报:
对话框界面用于添加新警报。点击主屏幕底部的 "+"RoundButton ,对话框就会弹出:
源文件位于 qtdoc 资源库中。您可以从 Qt 项目中获取 Qt 源文件,也可以将其作为 Qt 的一部分进行安装。该应用程序也可在Qt Creator 的Welcome 模式示例列表中找到。
创建 Alarms 项目
本节将介绍如何在Qt Creator 中创建项目。本节将讨论Qt Creator 自动生成的文件,以及程序员必须在Qt Creator 或其他编辑器中创建的两个文件。后两个文件包含在本教程的源代码中。
注: Qt Creator 中的用户界面文本和生成文件的内容取决于您使用的Qt Creator 版本。
Qt Creator
在Qt Creator 中设置新项目时,会有一个向导一步步引导您完成项目创建过程。向导会提示您输入特定类型项目所需的设置,并为您创建项目。
要创建警报项目,请选择File >New Project >Application (Qt) > 。 Qt Quick Application>Choose 。在Name 字段中键入警报,然后按照向导的指示操作。
Qt Quick 应用向导创建的项目包含以下源文件:
源文件 | 目的 |
---|---|
CMakeLists.txt | 项目文件 |
main.cpp | 应用程序的主要 C++ 代码文件。 |
Main.qml | 应用程序的主要 QML 代码文件。我们将在此文件中实例化自定义 QML 类型(AlarmDialog 、AlarmModel 、AlarmDelegate 和TumblerDelegate )。 |
向导会生成下面 main.cpp 文件中的代码。该代码块启用了高 DPI 缩放,并声明了app
和engine
。然后,引擎会加载我们的主 QML 文件。
int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QQmlApplicationEngine engine; QObject::connect( &engine, &QQmlApplicationEngine::objectCreationFailed, &app, []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection);
附加源文件
源文件 | 目的 |
---|---|
qtquickcontrols2.conf | 使用Dark 主题选择Material 风格。 |
AlarmDialog.qml | 定义添加新警报的对话框。 |
AlarmDelegate.qml | 定义应用程序主屏幕的布局。 |
AlarmModel.qml | 定义用于存储警报数据的ListModel 。 |
TumblerDelegate.qml | 定义滚动条的图形布局。 |
qml.qrc | 资源文件,其中包含除 main.cpp 和项目文件之外的源文件名。 |
qtquickcontrols2.conf
以下代码段显示了如何将Dark
主题设置为Material
风格:
[Controls] Style=Material [Material] Theme=Dark Accent=Red
Main.qml
mainWindow
ApplicationWindow QML 类型,是此应用程序的根项目。
ApplicationWindow { id: window width: 400 height: 500 visible: true
ListView alarmListView
将alarmModel
中的数据与alarmDelegate
中定义的布局相结合。
ListView { id: alarmListView anchors.fill: parent model: AlarmModel {} delegate: AlarmDelegate {} }
点击RoundButton addAlarmButton
可添加新的警报。单击后会打开一个Dialog 屏幕alarmDialog
。
RoundButton { id: addAlarmButton text: "+" anchors.bottom: alarmListView.bottom anchors.bottomMargin: 8 anchors.horizontalCenter: parent.horizontalCenter onClicked: alarmDialog.open() } AlarmDialog { id: alarmDialog x: Math.round((parent.width - width) / 2) y: Math.round((parent.height - height) / 2) alarmModel: alarmListView.model }
AlarmDialog.qml
该对话框屏幕上有一个RowLayout ,其中Tumbler 分别表示小时和分钟,另一个RowLayout ,其中 Tumbler 分别表示日、月和年。
contentItem: RowLayout { RowLayout { id: rowTumbler Tumbler { id: hoursTumbler model: 24 delegate: TumblerDelegate { text: alarmDialog.formatNumber(modelData) } } Tumbler { id: minutesTumbler model: 60 delegate: TumblerDelegate { text: alarmDialog.formatNumber(modelData) } } } RowLayout { id: datePicker Layout.leftMargin: 20 property alias dayTumbler: dayTumbler property alias monthTumbler: monthTumbler property alias yearTumbler: yearTumbler readonly property var days: [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] Tumbler { id: dayTumbler function updateModel() { // Populate the model with days of the month. For example: [0, ..., 30] var previousIndex = dayTumbler.currentIndex var array = [] var newDays = datePicker.days[monthTumbler.currentIndex] for (var i = 1; i <= newDays; ++i) array.push(i) dayTumbler.model = array dayTumbler.currentIndex = Math.min(newDays - 1, previousIndex) } Component.onCompleted: updateModel() delegate: TumblerDelegate { text: alarmDialog.formatNumber(modelData) } } Tumbler { id: monthTumbler onCurrentIndexChanged: dayTumbler.updateModel() model: 12 delegate: TumblerDelegate { text: alarmDialog.locale.standaloneMonthName(modelData, Locale.ShortFormat) } } Tumbler { id: yearTumbler // This array is populated with the next three years. For example: [2018, 2019, 2020] readonly property var years: (function() { var currentYear = new Date().getFullYear() return [0, 1, 2].map(function(value) { return value + currentYear; }) })() model: years delegate: TumblerDelegate { text: alarmDialog.formatNumber(modelData) } } } } }
如果单击对话框中的确定,输入的数据将添加到alarmModel
中:
onAccepted: { alarmModel.append({ "hour": hoursTumbler.currentIndex, "minute": minutesTumbler.currentIndex, "day": dayTumbler.currentIndex + 1, "month": monthTumbler.currentIndex + 1, "year": yearTumbler.years[yearTumbler.currentIndex], "activated": true, "label": "", "repeat": false, "daysToRepeat": [ { "dayOfWeek": 0, "repeat": false }, { "dayOfWeek": 1, "repeat": false }, { "dayOfWeek": 2, "repeat": false }, { "dayOfWeek": 3, "repeat": false }, { "dayOfWeek": 4, "repeat": false }, { "dayOfWeek": 5, "repeat": false }, { "dayOfWeek": 6, "repeat": false } ], }) } onRejected: alarmDialog.close()
AlarmDelegate.qml
主界面中的每个警报都是一个ItemDelegate 。ItemDelegate root
包含主界面和详细界面的所有字段。详细界面的字段只有在点击警报后才可见,即root.checked
是true
。
ItemDelegate { id: root width: parent.width checkable: true required property int index required property int hour required property int minute required property int day required property int month required property int year required property bool activated required property string label required property bool repeat required property list<var> daysToRepeat onClicked: ListView.view.currentIndex = index contentItem: ColumnLayout { spacing: 0 RowLayout { ColumnLayout { id: dateColumn readonly property date alarmDate: new Date( root.year, root.month - 1, root.day, root.hour, root.minute) Label { id: timeLabel font.pixelSize: (Qt.application as Application).font.pixelSize * 2 text: dateColumn.alarmDate.toLocaleTimeString(root.locale, Locale.ShortFormat) } RowLayout { Label { id: dateLabel text: dateColumn.alarmDate.toLocaleDateString(root.locale, Locale.ShortFormat) } Label { id: alarmAbout text: "⸱ " + root.label visible: root.label.length > 0 && !root.checked } } } Item { Layout.fillWidth: true } Switch { checked: root.activated Layout.alignment: Qt.AlignTop onClicked: root.activated = checked } } CheckBox { id: alarmRepeat text: qsTr("Repeat") checked: root.repeat visible: root.checked onToggled: root.repeat = checked } Flow { visible: root.checked && root.repeat Layout.fillWidth: true Repeater { id: dayRepeater model: root.daysToRepeat delegate: RoundButton { required property int dayOfWeek required property bool repeat text: Qt.locale().dayName(dayOfWeek, Locale.NarrowFormat) flat: true checked: repeat checkable: true Material.background: checked ? Material.accent : "transparent" onToggled: repeat = checked } } } TextField { id: alarmDescriptionTextField placeholderText: qsTr("Enter description here") cursorVisible: true visible: root.checked text: root.label onTextEdited: root.label = text } Button { id: deleteAlarmButton text: qsTr("Delete") visible: root.checked onClicked: root.ListView.view.model.remove(root.ListView.view.currentIndex, 1) } } }
AlarmModel.qml
该 QML 文件包含alarmModel
的定义,即管理警报数据的ListModel 。
它创建了五个带有警报示例的ListElements 。
import QtQuick // Populate the model with some sample data. ListModel { id: alarmModel ListElement { hour: 6 minute: 0 day: 2 month: 8 year: 2018 activated: true label: "Wake up" repeat: true daysToRepeat: [ ListElement { dayOfWeek: 0; repeat: false }, ListElement { dayOfWeek: 1; repeat: false }, ListElement { dayOfWeek: 2; repeat: false }, ListElement { dayOfWeek: 3; repeat: false }, ListElement { dayOfWeek: 4; repeat: false }, ListElement { dayOfWeek: 5; repeat: false }, ListElement { dayOfWeek: 6; repeat: false } ] } ListElement { hour: 6 minute: 0 day: 3 month: 8 year: 2018 activated: true label: "Wake up" repeat: true daysToRepeat: [ ListElement { dayOfWeek: 0; repeat: true }, ListElement { dayOfWeek: 1; repeat: true }, ListElement { dayOfWeek: 2; repeat: true }, ListElement { dayOfWeek: 3; repeat: true }, ListElement { dayOfWeek: 4; repeat: true }, ListElement { dayOfWeek: 5; repeat: false }, ListElement { dayOfWeek: 6; repeat: false } ] } ListElement { hour: 7 minute: 0 day: 3 month: 8 year: 2018 activated: false label: "Exercise" repeat: true daysToRepeat: [ ListElement { dayOfWeek: 0; repeat: true }, ListElement { dayOfWeek: 1; repeat: true }, ListElement { dayOfWeek: 2; repeat: true }, ListElement { dayOfWeek: 3; repeat: true }, ListElement { dayOfWeek: 4; repeat: true }, ListElement { dayOfWeek: 5; repeat: true }, ListElement { dayOfWeek: 6; repeat: true } ] } ListElement { hour: 5 minute: 15 day: 1 month: 9 year: 2018 activated: true label: "" repeat: false daysToRepeat: [ ListElement { dayOfWeek: 0; repeat: false }, ListElement { dayOfWeek: 1; repeat: false }, ListElement { dayOfWeek: 2; repeat: false }, ListElement { dayOfWeek: 3; repeat: false }, ListElement { dayOfWeek: 4; repeat: false }, ListElement { dayOfWeek: 5; repeat: false }, ListElement { dayOfWeek: 6; repeat: false } ] } ListElement { hour: 5 minute: 45 day: 3 month: 9 year: 2018 activated: false label: "" repeat: false daysToRepeat: [ ListElement { dayOfWeek: 0; repeat: false }, ListElement { dayOfWeek: 1; repeat: false }, ListElement { dayOfWeek: 2; repeat: false }, ListElement { dayOfWeek: 3; repeat: false }, ListElement { dayOfWeek: 4; repeat: false }, ListElement { dayOfWeek: 5; repeat: false }, ListElement { dayOfWeek: 6; repeat: false } ] } }
TumblerDelegate.qml
TumblerDelegate 定义了滚揉器的图形属性。
import QtQuick import QtQuick.Controls import QtQuick.Controls.Material Text { required property int modelData text: modelData color: Tumbler.tumbler.Material.foreground font: Tumbler.tumbler.font opacity: 1.0 - Math.abs(Tumbler.displacement) / (Tumbler.tumbler.visibleItemCount / 2) horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter }
输入新警报
在启动界面的底部,您可以看到一个用于添加警报的按钮。单击该按钮可打开添加新警报对话框。
RoundButton { id: addAlarmButton text: "+" anchors.bottom: alarmListView.bottom anchors.bottomMargin: 8 anchors.horizontalCenter: parent.horizontalCenter onClicked: alarmDialog.open() }
新警报对话框:
所有字段均使用Tumbler QML 类型输入。如果按OK
,在滚轮中选择的值将写入alarmModel
。
contentItem: RowLayout { RowLayout { id: rowTumbler Tumbler { id: hoursTumbler model: 24 delegate: TumblerDelegate { text: alarmDialog.formatNumber(modelData) } } Tumbler { id: minutesTumbler model: 60 delegate: TumblerDelegate { text: alarmDialog.formatNumber(modelData) } } } RowLayout { id: datePicker Layout.leftMargin: 20 property alias dayTumbler: dayTumbler property alias monthTumbler: monthTumbler property alias yearTumbler: yearTumbler readonly property var days: [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] Tumbler { id: dayTumbler function updateModel() { // Populate the model with days of the month. For example: [0, ..., 30] var previousIndex = dayTumbler.currentIndex var array = [] var newDays = datePicker.days[monthTumbler.currentIndex] for (var i = 1; i <= newDays; ++i) array.push(i) dayTumbler.model = array dayTumbler.currentIndex = Math.min(newDays - 1, previousIndex) } Component.onCompleted: updateModel() delegate: TumblerDelegate { text: alarmDialog.formatNumber(modelData) } } Tumbler { id: monthTumbler onCurrentIndexChanged: dayTumbler.updateModel() model: 12 delegate: TumblerDelegate { text: alarmDialog.locale.standaloneMonthName(modelData, Locale.ShortFormat) } } Tumbler { id: yearTumbler // This array is populated with the next three years. For example: [2018, 2019, 2020] readonly property var years: (function() { var currentYear = new Date().getFullYear() return [0, 1, 2].map(function(value) { return value + currentYear; }) })() model: years delegate: TumblerDelegate { text: alarmDialog.formatNumber(modelData) } } } } }
编辑警报
如果单击某个警报,则可以在详细界面中对其进行编辑。
单击警报会将root.checked
设为true
,从而显示详细屏幕的字段。
visible: root.checked
如果希望警报在其他日期也触发,请选中alarmRepeat
。中继器将为一周中的每一天显示可选中的RoundButton 。
Flow { visible: root.checked && root.repeat Layout.fillWidth: true Repeater { id: dayRepeater model: root.daysToRepeat delegate: RoundButton { required property int dayOfWeek required property bool repeat text: Qt.locale().dayName(dayOfWeek, Locale.NarrowFormat) flat: true checked: repeat checkable: true Material.background: checked ? Material.accent : "transparent" onToggled: repeat = checked } } }
如果您修改了警报描述,随后也会反映在主屏幕上。
TextField { id: alarmDescriptionTextField placeholderText: qsTr("Enter description here") cursorVisible: true visible: root.checked text: root.label onTextEdited: root.label = text }
删除警报
详细界面(见上文)有一个用于删除警报的按钮。当发出onClicked
时,当前的ListElement 将从alarmModel
中删除。
Button { id: deleteAlarmButton text: qsTr("Delete") visible: root.checked onClicked: root.ListView.view.model.remove(root.ListView.view.currentIndex, 1) }
摘要
该程序没有为闹钟添加声音或振动的代码,也没有以任何格式或数据库存储闹钟。也许添加这些功能会是一个有趣的编码项目。可以用JSON 格式快速、轻松地存储数据。
另请参阅 Qt 中的 JSON 支持。
© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.