このページでは

Qt Quick プログラミング入門:アラームアプリケーション

アラームアプリケーションを題材としたQt Quick のチュートリアル。

このチュートリアルでは、Qt Quick およびQt Quick コントロール入門として、簡単なアラームアプリケーションの開発方法を解説します。

このチュートリアルでは、アラームの登録、編集、削除を行うことができます。アラームは指定した日付に発動させることができ、連続する数日間にわたって繰り返し発動するように設定することも可能です。このアプリケーションは、Androidスマートフォンに通常搭載されているアラームアプリと類似しています。

例の動作確認

サンプルは以下の手順で実行できます。

Alarms プロジェクトの作成

このセクションでは、Qt Creator でプロジェクトを作成する方法について説明します。ここでは、Qt Creator によって自動的に生成されるファイルと、プログラマがQt Creator またはその他のエディタで作成する必要がある2つのファイルについて解説します。後者の2つのファイルは、このチュートリアルのソースコードに含まれています。

注: 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 ファイルにコードが生成されます。このコードブロックは、高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タンブラーのグラフィックレイアウトを定義します。
qml.qrcmain.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 )をクリックすることで、新しいアラームを追加できます。これをクリックすると、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形式で行うことができます。

ソースファイル

サンプルプロジェクト @ code.qt.io

関連項目: Qt での JSON サポート、すべての Qt サンプル、およびQt Quick のサンプルとチュートリアル。

© 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.