モノ創りで国造りを

ハード/ソフト問わず知見をまとめてます

Qt quickをサンプルプロジェクトから学ぶ

背景

Qt Quick、QMLを学習したいが参考書籍やYoutube動画が少ない。サンプルプロジェクトのGalleryがためになるという情報を入手したのでコードを閲覧しまとめる。

サンプルプロジェクト

今回ソースコードを確認するサンプルプロジェクトは、Qt Creatorを立ち上げ後にExampleから選択可能。 f:id:yuji2yuji:20220324205636p:plain

プロジェクトをビルドすると以下のようなwindowが表示される。

f:id:yuji2yuji:20220324205822p:plain

左上のハンバーガーメニューをクリックすると各コンポーネント名が表示される。

f:id:yuji2yuji:20220324205843p:plain

コンポーネントを選択しクリックすると、コンポーネント名に応じた画面が表示される。 以下はButtonを選択した場合の画面。

f:id:yuji2yuji:20220324210018p:plain

サンプルプロジェクトなので、当然全てのコードが閲覧可能である。従って各コンポーネントの記述方法・プロパティなどが確認できる。

ファイル構成

プロジェクトのファイル構成は、メイン画面となるgallery.qmlと各コンポーネント用にcomponent名.qmlがpageディレクトリに収められている。

gallery.qmlのコードは以下の通り。

import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Qt.labs.settings

import "." as App

