KursyPoradnikiInspirujące DIYForum

Kurs Qt – #5 – Budowa interfejsów z Qt Quick i QML

Kurs Qt – #5 – Budowa interfejsów z Qt Quick i QML

W tej części zajmiemy się omówieniem QML oraz Qt Quick. Poznanie tych nowych mechanizmów ułatwi nam tworzenie estetycznych interfejsów.

Zaczniemy od napisania i przetestowania prostej aplikacji. Następnie omówimy na jej przykładzie QML i porównamy takie podejście z Qt Widgets.


W pierwszej części tego kursu Qt zbudowaliśmy interfejs z trzema przyciskami, pamiętacie? A teraz, bez zagłębiania się w szczegóły, wykonamy podobny interfejs, ale tym razem za pomocą nowej technologii od Qt, czyli Qt Quick i QML. Zaczynamy od razu od przykładu, a później wrócimy do omówienia tych mechanizmów. Standardowo tworzymy nowy projekt, wybierając opcję Qt Quick Application - Empty:

Tworzenie nowego projektu w Qt

Tworzenie nowego projektu w Qt

W rezultacie powinniśmy znaleźć się w trybie edycji pliku main.qml o mniej więcej takiej zawartości:

Podczas pisania tego poradnika korzystano z Qt 5.12.5 i Qt Creatora 4.10.2 – przykładowy zapis import QtQuick 2.12 oznacza, że importujemy konkretną wersję modułu Qt Quick (tu 2.12).

Jeśli korzystacie ze starszej wersji Qt, prawdopodobnie będziecie musieli zastosować starsze wersje modułów, np. 2.5. Skorzystajcie wtedy z podpowiadania składni, które wskaże aktualnie dostępne wersje. Gdy będziecie chcieli użyć niedostępnej wersji, zobaczycie w konsoli następującą informację:

Dodajmy teraz do aplikacji przycisk, który zostanie umieszczony centralnie pośrodku okna i będzie posiadał podświetlony napis Close. Po jego kliknięciu w konsoli zostanie wypisana informacja o tym fakcie, a po upływie 2 sekund aplikacja zamknie się automatycznie.

Zadanie to można zrealizować za pomocą stosunkowo prostego programu:

Czytając kod, powinniście bez żadnego problemu domyślić się, za co odpowiadają konkretne fragmenty. Uruchomcie program, aby sprawdzić, czy działa zgodnie z oczekiwaniami.

Przykładowy efekt działania programu

Przykładowy efekt działania programu

Czym jest QML oraz Qt Quick?

Po takim praktycznym wstępie warto wyjaśnić, czym tak właściwie jest QML i Qt Quick. Dobrym startem będzie lektura dokumentacji Qt. Fragment na temat QML:

QML is a declarative language that allows user interfaces to be described in terms of their visual components and how they interact and relate with one another. It is a highly readable language that was designed to enable components to be interconnected in a dynamic manner, and it allows components to be easily reused and customized within a user interface. Using the QtQuick module, designers (…) have the option of connecting these user interfaces to any back-end C++ libraries.

Fragment na temat Qt Quick:

Qt Quick is the standard library of types and functionality for QML. It includes visual types, interactive types, animations, models and views, particle effects and shader effects. A QML application developer can get access to all of that functionality with a single import statement.

Według mnie najistotniejsze zalety QML to ogromna łatwość i szybkość tworzenia nowoczesnych, ładnie prezentujących się interfejsów graficznych oraz możliwość połączenia go z dowolnymi klasami lub bibliotekami napisanymi w C++.

Podobnej sytuacji nie mogliśmy uzyskać podczas korzystania z Qt Widgets, gdzie interfejs i logika były implementowane w tym samym miejscu. QML daje nam możliwości, które wspólnie tworzą HTML, CSS i JavaScript. Tak, QML ma dużo wspólnego z JavaScriptem, np. cały silnik do jego uruchamiania. Co więcej, QML posiada m.in. takie mechanizmy jak Garbage Collector czy Just-in-time compilation.

Wróćmy do przykładu i ważnych elementów QML

Teraz, gdy mamy już podstawową wiedzę na temat wprowadzonych tu rozwiązań, możemy przejść do omówienia prostego przykładu z początku artykułu.

