Hlavní navigace

Jazyk QML (Qt Modeling Language) a PySide 2

Pavel Tišnovský

V dnešní části seriálu o tvorbě aplikací s GUI v Pythonu s využitím frameworku PySide si ukážeme další možnosti nabízené jazykem QML. Oproti předchozí části se však zaměříme na PySide2, Qt 5 a tím pádem i na novější verzi QML.

Doba čtení: 24 minut

11. Korektní rozmístění vektorových výkresů na plochu okna

12. Relativní velikost výkresu: atribut fillMode

13. Další způsoby rozmístění prvků GUI na ploše okna

14. Správce geometrie Grid

15. Správce geometrie Column

16. Správce geometrie Row

17. Tvorba animací v Pyside – použití automaticky modifikované proměnné řídicí animaci

18. Další ukázky animace – plynulá změna velikosti vektorových kreseb

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Jazyk QML (Qt Modeling Language) a PySide 2

V předchozím článku jsme se seznámili se základními vlastnostmi jazyka QML určeného pro tvorbu grafického uživatelského rozhraní, nad nímž je postavena technologie Qt Quick. Ovšem prozatím jsme se zabývali pouze tou variantou jazyka QML, která je součástí Qt Quick 1.0, tj. starší verze použité v Qt 4.8.7. To je sice stále používaná verze Qt (je na ní postaveno relativně mnoho podnikových aplikací), ovšem oficiálně již není více než dva roky podporovaná, takže je poněkud problematické nad ní stavět novější aplikace. Novější QtQuick 2.x totiž už vyžaduje Qt 5 (a to konkrétně verze 5.9, 5.10 či 5.11) a tím pádem i PySide 2. Mezi oběma variantami existuje několik rozdílů a z tohoto důvodu si dnes ukážeme několik příkladů postavených nad PySide 2 a Qt Quick 2.0.

Obrázek 1: Příkladem aplikace postavené na PyQt je integrované vývojové prostředí Eric.

2. Porovnání rozdílů mezi PySide a PySide2 při práci s QML

Nejprve se v krátkosti podívejme na to, jaké základní rozdíly nalezneme mezi PySide a PySide2 u kódu (skriptu), který vlastně provádí pouze jedinou činnost – vytvoření hlavního okna aplikace, načtení QML s deklarací GUI a následného vykreslení grafického uživatelského rozhraní do tohoto okna na základě obsahu získaného z QML. Při použití PySide 1 celý kód tohoto skriptu vypadal následovně:

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
import sys
 
# import "jádra" frameworku Qt i modulu pro GUI
from PySide import QtCore
from PySide import QtGui
 
# modul pro práci s QML
from PySide import QtDeclarative
 
# jméno QML s deklarací grafického uživatelského rozhraní
QML_FILE = "173_load_qml_9.qml"
 
 
# nový widget bude odvozen od QDeclarativeView
class MainWindow(QtDeclarative.QDeclarativeView):
 
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        # nastavení titulku hlavního okna aplikace
        self.setWindowTitle("QML Example")
        # načtení souboru QML
        self.setSource(QtCore.QUrl.fromLocalFile(QML_FILE))
        # necháme QML změnit velikost okna
        self.setResizeMode(QtDeclarative.QDeclarativeView.SizeRootObjectToView)
 
 
def main():
    # vytvoření Qt aplikace
    app = QtGui.QApplication(sys.argv)
 
    # vytvoření hlavního okna
    window = MainWindow()
 
    # zobrazení hlavního okna
    window.show()
 
    # spuštění aplikace
    sys.exit(app.exec_())
 
 
if __name__ == '__main__':
    main()

Z předchozího výpisu vidíme, že třída hlavního okna je odvozena od třídy QtDeclarative.QDeclarativeView. V případě, že použijeme PySide2, je ovšem situace odlišná, neboť hlavní okno se v tomto případě odvodí od odlišné třídy pojmenované QtQuick.QQuickView. Také se bude odlišovat způsob nastavení titulku hlavního okna, neboť se namísto metody setWindowTitle() použije setTitle() (odlišné jsou i další metody, s nimi se však prozatím nesetkáme):

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
import sys
 
# import "jádra" frameworku Qt i modulu pro GUI
from PySide2 import QtCore
from PySide2 import QtGui
 
# modul pro práci s QML
from PySide2 import QtQuick
 
# jméno QML s deklarací grafického uživatelského rozhraní
QML_FILE = "01.qml"
 
 
# nový widget bude odvozen od QDeclarativeView
class MainWindow(QtQuick.QQuickView):
 
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        # nastavení titulku hlavního okna aplikace
        self.setTitle("QML Example @ PySide2")
        # načtení souboru QML
        self.setSource(QtCore.QUrl.fromLocalFile(QML_FILE))
        # necháme QML změnit velikost okna
        self.setResizeMode(QtQuick.QQuickView.SizeRootObjectToView)
 
 
