Kursy • Poradniki • Inspirujące DIY • Forum
Tematem tej serii jest pisanie aplikacji pozwalających na utworzenie połączenia pomiędzy panelem operatorskim (komputer PC, telefon z Androidem, komputer klasy SBC), a systemem wbudowanym (urządzenie z mikrokontrolerem). Nie będziemy skupiać się na tworzeniu interfejsów, oczywiście zajmiemy się tym tematem, ale tylko około 20-30% tej serii będzie poświęcone budowie interfejsu (frontend), reszta będzie dotyczyła tworzenia logiki działania aplikacji (backend).
Podczas tej serii zostaną poruszone takie kwestie jak:
- instalacja i konfiguracja Qt,
- budowa prostej aplikacji okienkowej w Qt,
- budowa aplikacji, która komunikuję się poprzez port szeregowy z mikrokontrolerem, wykorzystamy tutaj najpopularniejszą płytkę Arduino UNO (nie trzeba ograniczać się jednak tylko do Arduino, równie dobrze można wykorzystać STM32),
- konfiguracja Qt do budowania aplikacji na urządzenia mobilne z Androidem,
- przygotowanie aplikacji mobilnej, która wykorzysta wbudowany w telefon moduł Bluetooth do nawiązania połączenia z Arduino i HC-05.
Artykuły z tej serii przeznaczone są dla każdego, kto chce poznać podstawy Qt pod kątem budowy aplikacji desktopowych i mobilnych, które mają komunikować się z systemami wbudowanymi.
Nauka z tej serii wymaga jednak podstawowej znajomości C++, ponieważ nie będziemy zajmować się tutaj omawianiem składni samego języka.
Dlaczego Qt?
Qt to znacznie więcej niż tylko wieloplatformowy pakiet SDK - to strategia technologiczna, która pozwala szybko i ekonomicznie projektować, rozwijać, wdrażać i utrzymywać oprogramowanie, zapewniając jednocześnie bezproblemową obsługę wszystkich urządzeń - tak piszą o Qt jego twórcy.
Dlaczego ja wybrałem Qt?
Powód pierwszy: Qt daje mi nieograniczone możliwości do budowy cross-platformowych aplikacji, interfejsów graficznych lub paneli operatorskich. Napisałem aplikację na Windows, muszę ją zbudować na komputerze z Linuksem – nie ma problemu. Raz napisaną aplikację mogę bez większych problemów zbudować na Windowsie, Linuksie, na urządzeniu z Androidem lub iOS.
Prawdziwe jest więc stwierdzenie ze strony Qt: Code less, create more, deploy everywhere.
Powód drugi: Qt w wersji Open Source jest darmowe! Qt wydane jest na kilku licencjach w tym: LGPL3, GPL2 i GPLv3. Szczególnie interesująca jest ta pierwsza, ponieważ pozwala nam na zamknięcie źródła naszego oprogramowania, bez konieczności zakupu komercyjnej licencji. Czyli, możemy na tym zarabiać i jednocześnie chronić naszą pracę (oczywiście jeśli działamy zgodnie z warunkami licencji).
Instalacja i konfiguracja Qt
Instalator Qt pobieramy z oficjalnej strony Qt. Ważne jest, aby pobrać wersję open-source. Polecam obejrzeć poradnik producenta, który przeprowadzi nas przez proces instalacji i konfiguracji:
Podczas tej serii będziemy korzystać z systemu Windows (użytkownicy systemu Linux powinni zapoznać się z informacjami umieszczonymi na tej stronie).
Istotną częścią procesu instalacji jest wybranie odpowiedniej wersji Qt i narzędzi (patrz na wybrane opcje na poniższym zrzucie ekranu). Polecam wybranie najnowszej stabilnej wersji Qt i odznaczenie wszystkich narzędzi opisanych jako UWP, wystarczy nam jeden kompilator (zaznaczony na rysunku MinGW). Jednocześnie oszczędzi nam to sporo miejsca na dysku.
Gdyby, któreś z tych narzędzi było nam później potrzebne to możemy je w każdej chwili zainstalować korzystając z przygotowanego do tego celu narzędzia Qt (Qt Maintenance Tool).
Czasem, może pojawić się problem podczas pobierania plików instalacyjnych, wtedy najlepiej spróbować ponowić tę operację za chwilę.
Po udanej instalacji, możemy już uruchomić program Qt Creator – jest to nasze IDE, które zawiera edytor tekstu i narzędzia do tworzenia interfejsu graficznego aplikacji:
Pierwsza aplikacja w Qt
Rozpoczynamy od stworzenia nowego projektu:
Następnie wybieramy typ naszej aplikacji, w tym wypadku wybieramy aplikację Qt Widgets:
Następnie wybieramy nazwę i położenie naszego projektu. Istotne jest to, aby ścieżka projektu nie zawierała spacji! Unikniemy wtedy problemów z budowaniem aplikacji dla urządzeń z Androidem.
W kolejnym kroku wybieramy zestawy narzędzi dla naszych docelowych platform. Na razie wybieramy tylko platformę Desktop (zaznaczoną na czerwono). Później będziemy wykorzystywać także narzędzia dla platformy Android (zaznaczone na zielono). Zauważcie, że dostępne narzędzia odpowiadają tym, które wybraliśmy podczas instalacji (podczas swojej instalacji, z której widać tutaj zrzuty ekranu, nie odznaczyłem opcji zawierających w nazwie UWP).
Następnie musimy nazwać naszą domyślną główną klasę, zostawiamy proponowaną MainWindow. Będzie to nasza główna klasa, która zajmuje się obsługą interfejsu graficznego naszej aplikacji. Do niej będziemy też dołączać nasze własne klasy lub klasy Qt.
Następnie przechodzimy dalej. Gdy zakończymy proces konfiguracji, wtedy naszym oczom ukaże się edytor tekstowy, zobaczymy też drzewo stworzonego projektu.
Znajdziemy tam plik z rozszerzeniem .pro, jest to plik, który zawiera konfigurację projektu - są w nim informacje dla specjalnego preprocesora Qt - generalnie nie edytujemy tego pliku bez potrzeby, będziemy tam jedynie dodawać specjalne moduły Qt. Na liście znajdziemy tam też pliki z popularnymi rozszerzeniami .h i .cpp, gdzie będą się znajdować definicje i implementacje naszego programu.
Ostatnim rodzajem plików są pliki z rozszerzeniem .ui. Są to pliki, które korzystają z formatu XML i zawierają informacje o elementach (i ich właściwościach) naszego interfejsu graficznego.
Tego pliku nigdy nie edytujemy ręcznie, edytujemy go jedynie w trybie Design.
Tworzenie interfejsu aplikacji w Qt
Tryb Design uruchamia się domyślnie, gdy klikniemy 2x na plik mainwindow.ui, podgląd trybu Design widoczny jest poniżej. Tworzenie interfejsu jest bardzo intuicyjne i proste, polega na przeciąganiu elementów interfejsu (widgetów) z listy po lewej (obszar zaznaczony na zielono) i upuszczaniu ich na obszar głównego widgetu (centralWidget) aplikacji (obszar zaznaczony kolorem czerwonym).
Hierarchię obiektów (elementy i ich zależności) interfejsu znajdziemy w miejscu zaznaczonym na żółto. Ostatnia część okna, zaznaczona na niebiesko, pozwala na sprawdzenie właściwości elementu, który jest aktualnie wybrany.
Edycję interfejsu zaczynamy od usunięcia menuBar, mainToolBar i statusBar, nie będziemy ich używać:
Następnie dodamy do naszego interfejsu przycisk – robimy to przez przeciągnięcie elementu, który nazywa się Push Button. Dwukrotne kliknięcie w obszar nazwy przycisku spowoduje pojawienie się kursora edycji tekstu, zmieńmy nazwę przycisku z PushButton na Zamknij.
Po umieszczeniu elementu, możemy wprowadzić siatkę. W tym celu klikamy element centralWidget w Hierarchii obiektów (albo wybieramy go klikając w obszar naszej aplikacji). Następnie z menu wybieramy przycisk Rozmieść w siatce.
Siatka zapewni nam skalowanie okna aplikacji bez przemieszczania się elementów interfejsu. Jest to przydatne, gdy chcemy, aby interfejs wyglądał tak samo na różnych wyświetlaczach.
Teraz dodamy kolejne elementy interfejsu – Spacery. Jak sama nazwa wskazuje odpowiadają one za wolne miejsce na naszym interfejsie. Dodajemy je zgodnie z poniższym zrzutem ekranu:
Dobrą praktyką jest nazywanie obiektów w usystematyzowany sposób, ja robię to w ten sposób, że zostawiam człon pushButton (żeby podczas pisania kodu szybko znaleźć interesujący mnie element) i dodaje do jego nazwy, słowny opis jego funkcji (mój przycisk będzie zamykał aplikację) więc dodaję człon Close, uzyskując w ten sposób nazwę pushButtonClose - będzie to ID przycisku, którym będziemy się posługiwać w kodzie. Sposób nazwy obiektu został przedstawiony na powyższym zrzucie.
Teraz przechodzimy do zbudowania naszej aplikacji i jej uruchomienia. Proces ten jest bardzo prosty, wystarczy, że naciśniemy Ctrl + R lub wybierzemy odpowiedni przycisk:
Rozpocznie to etap kompilacji, a po jej zakończeniu uruchomi się aplikacja. Poniższy zrzut ekranu przedstawia widok uruchomionej aplikacji (obszar zaznaczony na czerwono). Obszar zaznaczony na zielono przedstawia z kolei komunikaty kompilatora, tam zobaczymy ewentualne błędy.
Spróbuj teraz powiększyć okno aplikacji! Co się dzieje z pozycją przycisku Zamknij, czy jego pozycja względem prawego dolnego rogu aplikacji ulega zmianie? Następnie wyłącz rozmieszczanie w siatce (przycisk obok Rozmieszczania w siatce) i znowu uruchom aplikację, sprawdź czy wtedy aplikacja skaluje się poprawnie. Po testach, przywróć umieszczanie w siatce.
Sygnały i sloty - podłączanie przycisków
Skoro mamy już pierwszy przycisk, to wypadałoby, aby jego wciśnięcie powodowało jakąś akcję. W tym celu skorzystamy z mechanizmu sygnałów i slotów Qt.
Mechanizm sygnałów i slotów jest jedną z funkcjonalności Qt, która wyróżnia je na tle podobnych projektów. Mechanizm ten pozwala na komunikację między różnymi obiektami bibliotek Qt, przy tym jest intuicyjny i prosty w użyciu. Ogólna zasada działania tego mechanizmu polega na wysyłaniu sygnału (na jakieś zdarzenie), który powoduje wykonanie zdefiniowanych w slocie akcji:
W praktyce możemy skorzystać z tego mechanizmu na 3 sposoby.
Pierwszy sposób wykorzystania mechanizmu sygnałów i slotów
Pierwszy sposób możemy zastosować dla sygnałów i slotów zdefiniowanych dla elementów interfejsu Qt. W tym celu, w trybie Design należy kliknąć w przycisk Modyfikuj sygnały:
Spowoduje to włączenie nowego, interaktywnego trybu łączenia sygnałów ze slotami. Musimy najechać kursorem na przycisk Zamknij, nacisnąć lewy przycisk myszy i przeciągnąć kursor w stronę obszaru aplikacji (jak na poniższym zrzucie ekranu).
Gdy puścimy przycisk myszki otworzy się nam okno dialogowe z wyborem opcji:
Wybieramy sygnał clicked(), zaznaczamy checkbox "Pokaż sygnały i sloty klasy Qwidget", a następnie wybieramy slot close(). Klikamy OK. Co teraz zrobiliśmy? Połączyliśmy sygnał kliknięcia przycisku ze slotem zamknięcia aplikacji. Teraz możemy uruchomić aplikację i przetestować działanie przycisku.
Drugi sposób wykorzystania mechanizmu sygnałów i slotów
Drugi sposób możemy zastosować dla sygnałów i slotów zdefiniowanych dla elementów interfejsu Qt, przy czym możemy zdefiniować własne akcje (w slocie). W tym celu stwórz kolejny przycisk, nazwij go OK, następnie kliknij na niego prawym przyciskiem myszy i wybierz opcję "Przejdź do slotu...".
Następnie spośród listy slotów wybierz clicked():
Czynność ta przeniesie nas do implementacji wybranego slotu – plik mainwindow.cpp. W tym miejscu musimy umieścić akcje, które mają się wykonać po wciśnięciu przycisku OK. Zatem do dzieła: pomiędzy klamrami wpisz następującą linijkę kodu:
qDebug() << "Wcisnąłeś przycisk OK";
Następnie (u góry pliku mainwindow.cpp) zaimportuj klasę QDebug dodając linijkę:
#include <QDebug>
Twój kod powinien wyglądać mniej więcej tak:
Klasa QDebug zapewnia strumień wyjściowy do debugowania. W naszej implementacji wciśnięcie przycisku spowoduje wyświetlenie w zakładce Komunikaty Aplikacji tekstu: "Wcisnąłeś przycisk OK".
Uruchom aplikację (tak jak poprzednio) i naciśnij przycisk OK, analizując wyjście strumienia w zakładce Komunikaty aplikacji, wynik powinien być następujący:
Zwróć uwagę jak nazywa się metoda tego slotu:
void MainWindow::on_pushButtonOK_clicked()
Możemy to rozumieć w ten sposób: na sygnał clicked() z obiektu pushButtonOK wykonuj akcje z ciała tej metody. Zauważ, że nazwa zawiera nazwę obiektu emitującego sygnał i nazwę sygnału. Jak pewnie się domyślasz, zdefiniowane sygnały i sloty można używać ręcznie przez zdefiniowanie nowych metod w klasie MainWindow. Jedyne co musisz zrobić to sprawdzić jakie sygnały i sloty posiadają dane obiekty (np. w Qt Creatorze jak poprzednio przy wyborze sygnałów/slotów lub w dokumentacji klas Qt) i na tej podstawie stworzyć nową metodę klasy MainWindow.
Przykładowo w pliku mainwindow.h pod klasyfikatorem private slots można dodać:
void on_pushButtonOK_pressed();
A w pliku mainwindow.cpp stworzyć odpowiednią implementację:
void MainWindow::on_pushButtonOK_pressed() {
qDebug() << "pressed";
//...
}
Trzeci sposób wykorzystania mechanizmu sygnałów i slotów
Trzeci sposób pozwala nam na wykorzystanie naszych sygnałów i slotów. Własne sygnały zdefiniujemy w późniejszych częściach tej serii, na tą chwilę zajmiemy się stworzeniem własnego slotu.
Dodaj kolejny przycisk i nazwij go Test, a nazwę tego obiektu zmień na pushButtonTest. Następnie w pliku mainwindow.h dodaj pod klasyfikatorem private slots linijkę:
void myCustomSlot();
Powinno to wyglądać mniej więcej w ten sposób:
Następnie stwórz implementację tego slotu w pliku mainwindow.cpp:
void MainWindow::myCustomSlot() {
qDebug() << "Wcisnales przycisk Test i wykorzystałeś mechanizm sygnałów i slotów";
}
Pozostaje jeszcze jedna czynność, którą wcześniej robiło za nas środowisko, czyli faktycznie łączenie sygnałów ze slotami. W konstruktorze MainWindow należy użyć metody klasy QObject - connect(). Klasa QObject jest klasą bazową dla wszystkich klas Qt (na razie, ta informacja nam wystarczy). A zatem, w konstruktorze dodajemy linijkę:
connect(ui->pushButtonTest, SIGNAL(clicked()), this, SLOT(myCustomSlot()));
Teraz musimy sobie wyjaśnić za co ten fragment kodu odpowiada. Metoda connect dokonuje fizycznego połączenia sygnału clicked() dodanego przycisku z naszym slotem myCustomSlot().
Składnia metody connect jest następująca:
connect(nadawca, sygnał, odbiorca, slot);
W naszym przypadku
- nadawcą jest: ui−>pushButtonTest, poprzez ui (który defacto jest wskaźnikiem na klasę, która zawiera implementację naszych elementów interfejsu) możemy odnosić się do elementów naszego interfejsu. ui−>pushButtonTest jest niczym innym jak wskaźnikiem na obiekt pushButtonTest. Co więcej, metoda connect oczekuje w miejscu argumentu nadawcy, właśnie wskaźnika na obiekt, który ma emitować sygnał,
- sygnałem jest: sygnał clicked() przycisku pushButtonTest i przekazujemy go w argumencie za pomocą makra SIGNAL,
- odbiorcą jest: nasza główna klasa MainWindow – to w niej zdefiniowaliśmy nasz slot, przesyłamy ją do metody connect za pomocą wskaźnika this,
- slotem jest: nasz zdefiniowany slot myCustomSlot, przekazujemy go za pomocą makra SLOT.
Ostatecznie implementacja powinna wyglądać mniej więcej tak:
Uruchom program i przetestuj go, wynik powinien być następujący:
Ostatnią ciekawą rzeczą jest wykorzystanie wartości zwracanej przez metodę connect. W tym celu zmień nieco poprzedni kod, zamień:
connect(ui->pushButtonTest, SIGNAL(clicked()), this, SLOT(myCustomSlot()));
na:
qDebug() << "Connect się powiódł?" << connect(ui->pushButtonTest, SIGNAL(clicked()), this, SLOT(myCustomSlot()));
Wynik tej zamiany powinien być następujący:
Jest to bardzo przydatny zabieg zwłaszcza wtedy, kiedy tworzymy swoje sygnały oraz sloty i zaczynamy łączyć je między różnymi klasami. Czasem nasza próba połączenia sygnału i slotu może się nie powieść. Wtedy ten zabieg oszczędzi nam bardzo dużo czasu podczas szukania błędów, które sprawiają, że nasz program nie działa tak jak chcemy, a także oszczędzi nam czasu na analizowaniu potencjalnych problemów (które, nie zawsze są powodem tego błędu).
Materiały dodatkowe
Najlepszym źródłem informacji o Qt jest niewątpliwie jego obszerna dokumentacja. Wystarczy wpisać interesującą nas klasę w wyszukiwarkę i mamy dostęp do wszystkich jej metod wraz z przykładami.
Bardzo dobre poradniki robi Bryan Cairns, który publikuje swoje filmy na You Tube. Polecam zwłaszcza pierwsze 20 odcinków (są bardzo krótkie i rzeczowe) - przydadzą się w dalszych częściach serii.
Z książek ciekawe wydają się:
- C++ i Qt. Wprowadzenie do wzorców projektowych, Alan Ezust, Paul Ezust
(zawiera dużo informacji o podstawach C++ i wykorzystaniu go w połączeniu z Qt)
- Biblioteki Qt. Zaawansowane programowanie przy użyciu C++, Mark Summerfield
(polecane dla bardziej zaawansowanych)
Podsumowanie
W tej części dowiedziałeś się czym jest Qt, wiesz jak je zainstalować i skonfigurować. Poznałeś zalety Qt. Nauczyłeś się jak zaprojektować prosty interfejs graficzny, zbudować aplikację i ją uruchomić. Poznałeś bardzo ważny mechanizm w Qt – mechanizm sygnałów i slotów.
W następnej części zbudujemy aplikację i zaprojektujemy jej interfejs, który pozwoli nam na uzyskanie połączenia poprzez port szeregowy. Pozwoli to na odbieranie/wysyłanie informacji do mikrokontrolera. Zbudujemy więc prosty panel operatorski, który będzie można wykorzystać do komunikacji z Arduino.
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.
Załączniki
Powiązane wpisy
android, arduino, programowanie, qt
Trwa ładowanie komentarzy...