Qt Quick编程入门
基于闹钟应用的Qt Quick教程。
本教程展示了如何开发一个简单的闹钟应用,作为Qt Quick和Qt Quick Controls的入门介绍。
此应用类似于在Android手机上通常会找到的闹钟应用。其功能允许您输入、编辑或删除闹钟。闹钟可以在特定日期触发,并可以设置为在一系列后续日期重复。
主屏幕显示了已保存闹钟的列表
详情屏幕允许您编辑或删除现有闹钟
对话框屏幕用于添加新闹钟。当您在主屏幕底部的 "+" 圆形按钮 上点击时,它会弹出
源文件位于qtdoc存储库中。您可以从中获取Qt项目中的Qt源代码,或将它们作为Qt的一部分安装。该应用也包含在Qt Creator欢迎模式的示例列表中。
创建闹钟项目
本节展示了如何在Qt Creator中创建项目。它讨论了Qt Creator自动生成的文件以及程序员必须在Qt Creator或其他编辑器中创建的两个文件。后两个文件包含在本教程的源代码中。
注意: Qt Creator中的UI文本和生成文件的内容取决于您使用的Qt Creator版本。
Qt Creator
使用Qt Creator中的向导可以帮助您设置新的项目。向导将逐步引导您完成项目创建过程,并要求您输入特定类型项目所需的设置,然后为您创建项目。
要创建闹钟项目,选择 文件 > 新建项目 > 应用程序(Qt) > Qt Quick应用程序 > 选择。在 名称 字段中键入 alarms,并按照向导的说明操作。
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; const QUrl url(u"qrc:/alarms/main.qml"_s); QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { if (!obj && url == objUrl) QCoreApplication::exit(-1); },
额外源文件
源文件 | 用途 |
---|---|
qtquickcontrols2.conf | 选择具有 暗色 主题的 Material 风格。 |
AlarmDialog.qml | 定义添加新闹钟的对话框。 |
AlarmDelegate.qml | 定义应用主界面的布局。 |
AlarmModel.qml | 定义用于存储闹钟数据的ListModel。 |
TumblerDelegate.qml | 定义Tumblers的图形布局。 |
qml.qrc | 资源文件,包含源文件名称,但不包括main.cpp和项目文件。 |
qtquickcontrols2.conf
以下片段展示了如何在Material
样式中使用Dark
主题。
[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
来添加新闹钟。点击它将打开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
此对话框屏幕包含一个包含用于小时和分钟的Tumbler的RowLayout
,以及一个包含用于天、月和年的Tumbler的另一个RowLayout
。
contentItem: RowLayout { RowLayout { id: rowTumbler Tumbler { id: hoursTumbler model: 24 delegate: TumblerDelegate { text: formatNumber(modelData) } } Tumbler { id: minutesTumbler model: 60 delegate: TumblerDelegate { text: 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: formatNumber(modelData) } } Tumbler { id: monthTumbler onCurrentIndexChanged: dayTumbler.updateModel() model: 12 delegate: TumblerDelegate { text: window.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: formatNumber(modelData) } } } } }
如果在对话框中点击OK,则输入的数据将添加到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 onClicked: ListView.view.currentIndex = index contentItem: ColumnLayout { spacing: 0 RowLayout { ColumnLayout { id: dateColumn readonly property date alarmDate: new Date( model.year, model.month - 1, model.day, model.hour, model.minute) Label { id: timeLabel font.pixelSize: Qt.application.font.pixelSize * 2 text: dateColumn.alarmDate.toLocaleTimeString(window.locale, Locale.ShortFormat) } RowLayout { Label { id: dateLabel text: dateColumn.alarmDate.toLocaleDateString(window.locale, Locale.ShortFormat) } Label { id: alarmAbout text: "⸱ " + model.label visible: model.label.length > 0 && !root.checked } } } Item { Layout.fillWidth: true } Switch { checked: model.activated Layout.alignment: Qt.AlignTop onClicked: model.activated = checked } } CheckBox { id: alarmRepeat text: qsTr("Repeat") checked: model.repeat visible: root.checked onToggled: model.repeat = checked } Flow { visible: root.checked && model.repeat Layout.fillWidth: true Repeater { id: dayRepeater model: daysToRepeat delegate: RoundButton { text: Qt.locale().dayName(model.dayOfWeek, Locale.NarrowFormat) flat: true checked: model.repeat checkable: true Material.background: checked ? Material.accent : "transparent" onToggled: model.repeat = checked } } } TextField { id: alarmDescriptionTextField placeholderText: qsTr("Enter description here") cursorVisible: true visible: root.checked text: model.label onTextEdited: model.label = text } Button { id: deleteAlarmButton text: qsTr("Delete") width: 40 height: 40 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定义了Tumblers的图形属性。
import QtQuick import QtQuick.Controls import QtQuick.Controls.Material Text { 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
,则选择的Tumbler中的值将写入alarmModel
。
contentItem: RowLayout { RowLayout { id: rowTumbler Tumbler { id: hoursTumbler model: 24 delegate: TumblerDelegate { text: formatNumber(modelData) } } Tumbler { id: minutesTumbler model: 60 delegate: TumblerDelegate { text: 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: formatNumber(modelData) } } Tumbler { id: monthTumbler onCurrentIndexChanged: dayTumbler.updateModel() model: 12 delegate: TumblerDelegate { text: window.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: formatNumber(modelData) } } } } }
编辑闹钟
如果您点击某个特定的闹钟,您可以在详情界面中编辑它。
点击闹钟将使root.checked
设置为true
,从而使详情界面上的字段可见。
visible: root.checked
如果您想使闹钟在其它天也能触发,请检查alarmRepeat
。Repeater将为每周的每一天显示一个可复选的RoundButton
。
Flow { visible: root.checked && model.repeat Layout.fillWidth: true Repeater { id: dayRepeater model: daysToRepeat delegate: RoundButton { text: Qt.locale().dayName(model.dayOfWeek, Locale.NarrowFormat) flat: true checked: model.repeat checkable: true Material.background: checked ? Material.accent : "transparent" onToggled: model.repeat = checked } } }
如果您修改闹钟的描述,它将在之后的界面中反映出来。
TextField { id: alarmDescriptionTextField placeholderText: qsTr("Enter description here") cursorVisible: true visible: root.checked text: model.label onTextEdited: model.label = text }
删除闹钟
详情页面(见上图)有一个用于删除警报的按钮。当onClicked
事件被触发时,当前的ListElement将从alarmModel
中删除。
Button { id: deleteAlarmButton text: qsTr("Delete") width: 40 height: 40 visible: root.checked onClicked: root.ListView.view.model.remove(root.ListView.view.currentIndex, 1) }
总结
应用程序没有为警报添加声音或振动的代码,也不会以任何格式或数据库存储警报。可能添加这些功能的编码项目会很有趣。数据存储可以快速简单地以JSON格式进行。
另请参阅Qt中的JSON支持。
© 2024 Qt公司。本文件中包含的文档贡献是各自所有者的版权。所提供的文档是根据由自由软件基金会发布并依照<查明阅许可证版本1.3<的可。