Obsah
1. Reakce na události v GUI v jazyku QML a PySide 2
2. Reakce na stisk tlačítka myši kdekoli na ploše hlavního okna
3. Úprava příkladu – omezení plochy reagující na stisk tlačítka myši
4. Zjištění souřadnic kurzoru myši
5. Lokální souřadnicový systém každého objektu MouseArea
6. Zdrojový kód třetího demonstračního příkladu
7. Reakce na otočení kolečkem myši
8. Zdrojový kód čtvrtého demonstračního příkladu
9. Událost, která vznikne po úplné inicializaci komponenty
10. Použití pojmenovaných (neanonymních) funkcí při programování reakcí na události
11. Zdrojový kód šestého demonstračního příkladu
12. Refaktoring předchozího kódu a použití „univerzálních“ callback funkcí
13. Zdrojový kód sedmého demonstračního příkladu
14. Tažení (drag) objektů s využitím myši
15. Demonstrační příklad, v němž je možné objekty přemisťovat
16. Deklarativní omezení plochy, po níž se jednotlivé objekty mohou přemisťovat
17. Alternativní způsob zápisu atributů, které se vztahují k operaci „drag“
18. Nastavení prahové hodnoty pro operaci tažení
19. Repositář s demonstračními příklady
1. Reakce na události v GUI v jazyku QML a PySide 2
V pořadí již dvacáté třetí části seriálu o tvorbě grafického uživatelského rozhraní v Pythonu s využitím frameworku PySide (2) se opět seznámíme s některými důležitými vlastnostmi jazyka QML (Qt Modeling Language). Tentokrát se bude jednat o popis některých možností, které vývojáři mají při programování reakcí na události, jež vznikají v grafickém uživatelském rozhraní činností uživatele (použití myši, klávesnice nebo dalšího vstupního zařízení, například dotykového displeje). Zajímavé a užitečné přitom je, že některé reakce je možné naprogramovat přímo v jazyku QML, takže není nutné pro každou podúlohu zajišťovat kooperaci mezi QML a Pythonem (což je ovšem většinou dobře, protože nám to umožní oddělit logiku aplikace od vzhledu a chování GUI). Samozřejmě je možné zkombinovat naprogramované reakce na GUI události s animacemi, o nichž jsme se ve stručnosti zmínili minule.
# 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_())
2. Reakce na stisk tlačítka myši kdekoli na ploše hlavního okna
Dnešní první demonstrační příklad bude velmi jednoduchý a bude vlastně pouze shrnovat znalosti, které již o jazyku QML máme. V příkladu je deklarován obdélník představující plochu okna aplikace, do kterého jsou vloženy tři čtverce s různobarevnou výplní:

Obrázek 1: Výchozí nastavení okna dnešního prvního demonstračního příkladu.
Navíc je však přes celé okno vytvořena plocha reagující na operace prováděné myší. Tato plocha je neviditelná a pokud uživatel stiskne kdekoli v okně tlačítko myši, změní se barva výplně prostředního čtverce. Toto chování zajišťuje následující kód:
MouseArea { anchors.fill: parent onClicked: { r2.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1); } }
Povšimněte si, že se vlastně jedná o deklaraci hodnoty přiřazené k atributu onClicked. Hodnotou je ovšem (anonymní) funkce, což je zcela korektní, protože QML využívá podmnožinu JavaScriptu, v němž jsou funkce plnohodnotným datovým typem.

