Tutorial Qt Markup Language (QML)

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:

qml1

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();

}

}

}

qml2

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:

qml3

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 :

qml4

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:

qml5

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ę.

qml6

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:

qml7

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:

qml8

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:

qml9

Stan przesunięty obiektu kwadrat:

qml10

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:

qml11

Stan przesuwany obiektu kwadrat (animacja):

qml12

Stan przesunięty obiektu kwadrat:

qml13

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:

qml14

Następnie:

qml15

I odbijanie:

qml16

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.

qml17

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:

qml18

Naciśnięcie w którą kolwiek komórkę zmienia kolor tekstu. Np. Klikamy na niebieską komórkę i mamy:

qml19

I tak dalej. Natomiast po naciśnięciu tekstu klawiszem myszy i przytrzymaniu go, mamy taki rezultat:

qml20

Tekst został animowany i zmienił się jego kolor na żółty, który jest kolorem domyślnym w aplikacji Bruto.qml. Po puszczeniu klawisza mamy:

qml21

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:

qml22

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.

Leave a comment