Hierarchia elementów w QML

Program napisany w QML tworzy hierarchię elementów – każdy zdefiniowany obiekt może stworzyć dowolną liczbę pochodnych obiektów (child objects), dla których staje się rodzicem (parent). W naszym przykładzie nadrzędnym obiektem jest typ Window. Tworzymy w nim dwa pochodne obiekty typu Timer i Button (aby użyć tego typu, musieliśmy zaimportować moduł QtQuick.Controls 2.12).

Właściwości obiektów w QML

Przyjrzymy się zawartości obiektu Button – text i highlighted to tzw. właściwości (properties). Możemy je porównać do zmiennych definiowanych wewnątrz klasy napisanej w C++, reprezentujących stan wewnętrzny obiektu tej klasy. W przypadku Button za pomocą text ustawiliśmy napis, który wyświetlany jest na przycisku, natomiast za pomocą highlighted podświetliliśmy nasz przycisk. Wszystkie dostępne właściwości tych typów znajdziemy w dokumentacji: Timer oraz Button.

Anchors, czyli pozycjonowanie w QML

Kolejną właściwością, ale nieco bardziej złożoną, jest anchors, czyli mechanizm geometrycznego łączenia obiektów – niezwykle ważna właściwość w QML. W przykładzie umieściliśmy przycisk w środku rodzica – czyli w środku Window. Po więcej informacji odsyłam do dokumentacji.

Ilustracja z dokumentacji Qt

Ilustracja z dokumentacji Qt

Mechanizm sygnałów i slotów w QML

Obsługa slotów w QML jest bardzo prosta. Sprawdzamy w dokumentacji, jakie zdefiniowane sygnały ma typ Button – w naszym przykładzie użyliśmy sygnału clicked(), jego obsługę definiujemy za pomocą połączenia on + NazwaSygnału, czyli u nas onClicked. Wielka litera w nazwie slotu jest bardzo istotna! W tym przykładzie w slocie uruchomiliśmy zegar i wypisaliśmy w konsoli informację o naciśnięciu przycisku:

Wyrażenia JavaScript w QML

Zauważcie, że oba typy, Button i Timer, obsługują sloty onClicked oraz onTriggered. Obsługa w jednym z nich jest zawarta między nawiasami klamrowymi, a w drugim bezpośrednio po dwukropku:

Obie formy są równoznaczne i poprawne. Nawiasy stosujemy, gdy chcemy wykonać pewien określony blok kodu, ale możemy ich również użyć przy pojedynczych wywołaniach. Poniżej znajdziecie (prawie) wszystkie przykłady, w jaki sposób można to zrobić:

Poniższy zapis jest także poprawny:

Bardzo ciekawym zastosowaniem JS w QML jest konwersja obiektu do formatu JSON lub stworzenie obiektu za pomocą definicji zawartej w formacie JSON. Załóżmy, że chcemy pewną strukturę danych przesłać dalej za pomocą formatu JSON – w tym celu możemy stworzyć obiekt z polami, które będą stanowić pary klucz–wartość. W programie operujemy wygodnie na danych za pomocą obiektu, natomiast gdy chcemy te dane przesłać, konwertujemy obiekt do JSON za pomocą JSON.stringify():

Atrybut ID w QML

Zauważcie, że jedynie obiekt typu Timer ma ustawiony atrybut id – możemy to rozumieć jako nazwę (symbol), dzięki której będziemy się odnosić do konkretnego obiektu z dowolnego miejsca w obrębie zakresu danego komponentu (jeden plik .qml).

Dzięki temu w obrębie zakresu Window możemy odnosić się do obiektu Timer za pomocą jego id. Co więcej, obiektom Window i Button też możemy nadać własne id (jeżeli musimy je wykorzystać).

Property Binding w QML

Porównaliśmy properties do zmiennych w klasie C++. Jednak w przypadku QML właściwości mają dużo potężniejsze możliwości – można powiedzieć, że wykorzystując Property Binding, mechanizm sygnałów i slotów działa automatycznie w tle. Dokumentacja mówi, że:

Property bindings are a core feature of QML that lets developers specify relationships between different object properties. When a property's dependencies change in value, the property is automatically updated according to the specified relationship.

