프로그래밍 시작하기 Qt Quick

알람 애플리케이션을 기반으로 한 Qt Quick 자습서입니다.

이 튜토리얼에서는 Qt QuickQt Quick Controls 에 대한 소개로 간단한 알람 애플리케이션을 개발하는 방법을 보여줍니다.

이 애플리케이션은 일반적으로 안드로이드 휴대폰에서 볼 수 있는 알람 애플리케이션과 유사합니다. 이 애플리케이션의 기능을 사용하면 알람을 입력, 편집 또는 삭제할 수 있습니다. 알람은 지정된 날짜에 트리거될 수 있으며, 이후 일련의 날짜에 반복되도록 설정할 수 있습니다.

메인 화면에는 저장된 알람 목록이 표시됩니다:

"Alarms application"

상세 화면에서는 기존 알람을 편집하거나 삭제할 수 있습니다:

"Detail screen"

대화 화면은 새 알람을 추가하는 데 사용됩니다. 메인 화면 하단의 "+" RoundButton 를 클릭하면 대화상자가 나타납니다:

"Add alarms"

소스 파일은 qtdoc 리포지토리에 있습니다. Qt 프로젝트에서 Qt 소스를 가져오거나 Qt의 일부로 설치할 수 있습니다. 이 애플리케이션은 Qt Creator 의 시작 모드의 예제 목록에서도 사용할 수 있습니다.

알람 프로젝트 생성하기

이 섹션에서는 Qt Creator 에서 프로젝트를 생성하는 방법을 보여줍니다. Qt Creator 에서 자동으로 생성되는 파일과 프로그래머가 Qt Creator 또는 다른 편집기에서 생성해야 하는 두 개의 파일에 대해 설명합니다. 후자의 두 파일은 이 튜토리얼의 소스 코드에 포함되어 있습니다.

참고: Qt Creator 의 UI 텍스트와 생성된 파일의 내용은 사용하는 Qt Creator 버전에 따라 다릅니다.

Qt Creator

Qt Creator 에서 새 프로젝트를 설정하면 프로젝트 생성 과정을 단계별로 안내하는 마법사의 도움을 받을 수 있습니다. 마법사는 특정 유형의 프로젝트에 필요한 설정을 입력하라는 메시지를 표시하고 프로젝트를 만들어 줍니다.

알람 프로젝트를 만들려면 File > New Project > Application (Qt) > Qt Quick Application > Choose 을 선택합니다. Name 필드에 알람을 입력하고 마법사의 안내를 따릅니다.

"Qt Creator New Project dialog"

"Project Location"

Qt Quick 애플리케이션 마법사가 다음 소스 파일을 포함하는 프로젝트를 만듭니다:

소스 파일목적
CMakeLists.txt프로젝트 파일
main.cpp애플리케이션의 메인 C++ 코드 파일입니다.
main.qml애플리케이션의 메인 QML 코드 파일입니다. 이 파일에서 사용자 지정 QML 유형(AlarmDialog, AlarmModel, AlarmDelegate, TumblerDelegate)을 인스턴스화합니다.

마법사는 아래 main.cpp 파일에 코드를 생성합니다. 이 코드 블록은 High DPI 스케일링을 활성화하고 appengine 을 선언합니다. 그런 다음 엔진이 기본 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.confDark 테마로 Material 스타일을 선택합니다.
AlarmDialog.qml새 알람을 추가하기 위한 대화 상자를 정의합니다.
AlarmDelegate.qml앱의 기본 화면 레이아웃을 정의합니다.
AlarmModel.qml알람 데이터를 저장하는 데 사용되는 ListModel 을 정의합니다.
TumblerDelegate.qml텀블러의 그래픽 레이아웃을 정의합니다.
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 alarmListViewalarmModel 의 데이터를 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

이 대화 상자 화면에는 시간과 분을 나타내는 RowLayoutTumbler 이 있고 일, 월, 연도를 나타내는 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

            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.checkedtrue 일 때만 표시됩니다.

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 의 정의가 포함되어 있습니다.

예제 알람으로 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

텀블러 델리게이트는 텀블러의 그래픽 속성을 정의합니다.

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()
    }

새 알람을 위한 대화창입니다:

"Add alarms"

모든 필드는 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.checkedtrue 으로 설정되어 세부정보 화면의 필드가 표시됩니다.

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 을 누르면 현재 ListElementalarmModel 에서 삭제됩니다.

        Button {
            id: deleteAlarmButton
            text: qsTr("Delete")
            visible: root.checked
            onClicked: root.ListView.view.model.remove(root.ListView.view.currentIndex, 1)
        }
요약

이 앱에는 알람에 소리나 진동을 추가하는 코드가 없으며, 알람을 어떤 형식이나 데이터베이스에 저장하지도 않습니다. 이러한 기능을 추가하는 것은 흥미로운 코딩 프로젝트가 될 수 있습니다. 데이터를 JSON 형식으로 빠르고 쉽게 저장할 수 있습니다.

예제 프로젝트 @ code.qt.io

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.