def main():
    # vytvoření Qt aplikace
    app = QtGui.QGuiApplication(sys.argv)
 
    # vytvoření hlavního okna
    window = MainWindow()
 
    # zobrazení hlavního okna
    window.show()
 
    # spuštění aplikace
    sys.exit(app.exec_())
 
 
if __name__ == '__main__':
    main()

3. Vizuální porovnání rozdílů mezi skripty psanými v Pythonu

Pro lepší přehled rozdílů mezi PySide 1 a PySide 2 je na screenshotu zobrazeném pod tímto odstavcem ukázáno, jak se oba skripty uvedené v předchozí kapitole vizuálně odlišují. I přes snahu o to, aby si oba zdrojové kódy byly co nejpodobnější, vidíme, že se odlišují použité třídy a tím pádem i jejich metody, které jsou volány:

Obrázek 2: Vizuální porovnání rozdílu mezi skriptem napsaným pro PySide 1 a skriptem pro PySide 2.

Poznámka: pro zobrazení rozdílů je použit režim diff v textovém editoru Vim.

Následují odkazy na dokumentaci k jednotlivým třídám (a prozatím jedné metodě), které se v PySide 1 a PySide 2 odlišují:

Třída/metoda PySide 1 PySide 2
hlavní aplikace QtGui.QApplication QtGui.QGuiApplication
předek okna QtDeclarative.QDeclarativeView QtQuick.QQuickView
titulek okna setWindowTitle setTitle
Poznámka: odlišnost mezi oběma verze PySide je způsobena tím, že se vlastně jedná o relativně tenké rozhraní mezi programovacím jazykem Python a nativní knihovnou Qt. Rozdíly jsou tedy způsobeny (nekompatibilními) změnami provedenými v samotném Qt, které se PySide nesnaží nijak maskovat.

4. Přechod od QtQuick 1.0 ke QtQuick 2.0

O změnách, které musely být provedeny ve skriptech naprogramovaných v Pythonu, jsme se již zmínili, takže si nyní ukažme, jaké další změny je nutné udělat v samotných QML souborech s popisem grafického uživatelského rozhraní. Prozatím zůstaneme u velmi jednoduchého (až primitivního) příkladu, v němž jsou na plochu okna umístěny tři různobarevné čtverce, u kterých je pro větší efekt nastavena průhlednost a jeden čtverec je otočen:

Obrázek 3: Okno s jednoduchým GUI – na ploše okna jsou zobrazeny tři čtverce s různými vizuálními a geometrickými vlastnostmi.

První varianta QML je určena pro QtQuick 1.0 a tudíž pro PySide 1:

import QtQuick 1.0
 
Rectangle {
    id: main
    width: 320
    height: 240
    color: "lightgray"
 
    Rectangle {
        id: r1
        width: 160
        height: 160
        color: "red"
        opacity: 0.5
        rotation: 45
        anchors.left: parent.left
        anchors.bottom: parent.bottom
    }
 
    Rectangle {
        id: r2
        width: 160
        height: 160
        color: "yellow"
        opacity: 0.5
        z: 1
        anchors.horizontalCenter: parent.horizontalCenter
        anchors.top: parent.top
    }
 
    Rectangle {
        id: r3
        width: 160
        height: 160
        color: "blue"
        opacity: 0.5
        rotation: 45
        anchors.right: parent.right
        anchors.bottom: parent.bottom
    }
}

Druhá varianta je již určena pro QtQuick 2.0 a tudíž pro PySide 2:

import QtQuick 2.0
 
Rectangle {
    id: main
    width: 320
    height: 240
    color: "lightgray"
 
    Rectangle {
        id: r1
        width: 160
        height: 160
        color: "red"
        opacity: 0.5
        rotation: 45
        anchors.left: parent.left
        anchors.bottom: parent.bottom
    }
 
    Rectangle {
        id: r2
        width: 160
        height: 160
        color: "yellow"
        opacity: 0.5
        z: 1
        anchors.horizontalCenter: parent.horizontalCenter
        anchors.top: parent.top
    }
 
    Rectangle {
        id: r3
        width: 160
        height: 160
        color: "blue"
        opacity: 0.5
        rotation: 45
        anchors.right: parent.right
        anchors.bottom: parent.bottom
    }
}

5. Vizuální porovnání rozdílů mezi oběma QML soubory

Zatímco mezi skripty napsanými pro knihovnu PySide 1 a PySide 2 bylo relativně velké množství rozdílů (nebyla – a to naschvál – dodržena zpětná kompatibilita), je tomu u QML souborů jinak, alespoň v našem jednoduchém příkladu grafického uživatelského rozhraní s trojicí čtverců. Ostatně se můžeme podívat na následující screenshot zobrazující všechny rozdíly. Ty nastaly na jediném řádku, ovšem je zapotřebí poznamenat, že se skutečně jedná o primitivní příklad a v praxi (reálná GUI) tomu bude jinak:

Obrázek 4: Vizuální porovnání rozdílu mezi QML napsaným pro QtQuick 1.0 a QML pro QtQuick 2.0.

6. Univerzální modul určený pro načtení a zobrazení QML aplikace

