Tutorial Qt Markup Language (QML)
Copyright by Tomasz “Tomza” Zackiewicz
1/ Wstęp
2/ Aplikacje jednoplikowe
3/ Aplikacje wieloplikowe
4/ Aplikacje wieloplikowe dynamiczne
5/ Skomplikowane aplikacje
1/ Wstęp
Mając do czynienia ze środowiskiem Qt musimy natknąć się nie tylko na język C++, ale i QML. QML (Qt Meta Language lub Qt Modeling Language) jest językiem deklaracyjnym opartym na JavaScript, dlatego znajomość tego ostatniego jest wskazana. QML jest językiem ukierunkowanym na aplikacje graficzne interfejsu użytkownika. Jest to część Qt Quick, zestawu graficznego interfejsu rozwijanego przez Nokię w ramach frameworka Qt. Używany jest w zasadzie tylko dla aplikacji mobilnych.
QML to poprostu hierarchicze drzewo różnych komponentów, składających się z mniejszych elementów. Są one wbudowane w środowisko Qt. Możemy też definiować swoje elementy. Innymi słowy jest to zestaw bloków do tworzenia aplikacji, gdzie możemy wyróżnić bloki graficzne jak Rectange i bloki zachowania się jak stan, przekształcenie, animacja. Te elementy są łączone i tworzą aplikacje od prostych guzików do rozwiniętych aplikacji. Nie ma takich ograniczeń jak typowe aplikacje desktopowe GUI, dlatego używany jest w urządzeniach mobilnych.
Z racji pokrewieństwa z JavaScript elementy QML są często wzbogacane o kod JavaScript albo wstawiony bezpośrednio do kodu (inline), albo przez dodanie oddzielnych plików z rozszerzeniem .js. Warstwa skryptowa tych aplikacji jest tu bardzo istotna, jeśli chcemy mieć wydajny logic jakiejś aplikacji. Elementy QML również są łatwo integrowane i wzbogacane o możliwości komponentów napisanych w C++ przy użyciu frameworka Qt.
Narzędziami deweloperskimi dla QML mogą być edytory dla JavaScript, bo jest duże podobieństwo kodu. Jednak aby uzyskać pełne kolorowanie składni, uzupełnianie kodu, zintegrowaną pomoc i podgląd w edytorze WYSIWYG, musimy użyć darmowego i dogodnego dla różnych platform systemowych Qt Creator IDE. Jest to część środowiska Qt, bo też tam są inne narzędzia, zlokalizowane w katalogu bin. Po zainstalowania środowiska mamy też przeglądarkę dla plików QML. Nazywa się ona QML Viewer. Możemy ją wywołać z linii poleceń, wpisując słowo qmlviewer. Język ten nazywa się QML, ale środowiskiem uruchomieniowym jest Qt Declarative.
QML wzasadzie nie wymaga wiedzy o Qt/C++ dla użycia, ale może być łatwo rozszerzony przez koncepty (concepts) zw Qt.Familiar. QML dostarcza bezpośredniego dostępu do następujących konceptów:
QAction – typ akcji
QObject (sygnały i sloty)– dostępne jako funkcje do wywołania w JavaScript
QObject (własności)– dostępne jako zmienne wJavaScript
QWidget – QDeclarativeView jest widgetem wyświetlającym QML
Q*Model – użyty bezpośrednio w wiązaniu danych (np. QAbstractItemModel)
Do tworzenia plików QML można użyć samego Qt. Aby uruchomić aplikację używamy guzika z zielonym trójkątem na dole po lewej stronie paska narzędzi w Qt Creator IDE. Kiedy plik główny (aplikacja) jest wybrany z listy plików i ten guzik jest naciśnięty, program działa z Main, a reszta plików jest dołączana podczas wykonywania aplikacji.
Jednak ja tu używałem edytora Notepad++ i uruchamiałem to w wierszu polecenia z przeglądarką qmlviewer.
D:\QML>qmlviewer Nazwa_pliku.qml
ENTER
Podstawowe elementy QML pozwalają na łatwe włączanie obiektów do scenerii tworzonej aplikacji:
Item
Rectangle
Image
Text
TextInput
TextEdit
FocusScope
Component
MouseArea
Kiedy używamy elementów QML, elementy mogą posiadać własności, które inne elementy też posiadają. To jest ponieważ QML i podlegający mu silnik jest implementowany w C++ przy użyciu Qt. Łańcuch dziedziczenia własności jest z powodu, że QML używa Qt Declarative Module i meta-object oraz property. Np. Elementy wizualne, które mają implementacje C++ są podklasami QDeclarativeItem. W rezultacie elementy takie jak Rectangle i Text dziedziczą właności takie jak clip i smooth.
Element Item
Wiele elementów QML dziedziczy własności Item. Item posiada ważne własności takie jak focus, children i właściwości wymiarów takie jak width i height. Chociaż Item ma fizyczne właności, to nie jest element wizualny. Używanie Item jako najważniejszego elementu QML (jako ekranu) nie da efektu wizualnego. Zamiast tego użyjmy elementu Rectangle oczywiście. Item tylko dla przezroczystości, np. Przy tworzeniu niewidocznego kontenera dla innych komponentów.
Element Rectangle
Element Rectangle jest podstawowym elementem wizualnym dla wyświetlenia różnych typów składników na ekranie. Jest on dostosowalny i używa inne elementy jak Gradient i BorderImage dla wyświetlenia zaawansowanej grafiki.
Element Image
Aby wstawić obrazek do QML deklarujemy element Image. Może on załadować obrazki w formaty wsparte przez Qt.
Elementy Text
Elementy Text i TextEdit wyświetlają sformatowany tekst na ekranie. TextEdit cechuje się wieloliniowych edytowaniem, gdy element TextInput jest dla wstawiania pojedynczej linii tekstu.
Using Elements as the Top-Level Component
Jak najważniejszy element QML możemy użyć różne elementy. Dla prostego tła wystarczy Rectangle lub Item. Inne elementy: FocusScope,Component, QtObject. Najwazniejszy element QML jest ważny przy importowaniu komponentów, bowiem własności komponentu są jedynymi własnościami dostępnymi dla rodzica.
Podstawowe typy QML
QML ma zestaw typów podstawowych, używanych za pomocą QML Elements
action Typ action ma wszystkie własności QAction.
bool Przybiera wartości binarne true/false.
color Nazwa standardowego koloru w cudzysłowie.
date Jest wg. wzoru “YYYY-MM-DD”.
double Liczba double ma cześć dziesiątną o podwójnej precyzji.
enumeration Typ enumeration składa sie z zestawu nazwanych wartości.
font Typ font ma własności z QFont.
int Liczba całkowita nie ma części dziesiętnej, np. 234.
list Lista obiektów.
point Typ point ma atrybuty x i y.
real Liczba rzeczywista ma częśc dziesiętną, np. 34.56.
rect Typ rect ma atrybuty x, y, width i height.
size Typ size ma atrybuty width i height.
string Tekst w cudzysłowiu np. “Program QML”.
time Jest wg wzoru “hh:mm:ss”.
url URL jest lokalizatorem zasobu np. pliku.
variant Typ variant jest ogólnym typem własności.
vector3d Typ vector3d ma atrybuty x, y i z.
Konwencje kodowanie w QML
Konwencje kodowania QML są w QML Coding Conventions. Styl kodowania QML można znaleźć w Qt Coding Style.
Importowanie plików do QML
Dla zaimportowania czegokolwiek używamy słowa kluczowego import:
import QtQuick 1.0
import QtWebKit 1.0
import “katalog”
import “skrypt1.js”
Dla ułatwienia importowania komponentów QML, dobrze jest zaczynać plik QML z dużej litery. Uzytkownik może po prostu zadeklarowac komponent przy użyciu nazwy pliku jako nazwę komponentu. Np. Mamy plik
elementBiały.qml
Potem użytkownik moż zaimportować komponent przez deklarowanie
Biały {}
Pliki QML muszą być w tym samym katalogu.
Można importować pliki QML, które mają nazwy zaczynające się małą literą lub pliki w róznych katalogach przez użycie pliku aqmldir. Mówi on aplikacji QML, jakie komponenty, wtyczki lub katalogi są do zaimportowania. Plik aqmldir musi być w importowanym katalogu.
Komentowanie kodu
Tak jak w C++ są dwa sposoby komentowania w QML:
— Komentarz jednoliniowy zaczyna się //, a kończy się z końcem linii.
— Komentarz wieloliniowy zaczyna się /* i kończy */
Własności grupowe
Wiele własności QML jest właściwościami typu attached lub group.
border.width: 1
border.color: “yellow”
anchors.bottom: parent.bottom
anchors.left: parent.left
Można potraktować grupy właściwości jako blok, co pomoże powiązanie jednych właściwości z innymi.
border {
width: 1;
color: “yellow”
}
anchors {
bottom: parent.bottom;
left: parent.left
}
2/ Aplikacje jednoplikowe
Przykładowy projekt QML wygląda tak:
import QtQuick 1.0
Rectangle
{
width: 500
height: 500
Text
{
anchors.centerIn: parent
text: “Witaj świecie!”
}
MouseArea
{
anchors.fill: parent
onClicked:
{
Qt.quit();
}
}
}
Nazwijmy go Kato.qml. Uruchomimy go w ten sposób:
Microsoft Windows XP [Wersja 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.
D:\QML-przy>qmlviewer Kato.qml
D:\QML-przy>
Rezultatem będzie:
Przy kodzie QML trzeba pamiętać o czterech rzeczach:
Kodowanie powinno być w UTF-8 (np. z ANSI są problemy).
Nie należy używać polskich liter jak ż, ó itd w elementach kodu.
Nazwy plików nie powinny zawierać spacji (może być podkreślnik) i zaleca się, aby nazwy plików zaczynały się od wielkiej litery
id musi zaczynać się z małej litery lub podkreślnika i nie może zawierać znaków innych niż litery, liczby i podkreślniki
Należy unikać spacji i innych pustych miejsc w kodzie, bo to może uniemożliwić przetworzenie kodu. Podany tu kod dla przejrzystości (hierarchia obiektów) zawiera puste miejsca, ale po skopiowaniu go do edytora nie będzie działał, zatem usuwamy wszelkie spacje. Optymalnie, aby każda linia kodu była dosunięta do brzegu edytora. Najlepiej więc, żeby w edytorze powyższy kod wyglądał tak:
import QtQuick 1.0
Rectangle
{
width: 500
height: 500
Text
{
anchors.centerIn: parent
text: “Witaj świecie!”
}
MouseArea
{
anchors.fill: parent
onClicked:
{
Qt.quit();
}
}
}
Kluczową kwestią jest poznanie filozofii tego języka na podstawie tego fragmentu. Na początek warto powiedzieć, że typ = obiekt = komponent = element. Traktuję te nazwy jako synonimy.
Po pierwsze trzeba importować obiekty, które potrzebujemy w danej aplikacji. Większość aplikacji będzie importować wbudowane typy QML (jak np. Rectangle, Item, Image itp.), które są dostarczane razem z frameworkiem Qt. Używamy dla tego celu słowa kluczowego import:
import QtQuick 1.0
Lub ewentualnie możemy napisać:
import Qt 4.7
Czyli importujemy całe środowisko Qt. Obie linie są równoważne. Trzeba tylko pamiętać, że linia ta ma się pojawić na samym początku kodu. Potrzebne typy QML są właściwie zawarte w QtQuick.
Kod QML dzieli się na komponenty, a rootem (elementem bazowym) każdego programu może być obiekt Rectangle.
Rectangle {
id: baza
width: 500; height: 300
color: “lightgreen”
}
Jest to podstawa aplikacji w QML. W ramach tego pojawiają się inne komponenty. Taki Rectangle ma swoje własności takie jak width, height, color itd, które nadają kształ i kolor temu komponentowi. Komponent ten ma też inne własności takie jak x i y, ale nie wpisując ich i zarazem nie podając ich wartości, pozwalamy na przyjęcie domyślnych wartości. Wpisujemy tylko te własności, jakie chcemy. Umieszczenie własności może być w schemacie:
Element {własność1: wartość; własność2: wartość, własność3: wartość}
albo:
Element {
własność1: wartość
własność2: wartość
własność3: wartość
}
Zamiast Rectangle może też być komponent Item. Jednak ten ostatni nie ma kształtu i barwy, a jest tylko zwykłym kontenerem na inne komponenty (jego dzieci). Dlatego Rectangle jest lepszy, zwłaszcza dla początkujących, bo jest widoczny. Obiekty QML są określane za pomocą ich typu, a potem jest para nawiasów blokowych. Taki typ obiektu jest zawsze z dużej litery. Tutaj mamy do dyspozycji dwa obiekty bazowe: Rectangle i Image. Między nawiasami blokowymi obiektu możemy zawrzeć informacje o danym obiekcie takie jak jego własności (properties).
I tak w naszym Rectangle zawieramy komponent Text, który również ma swoje własności jak text, czyli tekst do wyświetlenia, i oczywiście width, height, czyli też kształt i wielkość.
Element Text
Text {
id: tekst_ap
text: “Witaj świecie!”
y: 50
anchors.horizontalCenter: page.horizontalCenter
font.pointSize: 30; font.bold: true
}
Element Text jest dzieckiem Rectangle. Tutaj ma własność y, która pokazuje pozycję tekstu w pionie, wertykalną. Wartością tu jest 30 pikseli, licząc od góry jego rodzica. Natomiast x pokazywałby pozycję tekstu w poziomie, horyzontalną. Ale tutaj nie podajemy. Schemat jest taki:
własność: wartość
własność anchors.horizontalCenter odnosi się do poziomego wycentrowania elementu w rodzicu, czyli Rectangle o id zw. baza. Do czcionki odnoszą się takie własności jak font.pointSize i font.bold, font.italic. Używamy tu notacji kropki (dot notation). To jest jak gdyby schemat:
własność.podwłasność: wartość
Właściowość może mieć kilka podwłasności i trzeba je podawać w takiej kolejności, co jest pożyczką z JavaScript.
Mogą zatem też być własności związane z rozmiarem czcionki, jej kolorem i rodziną, do której należy, co jest pokazane w tym kodzie.
Ale na przykład własność anchors (spotykana też w wielu innych komponentach) odpowiada za to, jak komponent będzie łączony do innych elementów. Jest to tzw. Anchor-Based Layout, czyli struktura oparta o zakotwiczenie względem innego elementu. Ważne jest, aby wiedzieć, że QML działa na podstawie relacji rodzic-dziecko. Możemy łączyć dziecko do rodzica i dwa komponenty równoważne właśnie przy pomocy anchors.
import QtQuick 1.0
Rectangle {
id: baza
width: 500; height: 300
color: “lightgreen”
Text {
id: tekst_ap
text: “Witaj świecie!”
y: 50
anchors.horizontalCenter: page.horizontalCenter
font.pointSize: 30; font.bold: true
}
}
Tak wygląda cały plik. Warto zwrócić na zagnieżdżenie tych dwóch elementów, czyli umiejscowienie nawiasów blokowych ({}). One to sprawiają, że taki element z własnościami to jedna instrukcja do wykonania i przy okazji podają granicę zasięgu tych zmiennych.
Teraz przyjrzymy się drugiemu przykładowi:
import QtQuick 1.0
Rectangle {
id: podst_obraz
width: 300
height: 300
color: “yellow”
Image {
id: obraz
source: “obrazy/zachód.jpg”
anchors.centerIn: parent
x: canvas.height / 5
}
}
Obiekt Image ma własność zw. source, dla której przydzielamy wartość obrazy/zachód.jpg.
Trzeba jeszcze wspomnieć o możliwości dodawania komentarzy między /* a */ i po //, co jest znane z JavaScript.
//To jest komentarz
/* To jest komentarz */
Następny kod:
import QtQuick 1.0
Rectangle
{
id: root_elem
width: 500
height: 500
Text
{
id: tekst_ap
anchors.centerIn: parent
text: “Witaj świecie!”
}
MouseArea
{
id: mysz_ap
anchors.fill: parent
onClicked:
{
Qt.quit();
}
}
}
I tak np. anchors.centerIn: parent mówi, że wyświetlany tekst będzie wycentrowany w elemencie rodzica i to będzie działać, nawet jeśli rozmiar rodzica będzie zmieniany. Mamy więc tu znowu własność anchors, czyli zakotwiczenie, potem jest znany nam już znak łączący kropki (znany też w innych językach), następnie jakie ma być to zakotwiczenie (centerIn), czyli wycentrowany w rodzicu (Rectangle), dwukropek oddzielający własność od wartości i ostatecznie widzimy wartość, czyli rodzic (parent), którym tu jest Rectangle.
Drugim składnikiem jest MouseArea, który pozwala nam obsługiwać zdarzenia kliknięcia jak np. pojedyncze, podwójne, przytrzymanie i zwolnienie klawisza itp. I przez złapanie danego zdarzenia możemy coś zrobić w programie. Tutaj akurat przez kliknięcie zamykamy aplikację. Bo kliknięciem wywołujemy funkcję Qt.quit(). Odpowiada ona za zamknięcie aplikacji. Odpowiada za to zdarzenie onClicked, czyli przy kliknięciu zamknij aplikację.
MouseArea jest zakotwiczona w rodzicu (pośrednio przez obiekt MouseArea), którym jest też Rectangle. Jak widać MouseArea całkowicie wypełnia przestrzeń rodzica, więc możemy kliknąć gdziekolwiek w tym obszarze, a aplikacja się zamknie.
Obsługiwacze (handlers) sygnałów Qt zw. handlersSignal pozwalają na działania w odpowiedzi na zdarzenie, każde zdarzenie (nie tylko naciśnięcie klawisza myszy). Np. Element MouseArea ma obsługiwacze sygnałów dla obsłużenia naciśnięcia, zwolnienia i kliknięcia myszy. Tutaj np. Mamy zwolnienie klawisza:
MouseArea {
onPressed: console.log(“klawisz myszy został naciśnięty”)
}
Przy zwolnieniu klawisza mamy log w wierszu polecenia (konsoli), że klawisz myszy został naciśnięty).
Wszystkie obsługiwacze sygnałów zaczynają się wyrazem “on”.
Jednak kod QML ma jeszcze jeden składnik, bez którego teoretycznie może istnieć, ale w praktyce jest wręcz nieodzowny. Jest to id. W QML możemy wywołać wiele własności z bieżącego obiektu lub jakiegokolwiek innego, używając do tego id. Nie mogą one się powtarzać w aplikacji i ich nazwy powinny mieć charakter praktyczny i zrozumiały. Dzięki id możliwe jest odwołanie do innych obiektów. Bowiem w relacji rodzic-dziecko musi być sposób odwołania się. Dziecko odwołuje się do rodzica przez słowo kluczowe parent. I nie ma problemu. Jednak kiedy mamy zakotwiczyć dwoje dzieci, będących na tym samym poziomie znaczenia, sposobem na to jest użycie własności id. To samo odnosi się do skryptów (np. JavaScript), które też muszą się odwoływać do poszczególnych elementów kodu QML.
W poniższym przykładzie pierwszy element Rectangle ma id zw. myRect. Drugi element Rectangle określa swoją własną szerokość (width) przed odniesienie się do szerokości (width) pierwszego elementu myRect.width, co oznacza, że to będzie miało taką samą szerokość jak pierwszy element Rectangle.
Item {
Rectangle {
id: myRect
width: 100
height: 100
}
Rectangle {
width: myRect.width
height: 200
}
}
Warto dodać tu, że oba obiekty Rectangle są ze sobą powiązane w inny sposób – oba są w kontenerze zw. Item. Dzięki temu kod jest spójny i możliwe jest odwołanie przez id.
Wracamy do naszej aplikacji Kato.qml. Możemy taką aplikację nieco skomplikować dla pokazania działania komponentów aplikacji i ich własności. Nasz plik Kato.qml teraz wygląda tak:
import QtQuick 1.0
Rectangle
{
id: root_elem
width: 500
height: 500
color: “yellow”
border.color: “white”
Text
{
id: tekst_ap
anchors.centerIn: parent
font.pointSize: 24; font.bold: true; font.italic: true
text: “Witaj świecie!”
color: “blue”
width: 36
height: 36
}
Rectangle
{
id: kszt_szary
x: 207
y: 105
width: 41
height: 41
radius: width/1
color: “gray”
}
Rectangle
{
id: kszt_zielony
x: 142
y: 105
width: 41
height: 41
radius: width/2
color: “green”
}
Rectangle
{
id: kszt_czerwony
x: 31
y: 97
width: 41
height: 41
radius: width/3
color: “red”
}
Rectangle
{
id: kszt_kosc
x: 256
y: 105
width: 41
height: 41
radius: width/4
color: “ivory”
}
Rectangle
{
id: kszt_brazowy
x: 31
y: 249
width: 41
height: 41
radius: width/10
color: “brown”
}
Rectangle
{
id: kszt_czarny
x: 290
y: 290
width: 41
height: 41
radius: width/100
color: “black”
}
MouseArea
{
id: mysz_ap
anchors.fill: parent
onClicked:
{
Qt.quit();
}
}
}
własność radius (czyli promień) jest dodane do Rectangle, aby zaokrąglić rogi kwadratu.
radius: width/2
Używamy więc szerokości (width) elementu Rectangle i dzielimy ją na dwa, co tworzy koło. Inna liczba tworzy inny kształt na bazie kwadratu. Widzimy te elementy na powyższym obrazku. Mamy tutaj kwadraciki z różnym stopniem zaokrąglenia rogów aż do utworzenia kółka.
Teraz czas jeszcze bardziej skomplikować naszą aplikację przez dodanie nie tylko własności (properties), ale i sygnałów (signals) oraz animacji za pomocą stanów (states) i transformacji (transitions).
Po pierwsze trzeba zauważyć, że pewien kod w naszej aplikacji Kato.qml się powtarza, a więc element Rectangle (z takim samym zestawem własności, chociaż innymi wartościami):
Rectangle{
id: kszt_zielony
x: 271
y: 72
width: 63
height: 63
radius: width/2
color: “green”
}
Rectangle
{
id: kszt_czarny
x: 290
y: 290
width: 41
height: 41
radius: width/100
color: “black”
}
Widzimy zatem, że wszystkie Rectangle mają te same własności, tylko ich wartości zmieniają się. Można dzielić taki komponent Rectangle (jak każdy inny obiekt) między aplikacjami. Ale o tym w następnym rozdziale.
3/ Aplikacje wieloplikowe
Aby zmniejszyć ilość kodu w dużych aplikacjach, można podzielić jej kod na kilka plików. Część z tych plików może być komponentami dla wielokrotnego użytkowania. To z kolei prowadzi do redukcji ilości kodu i nakładu pracy. Jeśli pliki QML dzielą ten sam folder, automatycznie rozpoznają się i oddziaływują na siebie.
Tworzymy nową aplikację w oddzielnym folderze. Dodajemy tu najpierw plik Kształt.qml z kodem:
import QtQuick 1.0
Rectangle{
id: baza
property int pozycjaX: 0
property int pozycjaY: 0
property int rozmiar: 200
property string wybrKolor: “black”
x: pozycjaX
y: pozycjaY
width: rozmiar
height: rozmiar
radius: rozmiar/2
color: wybrKolor
}
Rezultatem powyższego tego kodu będzie:
Słowo kluczowe property pozwala nam mieć ogólne własności w danym komponencie, które mogą być użyte w całej aplikacji i będą dostępne z innego pliku. W tym wypadku nadajemy stałe wartości tym szczególnym własnościom. Są one domyślne, jeśli nie nadamy innych, które nadpiszą te domyślne. Schemat tego jest taki:
property typ_zmiennej nazwa_zmiennej: wartość
Pod spodem musi być przyporządkowanie tej zmiennej do wbudowanego elementu języka QML, czyli tutaj mamy obiekt Rectangle i odwołujemy się do jego elementów wbudowanych. Schemat tego jest taki:
własność_obiektu: nazwa_zmiennej
W powyższym kodzie mamy więc:
property int pozycjaX: 0
x: pozycjaX
Dzięki temu w innym pliku QML (pliku kółka, którego tworzymy w aplikacji np. Ol.qml) możemy odwołać się do tej zmiennej i nadajemy jej wartość. W kodzie Ol.qml mamy tak:
pozycjaX: 20
Na podstawie wzoru w Kształt.qml mamy odwołanie pozycjaX do własności x, co sprawia, że ta własność w pliku Ol.qml przyjmuje wartość 20.
Piszemy aplikację główną, która spaja wszystkie te pliki. Kod tej naszej aplikacji Cycero.qml wygląda tak:
import QtQuick 1.0
Rectangle {
id: baza
width: 400
height: 400
color: “darkgreen”
Kształt {
id: wzor_circle
}
MouseArea {
id: mysz_ap
anchors.fill: parent
onClicked: {
Qt.quit();
}
}
}
Otrzymujemy wtedy :
Plik ten zawiera trzy typy: Rectangle, będący podstawą całej aplikacji, MouseArea, do obsługiwania zdarzenia kliknięcia i wywołania funkcji kończącej aplikację Qt.quit(), Circle, rysujący kółka w aplikacji. Warto zauważyć, żw obiekt Circle ma tylko jeden element: id. Nie ma innych własności, które byłyby widoczne w aplikacji. Ale dzięki temu id ma powiązanie z obiektami Circle w aplikacjach Ol.qml (patrz niżej). Możemy utworzyć wiele takich plików, jeśli chcemy otrzymać wiele różnych kółek w naszej aplikacji bazowej.
Mamy dostęp do tych własności (properties) z zewnątrz przez słowo property. Kształt.qml zawiera wzór, na którym budowane są kółka, występujące w aplikacji Cycero.qml. Każde kółko to oddzielny plik, który korzysta ze wzoru Kształt.qml. Np. Chcemy małe kółko o średnicy 100 w niebieskim kolorze i pozycji 20 na 150. Piszemy więc następujący kod w Ol.qml:
import QtQuick 1.0
Rectangle {
id: baza
width: 400
height: 400
color: “darkgreen”
Kształt {
id: wzor_circle
wybrKolor: “blue”
pozycjaX: 20
pozycjaY: 150
rozmiar: 100
}
MouseArea {
id: mysz_ap
anchors.fill: parent
onClicked: {
Qt.quit();
}
}
}
Te własności w tym wypadku są czymś więcej niż zmiennymi. Te zmienne są dostępne dla pewnych własności, aby je łatwo zmieniać (jak size jest użyte dla 3 własności) i z zewnątrz (jak robiliśmy, kiedy integrowaliśmy circle w głównej aplikacji). Tutaj ustawiliśmy domyślne wartości dla tych własności. Lepiej to zrobić, chociaż nie ma przymusu.
Ostatecznie kody w trzech powyższych plikach będą wyglądać tak:
Cycero.qml (aplikacja)
import QtQuick 1.0
Rectangle {
id: baza
width: 400
height: 400
color: “darkgreen”
Kształt {
id: wzor_circle
}
Ol {
id: kol_nieb
}
MouseArea {
id: mysz_ap
anchors.fill: parent
onClicked: {
Qt.quit();
}
}
}
Kształt.qml (plik wzorca)
import QtQuick 1.0
Rectangle{
id: baza
property int pozycjaX: 0
property int pozycjaY: 0
property int rozmiar: 200
property string wybrKolor: “yellow”
x: pozycjaX
y: pozycjaY
width: rozmiar
height: rozmiar
radius: rozmiar/2
color: wybrKolor
}
Ol.qml (plik modyfikujący)
import QtQuick 1.0
Rectangle {
id: baza
width: 400
height: 400
color: “darkgreen”
Kształt {
id: wzor_circle
wybrKolor: “blue”
pozycjaX: 20
pozycjaY: 150
rozmiar: 100
}
MouseArea {
id: mysz_ap
anchors.fill: parent
onClicked: {
Qt.quit();
}
}
}
Co daje w wyniku:
Widzimy wyraźnie, że kółko żółte zostało zastąpione przez kółko niebieskie. Zmienił się nie tylko kolor, ale i rozmiar oraz jego położenie. Tło zostało takie same, bo modyfikowaliśmy tylko obiekt kółka używając kodu w pliku Ol.qml.
Warto też zwrócić uwagę na kod aplikacji, gdzie są są takie oto fragmenty kodu:
Kształt {
id: wzor_circle
}
Ol {
id: kol_nieb
}
Obiekty te to nic innego jak nazwy plików bez rozszerzeń qml. Wystarczy je wpisać z nawiasami blokowamy (koniecznie!), nawet bez id, a wtedy wywołamy taki element, jak gdyby to był typ wbudowany.
Tak to działa. Aby zaprezentować działanie aplikacji wieloplikowej, tworzymy inną aplikację, chociaż wykorzystującą kod powyższych plików. Tym razem w jednym katalogu mamy następujące pliki:
Nero.qml (aplikacja – plik główny, łączący wszystkie poniższe komponenty w jedno)
import QtQuick 1.0
Rectangle {
id: baza_ap
width: 800
height: 600
color: “darkgreen”
Kształt{
id: baza_jeden
}
Kształt1 {
id: baza_cztery
}
Kształt2 {
id: baza_dwa
}
Kształt3 {
id: baza_trzy
}
MouseArea {
id: mysz_ap
anchors.centerIn: parent
onClicked: {
Qt.quit();
}
}
}
Kształt.qml (komponent wielokrotnego użytku)
import QtQuick 1.0
Rectangle{
id: baza_jeden
property int pozycjaX: 0
property int pozycjaY: 0
property int rozmiar: 200
property string wybrKolor: “yellow”
x: pozycjaX
y: pozycjaY
width: rozmiar
height: rozmiar
radius: rozmiar/2
color: wybrKolor
}
Kształt1.qml (komponent wielokrotnego użytku)
import QtQuick 1.0
Rectangle{
id: baza_wz
property int pozycjaX: 350
property int pozycjaY: 350
property int rozmiar: 250
property string wybrKolor: “brown”
x: pozycjaX
y: pozycjaY
width: rozmiar
height: rozmiar
radius: rozmiar/2
color: wybrKolor
}
Kształt2.qml (komponent wielokrotnego użytku)
import QtQuick 1.0
Rectangle{
id: baza_trzy
property int pozycjaX: 550
property int pozycjaY: 100
property int rozmiar: 150
property string wybrKolor: “darkblue”
x: pozycjaX
y: pozycjaY
width: rozmiar
height: rozmiar
radius: rozmiar/2
color: wybrKolor
}
Kształt3.qml (komponent wielokrotnego użytku)
import QtQuick 1.0
Rectangle{
id: baza_dwa
property int pozycjaX: 350
property int pozycjaY: 0
property int rozmiar: 50
property string wybrKolor: “darkgray”
x: pozycjaX
y: pozycjaY
width: rozmiar
height: rozmiar
radius: rozmiar/2
color: wybrKolor
}
Wszystkie te pliki tworzą jedną aplikację.
Schemat działania tych 4 plików jest taki:
Cycero.qml
———————————————————————————————–
Kształt.qml Kształt1.qml Kształt2.qml Kształ3.qml
W tym wypadku wydaje się to niewarte stosowania, bo trzeba utworzyć dużo plików, jak można to zawrzeć w jednym. Jednak kiedy mamy do czynienia w dużymi aplikacjami, to naprawdę zmniejsza kod, a tym samym pracę programisty.
Innym rodzajem własności (property) jest alias, który pozwala na odwołanie się do wartości pola w bardziej ogólny sposób. Nie ma już tu typu zmiennej, który w poprzednim przykładzie był string.
Linia wyglądała tak:
property string wybrKolor: “yellow”
Linia wygląda tak:
property alias wybrKolor: baza_sk.color
Oba kody działają tak samo. W drugim przykładzie mamy odwołanie do samego plików z kodami źródłowymi wzoru (Kształt.qml). A przez notację kropki mamy dostęp własności color. Nie mamy więc tu nadpisywania wartości we wzorze w pliku Kształt.qml (jak było w pierwszym przypadku), ale jest tu bezpośrednie odwołanie do własności i przejęcie konkretnego koloru (wartości). Symbolem tego pliku w tym odwołaniu jest jego nazwa id.
Możemy napisać kolejny plik Kształt.qml. Ten kod robi dokładnie to samo, co jego odpowiednik wyżej, w poprzednim pliku Kształt.qml:
import QtQuick 1.0
Rectangle{
id: baza_wz
property int pozycjaX: 0
property int pozycjaY: 0
property int rozmiar: 200
property alias wybrKolor: baza_wz.color
x: pozycjaX
y: pozycjaY
width: rozmiar
height: rozmiar
radius: rozmiar/2
}
Jest pewna niewielka różnica między tymi dwoma sposobami. Można ich używać zależnie od potrzeby.
Podsumujmy: w jednym katalogu mamy zatem następujące pliki:
Mateo.qml (aplikacja)
import QtQuick 1.0
Rectangle {
id: baza
width: 400
height: 400
color: “darkgreen”
Kształt {
id: wzor_circle
}
Ol {
id: kol_nieb
}
MouseArea {
id: mysz_ap
anchors.fill: parent
onClicked: {
Qt.quit();
}
}
}
Kształt.qml (plik wzorca)
import QtQuick 1.0
Rectangle{
id: baza_wz
property int pozycjaX: 0
property int pozycjaY: 0
property int rozmiar: 200
property alias wybrKolor: baza_wz.color
x: pozycjaX
y: pozycjaY
width: rozmiar
height: rozmiar
radius: rozmiar/2
}
Ol.qml (plik modyfikujący)
import QtQuick 1.0
Rectangle {
id: baza
width: 400
height: 400
color: “darkgreen”
Kształt {
id: wzor_circle
wybrKolor: “blue”
pozycjaX: 20
pozycjaY: 150
rozmiar: 100
}
MouseArea {
id: mysz_ap
anchors.fill: parent
onClicked: {
Qt.quit();
}
}
}
To daje w wyniku:
Widzimy więc, że działanie aplikacji Cycero.qml i Mateo.qml jest takie same. W obu przypadkach uzyskujemy komponent, którego możemy używać wielokrotnie. Np. Możemy stosować obiekt Repeater w komponencie Row dla stworzenia rzędu kółek. Oto ten kod, który wstawimy do pliku Ol.qml:
import QtQuick 1.0
Rectangle {
id: baza
width: 400
height: 400
color: “darkgreen”
Row {
id: szereg
Repeater {
id: powtarz
model: 5
Circle {
id: wzor_circle
selectedColor: “blue”
size: 360 / powtarz.model
}
}
}
MouseArea {
id: mysz_ap
anchors.centerIn: parent
onClicked: {
Qt.quit();
}
}
}
W takim wypadku tworzymy nową aplikację w oddzielnym katalogu i nazwiemy ją Liwio.qml:
Liwio.qml (aplikacja)
import QtQuick 1.0
Rectangle {
id: baza
width: 500
height: 500
color: “darkgreen”
Kształt {
id: wzor_circle
}
Ol {
id: kol_nieb
}
MouseArea {
id: mysz_ap
anchors.fill: parent
onClicked: {
Qt.quit();
}
}
}
Kształt.qml (plik wzorca)
import QtQuick 1.0
Rectangle{
id: baza
property int pozycjaX: 0
property int pozycjaY: 0
property int rozmiar: 200
property alias wybrKolor: baza.color
x: pozycjaX
y: pozycjaY
width: rozmiar
height: rozmiar
radius: rozmiar/2
}
Ol.qml (plik modyfikujący)
import QtQuick 1.0
Rectangle {
id: baza
width: 500
height: 500
color: “darkgreen”
Row {
id: szereg
Repeater {
id: powtarz
model: 10
Kształt {
id: wzor_circle
wybrKolor: “orange”
rozmiar: 360 / powtarz.model
}
}
}
MouseArea {
id: mysz_ap
anchors.centerIn: parent
onClicked: {
Qt.quit();
}
}
}
Co daje:
Można też używać wielokrotnie własności model z komponentu Repeater, aby dostosować rozmiar kółka do szerokości (width) okna odnośnie liczby, jakiej chcemy użyć.
4/ Aplikacje wieloplikowe dynamiczne
Inny przykład aplikacji wieloplikowej będzie bardziej skomplikowany, bo wprowadzimy tu dynamikę jej elementów. Na początek jednak trochę terminologii.
Wiązanie własności (property binding) określa wartość własności w sposób deklaracyjny. Wartość tej własności jest automatycznie uaktualniana, jeśli inne własności lub wartości danych się zmieniają. Wiązania własności są tworzone całkowicie w QML, gdy samym własnościom są przydzielane wyrażenia JavaScript. Ten kod QML używa dwóch wiązań własności dla połączenia rozmiaru obiektu Rectangle (width i height) z rozmiarem obiektu otherItem (width i height). Istotna jest tu znana nam notacja kropki. Dzięki temu mamy dostęp do własności (i ich wartości) przez typ otherItem.
Rectangle {
width:otherItem.width
height:otherItem.height
}
QML rozszerza możliwości JavaScript, więc jakiekolwiek ważne wyrażenie JavaScript może być użyte jako wiązanie własności, nie tylko obiekty QML. Wiązania mogą mieć dostęp nie tylko do własności obiektu, ale też wywoływać funkcje i nawet używać wbudowanych obiektów JavaScript jak Date i Math. Np.:
Rectangle {
function calculateMyHeight() {
return Math.max(otherItem.height, thirdItem.height);
}
anchors.centerIn: parent
width: Math.min(otherItem.width, 10)
height: calculateMyHeight()
color: { if (width > 10) “blue”; else “red” }
}
Trzeba przyjrzeć się temu kodowi dokładnie.
function calculateMyHeight() {
return Math.max(otherItem.height, thirdItem.height);
}
Najpierw mamy funkcję (słowo kluczowe function) zw. CalculateMyHeight, która oblicza (i zwraca) maksymalną wartość własności height dla dwóch obiektów: otherItem i thirdItem. Tu mamy też tzw. Notację kropki.
width: Math.min(otherItem.width, 10)
height: calculateMyHeight()
Teraz mamy znane nam własności typów QML: width i height. W pierwszej własności mamy wartość powstałą z obliczenia minimalnej wartości z dwóch argumentów: otherItem.height i thirdItem.height. Znowu mamy notację kropki. W drugiej zaś własności mamy wartość powstałą z obliczenia powyższej funkcji.
color: { if (width > 10) “blue”; else “red” }
}
Tu jest też znana własność QML: color. Jednak tutaj nie mamy podanej wprost wartości np. “yellow”, ale mamy typowe wyrażenie warunkowe z if i else. Czyli własność color może przyjąć dwie wartości: “blue” lub “red”, zależnie od wartości warunku. A ten warunek mówi, że jeśli własność width będzie większa od 10, to własność color przyjmie wartość “blue”, ale jeśli własność width będzie mniejsza lub równa 10, to własność color przyjmie wartość “red”.
Stany (states) są mechanizmem łączenia zmian z własnościami w semantyczną jednostkę. Np. Guzik ma dwa stany naciśnięty i nienaciśnięty, a książka adresowa może być tylko do odczytu lub dla edycji kontaktów. Każdy element ma domyślny stan podstawowy. Każdy inny stan jest opisany przez wylistowanie danej własności i nadanie im wartości tych elementów, które różnią się od domyślnego stanu podstawowego.
MyRect ma jako domyślny stan ustawiony na 0,0. Po przejściu do innego stanu, jest w 50,50. Kliknięcie w MouseArea zmienia stan z domyślnego na przesunięty (moved), przesuwając wizualnie prostokąt.
import QtQuick 1.0
Item {
id: baza
width: 400; height: 400
Rectangle {
id: kwadrat
width: 100; height: 100
color: “darkblue”
}
states: [
State {
name: “przesuw”
PropertyChanges {
target: kwadrat
x: 250
y: 250
}
}
]
MouseArea {
anchors.fill: parent
onClicked: baza.state = “przesuw”
}
}
Przyjrzymy się temu plikowi dokładnie.
Mamy tutaj za podstawę kontener Item, który zawiera obiekty: Rectangle, State i MouseArea. Element Rectangle jest tym przesuwanym prostokątem. Istotny jest tu kod dla typu State:
states: [
State {
name: “przesuw”
PropertyChanges {
target: kwadrat
x: 250
y: 250
}
}
]
Słowo kluczowe states obejmuje za pomocą nawiasów kwadratowych obiekty: State i PropertyChanges. W pierwszym mamy zapisany zmieniony stan (w stosunku do tego normalnego) we własności name z wartością “przesuw”. W drugim zaś mamy trzy własności, gdzie własność target zawiera informację o obiekcie, którego ma dotyczyć zmiana. Tutaj jest to nasz Rectangle i mamy do niego odwołanie przez jego id, czyli kwadrat. Dalsze własności to x i y, czyli osie przesunięcie tegoż elementu.
Istotny jest też trzeci obiekt:
MouseArea {
anchors.fill: parent
onClicked: baza.state = “przesuw”
}
Mamy tu własność onClicked, czyli obsługiwacz zdarzenia, którym jest kliknięcie myszą. Po takim zdarzeniu mamy przesył sygnału do baza, czyli elementu bazowego Item (będącego rodzicem dla pozostałych obiektów) i z tego punktu może on dotrzeć do innego typu tu zawartego, czyli State, a tam już mamy własność name z wartością “przesuw”. Następuje przesunięcie obiektu Rectangle.
Droga sygnału musi być taka, bo jak wiemy, kod QML ma postać hierarchicznego drzewa. Schemat powyższego pliku jest taki:
I poziom Item
———————————————————————————————-
II poziom Rectangle State PropertyChanges MouseArea
Kontakt obiektu z II poziomu z innym obiektem II poziomu tylko przez uprzednie wejście do poziomu I. Bowiem z I poziomu mamy dostęp do każdego obiektu z II poziomu.
W katalogu mamy więc taki plik Tito.qml:
import QtQuick 1.0
Item {
id: baza
width: 400
height: 400
Rectangle {
id: kwadrat
width: 100
height: 100
color: “darkblue”
}
states: [
State {
name: “przesuw”
PropertyChanges {
target: kwadrat
x: 250
y: 250
}
}
]
MouseArea {
anchors.fill: parent
onClicked: baza.state = “przesuw”
}
}
Stan normalny obiektu kwadrat:
Stan przesunięty obiektu kwadrat:
Dla celów estetycznych stosujemy animacje. Powyższy kod możemy zmodyfikować, aby zmiana stanów była płynna i elegancka. Zmiany stanu mogą być animowane przy użyciu Transitions. Np. Dodanie tego kodu do powyższego bazowego elementu Item animuje przejście ze stanu normalnego do stanu moved.
transitions: [
Transition {
NumberAnimation {
properties: “x,y”
duration: 500
}
}
]
Słowo kluczowe transitions obejmuje za pomocą nawiasów kwadratowych obiekt Transitions. On to zawier inny obiekt NumberAnimation. Zawiera on własności taką jak properties, z wartościami, które są nazwami własności, których to dotyczy, czyli tutaj x i y. Drugą własnością jest duration, czyli trwanie animacji. Wartością optymalną jest 500.
Mamy teraz:
I poziom Item
————————————————————————————
II poziom Rectangle Transitions State PropertyChanges MouseArea
W katalogu mamy teraz taki plik Roberto.qml:
import QtQuick 1.0
Item {
id: baza
width: 400
height: 400
Rectangle {
id: kwadrat
width: 100
height: 100
color: “darkblue”
}
transitions: [
Transition {
NumberAnimation {
properties: “x,y”
duration: 500
}
}
]
states: [
State {
name: “przesuw”
PropertyChanges {
target: kwadrat
x: 250
y: 250
}
}
]
MouseArea {
anchors.fill: parent
onClicked: baza.state = “przesuw”
}
}
Stan normalny obiektu kwadrat:
Stan przesuwany obiektu kwadrat (animacja):
Stan przesunięty obiektu kwadrat:
Animacje w QML są robione przez animowanie własności obiektów. własności typu real, int, color, rect, point, size i vector3d mogą wszystkie być animowane. QML wspiera trzy główne formy animacji: animacja podstawowej własności (basic property animation), przekształcenie (transitions) i zachowania się własności (property behaviors). Najprostszą jest metoda PropertyAnimation, która może animować wszystkie typy własności, o których pisałem wyżej.
Animacja podstawowej własności może być określona jako wartość źródłowa przy użyciu Animation na składni własności. To jest szczególnie użyteczne dla powtarzających się animacji. Poniżej mamy efekt odbijania się:
import QtQuick 1.0
Rectangle {
id: baza
width: 200; height: 700
color: “yellow”
Image {
id: obraz
source: “obrazy/lin.jpg”
x: 60 – obraz.width/2
y: 0
SequentialAnimation on y {
loops: Animation.Infinite
NumberAnimation {
to: 700 – obraz.height
easing.type: Easing.OutBounce
duration: 3000
}
PauseAnimation {
duration: 1000
}
NumberAnimation {
to: 0
easing.type: Easing.OutQuad
duration: 2000
}
}
}
}
W katalogu mam więc plik Eduardo.qml:
import QtQuick 1.0
Rectangle {
id: baza
width: 200; height: 700
color: “yellow”
Image {
id: obraz
source: “obrazy/lin.jpg”
x: 100 – obraz.width/2
y: 0
SequentialAnimation on y {
loops: Animation.Infinite
NumberAnimation {
to: 700 – obraz.height
easing.type: Easing.OutBounce
duration: 3000
}
PauseAnimation {
duration: 1000
}
NumberAnimation {
to: 0
easing.type: Easing.OutQuad
duration: 2000
}
}
}
}
W wyniku tego mamy:
Następnie:
I odbijanie:
Stwórzmy kolejną aplikację. Nazwijmy ją Bruto.qml. Ona to nam podsumuje najważniejsze cechy języka QML.
Możemy stworzyć paletę do wyboru sześciu komórek z różnymi kolorami. Aby nie pisać tego samego kodu wiele razy, tworzymy komponent Komórka.qml. Ten komponent dostarcza nam sposobu zdefiniowania nowego typu, którego możemy używać wielokrotnie w innych plikach QML, co już jest nam znane z przykładów powyższych. Taki komponent QML jest jak “czarne pudełko” (black box), bo widzimy go tylko przez jego dane wejściowe i dane wyjściowe, bez wiedzy, jak działa w środku. Oczywiście wiemy, co jest w środku, bo to jest przenośnia. Chodzi o to, że komponent wchodzi w interakcje ze światem zewnętrznym jedynie przez swoje własności, sygnały i funkcje i jest ogólnie definiowany w swoim własnym pliku QML.
Kod dla Komórka.qml:
import QtQuick 1.0
Item {
id: kontener
property alias cellColor: kwadrat.color
signal clicked(color cellColor)
width: 40
height: 25
Rectangle {
id: kwadrat
border.color: “white”
anchors.fill: parent
}
MouseArea {
anchors.fill: parent
onClicked: kontener.clicked(kontener.cellColor)
}
}
Zaczynamy omawianie od początku.
Item {
id: kontener
property alias cellColor: kwadrat.color
signal clicked(color cellColor)
width: 40
height: 25
Tutaj elementem typu root, czyli bazowym, jest Item. Przyjmijmy, że jest tak prymitywny, że służy w zasadzie tylko jako kontener dla innych elementów. Tutaj trzeba dodać, że mamy definicję koloru z notacją kropki do id kwadrat, co jest symbolem obiektu Rectangle. Mamy więc tu wiązanie własności.
property alias cellColor: kwadrat.color
Deklarujemy własność cellColor. Ta własność jest dostępna z zewnątrz naszego komponentu (słowo kluczowe property), co pozwala nam tworzyć egzemplarze komórek z różnymi kolorami. Ta własność jest po prostu aliasem (słowo kluczowe alias) do istniejącej własności, czyli koloru kwadratu, który tworzy daną komórkę.
signal clicked(color cellColor)
Chcemy, aby nasz komponent też miał sygnał (słowo kluczowe signal), który zwiemy clicked z parametrem cellColor w typie color. Będziemy używali sygnału dla zmiany koloru tekstu w głównym pliku QML.
Rectangle {
id: kwadrat
border.color: “white”
anchors.fill: parent
}
Nasz komponent cell jest w zasadzie kolorowym prostokątem z id zw. kwadrat. Bowiem reszta obiektów w pliku Komórka.qml jest niewidoczna.
własność anchors.fill jest wygodnym sposobem na ustawienie rozmiaru elementu. W tym wypadku ten prostokąt będzie miał taki sam rozmiar jak jego rodzic, czyli tutaj niewidoczny Item. Zobaczymy więc tylko jego dziecko, czyli Rectangle.
MouseArea {
anchors.fill: parent
onClicked: kontener.clicked(kontener.kolorKom)
}
Aby zmienić kolor tekstu, kiedy klikamy komórkę, tworzymy element MouseArea w tym samym rozmiarze jak jego rodzic.
MouseArea określa sygnał zw. clicked. Kiedy ten sygnał jest wywołany, chcemy emitować nasz sygnał clicked z kolorem jako parametrem. Wszystko to ma id zw. container, czyli odwołuje się do Item.
W naszym głównym pliku QML, używamy komponentu Cell dla stworzenia palety wyboru kolorów. Kod aplikacji Bruto.qml wygląda tak:
import QtQuick 1.0
Rectangle {
id: baza
width: 500
height: 200
color: “lightgray”
Text {
id: tekst_ap
text: “Witaj świecie!”
y: 30
anchors.horizontalCenter: baza.horizontalCenter
font.pointSize: 24
font.bold: true
}
Grid {
id: wbor_kol
x: 4; anchors.bottom: baza.bottom; anchors.bottomMargin: 4
rows: 2; columns: 3; spacing: 3
Komorka { kolorKom: “red”; onClicked: tekst_ap.color = kolorKom }
Komorka { kolorKom: “green”; onClicked: tekst_ap.color = kolorKom }
Komorka { kolorKom: “blue”; onClicked: tekst_ap.color = kolorKom }
Komorka { kolorKom: “yellow”; onClicked: tekst_ap.color = kolorKom }
Komorka { kolorKom: “steelblue”; onClicked: tekst_ap.color = kolorKom }
Komorka { kolorKom: “black”; onClicked: tekst_ap.color = kolorKom }
}
}
Tworzymy paletę kolorów przez wstawienie 6 komórek z różnymi kolorami w kratkę (zapewnia nam to obiekt Grid).
Komorka { kolorKom: “red”; onClicked: tekst_ap.color = kolorKom }
Kiedy sygnał clicked naszej konkretnej cell jest wywoływany, chcemy ustawić kolor tekstu na cellColor, przekazywany jako parametr. Możemy reagować na jakikolwiek sygnał naszego komponentu przez własność o nazwie onSignalName, czyli u nas onClicked, co możemy przetłumaczyć jako przy kliknięciu.
Stany (state) i przekształcenia (transition) dają dynamikę dla aplikacji. Tekst ma poruszać się na dół, obracać się i stawać się czerwonym, kiedy kliknięty.
Oto kod QML naszej aplikacji Bruto.qml:
import QtQuick 1.0
Rectangle {
id: baza
width: 500; height: 200
color: “lightgray”
Text {
id: tekst_ap
text: “Witaj świecie!”
y: 30
anchors.horizontalCenter: baza.horizontalCenter
font.pointSize: 24
font.bold: true
MouseArea {
id: mysz_ap
anchors.fill: parent
}
states: State {
name: “down”; when: mysz_ap.pressed == true
PropertyChanges { target: tekst_ap; y: 160; rotation: 180; color: “yellow” }
}
transitions: Transition {
from: “”; to: “down”; reversible: true
ParallelAnimation {
NumberAnimation { properties: “y,rotation”; duration: 500; easing.type: Easing.InOutQuad }
ColorAnimation { duration: 500 }
}
}
}
Grid {
id: wybor_kol
x: 4; anchors.bottom: baza.bottom; anchors.bottomMargin: 4
rows: 2; columns: 3; spacing: 3
Komorka { kolorKom: “red”; onClicked: tekst_ap.color = kolorKom }
Komorka { kolorKom: “green”; onClicked: tekst_ap.color = kolorKom }
Komorka { kolorKom: “blue”; onClicked: tekst_ap.color = kolorKom }
Komorka { kolorKom: “yellow”; onClicked: tekst_ap.color = kolorKom }
Komorka { kolorKom: “steelblue”; onClicked: tekst_ap.color = kolorKom }
Komorka { kolorKom: “black”; onClicked: tekst_ap.color = kolorKom }
}
}
Stan wygląda tak:
states: State {
name: “down”
when: mysz_ap.pressed == true
PropertyChanges {
target: tekst_ap
y: 160
rotation: 180
color: “red”
}
}
Najpierw tworzymy nowy stan down dla elementu text. Ten stan będzie aktywowany, kiedy MouseArea jest naciśnięte myszką i dezeaktywowane, kiedy jest puszczone. Stan down zawiera zestaw zmian własności w stosunku do naszego domyślnego stanu domyślnego (default state), czyli pozycje w stanie początkowym, określonym w QML. Szczególnie ustawiamy dla naszego celu (target) własności y na 160, rotation na 180 i color na red.
transitions: Transition {
from: “”; to: “down”; reversible: true
ParallelAnimation {
NumberAnimation { properties: “y,rotation”; duration: 500; easing.type: Easing.InOutQuad }
ColorAnimation { duration: 500 }
}
}
Chcemy animacji, aby tekst nie pojawiał się na dole od razu, ale płynnie, więc dodajemy transition między te dwa stany. W kodzie zapisujemy to tak:
from: “”; to: “down”
from i to określa te stany między którymi przekształcenie będzie działać. W tym wypadku chcemy przekształcenia z domyślnego stanu (“”) do stanu down.
Chcemy, aby to samo przekształcenie działało i na odwrót, kiedy idziemy od stanu down do domyślnego, więc ustawiamy odwracalność animacji (reversible) na true. To jest równoważnik dla napisania tych dwóch przekształceń osobno.
Element ParallelAnimation zapewnia, że te dwa typy animacji (number i color) zaczynają się w tym samym czasie. Można też uruchamiać to jeden za drugim przez użycie SequentialAnimation.
W katalogu mamy więc pliki:
Komorka.qml
import QtQuick 1.0
Item {
id: kontener
property alias kolorKom: kwadrat.color
signal clicked(color kolorKom)
width: 80/2
height: 50
Rectangle {
id: kwadrat
border.color: “white”
anchors.fill: parent
}
MouseArea {
anchors.fill: parent
onClicked: kontener.clicked(kontener.kolorKom)
}
}
Bruto.qml
import QtQuick 1.0
Rectangle {
id: baza
width: 700
height: 400
color: “lightgray”
Text {
id: tekst_ap
text: “Witaj świecie!”
y: 30
anchors.horizontalCenter: baza.horizontalCenter
font.pointSize: 30
font.bold: true
MouseArea {
id: mysz_ap
anchors.fill: parent
}
states: [ State {
name: “down”
when: mysz_ap.pressed == true
PropertyChanges {
target: tekst_ap
y: 160
rotation: 180
color: “yellow”
}
}
]
transitions: [ Transition {
from: “”
to: “down”
reversible: true
ParallelAnimation {
NumberAnimation {
properties: “y,rotation”
duration: 500
easing.type: Easing.InOutQuad
}
ColorAnimation {
duration: 500
}
}
}
]
}
Grid {
id: wybor_kol
x: 250
anchors.bottom: baza.bottom
anchors.bottomMargin: 20
rows: 2
columns: 4
spacing: 3
Komorka {
kolorKom: “red”
onClicked: tekst_ap.color = kolorKom
}
Komorka {
kolorKom: “green”
onClicked: tekst_ap.color = kolorKom
}
Komorka {
kolorKom: “blue”
onClicked: tekst_ap.color = kolorKom
}
Komorka {
kolorKom: “yellow”
onClicked: tekst_ap.color = kolorKom
}
Komorka {
kolorKom: “steelblue”
onClicked: tekst_ap.color = kolorKom
}
Komorka {
kolorKom: “black”
onClicked: tekst_ap.color = kolorKom
}
Komorka {
kolorKom: “orange”
onClicked: tekst_ap.color = kolorKom
}
Komorka {
kolorKom: “darkblue”
onClicked: tekst_ap.color = kolorKom
}
}
}
Mamy więc taki rezultat:
Naciśnięcie w którą kolwiek komórkę zmienia kolor tekstu. Np. Klikamy na niebieską komórkę i mamy:
I tak dalej. Natomiast po naciśnięciu tekstu klawiszem myszy i przytrzymaniu go, mamy taki rezultat:
Tekst został animowany i zmienił się jego kolor na żółty, który jest kolorem domyślnym w aplikacji Bruto.qml. Po puszczeniu klawisza mamy:
Wszystko więc wraca do punktu wyjścia.
5/ Skomplikowane aplikacje
Dla treningu mamy kolejną aplikację, także podsumowującą naszą wiedzę. Mamy tutaj zwykły guzik i dialpad z guzikami. Brak tu 0 i nnych guzików.
Tworzymy pusty projekt w QML w Qt Creator. Projekt zawiera następujące pliki:
Dialpad.qml (this is going to be my main, renders here a numeric keypad)
Guzik.qml (this contains the button code)
Guzik.qml:
import QtQuick 1.0
Rectangle {
id: baza
width: 60
height: 60;
radius: 10
border.color: “black”
border.width: 5
gradient: Gradient {
GradientStop {
position: 0.0
color: “blue”
}
GradientStop {
position: 0.33
color: “lightblue”
}
GradientStop {
position: 1.0
color: “darkblue”
}
}
states: [
State {
name: “up”
PropertyChanges {
target: down_anim
running: false
}
PropertyChanges {
target: up_anim
running: true
}
},
State {
name: “down”
PropertyChanges {
target: down_anim
running: true
}
PropertyChanges {
target: up_anim
running: false
}
}
]
transitions: [
Transition {
from: “up”
to: “down”
}
]
MouseArea {
anchors.fill: parent
onPressed: parent.state = “down”
onReleased: parent.state = “up”
}
SequentialAnimation {
id: down_anim
NumberAnimation {
target: baza
properties: “scale”
from: 1.0
to: 0.8
duration: 100
easing.type: “OutExpo”
}
NumberAnimation {
target: baza
properties: “opacity”
from: 1.0
to: 0.5
duration: 100
easing.type: “OutExpo”
}
running: false
}
SequentialAnimation {
id: up_anim
NumberAnimation {
target: baza
properties: “opacity”
from: 0.5
to: 1.0
duration: 10
easing.type: “OutExpo”
}
NumberAnimation {
target: baza
properties: “scale”
from: 0.8
to: 1.0
duration: 10
}
running: false
}
Text {
anchors.centerIn: parent
id: guzik_tekst
font.pointSize: 14
font.bold: true
color: “red”
text: “0”
}
property alias label: guzik_tekst.text
}
Awio.qml:
import QtQuick 1.0
Rectangle {
id: baza_ap
color: “green”
width: 600
height: 600
Text {
id: tekst_ap
font.pointSize: 14
font.bold: true
color: “red”
x: 150
text: “Wzór klawiatury do telefonu”
}
Rectangle {
id: baza
width: 300
height: 500
color: “navy”
anchors.centerIn: parent
transform: Rotation { origin.x: 30; origin.y: 30; axis { x: 0; y: 1; z: 0 } angle: -10 }
Rectangle {
id: klaw
y: 130
x: -45
Guzik {
x:100
y:100
label: “1”
}
Guzik {
x:100
y:100+64
label: “4”
}
Guzik {
x:100
y:100+64+64
label: “7”
}
Guzik {
x:100+64
y:100
label: “2”
}
Guzik {
x:100+64
y:100+64
label: “5”
}
Guzik {
x:100+64
y:100+64+64
label: “8”
}
Guzik {
x:100+64+64
y:100
label: “3”
}
Guzik {
x:100+64+64
y:100+64
label: “6”
}
Guzik {
x:100+64+64
y:100+64+64
label: “9”
}
Guzik {
x:100+64+64
y:100+64+64+64
label: “#”
}
Guzik {
x:100+64
y:100+64+64+64
label: “0”
}
Guzik {
x:100
y:100+64+64+64
label: “*”
}
Rectangle {
id: ekran
width: 270
height: 200
color: “gray”
y: -120
x: 60
}
Rectangle {
id: bok
width: 20
height: 500
color: “black”
y: -130
x: 345
transform: Rotation { origin.x: 0; origin.y: 0; axis { x: 0; y: 0; z: 0 } angle: -10 }
}
Rectangle {
id: góra
width: 320
height: 10
color: “black”
y: -135
x: 45
}
}
}
}
To daje nam:
Można to połączyć z kodem C++ dla wykonywania obliczeń, bo na razie to tylko dekoracja.
Inna aplikacja. Tym razem to jest kalkulator. Mamy plik Mauro.qml:
import Qt 4.7
Rectangle {
property alias operation: label.text
property bool toggable: false
property bool toggled: false
signal clicked
id: button; width: 50; height: 30
border.color: palette.mid
radius: 6
gradient: Gradient {
GradientStop { id: gradientStop1; position: 0.0; color: Qt.lighter(palette.button) }
GradientStop { id: gradientStop2; position: 1.0; color: palette.button }
}
Text { id: label; anchors.centerIn: parent; color: palette.buttonText }
MouseArea {
id: clickRegion
anchors.fill: parent
onClicked: {
doOp(operation);
button.clicked();
if (!button.toggable) return;
button.toggled ? button.toggled = false : button.toggled = true
}
}
states: [
State {
name: “Pressed”; when: clickRegion.pressed == true
PropertyChanges { target: gradientStop1; color: palette.dark }
PropertyChanges { target: gradientStop2; color: palette.button }
},
State {
name: “Toggled”; when: button.toggled == true
PropertyChanges { target: gradientStop1; color: palette.dark }
PropertyChanges { target: gradientStop2; color: palette.button }
}
]
}
Przeanalizujmy kod.
Inna aplikacja. Tym razem to jest kalkulator. Mamy plik Mauro.qml.
import Qt 4.7
Na początek formułka importująca wbudowane typy.
Rectangle {
property alias operation: label.text
Linijka property button.operation jest wystawiona na zewnątrz i jest połączona z własnością (property) wewnętrznej pozycji label.text.
property bool toggable: false
Przełączające zachowanie guzika, domyślnie nie jest przełączający. własność guzika “Advanced Mode” jest ustawiona na true.
property bool toggled: false
własność do śledzenia stanu przełączającego guzika.
signal clicked
Chcemy, aby guzik miał sygnał zw. clicked.
id: button; width: 50; height: 30
border.color: palette.mid
border.color jest pozycją zgrupowaną we własności border. Palette daje dostęp do systemowego Qt color. np. dark, light, mid itd. (QML SystemPalette Element)
radius: 6
Ta własność decyduje o promieniu rogu w celu narysowania zaokrąglonego prostokąta.
gradient: Gradient {
GradientStop { id: gradientStop1; position: 0.0; color: Qt.lighter(palette.button) }
GradientStop { id: gradientStop2; position: 1.0; color: palette.button }
gradient jest używany dla wypełnienia prostokąta (rectangle). GradientStop1 jest początkiem kolorowania od górnej granicy, a gradientStop2 jest końcem kolorowania na dolnej granicy. Qt.lighter jest globalną funkcją zwracającą kolor jaśniejszy niż dany kolor. (Global funkction).
}
Text { id: label; anchors.centerIn: parent; color: palette.buttonText }
Tekst guzika jest umieszczany przy wprowadzeniu guzika i używa domyślnego systemowego buttonText.
MouseArea {
id: clickRegion
Wpis MouseArea umożliwia proste obsługiwanie myszy.
anchors.fill: parent
Obszar pokrywa cały guzik.
OnClicked: {
Obsługiwacz sygnału dla kliknięcia w MouseArea.
doOp(operation);
/*???*/
button.clicked();
Jest emitowany sygnał clicked rodzica.
if (!button.toggable) return;
Dla zachowania się przełączania guzika.
button.toggled ? button.toggled = false : button.toggled = true
}
}
states: [
State {
name: “Pressed”; when: clickRegion.pressed == true
PropertyChanges { target: gradientStop1; color: palette.dark }
PropertyChanges { target: gradientStop2; color: palette.button }
},
State {
name: “Toggled”; when: button.toggled == true
PropertyChanges { target: gradientStop1; color: palette.dark }
PropertyChanges { target: gradientStop2; color: palette.button }
}
]
}
Poza stanem domyślnym guzik ma inne stany: Pressed i Toggled. One mają swoje własne warunki dla wprowadzenia tego stanu. W tych dwóch stanach początkowy gradient zmienia się na palette.dark dla odróżnienia od domyślnego wyglądu.