LoginSignup
13
11

More than 3 years have passed since last update.

Qtでのアプリ開発

Last updated at Posted at 2019-10-22

Qtとは

Qtとはクロスプラットフォームフレームワークです。
様々なライブラリが用意され、プラットフォームに依存しない形でアプリケーションを作成できます。対応しているプラットフォームは豊富で、デスクトップアプリケーションだけでなく、組み込みでも利用がされています。C++とPythonでのアプリケーション開発が行えます。

採用事例

  • Photoshop Elements
  • Skype
  • Google Earth

Qtで作られたアプリケーションを調べてみるも参照ください。

Qtのバージョン

  • Qt 5.11.2
  • MinGW 32bit(コンパイラにはMSVCも使用できますが、MSVC2017をインストールしていないため使用不可)
    image.png

  • JDK1.8.0

  • SDK26.1.1

  • NDKr14.1
    image.png

Qtの開発で重要な要素

以降の内容のソースコードはこちらからCloneできます。
https://github.com/ora1027/QiitaApplication.git

complete.gif

プロパティバインディング

プロパティバインディングとは、プロパティを別のプロパティと連動させる時に使用します。
例えば、ボタンの幅を、ボタンをクリックするごとに1.2倍にする時には下記のように書けます。

    Button {
        id: button
        width: root.width / 2
        height: button.width * 0.75

        anchors.centerIn: parent
        highlighted: true

        Text {
            id: button_text
            anchors.centerIn: parent
            text: qsTr("Button")
            font.pointSize: 20
        }

        onClicked: {
            if (button.font.pointSize <= 20 * 1.2 * 1.2)
                button.font.pointSize *= 1.2
            else
                button.font.pointSize = 20
        }
    }

propertyBinding.gif

StackView

先程作成したPropertyBidingの要素をPropertyBinding.qmlファイルに移します。
MainPage.qmlを作成し、MainPageからPropertyBindingページに画面遷移するようにしましょう。

ApplicationWindow {
    id: root

    visible: true
    width: 540
    height: 960

    StackView {
        id: stackView
        anchors.fill: parent
        initialItem: MainPage {}
    }

}

anchors.fill: parentに設定することを忘れがちになります。
MainPage.qmlも作成します。

Page {
    id: root

    header: ToolBar {
        ToolButton {
            id: menuButton
            anchors.left: parent.left
            anchors.verticalCenter: parent.verticalCenter
        }
        Label {
            anchors.centerIn: parent
            text: qsTr("Qiita Application")
            font.pixelSize: 16
            elide: Label.ElideRight
        }
    }

    Column {
        spacing: 10

        Button {
            text: qsTr("Property Binding")
            onClicked: root.StackView.view.push("PropertyBindingPage.qml")
        }
        // ページが増えるとボタンを追加していく。
    }
}
レイアウト

LayoutPage.qmlファイルを作成します。

import QtQuick 2.9
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3

Page {
    id: root

    header: MyHeader{
        toolButtonIcon: "qrc:/images/arrow_back-24px.svg"
        onToolButtonClicked: root.StackView.view.pop()
    }

    GridLayout {
        id: grid
        anchors.fill: parent
        columns: 2
        rowSpacing: 5
        columnSpacing: 5
        anchors.margins: 5
        // example models
        property var titles: [ "title1", "title2", "title3", "title4", "title5" ]
        property var values: [ "value1", "value2", "value3", "value4", "value5" ]

        Repeater {
            model: grid.titles
            Label {
                Layout.row: index
                Layout.column: 0
                Layout.fillWidth: true
                Layout.fillHeight: true
                text: modelData
            }
        }

        Repeater {
            model: grid.values
            TextArea {
                Layout.row: index
                Layout.column: 1
                Layout.fillWidth: true
                Layout.fillHeight: true
                text: modelData
            }
        }
    }
}

カスタムコンポーネント

このアプリで使用するボタンとヘッダーを共通のコンポーネントとして作成しましょう。

MyButton.qml

import QtQuick 2.9
import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.2

Button {
    id: control
    width: 270
    height:  100

    text: qsTr("Button")

    property color buttonColor: "lightblue"

    style: ButtonStyle {
        background: Rectangle {
            border.width: control.activeFocus ? 2 : 1
            border.color: "#888"
            radius: 4
            gradient: Gradient {
                GradientStop { position: 0 ; color: control.pressed ? Qt.darker(buttonColor, 1.5) : buttonColor }
                GradientStop { position: 1 ; color: control.pressed ? Qt.darker(buttonColor, 3.0) : Qt.darker(buttonColor, 1.0) }
            }
        }
        label: Text {
            renderType: Text.NativeRendering
            verticalAlignment: Text.AlignVCenter
            horizontalAlignment: Text.AlignHCenter
            font.family: "Helvetica"
            font.pointSize: 20
            color: "blue"
            text: control.text
        }
    }

}