Ve všech demonstračních příkladech popsaných v navazujících kapitolách budeme používat „univerzální“ modul, který je určený pro vytvoření hlavního okna aplikace, načtení zvoleného QML souboru a inicializaci grafického uživatelského rozhraní. Modul neobsahuje přímo spustitelný kód (__main__), takže ho budeme používat jako knihovnu a tím pádem se nám zdrojové kódy příkladů zminimalizují na doslova několik řádků. Ve skutečnosti sice není dále uvedený skript zcela univerzální, protože nedokáže zpracovat QML soubory s deklarací hlavního okna, ovšem to nám pro účely dnešního článku nebude vadit (případná vylepšení budou popsána příště). Úplný zdrojový kód výše popsaného modulu naleznete na adrese https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/PySide2/QmlViewer­.py:

# vim: set fileencoding=utf-8
 
# univerzální prohlížeč QML souborů
 
import sys
 
# import "jádra" frameworku Qt i modulu pro GUI
from PySide2 import QtCore
from PySide2 import QtGui
 
# modul pro práci s QML
from PySide2 import QtQuick
 
 
# nový widget bude odvozen od QDeclarativeView
class MainWindow(QtQuick.QQuickView):
 
    def __init__(self, qml_file, parent=None):
        super(MainWindow, self).__init__(parent)
        # nastavení titulku hlavního okna aplikace
        self.setTitle("QML Example @ PySide2: " + qml_file)
        # načtení souboru QML
        self.setSource(QtCore.QUrl.fromLocalFile(qml_file))
        # necháme QML změnit velikost okna
        self.setResizeMode(QtQuick.QQuickView.SizeRootObjectToView)
 
 
def main(qml_file):
    # vytvoření Qt aplikace
    app = QtGui.QGuiApplication(sys.argv)
 
    # vytvoření hlavního okna
    window = MainWindow(qml_file)
 
    # zobrazení hlavního okna na desktopu
    window.show()
 
    # spuštění aplikace
    sys.exit(app.exec_())

7. Příklad použití modulu popsaného v předchozí kapitole