ApplicationWindow {
    id: window
    width: 360
    height: 520
    visible: true
    title: "Qt Quick Controls"

    function help() {
        let displayingControl = listView.currentIndex !== -1
        let currentControlName = displayingControl
            ? listView.model.get(listView.currentIndex).title.toLowerCase() : ""
        let url = "https://doc.qt.io/qt-5/"
            + (displayingControl
               ? "qml-qtquick-controls2-" + currentControlName + ".html"
               : "qtquick-controls2-qmlmodule.html");
        Qt.openUrlExternally(url)
    }

    required property var builtInStyles

    Settings {
        id: settings
        property string style
    }

    Shortcut {
        sequences: ["Esc", "Back"]
        enabled: stackView.depth > 1
        onActivated: navigateBackAction.trigger()
    }

    Shortcut {
        sequence: StandardKey.HelpContents
        onActivated: help()
    }

    Action {
        id: navigateBackAction
        icon.name: stackView.depth > 1 ? "back" : "drawer"
        onTriggered: {
            if (stackView.depth > 1) {
                stackView.pop()
                listView.currentIndex = -1
            } else {
                drawer.open()
            }
        }
    }

    Shortcut {
        sequence: "Menu"
        onActivated: optionsMenuAction.trigger()
    }

    Action {
        id: optionsMenuAction
        icon.name: "menu"
        onTriggered: optionsMenu.open()
    }

    header: App.ToolBar {
        RowLayout {
            spacing: 20
            anchors.fill: parent

            ToolButton {
                action: navigateBackAction
            }

            Label {
                id: titleLabel
                text: listView.currentItem ? listView.currentItem.text : "Gallery"
                font.pixelSize: 20
                elide: Label.ElideRight
                horizontalAlignment: Qt.AlignHCenter
                verticalAlignment: Qt.AlignVCenter
                Layout.fillWidth: true
            }

            ToolButton {
                action: optionsMenuAction

                Menu {
                    id: optionsMenu
                    x: parent.width - width
                    transformOrigin: Menu.TopRight

                    Action {
                        text: "Settings"
                        onTriggered: settingsDialog.open()
                    }
                    Action {
                        text: "Help"
                        onTriggered: help()
                    }
                    Action {
                        text: "About"
                        onTriggered: aboutDialog.open()
                    }
                }
            }
        }
    }

    Drawer {
        id: drawer
        width: Math.min(window.width, window.height) / 3 * 2
        height: window.height
        interactive: stackView.depth === 1

        ListView {
            id: listView

            focus: true
            currentIndex: -1
            anchors.fill: parent

            delegate: ItemDelegate {
                width: listView.width
                text: model.title
                highlighted: ListView.isCurrentItem
                onClicked: {
                    listView.currentIndex = index
                    stackView.push(model.source)
                    drawer.close()
                }
            }

            model: ListModel {
                ListElement { title: "BusyIndicator"; source: "qrc:/pages/BusyIndicatorPage.qml" }
                ListElement { title: "Button"; source: "qrc:/pages/ButtonPage.qml" }
                ListElement { title: "CheckBox"; source: "qrc:/pages/CheckBoxPage.qml" }
                ListElement { title: "ComboBox"; source: "qrc:/pages/ComboBoxPage.qml" }
                ListElement { title: "DelayButton"; source: "qrc:/pages/DelayButtonPage.qml" }
                ListElement { title: "Dial"; source: "qrc:/pages/DialPage.qml" }
                ListElement { title: "Dialog"; source: "qrc:/pages/DialogPage.qml" }
                ListElement { title: "Delegates"; source: "qrc:/pages/DelegatePage.qml" }
                ListElement { title: "Frame"; source: "qrc:/pages/FramePage.qml" }
                ListElement { title: "GroupBox"; source: "qrc:/pages/GroupBoxPage.qml" }
                ListElement { title: "PageIndicator"; source: "qrc:/pages/PageIndicatorPage.qml" }
                ListElement { title: "ProgressBar"; source: "qrc:/pages/ProgressBarPage.qml" }
                ListElement { title: "RadioButton"; source: "qrc:/pages/RadioButtonPage.qml" }
                ListElement { title: "RangeSlider"; source: "qrc:/pages/RangeSliderPage.qml" }
                ListElement { title: "ScrollBar"; source: "qrc:/pages/ScrollBarPage.qml" }
                ListElement { title: "ScrollIndicator"; source: "qrc:/pages/ScrollIndicatorPage.qml" }
                ListElement { title: "Slider"; source: "qrc:/pages/SliderPage.qml" }
                ListElement { title: "SpinBox"; source: "qrc:/pages/SpinBoxPage.qml" }
                ListElement { title: "StackView"; source: "qrc:/pages/StackViewPage.qml" }
                ListElement { title: "SwipeView"; source: "qrc:/pages/SwipeViewPage.qml" }
                ListElement { title: "Switch"; source: "qrc:/pages/SwitchPage.qml" }
                ListElement { title: "TabBar"; source: "qrc:/pages/TabBarPage.qml" }
                ListElement { title: "TextArea"; source: "qrc:/pages/TextAreaPage.qml" }
                ListElement { title: "TextField"; source: "qrc:/pages/TextFieldPage.qml" }
                ListElement { title: "ToolTip"; source: "qrc:/pages/ToolTipPage.qml" }
                ListElement { title: "Tumbler"; source: "qrc:/pages/TumblerPage.qml" }
            }

            ScrollIndicator.vertical: ScrollIndicator { }
        }
    }

    StackView {
        id: stackView
        anchors.fill: parent

        initialItem: Pane {
            id: pane

            Image {
                id: logo
                width: pane.availableWidth / 2
                height: pane.availableHeight / 2
                anchors.centerIn: parent
                anchors.verticalCenterOffset: -50
                fillMode: Image.PreserveAspectFit
                source: "images/qt-logo.png"
            }

            Label {
                text: "Qt Quick Controls provides a set of controls that can be used to build complete interfaces in Qt Quick."
                anchors.margins: 20
                anchors.top: logo.bottom
                anchors.left: parent.left
                anchors.right: parent.right
                anchors.bottom: arrow.top
                horizontalAlignment: Label.AlignHCenter
                verticalAlignment: Label.AlignVCenter
                wrapMode: Label.Wrap
            }

            Image {
                id: arrow
                source: "images/arrow.png"
                anchors.left: parent.left
                anchors.bottom: parent.bottom
            }
        }
    }

    Dialog {
        id: settingsDialog
        x: Math.round((window.width - width) / 2)
        y: Math.round(window.height / 6)
        width: Math.round(Math.min(window.width, window.height) / 3 * 2)
        modal: true
        focus: true
        title: "Settings"

        standardButtons: Dialog.Ok | Dialog.Cancel
        onAccepted: {
            settings.style = styleBox.displayText
            settingsDialog.close()
        }
        onRejected: {
            styleBox.currentIndex = styleBox.styleIndex
            settingsDialog.close()
        }

        contentItem: ColumnLayout {
            id: settingsColumn
            spacing: 20

            RowLayout {
                spacing: 10

                Label {
                    text: "Style:"
                }

                ComboBox {
                    id: styleBox
                    property int styleIndex: -1
                    model: window.builtInStyles
                    Component.onCompleted: {
                        styleIndex = find(settings.style, Qt.MatchFixedString)
                        if (styleIndex !== -1)
                            currentIndex = styleIndex
                    }
                    Layout.fillWidth: true
                }
            }

            Label {
                text: "Restart required"
                color: "#e41e25"
                opacity: styleBox.currentIndex !== styleBox.styleIndex ? 1.0 : 0.0
                horizontalAlignment: Label.AlignHCenter
                verticalAlignment: Label.AlignVCenter
                Layout.fillWidth: true
                Layout.fillHeight: true
            }
        }
    }

    Dialog {
        id: aboutDialog
        modal: true
        focus: true
        title: "About"
        x: (window.width - width) / 2
        y: window.height / 6
        width: Math.min(window.width, window.height) / 3 * 2
        contentHeight: aboutColumn.height

        Column {
            id: aboutColumn
            spacing: 20

            Label {
                width: aboutDialog.availableWidth
                text: "The Qt Quick Controls module delivers the next generation user interface controls based on Qt Quick."
                wrapMode: Label.Wrap
                font.pixelSize: 12
            }

            Label {
                width: aboutDialog.availableWidth
                text: "In comparison to Qt Quick Controls 1, Qt Quick Controls "
                    + "are an order of magnitude simpler, lighter, and faster."
                wrapMode: Label.Wrap
                font.pixelSize: 12
            }
        }
    }
}