MyHeader.qml
signal toolButtonClickedを登録することで、各ページごとにToolButtonが押された時の処理を切り替えることができます。

import QtQuick 2.9
import QtQuick.Controls 2.3

ToolBar {
    property string toolButtonIcon: toolButton.icon.source

    signal toolButtonClicked

    ToolButton {
        id: toolButton
        icon.source: toolButtonIcon
        anchors.left: parent.left
        anchors.verticalCenter: parent.verticalCenter
        onClicked: toolButtonClicked()
    }
    Label {
        anchors.centerIn: parent
        text: qsTr("Qiita Application")
        font.pixelSize: 16
        elide: Label.ElideRight
    }
}

これによって全てのページにヘッダーの詳細を書くことが必要なくなる。共通部分はまとめてMyHeader.qmlに書きます。
アイコンやクリックされた時の処理はページごとに切り替えることができます。

//    header: ToolBar {
//        ToolButton {
//            id: menuButton
//            anchors.left: parent.left
//            anchors.verticalCenter: parent.verticalCenter
//        }
//        Label {
//            anchors.centerIn: parent
//            text: qsTr("Qiita Application")
//            font.pixelSize: 16
//            elide: Label.ElideRight
//        }
//    }

    header: MyHeader{
        toolButtonIcon: "qrc:/images/arrow_back-24px.svg"
        onToolButtonClicked: root.StackView.view.pop()
    }
リソースファイル

Resources上で右クリックを押す。Add Newを選択肢、Resourceファイルを追加します。

image.png

image.qrcファイルが作成されます。

image.png

① プロジェクトフォルダにimagesフォルダを作成し、その中に必要なファイルを追加します。
② Add > Add prefix => /
③ Add > Add Files

image.png

image.png

リソースファイルにアイコンなどの画像を追加しないとアプリケーション内で使用できません。

アニメーション

AnimationPage.qmlを作成します。

import QtQuick 2.9
import QtQuick.Controls 2.3

Page {
    id: root

    header: MyHeader{
        toolButtonIcon: "qrc:/images/arrow_back-24px.svg"
        onToolButtonClicked: root.StackView.view.pop()
    }


    property string text: "This is Animation Page !!"
    property bool animated: true

    focus: true

    Keys.onPressed: {
        if (event.key == Qt.Key_Delete || event.key == Qt.Key_Backspace)
            root.remove()
        else if (event.text != "") {
            root.append(event.text)
        }
    }

    function append(text) {
        root.animated = false
        var lastLetter = root.children[root.children.length - 1]
        var newLetter = letterComponent.createObject(root)
        newLetter.text = text
        newLetter.follow = lastLetter
        root.animated = true
    }

    function remove() {
        if (root.children.length)
            root.children[root.children.length - 1].destroy()
    }

    function doLayout() {
        var follow = null
        for (var i = 0; i < root.text.length; ++i) {
            var newLetter = letterComponent.createObject(root)
            newLetter.text = root.text[i]
            newLetter.follow = follow
            follow = newLetter
        }
    }

    Component {
        id: letterComponent
        Text {
            id: letter
            property variant follow

            x: follow ? follow.x + follow.width : root.width / 6
            y: follow ? follow.y : root.height / 2

            font.pixelSize: 40;
            font.bold: true
            color: "grey";
            styleColor: "#222222";
            style: Text.Outline

            MouseArea {
                anchors.fill: parent
                drag.target: letter; drag.axis: Drag.XAndYAxis
                onPressed: letter.color = "#dddddd"
                onReleased: letter.color = "#999999"
            }

            Behavior on x { enabled: root.animated; SpringAnimation { spring: 3; damping: 0.3; mass: 1.0 } }
            Behavior on y { enabled: root.animated; SpringAnimation { spring: 3; damping: 0.3; mass: 1.0 } }
        }
    }

    Component.onCompleted: doLayout()
}

animation.gif

スプラッシュ

SplashShowクラスを作成します。

ヘッダーファイル

#ifndef SPLASHSHOW_H
#define SPLASHSHOW_H

#include <QObject>
#include <QQuickView>

class SplashShow : public QObject
{
    Q_OBJECT
public:
    SplashShow(QQuickView* viewer);
    void start();
    Q_INVOKABLE end();

public slots:
    void init();

private:
    QQuickView* viewer_;
};

#endif // SPLASHSHOW_H

ソースファイル