Obrázek 2: Změna barvy prostředního čtverce kliknutím tlačítkem myši.
Úplný zdrojový kód dnešního prvního demonstračního příkladu vypadá následovně:
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 } MouseArea { anchors.fill: parent onClicked: { r2.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1); } } }
Zdrojový kód demonstračního příkladu využívá modul QmlViewer popsaný v první kapitole:
#!/usr/bin/env python # vim: set fileencoding=utf-8 from QmlViewer import * QML_FILE = "13_mouse_click.qml" if __name__ == '__main__': main(QML_FILE)
3. Úprava příkladu – omezení plochy reagující na stisk tlačítka myši
Pokud budeme vyžadovat, aby se barva druhého čtverce změnila pouze ve chvíli, kdy uživatel klikne do plochy tohoto čtverce a nikoli na libovolné místo v hlavním okně aplikace, je úprava velmi snadná – prostě přesuneme deklaraci MouseArea dovnitř deklarace příslušného čtverce (význam anchors.fill: parent se tedy změní, protože rodičem MouseArea je nyní čtverec s id=r2):
Rectangle { id: r2 width: 160 height: 160 color: "yellow" opacity: 0.5 z: 1 anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top MouseArea { anchors.fill: parent onClicked: { r2.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1); } } }
Jen pro úplnost si uveďme úplný zdrojový kód druhé varianty předchozího demonstračního příkladu:
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 MouseArea { anchors.fill: parent onClicked: { r2.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1); } } } Rectangle { id: r3 width: 160 height: 160 color: "blue" opacity: 0.5 rotation: 45 anchors.right: parent.right anchors.bottom: parent.bottom } }
4. Zjištění souřadnic kurzoru myši
V některých případech nám nestačí pouze reagovat na samotný stisk tlačítka myši, ale budeme potřebovat vědět, na kterém místě se nachází kurzor myši. To lze zjistit (v handleru příslušné události) velmi snadno s využitím mouse.x a mouse.y:
MouseArea { anchors.fill: parent onClicked: { r2.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1); console.log("mouse coordinates:", mouse.x, mouse.y); } }
Příklad výstupu (na konzoli/terminál) s využitím console.log():
qml: mouse coordinates: 9 10 qml: mouse coordinates: 4 5 qml: mouse coordinates: 314 236 qml: mouse coordinates: 14 233 qml: mouse coordinates: 281 25 qml: mouse coordinates: 276 75 qml: mouse coordinates: 240 85
V handleru události onClicked totiž máme k dispozici instanci třídy QQuickMouseEvent, která nám nabízí mj. i tyto atributy:
Atribut | Význam |
---|---|
x | horizontální souřadnice kurzoru myši |
y | vertikální souřadnice kurzoru myši |
button | tlačítko myši, které bylo stisknuto (levé, pravé, prostřední) a vyvolalo událost |
buttons | bitové pole s kombinacemi právě stisknutých tlačítek |
flags | v současnosti obsahuje pouze příznak, zda stisk vyvolá událost typu doubleclick |
modifiers | bitové pole obsahující příznaky modifikátorů (Shift, Control, Alt) stisknutých během události |
source | rozlišení mezi reálnou myší a jiným zařízením (touchscreen atd.) |
Opět si ukažme úplný zdrojový kód demonstračního příkladu, který dokáže vypsat souřadnice kurzoru myši:
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 } MouseArea { anchors.fill: parent onClicked: { r2.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1); console.log("mouse coordinates:", mouse.x, mouse.y); } } }
#!/usr/bin/env python # vim: set fileencoding=utf-8 from QmlViewer import * QML_FILE = "14_mouse_click_coordinates.qml" if __name__ == '__main__': main(QML_FILE)
5. Lokální souřadnicový systém každého objektu MouseArea
V předchozí kapitole jsme se zmínili o použití souřadnice kurzoru myši ve chvíli, kdy vznikla nějaká událost vyvolaná myší. Tyto souřadnice však nejsou absolutní (ani v rámci okna a samozřejmě ani v rámci celého desktopu), ale jsou vztaženy k objektu typu MouseArea. Záleží tedy na tom, jak přesně je tato plocha definována: pokud bude vytvořena v rámci menšího objektu, popř. když bude celý objekt otočen, změní se příslušným způsobem i lokální souřadnicový systém, k němuž jsou vztaženy souřadnice kurzoru myši. To je ve většině případů přesně takové chování, které dává smysl (představme si například kreslicí plochu umístěnou do libovolného místa okna aplikace).
Například následující čtverec má rozměry 160×160 délkových jednotek a je natočen o 45°:
Rectangle { id: r1 width: 160 height: 160 color: "red" opacity: 0.5 rotation: 45 anchors.left: parent.left anchors.bottom: parent.bottom MouseArea { anchors.fill: parent onClicked: { console.log("mouse coordinates:", mouse.x, mouse.y); } } }
Souřadnice [0, 0] a [160, 0] budou umístěny do dvou bodů naznačených na dalším obrázku:

Obrázek 3: Umístění souřadnic [0, 0] a [160, 0] pro první čtverec.
6. Zdrojový kód třetího demonstračního příkladu
V dnešním třetím demonstračním příkladu jsou použity tři samostatné plochy reagující na stisk tlačítka myši. Vyzkoušejte si, že se tyto plochy překrývají (v jedné oblasti dokonce všechny tři) a příslušná událost je vždy zachycena pouze jednou plochou, konkrétně tou plochou, která virtuálně leží nad ostatními dvěma plochami (to lze ovlivnit atributem z):
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 MouseArea { anchors.fill: parent onClicked: { parent.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1); console.log("mouse coordinates:", mouse.x, mouse.y); } } } Rectangle { id: r2 width: 160 height: 160 color: "yellow" opacity: 0.5 z: 1 anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top MouseArea { anchors.fill: parent onClicked: { parent.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1); console.log("mouse coordinates:", mouse.x, mouse.y); } } } Rectangle { id: r3 width: 160 height: 160 color: "blue" opacity: 0.5 rotation: 45 anchors.right: parent.right anchors.bottom: parent.bottom MouseArea { anchors.fill: parent onClicked: { parent.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1); console.log("mouse coordinates:", mouse.x, mouse.y); } } } }
Skript pro spuštění příkladu:
#!/usr/bin/env python # vim: set fileencoding=utf-8 from QmlViewer import * QML_FILE = "15_more_mouse_areas.qml" if __name__ == '__main__': main(QML_FILE)
7. Reakce na otočení kolečkem myši
V mnoha aplikacích můžeme využít i kolečko myši. Ve chvíli, kdy uživatel kolečkem otočí, se zavolá callback funkce pojmenovaná onWheel, které se předá jak aktuální souřadnice kurzoru myši, tak i relativní hodnota otočení. Ve skutečnosti tato callback funkce podporuje dvě relativní hodnoty – x-ovou a y-ovou (angleDelta.x/angleDelta.y). Záleží na konkrétním provedení myši, jaké informace (zda vůbec nějaké) se přenesou v parametru odpovídajícím x-ové ose; typicky je tato hodnota využitelná u myší, které namísto standardního kolečka obsahují buď malý touchpad nebo tzv. scroll ball (malý trackball). Naproti tomu náklon kolečka se většinou považuje za stisk čtvrtého, resp. pátého tlačítka myši. Příklad naprogramování reakce na otočení kolečkem myši:
MouseArea { anchors.fill: parent onClicked: { ... ... ... } onWheel: { console.log("mouse wheel:", wheel.angleDelta.y); } }
Typicky se setkáme s tím, že je otočení kolečkem myši o jeden „zub“ vráceno jako hodnota +120 nebo –120, v závislosti na směru otáčení. Tomuto chování je možné přizpůsobit aplikaci. Například v následujícím kódu se otáčením kolečkem myši mění natočení čtverce nebo jakéhokoli jiného předka objektu MouseArea:
Rectangle { id: r1 width: 160 height: 160 color: "red" opacity: 0.5 rotation: 45 anchors.left: parent.left anchors.bottom: parent.bottom MouseArea { anchors.fill: parent onClicked: { parent.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1); console.log("mouse coordinates:", mouse.x, mouse.y); } onWheel: { console.log("mouse wheel:", wheel.angleDelta.y); parent.rotation += wheel.angleDelta.y / 30; } } }
8. Zdrojový kód čtvrtého demonstračního příkladu
Ve čtvrtém příkladu je ukázáno, jak lze naprogramovat otáčení libovolným čtvercem v okně tím nejjednodušším způsobem – kolečkem myši. K tomuto účelu se používají tři objekty MouseArea s prakticky totožnými handlery událostí:
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 MouseArea { anchors.fill: parent onClicked: { parent.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1); console.log("mouse coordinates:", mouse.x, mouse.y); } onWheel: { console.log("mouse wheel:", wheel.angleDelta.y); parent.rotation += wheel.angleDelta.y / 30; } } } Rectangle { id: r2 width: 160 height: 160 color: "yellow" opacity: 0.5 z: 1 anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top MouseArea { anchors.fill: parent onClicked: { parent.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1); console.log("mouse coordinates:", mouse.x, mouse.y); } onWheel: { console.log("mouse wheel:", wheel.angleDelta.y); parent.rotation += wheel.angleDelta.y / 30; } } } Rectangle { id: r3 width: 160 height: 160 color: "blue" opacity: 0.5 rotation: 45 anchors.right: parent.right anchors.bottom: parent.bottom MouseArea { anchors.fill: parent onClicked: { parent.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1); console.log("mouse coordinates:", mouse.x, mouse.y); } onWheel: { console.log("mouse wheel:", wheel.angleDelta.y); parent.rotation += wheel.angleDelta.y / 30; } } } }
Skript pro spuštění příkladu:
#!/usr/bin/env python # vim: set fileencoding=utf-8 from QmlViewer import * QML_FILE = "16_mouse_wheel.qml" if __name__ == '__main__': main(QML_FILE)
9. Událost, která vznikne po úplné inicializaci komponenty
Před popisem dalších operací, které souvisí s myší či podobným polohovacím zařízením, se musíme zmínit o události, která nastane (přesněji řečeno je vyvolána) ve chvíli, kdy je určitá komponenta plně inicializována a celé prostředí (QML) je kompletně připraveno pro provedení uživatelských skriptů. Tato událost se jmenuje completed a popsána je na stránce https://doc.qt.io/qt-5/qml-qtqml-component.html#completed-signal. Příslušný handler má jméno onCompleted a je možné ho použít u jakékoli komponenty:
Component.onCompleted: { ... ... ... uživatelský skript ... ... ... }
Podívejme se nyní na jednoduchý demonstrační příklad, který ukazuje použití handleru této události. V příkladu je použit obdélník reprezentující celou plochu okna, na němž je další čtverec:
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 } Component.onCompleted: { console.log("ok, everything is prepared"); } }
10. Použití pojmenovaných (neanonymních) funkcí při programování reakcí na události
Další problematikou, o které se musíme alespoň ve stručnosti zmínit, je použití klasických pojmenovaných (tj. neanonymních) funkcí, které je možné zavolat v handlerech událostí. Prozatím jsme celý programový kód související se zpracováním určité události vkládali přímo do handleru příslušné události, například:
onClicked: { parent.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1); console.log("mouse coordinates:", mouse.x, mouse.y); } onWheel: { console.log("mouse wheel:", wheel.angleDelta.y); parent.rotation += wheel.angleDelta.y / 30; }
Ovšem ve skutečnosti můžeme použít i běžné JavaScriptové funkce, tedy takto:
function onRect1Click(mouse) { r1.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1); console.log("mouse coordinates:", mouse.x, mouse.y); } function onRect1WheelRotate(wheel) { console.log("mouse wheel:", wheel.angleDelta.y); r1.rotation += wheel.angleDelta.y / 30; }
Tyto funkce již můžeme zavolat z handleru popř. je můžeme přímo propojit s příslušným signálem, což je ještě lepší:
Component.onCompleted: { console.log("completed") mouseArea1.clicked.connect(onRect1Click) mouseArea1.wheel.connect(onRect1WheelRotate) }
11. Zdrojový kód šestého demonstračního příkladu
V šestém demonstračním příkladu je deklarováno šest funkcí, které jsou (postupně) propojeny s následujícími událostmi:
- Kliknutí na první čtverec
- Otočení kolečkem myši nad prvním čtvercem
- Kliknutí na druhý čtverec
- Otočení kolečkem myši nad druhým čtvercem
- Kliknutí na třetí čtverec
- Otočení kolečkem myši nad třetím čtvercem
Zdrojový kód příkladu není nijak refaktorován a je psán podobným způsobem, jakoby se jednalo o kód, který je exportovaný z nástrojů určených pro návrh grafického uživatelského rozhraní v QML:
import QtQuick 2.0 Rectangle { id: main width: 320 height: 240 color: "lightgray" function onRect1Click(mouse) { r1.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1); console.log("mouse coordinates:", mouse.x, mouse.y); } function onRect1WheelRotate(wheel) { console.log("mouse wheel:", wheel.angleDelta.y); r1.rotation += wheel.angleDelta.y / 30; } function onRect2Click(mouse) { r2.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1); console.log("mouse coordinates:", mouse.x, mouse.y); } function onRect2WheelRotate(wheel) { console.log("mouse wheel:", wheel.angleDelta.y); r2.rotation += wheel.angleDelta.y / 30; } function onRect3Click(mouse) { r3.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1); console.log("mouse coordinates:", mouse.x, mouse.y); } function onRect3WheelRotate(wheel) { console.log("mouse wheel:", wheel.angleDelta.y); r3.rotation += wheel.angleDelta.y / 30; } Rectangle { id: r1 width: 160 height: 160 color: "red" opacity: 0.5 rotation: 45 anchors.left: parent.left anchors.bottom: parent.bottom MouseArea { id: mouseArea1 anchors.fill: parent } } Rectangle { id: r2 width: 160 height: 160 color: "yellow" opacity: 0.5 z: 1 anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top MouseArea { id: mouseArea2 anchors.fill: parent } } Rectangle { id: r3 width: 160 height: 160 color: "blue" opacity: 0.5 rotation: 45 anchors.right: parent.right anchors.bottom: parent.bottom MouseArea { id: mouseArea3 anchors.fill: parent } } Component.onCompleted: { console.log("completed") mouseArea1.clicked.connect(onRect1Click) mouseArea1.wheel.connect(onRect1WheelRotate) mouseArea2.clicked.connect(onRect2Click) mouseArea2.wheel.connect(onRect2WheelRotate) mouseArea3.clicked.connect(onRect3Click) mouseArea3.wheel.connect(onRect3WheelRotate) } }
12. Refaktoring předchozího kódu a použití „univerzálních“ callback funkcí
Předchozí demonstrační příklad nebyl naprogramován příliš dobře, protože se podobný programový kód (handler pro kliknutí tlačítkem myši a pro otočení kolečkem) musel psát třikrát. Je tomu tak z toho důvodu, že do handlerů událostí jsou implicitně předávány pouze objekty představující vlastní událost. Pokud budeme chtít do handlerů předávat i další data, musíme si nepatrně pomoci, a to takto:
Component.onCompleted: { console.log("completed") mouseArea1.clicked.connect(function(event) {onRectClick(r1, event)}) mouseArea2.clicked.connect(function(event) {onRectClick(r2, event)}) mouseArea3.clicked.connect(function(event) {onRectClick(r3, event)}) mouseArea1.wheel.connect(function(event) {onRectWheelRotate(r1, event)}) mouseArea2.wheel.connect(function(event) {onRectWheelRotate(r2, event)}) mouseArea3.wheel.connect(function(event) {onRectWheelRotate(r3, event)}) }
V JavaScriptu se jedná o zápis anonymní funkce, která volá handler a předává mu kromě objektu představujícího událost i identifikátor objektu, nad nímž k události došlo. Samotné handlery jsou tedy již jen dva:
function onRectClick(rectangle, mouse) { rectangle.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1); console.log("mouse coordinates:", mouse.x, mouse.y); } function onRectWheelRotate(rectangle, wheel) { console.log("mouse wheel:", wheel.angleDelta.y); rectangle.rotation += wheel.angleDelta.y / 30; }
13. Zdrojový kód sedmého demonstračního příkladu
Opět se pro úplnost podívejme na úplný zdrojový kód dnešního sedmého demonstračního příkladu:
import QtQuick 2.0 Rectangle { id: main width: 320 height: 240 color: "lightgray" function onRectClick(rectangle, mouse) { rectangle.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1); console.log("mouse coordinates:", mouse.x, mouse.y); } function onRectWheelRotate(rectangle, wheel) { console.log("mouse wheel:", wheel.angleDelta.y); rectangle.rotation += wheel.angleDelta.y / 30; } Rectangle { id: r1 width: 160 height: 160 color: "red" opacity: 0.5 rotation: 45 anchors.left: parent.left anchors.bottom: parent.bottom MouseArea { id: mouseArea1 anchors.fill: parent } } Rectangle { id: r2 width: 160 height: 160 color: "yellow" opacity: 0.5 z: 1 anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top MouseArea { id: mouseArea2 anchors.fill: parent } } Rectangle { id: r3 width: 160 height: 160 color: "blue" opacity: 0.5 rotation: 45 anchors.right: parent.right anchors.bottom: parent.bottom MouseArea { id: mouseArea3 anchors.fill: parent } } Component.onCompleted: { console.log("completed") mouseArea1.clicked.connect(function(event) {onRectClick(r1, event)}) mouseArea2.clicked.connect(function(event) {onRectClick(r2, event)}) mouseArea3.clicked.connect(function(event) {onRectClick(r3, event)}) mouseArea1.wheel.connect(function(event) {onRectWheelRotate(r1, event)}) mouseArea2.wheel.connect(function(event) {onRectWheelRotate(r2, event)}) mouseArea3.wheel.connect(function(event) {onRectWheelRotate(r3, event)}) } }
14. Tažení (drag) objektů s využitím myši
Další operací, kterou je možné provádět s využitím myši popř. podobného polohovacího zařízení (zde samozřejmě včetně touchscreenu), je tažení (drag) objektů, nebo ještě lépe úplná operace typu drag and drop. Pokud například budeme potřebovat, aby uživatel mohl manipulovat s objektem s identifikátorem object1 na nějaké ploše, postačuje použít nám použít již známý objekt MouseArea a v něm specifikovat, jak se má operace tažení chovat a jakého prvku grafického uživatelského rozhraní se týká (zde objektu object1):
MouseArea { id: mouseArea1 anchors.fill: parent drag.target: object1 drag.axis: Drag.XAndYAxis }
Zajímavé je použití atributu drag.axis, kterým specifikujeme, jakým směrem lze tažení provést:
- Drag.XAxis – pouze horizontálně
- Drag.YAxis – pouze vertikálně
- Drag.XAndYAxis – oběma směry
Také je možné specifikovat další atributy, jejichž význam si postupně popíšeme v dalších demonstračních příkladech.
15. Demonstrační příklad, v němž je možné objekty přemisťovat
V dalším demonstračním příkladu je ukázána deklarace operace tažení. Každý z barevných čtverců lze přemisťovat pomocí myši:
- Levý čtverec lze přemístit libovolným směrem
- Prostřední čtverec se dá přesunovat jen nahoru nebo dolů (tedy vertikálně)
- Pravý čtverec se naproti tomu přesunuje pouze vlevo či vpravo (tedy horizontálně)