Najlepiej pokazać to na przykładzie:

Działanie takiego programu będzie wyglądało następująco:

W przykładzie tym uzależniliśmy właściwość text przycisku od właściwości text pola tekstowego w taki sposób, że każda zmiana w polu tekstowym powoduje zmianę tekstu na przycisku. Relacje mogą być bardziej skomplikowane i zawierać warunki logiczne, np.:

Warto poeksperymentować z tą funkcjonalnością, bo daje ona ogromne możliwości. To tylko część opcji, które zapewnia nam QML (na poznanie kolejnych przyjdzie czas). Na przykład w C++ możemy zdefiniować właściwości, które będą reprezentować stan wewnętrzny obiektu. Właściwości te będziemy mogli zastosować w Property Binding, co pozwoli na automatyczne odświeżanie informacji.

Porównanie QML z Qt Widgets

Dla testu stworzyłem za pomocą Qt Widgets program realizujący te same wymagania co wcześniejszy przykład napisany w QML (kod obu projektów znajduje się w załączniku).

Działanie obu aplikacji

Działanie obu aplikacji

Dla Qt Widgets konieczne były 3 pliki (.h, .cpp, .ui), liczba linii (nie uwzględniając .ui) wyniosła ~80, w QML był to 1 plik o 30 liniach kodu. Jak można zauważyć, za pomocą QML osiągamy ten sam efekt w ponad dwa razy mniejszej objętości kodu (oczywiście liczba linii kodu nie jest żadną ważną miarą, ale w tym wypadku idea jest chyba dość jasna). Ponadto kod napisany w QML jest zdecydowanie bardziej prostszy do analizy niż ten napisany w C++.

Czy w takim razie QML nie ma słabych stron? Owszem, ma. Najważniejszą jest chyba brak możliwości wyłapywania części błędów na etapie kompilacji (jak w przypadku C++), która w QML przebiega bezpośrednio przed wykonaniem danego fragmentu kodu (JIT). O błędach dowiemy się dopiero w trakcie działania programu. Jednak możliwości, które daje QML, wygrywają z jego słabymi stronami.

Tryb Design i bonusowy przykład z oknem Dialog

Zauważcie, że dotychczas budowaliśmy interfejs „programowo” – każdy jego element był tworzony za pomocą linijek kodu. Interfejs możemy jednak budować też w trybie Design, podobnie jak robiliśmy to przy użyciu Qt Widgets.

Tryb Design (dla plików .qml lub .ui.qml) aktywujemy przez naciśniecie przycisku "Design" na pasku po lewej stronie (na zdjęciu poniżej jest on właśnie aktywny i podświetlony). Przed wejściem do tego trybu należy wybrać interesujący nas plik - klikając na niego dwukrotnie w drzewie projektu (otworzy to plik w trybie edycji tekstowej, co następnie pozwoli otworzyć plik w trybie Design).

Budowanie interfejsu w trybie Design

Budowanie interfejsu w trybie Design

Użycie trybu Design pozwala na znaczne przyspieszenie naszej pracy. Gotowe elementy i komponenty możemy przeciągać na płótno naszej aplikacji. Po wybraniu danego elementu mamy też dostęp do jego właściwości, które możemy dosłownie dowolnie „wyklikać” (w menu po prawej stronie).

Stwórzmy teraz nowy projekt – tym razem wybierzmy typ Qt Quick Application - Scroll. Następnie wybierzmy styl aplikacji Material Light. Projekty Scroll, Stack oraz Swipe stanowią szkielety pod dany typ aplikacji. Tworzenie nowego projektu przedstawia poniższe nagranie:

Na wcześniejszym zrzucie widać zawartość ekranu w trybie Design, która pojawi się po uruchomieniu naszego przykładu. Znajdziemy tam przycisk oraz kręcący się BusyIndicator. Po kliknięciu przycisku zobaczymy dostosowane przez nas okno typu Dialog z informacją o tym, że wystąpił błąd (którego numer będziemy za każdym razem losować). Zamknięcie okna z informacją nastąpi tylko wtedy, gdy użytkownik potwierdzi Zrozumiałem i kliknie OK – wygląda to tak:

Zamiana koloru akcentów interfejsu