Podívejme se nyní, jak se výše popsaný modul QmlViewer použije. Další demonstrační příklad je skutečně velmi krátký, protože obsahuje pouze import modulu QmlViewer, definici QML souboru s popisem grafického uživatelského rozhraní a zavolání funkce main deklarované v modulu (pro rozsáhlejší aplikace je vhodnější provést import QmlViewer, aby nedocházelo k „zašpinění“ jmenného prostoru:

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
from QmlViewer import *
 
QML_FILE = "02_use_qml_viewer.qml"
 
if __name__ == '__main__':
    main(QML_FILE)

Obrázek 5: Výsledek v této kapitole popsaného demonstračního příkladu.

8. Kotevní přímky a jejich vliv na umístění a rozměry prvků

Již v předchozí části tohoto seriálu jsme se zmínili o tom, že jednou z možností rozmístění ovládacích prvků grafického uživatelského rozhraní do plochy oken a dialogů představuje použití takzvaných kotevních přímek, což jsou virtuální prvky procházející jak všemi čtyřmi krajními body prvků (vlevo, vpravo, nahoře i dole), tak i jejich středy. Změnou umístění těchto přímek se mění i velikost (plocha) prvků. V dalším příkladu je ukázán způsob rozmístění tří obdélníků v ploše okna.

První obdélník (červený) je „přilepen“ k hornímu, spodnímu a současně i k levému okraji okna. Jeho šířka je nastavena na 64 délkových jednotek (zde pixelů) a vzhledem k tomu, že pravý okraj obdélníku není ničím omezen, bude tato šířka zachována i při změně velikosti okna:

Rectangle {
    id: r1
    width: 64
    color: "red"
    opacity: 0.5
    anchors.left: parent.left
    anchors.top: parent.top
    anchors.bottom: parent.bottom
}

Třetí obdélník (modrý) má taktéž šířku 64 délkových jednotek a je „přilepen“ k hornímu, spodnímu a pravému okraji okna. Jeho šířka zůstane opět zachována i ve chvíli, kdy se velikost okna z různých důvodů změní:

Rectangle {
    id: r3
    width: 64
    height: 160
    color: "blue"
    opacity: 0.5
    anchors.right: parent.right
    anchors.top: parent.top
    anchors.bottom: parent.bottom
}

A konečně prostřední (žlutý) obdélník má nastavenou šířku i výšku na 160 délkových jednotek a je vycentrován:

Rectangle {
    id: r2
    width: 160
    height: 160
    color: "yellow"
    opacity: 0.5
    z: 1
    anchors.horizontalCenter: parent.horizontalCenter
    anchors.verticalCenter: parent.verticalCenter
}

Podívejme se nyní na chování všech tří prvků při změně velikosti okna:

Obrázek 6: Rozmístění prvků – původní velikost okna.

Obrázek 7: Rozmístění prvků v širším okně.

U užšího okna dojde k překryvu prvků a to z toho důvodu, že nikde nespecifikujeme jejich vzájemné postavení:

Obrázek 8: Rozmístění prvků v užším okně.

Extrémní případ, kdy se překrývají všechny tři prvky:

Obrázek 9: Rozmístění prvků v ještě užším okně.

Úplný zdrojový kód tohoto příkladu vypadá následovně:

import QtQuick 2.0
 
Rectangle {
    id: main
    width: 320
    height: 240
    color: "lightgray"
 
    Rectangle {
        id: r1
        width: 64
        color: "red"
        opacity: 0.5
        anchors.left: parent.left
        anchors.top: parent.top
        anchors.bottom: parent.bottom
    }
 
    Rectangle {
        id: r2
        width: 160
        height: 160
        color: "yellow"
        opacity: 0.5
        z: 1
        anchors.horizontalCenter: parent.horizontalCenter
        anchors.verticalCenter: parent.verticalCenter
    }
 
    Rectangle {
        id: r3
        width: 64
        height: 160
        color: "blue"
        opacity: 0.5
        anchors.right: parent.right
        anchors.top: parent.top
        anchors.bottom: parent.bottom
    }
}

Zdrojový kód demonstračního příkladu využívá modul QmlViewer:

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
from QmlViewer import *
 
QML_FILE = "03_springy_widgets.qml"
 
if __name__ == '__main__':
    main(QML_FILE)

9. Použití rastrových obrázků v GUI

Při tvorbě grafického uživatelského rozhraní reálných aplikací se většinou neobejdeme bez nutnosti použití rastrových obrázků, které například mohou sloužit jako ikony. Pro tento účel slouží prvek nazvaný jednoduše Image. Důležitým atributem tohoto prvku je atribut nazvaný source se jménem souboru s uloženým rastrovým obrázkem (tento atribut je typu string a proto by měl být zapsán v uvozovkách). Podporovány jsou samozřejmě i obrázky uložené ve formátu PNG:

Image {
    id: image
    source: "images/voronoi.png"
    anchors.horizontalCenter: parent.horizontalCenter
    anchors.verticalCenter: parent.verticalCenter
}
Poznámka: využití rastrových obrázků může být pro některé typy aplikací poněkud omezující, protože celé QML je navrženo takovým způsobem, aby bylo použitelné i na smartphonech a podobných zařízeních s odlišnými proporcemi displejů v porovnání s klasickými desktopy. Ovšem záleží samozřejmě na tom, jaká aplikace se vlastně vytváří a jak omezující mohou být například malé pidiikonky 22×22 pixelů, které jsou stále v desktopových aplikacích poměrně masivně používány.

Obrázek 10: Umístění rastrového obrázku do plochy okna.

Podívejme se nyní na zdrojový kód demonstračního příkladu, v němž je použit rastrový obrázek bez uvedení velikosti a kromě tohoto obrázku dva obdélníky, které tvoří jeho pravý a levý okraj. Vzhledem k tomu, že velikost rastrového obrázku není explicitně zadána, zjistí se z jeho aktuálních rozměrů (metadat), popř. pouze z rozlišení:

import QtQuick 2.0
 
Rectangle {
    id: main
    width: 320
    height: 240
    color: "lightgray"
 
    Rectangle {
        id: left_rectangle
        color: "red"
        opacity: 0.5
        anchors.left: parent.left
        anchors.right: image.left
        anchors.top: parent.top
        anchors.bottom: parent.bottom
    }
 
    Image {
        id: image
        source: "images/voronoi.png"
        anchors.horizontalCenter: parent.horizontalCenter
        anchors.verticalCenter: parent.verticalCenter
    }
 
    Rectangle {
        id: right_rectangle
        color: "blue"
        opacity: 0.5
        anchors.left: image.right
        anchors.right: parent.right
        anchors.top: parent.top
        anchors.bottom: parent.bottom
    }
}

Zdrojový kód demonstračního příkladu opět využívá modul QmlViewer:

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
from QmlViewer import *
 
QML_FILE = "04_raster_image.qml"
 
if __name__ == '__main__':
    main(QML_FILE)

10. Načtení a zobrazení vektorových výkresů v GUI

Kromě rastrových obrázků podporuje jazyk QML i použití vektorových výkresů (či kreseb) uložených ve formátu SVG. K tomuto účelu se používá stejný prvek nazvaný Image, se kterým jsme se seznámili v předchozí kapitole. Většina vektorových kreseb má sice zadané rozměry, ty nám však nemusí vyhovovat, takže je možné zadat vlastní šířku a výšku. Zvětšení či zmenšení vektorové kresby se samozřejmě obejde bez ztráty kvality výsledku:

Image {
    id: left_image
    source: "editors/vim.svg"
    width: 200
    height: 200
    anchors.horizontalCenter: parent.horizontalCenter
}

Podívejme se nyní na demonstrační příklad, v němž je trojice vektorových kreseb (konkrétně ikon tří slavných textových editorů) umístěna do okna, a to tak, že jsou všechny kresby vycentrovány. Vzhledem k tomu, že kresby mají průhledné pozadí, jsou všechny tři ikony stále dobře rozeznatelné:

Obrázek 11: Umístění tří vektorových kreseb do plochy okna. Všechny kresby jsou vycentrovány a zobrazeny přes sebe.

Následuje úplný zdrojový kód tohoto příkladu:

import QtQuick 2.0
 
Rectangle {
    id: main
    width: 320
    height: 240
    color: "lightgray"
 
    Image {
        id: left_image
        source: "editors/vim.svg"
        width: 200
        height: 200
        anchors.horizontalCenter: parent.horizontalCenter
    }
 
    Image {
        id: center_image
        source: "editors/emacs.svg"
        width: 200
        height: 200
        anchors.horizontalCenter: parent.horizontalCenter
    }
 
    Image {
        id: right_image
        source: "editors/atom.svg"
        width: 200
        height: 200
        anchors.horizontalCenter: parent.horizontalCenter
    }
}

Skript určený pro načtení QML:

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
from QmlViewer import *
 
QML_FILE = "05_vector_drawings_A.qml"
 
if __name__ == '__main__':
    main(QML_FILE)

11. Korektní rozmístění vektorových výkresů na plochu okna

Předchozí příklad byl sice zajímavý z technologického hlediska, ale v praktických aplikacích samozřejmě budou zákazníci vyžadovat, aby se jednotlivé prvky grafického uživatelského rozhraní umístily vedle sebe a nikoli na sebe. Úprava je ve skutečnosti snadná – u všech tří vektorových výkresů zvolíme vycentrování ve vertikální ose:

anchors.verticalCenter: parent.verticalCenter

Naproti tomu na horizontální ose bude zarovnání odlišné – první výkres bude „přilepen“ k levému okraji okna, druhý výkres zůstane vycentrován a konečně třetí výkres bude „přilepen“ k okraji pravému:

// první
anchors.left: parent.left
 
// druhý
anchors.verticalCenter: parent.verticalCenter
 
// třetí
anchors.right: parent.right

Obrázek 12: Takto má vypadat korektní rozmístění všech tří vektorových výkresů na ploše okna.

Upravený QML kód vypadá následovně:

import QtQuick 2.0
 
Rectangle {
    id: main
    width: 320
    height: 240
    color: "lightgray"
 
    Image {
        id: left_image
        source: "editors/vim.svg"
        width: 100
        height: 100
        anchors.verticalCenter: parent.verticalCenter
        anchors.left: parent.left
    }
 
    Image {
        id: center_image
        source: "editors/emacs.svg"
        width: 100
        height: 100
        anchors.horizontalCenter: parent.horizontalCenter
        anchors.verticalCenter: parent.verticalCenter
    }
 
    Image {
        id: right_image
        source: "editors/atom.svg"
        width: 100
        height: 100
        anchors.verticalCenter: parent.verticalCenter
        anchors.right: parent.right
    }
}

Skript určený pro načtení QML vypadá prakticky totožně jako skript předchozí, samozřejmě až na odlišné jméno QML výkresu:

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
from QmlViewer import *
 
QML_FILE = "05_vector_drawings_B.qml"
 
if __name__ == '__main__':
    main(QML_FILE)

12. Relativní velikost výkresu: atribut fillMode

Další zajímavou možností, která se může hodit například při programování různých prohlížečů obrázků, CAD atd., je použití atributu pojmenovaného fillMode. Název tohoto atributu může být matoucí, protože se nejedná o specifikaci barvy pozadí, která má vyplnit nějakou plochu, ale o určení, jak má widget změnit svoji velikost při změně velikosti svého předka (parent). Můžeme si to vyzkoušet. U prvního obrázku (či kresby) zvolíme, že se má obrázek zvětšovat v závislosti na velikosti předka, ovšem současně musí být zachován poměr šířka:výška:

Image {
    id: left_image
    source: "editors/vim.svg"
    fillMode: Image.PreserveAspectFit
    anchors.verticalCenter: parent.verticalCenter
    anchors.left: parent.left
    anchors.right: center_image.left
}

Naproti tomu u obrázku druhého explicitně zvolíme jeho velikost:

Image {
    id: center_image
    source: "editors/emacs.svg"
    width: 100
    height: 100
    anchors.horizontalCenter: parent.horizontalCenter
    anchors.verticalCenter: parent.verticalCenter
}

Odlišné chování obou obrázků můžeme porovnat na následující dvojici screenshotů:

Obrázek 13: Původní velikost widgetů v okně.

Obrázek 14: Velikost widgetů po zvětšení okna.

Opět si ukažme úplný zdrojový kód tohoto příkladu:

import QtQuick 2.0
 
Rectangle {
    id: main
    width: 320
    height: 240
    color: "lightgray"
 
    Image {
        id: left_image
        source: "editors/vim.svg"
        fillMode: Image.PreserveAspectFit
        anchors.verticalCenter: parent.verticalCenter
        anchors.left: parent.left
        anchors.right: center_image.left
    }
 
    Image {
        id: center_image
        source: "editors/emacs.svg"
        width: 100
        height: 100
        anchors.horizontalCenter: parent.horizontalCenter
        anchors.verticalCenter: parent.verticalCenter
    }
 
    Image {
        id: right_image
        source: "editors/atom.svg"
        fillMode: Image.PreserveAspectFit
        anchors.verticalCenter: parent.verticalCenter
        anchors.right: parent.right
        anchors.left: center_image.right
    }
}

13. Další způsoby rozmístění prvků GUI na ploše okna

Prozatím jsme si ukázali pouze jeden způsob rozmístění prvků grafického uživatelského rozhraní na plochu okna popř. na plochu jakéhokoli předka. Jednalo se o použití kotevních přímek, které jsou sice velmi flexibilní, ovšem jejich použití může být problematické například při návrhu uživatelského rozhraní v Qt Creatoru a podobných nástrojích. I z tohoto důvodu QML umožňuje použít další správce geometrie (v QML se sice toto označení většinou nepoužívá, ovšem jedná se o ustálený termín). Mezi alternativní správce geometrie patří především běžná mřížka (grid), dále pak správce pro umístění komponent do jednoho sloupce (column) a další pro umístění komponent do jednoho řádku (row). Jednotlivé správce geometrie je samozřejmě možné kombinovat a použít tak například column pro základní rozvržení hlavního okna (menu, nástrojový pruh, hlavní plocha, stavový řádek) a uvnitř použít správce další, například row pro nástrojový pruh.

14. Správce geometrie Grid

Při použití správce geometrie, který se jmenuje příznačně Grid, se jednotlivé prvky grafického uživatelského rozhraní umisťují do neviditelné mřížky. Velikost mřížky (šířky sloupců a výšky řádků) je automaticky měněna takovým způsobem, aby se do ní všechny vkládané widgety umístily v „rozumné“ či nastavené velikosti. Programově je však možné měnit vzdálenost mezi jednotlivými widgety a tím také měnit velikost mřížky. Taktéž je možné nastavit šířky mezer mezi jednotlivými buňkami a důležité je specifikovat i počet sloupců (zatímco počet řádků se již dopočítá automaticky podle počtu vkládaných widgetů):

Grid {
    columns: 3
    spacing: 2
    anchors.verticalCenter: parent.verticalCenter
    anchors.horizontalCenter: parent.horizontalCenter
    ...
    ...
    ...
}

Obrázek 16: Ukázka použití správce geometrie Grid.

V následujícím demonstračním příkladu je do pomyslné mřížky vloženo hned několik prvků grafického uživatelského rozhraní. Konkrétně se jedná o pět obdélníků různých barev a velikostí a taktéž o vektorový obrázek. Povšimněte si (i když samotná mřížka není viditelná), že se šířky sloupců a výšky řádků upravují na základě velikosti jednotlivých komponent do mřížky vkládaných:

import QtQuick 2.0
 
 
Rectangle {
    id: main
    width: 320
    height: 240
    color: "lightgray"
 
    Grid {
        columns: 3
        spacing: 2
        anchors.verticalCenter: parent.verticalCenter
        anchors.horizontalCenter: parent.horizontalCenter
 
        Rectangle {
            color: "#ff8080"
            width: 75
            height: 75
        }
 
        Rectangle {
            color: "yellow"
            width: 32
            height: 75
        }
 
        Rectangle {
            color: "#8080ff"
            width: 75
            height: 32
        }
 
        Rectangle {
            color: "#8080ff"
            width: 75
            height: 75
        }
 
        Rectangle {
            color: "black"
            width: 10
            height: 10
        }
 
        Image {
            id: left_image
            source: "editors/vim.svg"
            width: 100
            height: 100
        }
    }
}

Skript pro načtení a zobrazení QML souboru:

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
from QmlViewer import *
 
QML_FILE = "08_grid.qml"
 
if __name__ == '__main__':
    main(QML_FILE)

15. Správce geometrie Column

Další správce geometrie se jmenuje Column a jeho chování při rozmisťování prvků přesně odpovídá jeho jménu – prvky jsou jednoduše umístěny do jediného sloupce, přičemž je ovšem samozřejmě možné nastavit jejich velikost a zarovnání doleva a doprava. Šířka sloupce se přizpůsobuje šířce jednotlivých prvků. Příklad použití:

Column {
    spacing: 2
    anchors.verticalCenter: parent.verticalCenter
    anchors.horizontalCenter: parent.horizontalCenter
    ...
    ...
    ...
}
Poznámka: opět si povšimněte, že i prvek Column je zapotřebí správně umístit do jeho rodičovského prvku.

Obrázek 16: Ukázka použití správce geometrie Column.

V demonstračním příkladu použijeme stejné prvky, jako v příkladu předchozím – jednu vektorovou kresbu a sadu obdélníků. Ovšem budeme muset změnit velikost okna, aby se do něj všechny prvky vešly:

import QtQuick 2.0
 
 
Rectangle {
    id: main
    width: 240
    height: 400
    color: "lightgray"
 
    Column {
        spacing: 2
        anchors.verticalCenter: parent.verticalCenter
        anchors.horizontalCenter: parent.horizontalCenter
 
        Rectangle {
            color: "#ff8080"
            width: 75
            height: 75
        }
 
        Rectangle {
            color: "yellow"
            width: 32
            height: 75
        }
 
        Rectangle {
            color: "#8080ff"
            width: 75
            height: 32
        }
 
        Rectangle {
            color: "#8080ff"
            width: 75
            height: 75
        }
 
        Rectangle {
            color: "black"
            width: 10
            height: 10
        }
 
        Image {
            id: left_image
            source: "editors/vim.svg"
            width: 100
            height: 100
        }
    }
}

Skript pro načtení a zobrazení QML souboru:

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
from QmlViewer import *
 
QML_FILE = "09_column.qml"
 
if __name__ == '__main__':
    main(QML_FILE)

16. Správce geometrie Row

Jak již zajisté správně tušíte, existuje vedle správce geometrie Column i správce geometrie nazvaný Row, s jehož použitím jsou jednotlivé prvky grafického uživatelského rozhraní umisťovány do jednoho řádku:

Row {
    spacing: 2
    anchors.verticalCenter: parent.verticalCenter
    anchors.horizontalCenter: parent.horizontalCenter
    ...
    ...
    ...
}

Obrázek 17: Ukázka použití správce geometrie Row.

Opět si ukažme příklad, v němž jsou jednotlivé prvky (vektorová kresba a sada obdélníků) umístěny s využitím správce geometrie Row:

import QtQuick 2.0
 
 
Rectangle {
    id: main
    width: 320
    height: 240
    color: "lightgray"
 
    Row {
        spacing: 2
        anchors.verticalCenter: parent.verticalCenter
        anchors.horizontalCenter: parent.horizontalCenter
 
        Rectangle {
            color: "#ff8080"
            width: 75
            height: 75
        }
 
        Rectangle {
            color: "yellow"
            width: 32
            height: 75
        }
 
        Rectangle {
            color: "#8080ff"
            width: 75
            height: 32
        }
 
        Rectangle {
            color: "#8080ff"
            width: 75
            height: 75
        }
 
        Rectangle {
            color: "black"
            width: 10
            height: 10
        }
 
        Image {
            id: left_image
            source: "editors/vim.svg"
            width: 100
            height: 100
        }
    }
}

Skript pro načtení a zobrazení QML souboru je až na jméno QML totožný s předchozími skripty:

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
from QmlViewer import *
 
QML_FILE = "10_row.qml"
 
if __name__ == '__main__':
    main(QML_FILE)

17. Tvorba animací v Pyside – použití automaticky modifikované proměnné řídicí animaci

Prozatím jen ve stručnosti se dnes seznámíme s další důležitou vlastností QML (a nepřímo též PySide). Jedná se o podporu animací, které mohou probíhat buď zcela automaticky nebo s využitím pomocných skriptů, jenž mohou reagovat na akce prováděné uživatelem. V dalším příkladu je ukázána základní animace, která využívá řídicí proměnnou nazvanou animatedValue:

property int animatedValue: 0

Tato proměnná je automaticky periodicky měněna od 0 do 9 a potom zpět k nule. Interval změny 0→9 je nastaven na jednu sekundu (1000 ms); interval změny 9→0 je taktéž roven jedné sekundě:

SequentialAnimation on animatedValue {
    loops: Animation.Infinite
    PropertyAnimation { to: 10; duration: 1000 }
    PropertyAnimation { to: 0; duration: 1000 }
}

Úplný zdrojový kód příkladu:

import QtQuick 2.0
 
 
Rectangle {
    color: "lightgray"
    width: 200
    height: 200
 
    property int animatedValue: 0
    SequentialAnimation on animatedValue {
        loops: Animation.Infinite
        PropertyAnimation { to: 10; duration: 1000 }
        PropertyAnimation { to: 0; duration: 1000 }
    }
 
    Text {
        anchors.centerIn: parent
        text: parent.animatedValue
    }
}

Pro jistotu ještě jednou skript pro načtení QML a inicializaci GUI:

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
from QmlViewer import *
 
QML_FILE = "11_animation_control_variable.qml"
 
if __name__ == '__main__':
    main(QML_FILE)

18. Další ukázky animace – plynulá změna velikosti vektorových kreseb

Nic nás samozřejmě neomezuje v použití pouze jediné proměnné, jejíž hodnota se automaticky mění. V dalším skriptu jsou použity hned tři proměnné:

property int animatedValue1: 50
property int animatedValue2: 50
property int animatedValue3: 50

Tyto proměnné mění velikosti tří vektorových kreseb umístěných do hlavního okna (situaci máme ulehčenou tím, že všechna tři loga textových editorů mají shodnou šířku a výšku):

Image {
    id: left_image
    source: "editors/vim.svg"
    width: parent.animatedValue1
    height: parent.animatedValue1
    anchors.verticalCenter: parent.verticalCenter
    anchors.left: parent.left
}

Úplný zdrojový kód příkladu dnes již posledního příkladu vypadá takto:

import QtQuick 2.0
 
Rectangle {
    id: main
    width: 320
    height: 240
    color: "lightgray"
 
    property int animatedValue1: 50
    SequentialAnimation on animatedValue1 {
        loops: Animation.Infinite
        PropertyAnimation { to: 100; duration: 1000 }
        PropertyAnimation { to: 50; duration: 1000 }
    }
 
    property int animatedValue2: 50
    SequentialAnimation on animatedValue2 {
        loops: Animation.Infinite
        PropertyAnimation { to: 100; duration: 2000 }
        PropertyAnimation { to: 50; duration: 2000 }
    }
 
    property int animatedValue3: 50
    SequentialAnimation on animatedValue3 {
        loops: Animation.Infinite
        PropertyAnimation { to: 100; duration: 3000 }
        PropertyAnimation { to: 50; duration: 3000 }
    }
 
    property int animatedValue4: 0
    SequentialAnimation on animatedValue4 {
        loops: Animation.Infinite
        PropertyAnimation { to: 320; duration: 6000 }
        PropertyAnimation { to: 0; duration: 6000 }
    }
 
    Image {
        id: left_image
        source: "editors/vim.svg"
        width: parent.animatedValue1
        height: parent.animatedValue1
        anchors.verticalCenter: parent.verticalCenter
        anchors.left: parent.left
    }
 
    Image {
        id: center_image
        source: "editors/emacs.svg"
        width: parent.animatedValue2
        height: parent.animatedValue2
        anchors.horizontalCenter: parent.horizontalCenter
        anchors.verticalCenter: parent.verticalCenter
    }
 
    Image {
        id: right_image
        source: "editors/atom.svg"
        width: parent.animatedValue3
        height: parent.animatedValue3
        anchors.verticalCenter: parent.verticalCenter
        anchors.right: parent.right
    }
 
    Rectangle {
        id: r1
        width: parent.animatedValue4
        height: 30
        color: "red"
        opacity: 0.5
        anchors.left: parent.left
        anchors.top: parent.top
    }
 
}
#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
from QmlViewer import *
 
QML_FILE = "12_animation_vector_images.qml"

if __name__ == '__main__':
    main(QML_FILE)

18. Repositář s demonstračními příklady

Zdrojové kódy všech dnes popsaných demonstračních příkladů byly, podobně jako tomu bylo i v předchozích článcích, uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/pre­sentations. Pokud nechcete klonovat celý repositář, můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:

# Příklad Adresa
1 01_load_qml.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/PySide2/01_load_qml­.py
2 QmlViewer.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/PySide2/QmlViewer­.py
3 02_use_qml_viewer.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/PySide2/02_use_qml_vi­ewer.py
4 03_springy_widgets.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/PySide2/03_springy_wid­gets.py
5 04_raster_image.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/PySide2/04_raster_i­mage.py
6 05_vector_drawings_A.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/PySide2/05_vector_dra­wings_A.py
7 06_vector_drawings_B.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/PySide2/06_vector_dra­wings_B.py
8 07_vector_drawings_C.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/PySide2/07_vector_dra­wings_C.py
9 08_grid.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/PySide2/08_grid.py
10 09_column.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/PySide2/09_column­.py
11 10_row.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/PySide2/10_row.py
12 11_animation_control_variable.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/PySide2/11_animati­on_control_variable.py
13 12_animation_vector_images.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/PySide2/12_animati­on_vector_images.py

Následuje tabulka s odkazy na soubory QML s popisem grafického uživatelského rozhraní, které taktéž budete potřebovat:

20. Odkazy na Internetu

  1. QML Tutorial
    https://pyside.github.io/doc­s/pyside/tutorials/qmltuto­rial/index.html
  2. QML Advanced Tutorial
    https://pyside.github.io/doc­s/pyside/tutorials/qmladvan­cedtutorial/index.html
  3. User interface markup language
    https://en.wikipedia.org/wi­ki/User_interface_markup_lan­guage
  4. UsiXML
    https://en.wikipedia.org/wiki/UsiXML
  5. Anchor-based Layout in QML
    https://het.as.utexas.edu/HET/Sof­tware/html/qml-anchor-layout.html#anchor-layout
  6. PySide.QtDeclarative
    https://pyside.github.io/doc­s/pyside/PySide/QtDeclara­tive/index.html
  7. PySide and Qt Quick/QML Playground
    https://wiki.qt.io/PySide-and-QML-Playground
  8. Hand Coded GUI Versus Qt Designer GUI
    https://stackoverflow.com/qu­estions/387092/hand-coded-gui-versus-qt-designer-gui
  9. Qt Creator Manual
    http://doc.qt.io/qtcreator/
  10. Qt Designer Manual
    http://doc.qt.io/qt-5/qtdesigner-manual.html
  11. Qt Creator (Wikipedia)
    https://en.wikipedia.org/wi­ki/Qt_Creator
  12. PySide 1.2.1 documentation
    https://pyside.github.io/doc­s/pyside/index.html
  13. PySide na PyPi
    https://pypi.org/project/PySide/
Našli jste v článku chybu?