Obrázek 4: Výchozí umístění čtverců v ploše okna.

Obrázek 5: Horizontální přesun pravého čtverce.

Obrázek 6: Vertikální přesun prostředního čtverce.

Obrázek 7: Levý čtverec můžeme přesunou kamkoli, i mimo plochu okna.
Opět následuje výpis zdrojového kódu tohoto demonstračního příkladu:
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 x: 0 y: 0 MouseArea { id: mouseArea1 anchors.fill: parent drag.target: r1 drag.axis: Drag.XAndYAxis } } Rectangle { id: r2 width: 160 height: 160 color: "yellow" opacity: 0.5 x: 80 y: 80 z: 1 MouseArea { id: mouseArea2 anchors.fill: parent drag.target: r2 drag.axis: Drag.YAxis } } Rectangle { id: r3 width: 160 height: 160 color: "blue" opacity: 0.5 x: 160 y: 0 MouseArea { id: mouseArea3 anchors.fill: parent drag.target: r3 drag.axis: Drag.XAxis } } }
16. Deklarativní omezení plochy, po níž se jednotlivé objekty mohou přemisťovat
V případě, že budeme chtít omezit přesuny čtverců pouze po ploše okna (tj. aby nám čtverce „neujely“ mimo viditelnou plochu), musíme specifikovat atributy minimumX, maximumX, minimumY a maximumY. Hodnoty těchto atributů je nutné dopočítat. U minimálních hodnot je to jednoduché – nastavíme je na nulu. U hodnot maximálních bude maximální hodnota vypočtena z výšky/šířky okna a výšky/šířky příslušného čtverce:
MouseArea { id: mouseArea1 anchors.fill: parent drag.target: r1 drag.axis: Drag.XAndYAxis drag.minimumX: 0 drag.maximumX: main.width - r1.width drag.minimumY: 0 drag.maximumY: main.height - r1.height }
Opět následuje výpis úplného zdrojového kódu příkladu, který tentokrát neumožní, aby se čtverce přesunuly mimo hlavní okno:
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 x: 0 y: 0 MouseArea { id: mouseArea1 anchors.fill: parent drag.target: r1 drag.axis: Drag.XAndYAxis drag.minimumX: 0 drag.maximumX: main.width - r1.width drag.minimumY: 0 drag.maximumY: main.height - r1.height } } Rectangle { id: r2 width: 160 height: 160 color: "yellow" opacity: 0.5 x: 80 y: 80 z: 1 MouseArea { id: mouseArea2 anchors.fill: parent drag.target: r2 drag.axis: Drag.YAxis drag.minimumY: 0 drag.maximumY: main.height - r2.height } } Rectangle { id: r3 width: 160 height: 160 color: "blue" opacity: 0.5 x: 160 y: 0 MouseArea { id: mouseArea3 anchors.fill: parent drag.target: r3 drag.axis: Drag.XAxis drag.minimumX: 0 drag.maximumX: main.width - r3.width } } }
17. Alternativní způsob zápisu atributů, které se vztahují k operaci „drag“
Ještě se podívejme na alternativní způsob zápisu atributů pro jeden QML objekt. Prozatím jsme například u objektu popisujícího operaci tažení používali „tečkovou“ notaci:
drag.target: r1 drag.axis: Drag.XAndYAxis drag.minimumX: 0 drag.maximumX: main.width - r1.width drag.minimumY: 0 drag.maximumY: main.height - r1.height
Alternativně můžeme využít i jiný zápis:
drag { target: r1 axis: Drag.XAndYAxis minimumX: 0 maximumX: main.width - r1.width minimumY: 0 maximumY: main.height - r1.height }
Předchozí demonstrační příklad lze tedy přepsat následujícím způsobem:
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 x: 0 y: 0 MouseArea { id: mouseArea1 anchors.fill: parent drag { target: r1 axis: Drag.XAndYAxis minimumX: 0 maximumX: main.width - r1.width minimumY: 0 maximumY: main.height - r1.height } } } Rectangle { id: r2 width: 160 height: 160 color: "yellow" opacity: 0.5 x: 80 y: 80 z: 1 MouseArea { id: mouseArea2 anchors.fill: parent drag { target: r2 axis: Drag.YAxis minimumY: 0 maximumY: main.height - r2.height } } } Rectangle { id: r3 width: 160 height: 160 color: "blue" opacity: 0.5 x: 160 y: 0 MouseArea { id: mouseArea3 anchors.fill: parent drag { target: r3 axis: Drag.XAxis minimumX: 0 maximumX: main.width - r3.width } } } }
18. Nastavení prahové hodnoty pro operaci tažení
Posledním užitečným atributem při deklaraci operace tažení je tzv. prahová hodnota (threshold). Ta udává, o kolik pixelů se musí posunout kurzor myši se stlačeným tlačítkem, aby se vůbec tažení uskutečnilo. Vyšší hodnoty se hodí použít pro zamezení tažení ve chvíli, kdy uživatel pouze potřebuje na objekt kliknout a omylem myš o několik pixelů posune. Nastavení prahové hodnoty je snadné (v příkladu je ovšem nastavena na příliš vysokou hodnotu):
drag { target: r1 axis: Drag.XAndYAxis minimumX: 0 maximumX: main.width - r1.width minimumY: 0 maximumY: main.height - r1.height threshold: 40 }
Upravený příklad bude vypadat takto:
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 x: 0 y: 0 MouseArea { id: mouseArea1 anchors.fill: parent drag { target: r1 axis: Drag.XAndYAxis minimumX: 0 maximumX: main.width - r1.width minimumY: 0 maximumY: main.height - r1.height threshold: 40 } } } Rectangle { id: r2 width: 160 height: 160 color: "yellow" opacity: 0.5 x: 80 y: 80 z: 1 MouseArea { id: mouseArea2 anchors.fill: parent drag { target: r2 axis: Drag.YAxis minimumY: 0 maximumY: main.height - r2.height threshold: 40 } } } Rectangle { id: r3 width: 160 height: 160 color: "blue" opacity: 0.5 x: 160 y: 0 MouseArea { id: mouseArea3 anchors.fill: parent drag { target: r3 axis: Drag.XAxis minimumX: 0 maximumX: main.width - r3.width threshold: 40 } } } }
19. 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/presentations. Pokud nechcete klonovat celý repositář, můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:
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
- QML Tutorial
https://pyside.github.io/docs/pyside/tutorials/qmltutorial/index.html - QML Advanced Tutorial
https://pyside.github.io/docs/pyside/tutorials/qmladvancedtutorial/index.html - User interface markup language
https://en.wikipedia.org/wiki/User_interface_markup_language - Signal and Handler Event System
https://doc.qt.io/qt-5/qtqml-syntax-signals.html - Qt Documentation: MouseEvent QML Type
https://doc.qt.io/qt-5/qml-qtquick-mouseevent.html - Qt Documentation: WheelEvent QML Type
https://doc.qt.io/qt-5/qml-qtquick-wheelevent.html - Qt Documentation: MouseArea QML Type
https://doc.qt.io/qt-5/qml-qtquick-mousearea.html - UsiXML
https://en.wikipedia.org/wiki/UsiXML - Anchor-based Layout in QML
https://het.as.utexas.edu/HET/Software/html/qml-anchor-layout.html#anchor-layout - PySide.QtDeclarative
https://pyside.github.io/docs/pyside/PySide/QtDeclarative/index.html - PySide and Qt Quick/QML Playground
https://wiki.qt.io/PySide-and-QML-Playground - Hand Coded GUI Versus Qt Designer GUI
https://stackoverflow.com/questions/387092/hand-coded-gui-versus-qt-designer-gui - Qt Creator Manual
http://doc.qt.io/qtcreator/ - Qt Designer Manual
http://doc.qt.io/qt-5/qtdesigner-manual.html - Qt Creator (Wikipedia)
https://en.wikipedia.org/wiki/Qt_Creator - PySide 1.2.1 documentation
https://pyside.github.io/docs/pyside/index.html - PySide na PyPi
https://pypi.org/project/PySide/ - QML for JavaScript programmers
https://wiki.qt.io/QML_for_JavaScript_programmers - JavaScript Expressions in QML Documents
https://doc.qt.io/qt-5/qtqml-javascript-expressions.html - JavaScript Host Environment
https://doc.qt.io/qt-5/qtqml-javascript-hostenvironment.html