Gdy uruchomicie poniższy kod u siebie to akcent Waszych kontrolek na interfejsie będzie koloru różowego (Material Pink) – u mnie akcent ma barwę indygo (Material Indigo). Taką zmianę można dokonać w qtquickcontrols2.conf. Wystarczy sprawdzić zawartość nowo powstałego pliku w zakładce Resources: qml.qrc/qtquickcontrols2.conf.

Ja zmieniłem nieco jego zawartość na taką, która znajduje się poniżej:

Istotna zmiana, jaką wprowadziłem, to odkomentowanie linii, w której zdefiniowany jest akcent naszej aplikacji – podałem, że ma to być kolor indygo.

Kod tej aplikacji jest nieco dłuższy, ale równie prosty co poprzedniej:

Zastosowałem tutaj typy Column i Row oraz ich połączenie – są to podstawowe elementy, dzięki którym możemy ułożyć komponenty interfejsu w odpowiednim porządku. W QML bardzo ważna jest kolejność definiowania obiektów, np. wewnątrz takich typów jak Column czy Row. Kolejność definiowania określa ich kolejność wyświetlania w takim kontenerze. Gdy zdefiniujemy np. dwa przyciski bezpośrednio w Window, to ten zdefiniowany jako drugi przysłoni nam pierwszy – jest to związane z właściwością z.

W typie Dialog dodałem zawartość w postaci CheckBoxa, Labela i DialogButtonBoxa, które zostały umieszczone w konfiguracji elementów Row i Column, tak aby stworzyły ładnie wyglądający interfejs. Wykorzystałem sygnał onAboutToShow() typu Dialog, aby odznaczyć CheckBoxa i wylosować nowy numer błędu. Ustawiłem też właściwości: modal: true oraz closePolicy: Dialog.NoAutoClose.

Istotny fragment znajduje się też w DialogButtonBoxie, gdzie zastosowałem Property Binding dla właściwości enabled, a na kliknięcie wywołuję metodę close() oraz emituję sygnał accepted(), który można byłoby wykorzystać gdzieś w aplikacji, np. gdybym miał przyciski Tak/Nie, to mógłbym emitować odpowiednie dla nich sygnały i wykorzystać je w logice. Ważniejsze fragmenty opisałem w komentarzach. Zachęcam do wprowadzania własnych zmian i testowania efektów.

Zadanie dodatkowe

Osoby, które chciałyby poznać lepiej ten temat, powinny zainteresować się poniższymi materiałami:

Podsumowanie

W tej części poznaliśmy nowy sposób na budowanie nowoczesnych interfejsów w Qt, a także możliwości QML i Qt Quick. Porównaliśmy Qt Widgets z QML, realizując te same wymagania.

Czy wpis był pomocny? Oceń go:

Średnia ocena 4.9 / 5. Głosów łącznie: 36

Nikt jeszcze nie głosował, bądź pierwszy!

Artykuł nie był pomocny? Jak możemy go poprawić? Wpisz swoje sugestie poniżej. Jeśli masz pytanie to zadaj je w komentarzu - ten formularz jest anonimowy, nie będziemy mogli Ci odpowiedzieć!

W następnej części zaznajomimy się dokładniej ze strukturą projektu oraz poznamy jedną z najlepszych cech QML, czyli połączymy interfejs użytkownika napisany w QML z logiką biznesową napisaną w C++. Poznamy też sposób na przesyłanie danych oraz obsługę mechanizmu sygnałów i slotów między QML a C++. Dowiemy się również, jak łatwo można wybrać styl graficzny aplikacji, np. Material, Imagine czy Universal, w celu nadania natywnego wyglądu aplikacji na docelowym urządzeniu.

Autor: Mateusz Patyk

Nawigacja kursu


O Autorze

Autorem tej serii wpisów jest Mateusz Patyk, który zawodowo zajmuje się programowaniem systemów wbudowanych oraz rozwijaniem aplikacji na desktopy i urządzenia mobilne. Jego głównymi obszarami zainteresowań są systemy sterowania, egzoszkielety i urządzenia do wspomagania chodu człowieka. Prywatnie miłośnik dobrego kina i gier strategicznych.

C, interfejs, programowanie, QML, qt

Trwa ładowanie komentarzy...