#include "SplashShow.h"

#include <QCoreApplication>
#include <QtQml/QQmlContext>
#include <QQmlApplicationEngine>
#include <QDir>
#include <QThread>

#include <QDebug>

SplashShow::SplashShow(QQuickView* viewer)
    : viewer_(viewer)
{}

void SplashShow::start()
{
    qDebug() << "SplashShow::start() is called";
    QDir directory(QCoreApplication::applicationDirPath());

    QString path = directory.absoluteFilePath("../../SplashPage.qml");
    qDebug() << "SplashShow::init() path = " << path;
    viewer_->engine()->rootContext()->setContextProperty("SplashShow", this);
    viewer_->setSource(QUrl::fromLocalFile(path));
    viewer_->show();
}

void SplashShow::init()
{
    qDebug() << "SplashShow::init() is called";
    QThread::msleep(1000);
}

void SplashShow::end()
{
    qDebug() << "SplashShow::end() is called";
    viewer_->close();
}

import QtQuick 2.0


Item {
    id: root
    width: 540
    height: 960

    /* 
    スプラッシュ時に表示する画像などを記述する
    :
    :
    */

    Loader {
        id: main
        anchors.fill: parent
        asynchronous: true
        visible: status == Loader.Ready
    }

    PauseAnimation {
        id: fakeLoadingDelay
        duration: 50
        onRunningChanged: {
            if ( !running ) {
                // Call the init() function of SplashShow
                SplashShow.init();

                console.log("Inside Puase Animation")

                // When the init() returns, load main.qml
                main.source = "main.qml"
            }
        }
    }

    Component.onCompleted: fakeLoadingDelay.start()
}

splash.gif

Model

MainPageから遷移するページが増える度にButtonのコンポーネントを追加したくないです。
そこで、ModelとRepeaterの機能を使用して、ModelからLabelとqmlファイルを取得し、Repeaterで表示するように変更しましょう。

現状

        Button {
            text: qsTr("Property Binding")
            onClicked: root.StackView.view.push("PropertyBindingPage.qml")
        }
        Button {
            text: qsTr("Layout")
            onClicked: root.StackView.view.push("LayoutPage.qml")
        }
        Button {
            text: qsTr("Animation")
            onClicked: root.StackView.view.push("AnimationPage.qml")
        }

ScreenTransitionModel.qmlを作成します。

import QtQuick 2.9

ListModel {
    ListElement {label: "Property Binding"; path: "PropertyBindingPage.qml"}
    ListElement {label: "Layout"; path: "LayoutPage.qml"}
    ListElement {label: "Animation"; path: "AnimationPage.qml"}
    ListElement {label: "End"; path: ""}
}

最終的なMainPage.qml

import QtQuick 2.9
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3

Page {
    id: root

    header: MyHeader {
        id: header
        toolButtonIcon: "qrc:/images/headline-24px.svg"
        onToolButtonClicked: drawer.open()
    }

    Drawer {
        id: drawer

        width: Math.min(root.width, root.height) / 3
        height: root.height

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

            delegate: ItemDelegate {
                width: parent.width
                text: model.text
                highlighted: ListView.isCurrentItem
                onClicked: {
                    drawer.close()
                    model.triggered()
                }
            }

            model: ListModel {
                ListElement {
                    text: qsTr("Open...")
                }
                ListElement {
                    text: qsTr("About...")
                }
            }

            ScrollIndicator.vertical: ScrollIndicator { }
        }
    }

    ColumnLayout {
        anchors.fill: parent
        spacing: 10

        Image {
            width: 400
            height: 400
            Layout.alignment: Qt.AlignHCenter
            fillMode: Image.PreserveAspectFit
            clip: true
            source: "qrc:/images/DotPicture.png"
        }

        Repeater {
            model: ScreenTransitionModel {}
            Component.onCompleted: console.log("inside Repeater delegate. model = ", model)

            delegate: MyButton {
                text: model.label
                Layout.alignment: Qt.AlignHCenter
                onClicked: model.label === "End" ? SplashShow.end() : root.StackView.view.push(model.path)
                Component.onCompleted: console.log("MyButton width = ", width + ", MyButton height = ",  height)
            }
        }

    }
}

参考

QtのExample

Qtのサンプルは豊富で実はいい情報がたくさん詰まっています。慣れてくると非常に勉強になります。

  • Chat Tutorial
  • animation

下のページからQMLについて豊富な情報がたくさん仕入れられます。
Qt5 Cadaques

最後まで読んで下さりありがとうございました。
今後もWebView, SQL, Bluetooth接続, Camera, OpenGLなどの機能を追加していきます。

13
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
11