QMLレイアウトの基本

Basics of QML layout
QMLではプロパティバインドを使わなくても、LayoutコンポーネントやAnchorプロパティを使うことで、ウィンドウのサイズに応じた位置調整をおこなえます。

まずは独自のコンポーネントを作ってみましょう。プロジェクト一覧の右クリックメニューより「Add new...」を選択し、ファイル種別から「QMLファイル(Qt Quick 2)」を指定し、QMLファイルをプロジェクトに追加します。プロジェクトに埋め込むのであれば、ファイル名がそのままオリジナルのコンポーネント名になります。今回のサンプルでは「SelectSample.qml」にしています。
「QtQuick.Layout」では、ColumnLayoutやGridLayoutといった、コンポーネントを並べて表示するのに便利なパネルが用意されていますので、これをインポートします。
l1.qml
import QtQuick 2.0
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.1

ColumnLayout{
    ComboBox {
        id: cmbSelect
    }

    RowLayout{
        TextField {
            id: txtAdd
        }

        Button {
            id: btnAdd
        }
    }
}
このコードではColumnLayoutにアイテムを入れ子にすることで、上半分がcmbSelectの領域に、下半分がRowLayoutの領域になります。さらにRowLayoutにアイテムを挿入すると、左下がtxtAdd、右下がbtnAddの領域に割り当てられます。

各種レイアウトコンポーネントの中にあるアイテムにはLayoutプロパティーが追加され、このLayoutプロパティを定義することで、アイテムのサイズを決めることができます。逆にプロパティバインディングによるサイズ指定は機能しませんので気を付けましょう。

[Layoutプロパティの設定例]
Layout.fillWidth:trueアイテムが幅いっぱいに表示される
Layout.preferredWidth:parent.widthアイテムの幅が親の幅と同じになる
Layout.alignment:Qt.AlignHCenterアイテムがパネルの左右中央に配置される
Layout.maximumHeight:10アイテムの最大幅を10pxにする
Layout.rightMargin:5右側のマージンを5pxにする

アプリのプログラムで特に指定しなければ、DPIスケーリングは自動で行われるので、通常は高解像度でのピクセル値を気にすることはないでしょう。なお、「Screen.pixelDensity」には1mmあたりのピクセル数が格納されているので、これを使うことで解像度に依存しないサイズの指定ができます。
l2.qml
import QtQuick 2.0
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.1

ColumnLayout{
    ComboBox {
        id: cmbSelect
        Layout.fillWidth:true
    }

    RowLayout{
        TextField {
            id: txtAdd
            Layout.fillWidth:true
        }

        Button {
            id: btnAdd
            text: qsTr("Add")
        }
    }
}
コンボボックスのリストアイテムの初期値は「model」プロパティに格納します。単純なテキスト選択方式であれば「model:["item1","item2","item3"]」のように、配列を入れるだけで構いません。動的に処理したい場合は、画像+テキストのように凝ったレイアウトを表現したいのであれば、modelプロパティにListModelを指定します。ListModelの詳細な使い方は別の章で紹介します。
l3.qml
ComboBox {
    id: cmbSelect
    model: ListModel {
        ListElement { text:"item1"}
        ListElement { text:"item2"}
        ListElement { text:"item3"}
    }
}
ボタンを押したら、コンボボックスに入力済みのテキストを追加するようにしてみましょう。ボタンに「onClicked」関数を用意し、その関数内で「model.append」を呼び、JSON形式でデータを追加します。また、「console.log」ではJavaScriptと同様に、コンソールデバッガに文字列を出力できます。
l4.qml
Button {
    id: btnAdd
    text: qsTr("Add")
    onClicked: {
        var text = txtAdd.text;
        console.log(text);
        if(text) cmbSelect.model.append({text:text});
    }
}
QMLファイルに記述したアイテムへは、そのファイル内にあるアイテムからしかアクセスできません。つまり、このままでは、ほかのQMLファイルからはコンボボックスの値を取得することはできないということになります。

これを解決するのがプロパティエイリアスです。一番上位にあるアイテムに「property alias [外部に公開する名称]:[転送先の内部プロパティ]」を追加することで、代わりとなるプロパティ名を外部に公開することができます。propertyの前に「readonly」を付け加えると、そのプロパティは読み込み専用になります。

このコードでは「SelectSample.selectedText」によって、コンボボックスにある現在のテキストを取得できるようになっています。
l5.qml
import QtQuick 2.0
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.1

ColumnLayout{
    property alias selectedText : cmbSelect.currentText

    ComboBox {
        id: cmbSelect
        Layout.fillWidth:true
        model: ListModel {
            ListElement { text:"item1"}
            ListElement { text:"item2"}
            ListElement { text:"item3"}
        }
    }

    RowLayout{
        TextField {
            id: txtAdd
            Layout.fillWidth:true
        }

        Button {
            id: btnAdd
            text: qsTr("Add")
            onClicked: {
                var text = txtAdd.text;
                console.log(text);
                if(text) cmbSelect.model.append({text:text});
            }
        }
    }
}
signal構文を使うと、コンポーネントに独自のシグナルを用意することができます。例えば、ItemEx.qmlから生成されたコンポーネントに「onClosing」を用意しておくと、「closing()」を実行したのを機に、「onClosing」が呼び出されます。一般的なメソッドとは異なり、closing()に何も関連付けられていない状態でclosing()を実行しても、何も起きないだけで、エラーも発生しません。
signal.qml
//ItemEx.qml
Item{
    id: i1
    signal closing()
    
    // このメソッドを呼び出すとclosingシグナルが発生する
    function close(){
        i1.closing();
    }
}

// main.qml
ItemEx{
    onClosing:{
    }
}
オリジナルコンポーネントをメインウィンドウに追加します。Windowコンポーネントにはレイアウトの概念がないので、位置の調整は「anchors」プロパティで行います。anchorsと連動するのはプロパティ自体であり、その数値ではない点に注意しましょう。
main.qml
import QtQuick 2.9
import QtQuick.Window 2.2

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    SelectSample {
        id:ss
        // 親の上下中央。「anchors.fill:parent」なら親の枠いっぱいに表示される
        anchors.centerIn: parent
    }

    Text{
        // SelectSampleのselectedText(=cmbSelect.currentText)とテキストを紐づけ
        text:ss.selectedText

        // 親の底辺と連動。マージンは5px
        anchors.bottom:parent.bottom
        anchors.bottomMargin:5

        // 親の左右中央に配置
        anchors.horizontalCenter: parent.horizontalCenter
    }
}
2018/10/29