Qt Quick 프로그래밍 시작하기: 알람 애플리케이션
알람 애플리케이션을 기반으로 한 Qt Quick 튜토리얼.
이 튜토리얼은 Qt Quick 와 Qt Quick Controls 를 소개하는 차원에서 간단한 알람 애플리케이션을 개발하는 방법을 보여줍니다.
이 튜토리얼에서는 알람을 추가, 수정 또는 삭제할 수 있습니다. 알람은 지정된 날짜에 작동할 수 있으며, 이후 일련의 날짜에 반복되도록 설정할 수도 있습니다. 이 애플리케이션은 일반적으로 안드로이드 폰에서 볼 수 있는 알람 애플리케이션과 유사합니다.
예제 실행
다음 경로에서 예제를 실행할 수 있습니다.
- Qt Creator
Welcome 모드를 열고 Examples 에서 예제를 선택하십시오. 자세한 내용은 Qt Creator: 튜토리얼: 빌드 및 실행을 참조하십시오.
- Qt Extension for Visual Studio Code
Command Palette 에서 Qt: Open Qt examples 명령을 실행하고, 목록에서 예제를 선택합니다. 자세한 내용은 Qt Extension for Visual Studio Code: 튜토리얼: 빌드 및 실행을 참조하십시오.
Alarms 프로젝트 생성
이 섹션에서는 Qt Creator 에서 프로젝트를 생성하는 방법을 설명합니다. 여기서는 Qt Creator 에서 자동으로 생성되는 파일과, 프로그래머가 Qt Creator 또는 다른 편집기에서 직접 만들어야 하는 두 개의 파일에 대해 다룹니다. 후자의 두 파일은 이 튜토리얼의 소스 코드에 포함되어 있습니다.
참고: Qt Creator 의UI 텍스트와 생성된 파일의 내용은 사용 중인 Qt Creator 버전에 따라 달라집니다.
Qt Creator
Qt Creator 에서 새 프로젝트를 설정할 때는 프로젝트 생성 과정을 단계별로 안내하는 마법사가 도움을 줍니다. 이 마법사는 해당 프로젝트 유형에 필요한 설정을 입력하도록 안내하고, 프로젝트를 자동으로 생성해 줍니다.
‘Alarms’ 프로젝트를 생성하려면 File > New Project > Application (Qt) > Qt Quick Application > ‘ Choose ’을 선택하십시오. ‘ Name ’ 필드에 ‘alarms’를 입력하고 마법사의 지침을 따르십시오. CMake 이외의 빌드 시스템을 사용하거나 Qt 6보다 낮은 버전을 사용하려면 ‘ Qt Quick Application (compact)’을 선택하십시오.


Qt Quick 애플리케이션 마법사는 다음 소스 파일이 포함된 프로젝트를 생성합니다:
| 소스 파일 | 용도 |
|---|---|
| CMakeLists.txt | 프로젝트 파일 |
| main.cpp | 애플리케이션의 주요 C++ 코드 파일입니다. |
| Main.qml | 애플리케이션의 주요 QML 코드 파일입니다. 이 파일에서 사용자 정의 QML 유형(AlarmDialog, AlarmModel, AlarmDelegate 및 TumblerDelegate)을 인스턴스화할 것입니다. |
마법사는 아래의 main.cpp 파일에 코드를 생성합니다. 이 코드 블록은 고해상도(High 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 | Material 스타일을 Dark 테마와 함께 선택합니다. |
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=RedMain.qml
mainWindow ApplicationWindow 의 QML 유형인 [...], 이 앱의 루트 항목입니다.
ApplicationWindow {
id: window
width: 400
height: 500
visible: trueListView 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
이 대화 상자 화면에는 시간과 분을 각각 설정할 수 있는 Tumbler 이 있는 RowLayout 과, 일, 월, 연도를 각각 설정할 수 있는 Tumbler이 있는 또 다른 RowLayout 이 있습니다.
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
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 (let 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)
}
}
}
}
}이 대화 상자에서 ‘확인(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
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 파일에는 알람 데이터를 관리하는 ListModel 인 alarmModel 의 정의가 포함되어 있습니다.
이 파일은 예시 알람이 포함된 5개의 ` 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
required property int index
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
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 (let 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 형식으로 수행할 수 있습니다.
소스 파일
© 2026 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.