中腹にある以下のコードで別ファイルのコードを呼び出している。

ListVIew{
    model:ListModel{
        ListElement { title: "Button"; source: "qrc:/pages/ButtonPage.qml"}
}

各種コンポーネント

頻繁に使うことが予想されるコンポーネントについて説明する。 ここでは、以下について説明する。 1. Button 1. CheckBox 1. ComboBox 1. DelayButton 1. Slider 1. Switch 1. TextField

Button

f:id:yuji2yuji:20220324210018p:plain

コードは以下。

import QtQuick.Layouts
import QtQuick.Controls

ScrollablePage {
    id: page

    Column {
        spacing: 40
        width: parent.width

        Label {
            width: parent.width
            wrapMode: Label.Wrap
            horizontalAlignment: Qt.AlignHCenter
            text: "Button presents a push-button that can be pushed or clicked by the user. "
                + "Buttons are normally used to perform an action, or to answer a question."
        }

        ColumnLayout {
            spacing: 20
            anchors.horizontalCenter: parent.horizontalCenter

            Button {
                text: "First First First"
                Layout.fillWidth: true
            }
            Button {
                id: button
                text: "Second"
                highlighted: true
                Layout.fillWidth: true
            }
            Button {
                text: "Third"
                enabled: false
                Layout.fillWidth: true
            }
        }
    }
}

ここではColumnLayoutを用いてボタンを3つ並べているが、Buttonコンポーネント自体は以下のような記述で表示できる。

Button {
    id: button
    text: "Second"
    highlighted: true
    enabled: true
    Layout.fillWidth: true
}

ButtonではなくRoundButtonとすれば丸みを帯びたボタンになる。

f:id:yuji2yuji:20220324211454p:plain

CheckBox

f:id:yuji2yuji:20220324211602p:plain

コードは以下。

import QtQuick
import QtQuick.Controls

ScrollablePage {
    id: page

    Column {
        spacing: 40
        width: parent.width

        Label {
            width: parent.width
            wrapMode: Label.Wrap
            horizontalAlignment: Qt.AlignHCenter
            text: "CheckBox presents an option button that can be toggled on or off. "
                + "Check boxes are typically used to select one or more options from a set of options."
        }

        Column {
            spacing: 20
            anchors.horizontalCenter: parent.horizontalCenter

            CheckBox {
                text: "First"
                checked: true
            }
            CheckBox {
                text: "Second"
            }
            CheckBox {
                text: "Third"
                checked: true
                enabled: false
            }
        }
    }
}

CheckBox自体は以下の記述で表示可能。

CheckBox {
    text: "First"
    checked: true
    enabled: true
}

ComboBox

f:id:yuji2yuji:20220324211945p:plain

コードは以下。

import QtQuick
import QtQuick.Controls

ScrollablePage {
    id: page

    Column {
        spacing: 40
        width: parent.width

        Label {
            width: parent.width
            wrapMode: Label.Wrap
            horizontalAlignment: Qt.AlignHCenter
            text: "ComboBox is a combined button and popup list. It presents "
                + "a list of options to the user that occupies minimal screen space."
        }

        ComboBox {
            model: ["First", "Second", "Third"]
            anchors.horizontalCenter: parent.horizontalCenter
        }

        Label {
            width: parent.width
            wrapMode: Label.Wrap
            horizontalAlignment: Qt.AlignHCenter
            text: "ComboBox can be made \l editable. An editable combo box auto-"
                + "completes its text based on what is available in the model."
        }

        ComboBox {
            editable: true
            model: ListModel {
                id: model
                ListElement { text: "Banana" }
                ListElement { text: "Apple" }
                ListElement { text: "Coconut" }
            }
            onAccepted: {
                if (find(editText) === -1)
                    model.append({text: editText})
            }
            anchors.horizontalCenter: parent.horizontalCenter
        }
    }
}

シンプルな記述方法は以下。

ComboBox {
    model: ["First", "Second", "Third"]
}

DelayButton

Delayボタンは、長押しすることで反応するボタン。

f:id:yuji2yuji:20220324212313p:plain

コードは以下。

import QtQuick
import QtQuick.Controls

ScrollablePage {
    id: page

    Column {
        spacing: 40
        width: parent.width

        Label {
            width: parent.width
            wrapMode: Label.Wrap
            horizontalAlignment: Qt.AlignHCenter
            text: "DelayButton is a checkable button that incorporates a delay before the "
                + "button is activated. This delay prevents accidental presses."
        }

        DelayButton {
            text: "DelayButton"
            anchors.horizontalCenter: parent.horizontalCenter
        }
    }
}

delayプロパティで長押しする時間を調整可能(単位はms)。

DelayButton {
    text: "DelayButton"
    delay: 10000
}

Slider

f:id:yuji2yuji:20220324212735p:plain

コードは以下。

import QtQuick
import QtQuick.Controls

ScrollablePage {
    id: page

    Column {
        spacing: 40
        width: parent.width

        Label {
            width: parent.width
            wrapMode: Label.Wrap
            horizontalAlignment: Qt.AlignHCenter
            text: "Slider is used to select a value by sliding a handle along a track."
        }

        Slider {
            id: slider
            value: 0.5
            anchors.horizontalCenter: parent.horizontalCenter
        }

        Slider {
            orientation: Qt.Vertical
            value: 0.5
            anchors.horizontalCenter: parent.horizontalCenter
        }
    }
}

Sliderのデフォルト値や縦横の向きが設定可能である。

Slider {
    orientation: Qt.Vertical
    value: 0.5
    from: 0.0
    to: 1.0
}

Switch

f:id:yuji2yuji:20220324213451p:plain

コードは以下。

import QtQuick
import QtQuick.Controls

ScrollablePage {
    id: page

    Column {
        spacing: 40
        width: parent.width

        Label {
            width: parent.width
            wrapMode: Label.Wrap
            horizontalAlignment: Qt.AlignHCenter
            text: "Switch is an option button that can be dragged or toggled on or off. "
                + "Switches are typically used to select between two states."
        }

        Column {
            spacing: 20
            anchors.horizontalCenter: parent.horizontalCenter

            Switch {
                text: "First"
            }
            Switch {
                text: "Second"
                checked: true
            }
            Switch {
                text: "Third"
                enabled: false
            }
        }
    }
}

Switch部は下記。

Switch {
    text: "Second"
    enabled: true
}

TextField

f:id:yuji2yuji:20220324213705p:plain

コードは以下。

import QtQuick
import QtQuick.Controls

ScrollablePage {
    id: page

    Column {
        spacing: 40
        width: parent.width

        Label {
            width: parent.width
            wrapMode: Label.Wrap
            horizontalAlignment: Qt.AlignHCenter
            text: "TextField is a single-line text editor."
        }

        TextField {
            id: field
            placeholderText: "TextField"
            anchors.horizontalCenter: parent.horizontalCenter
        }
    }
}

TextField部は下記。

TextField {
    id: field
    placeholderText: "TextField"
}

まとめ

今回は表示に関する内容のみにとどめた。実際には入力情報をもとに状態を変更する必要がある。 その方法については今後まとめて記載する予定。