Edited at

Qtでのアプリ開発


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などの機能を追加していきます。