PROGRAMOWANIE W QT
KURS by moux 2004/2005
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
Wstęp:
Dla kogo jest ten kurs ???
Podstawowe pytanie i prosta odpowiedź. Kurs ten jest dla wszystkich, którzy "wychowali
się" na Windowsie, Delphi albo VBasic'u. Celem autora w wiekszym stopniu jest przekonanie do
programowania w Qt, a co za tym idzie na Linuxa niż nauczanie z punktu widzenia
merytorycznego.
Dlaczego warto ?? Dlaczego Qt ??
Czy nie wydaje się wam, że windows walczy ze śmiercią ?? Czy nie macie przypadkiem,
głęboko zakorzenionego przeświadczenia, że system microsoftu jest już totalnie
skompromitowany ?? Przede wszystkim wirusami (blaster, czarnobyl, dialery), ale także całą tą
kampanią przeszukiwania mieszkań (cHowaj Windows Do Piwnicy :)))). Czy nie macie
przypadkiem takiego gorącego marzenia żeby któregoś dnia przyjść do domu z pracy, czy szkoły,
włączyć komputer i nie bać się niespodzianek tylko mieć pewność, że komputer się włączy i będzie
działał, aż do wyłączenia ?? Czy nie przerażają was ceny komercyjnego oprogramowania, nie
mówię tu o windowsie bo 400 zł to nie majątek, ale o Visual Studio, Office czy Photoshopie
chodzi o oprogramowanie, bez którego komputer jest tylko kurzącym się meblem ?? Jeśli
odpowiedź na którekolwiek z postawionych tu pytań brzmi twierdząco to najwyższy czas
zaopatrzyć się w system operacyjny LINUX.
Co dalej ?? Mając linuxa na pewno dojdziesz w końcu do punktu, w którym powiesz:
"przydał by mi się program x". No ale niestety nikt jeszcze nie wpadł na to aby napisać x pod
linuxa. Powód do załamania nerwowego ?? Powrót do windowsa?? Ależ skąd moje panie, czas
wziąść się za programowanie...
A jeśli chodzi o Qt, no cóż. KDE powstało na bazie Qt. Wszystkie KMaile, Konquerory czy
Quanty to Qt. Olbrzymią zaletą Qt jest masa komponentów/klas, które sprawiają, że pisząc program
nie musimy myśleć o pewnych podstawowych rzeczach. Nie musimy odkrywać ameryki, ani
wywarzać otwartych drzwi. Czytając ten kurs przekonasz się, że Qt posiada wszystko czego można
potrzebować do napisania programu i że jedynym zadaniem programisty jest umiejętne połączenie
tego wszystkiego.
Forma kursu
Ponieważ biblioteka Qt bazuje na języku C++ wypadało by ten język znać. Jednak ponieważ
autor nie jest zbyt biegły w tym języku, każda lekcja będzie zawierała szczegółowy opis każdego
wpisywanego z palca kawałka kodu.
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
Przygotowanie
Przygotowanie rozpoczynamy od zaparzenia mocnej kawy Maxwell House :)))). Ponieważ
ten kurs dotyczy programowania w Qt pod linuxem nalezało by się więc zaopatrzyć w ten system
operacyjny. Qt jest dostępne również pod wina jednak pisanie w Qt pod ten system operacyjny nie
jest zbyt dobrym pomysłem. Na wina jest VB albo Delphi, które nadają się do tego o wiele lepiej.
Wracając do tematu: przygotowanie powinno w normalnych przypadkach polegać na
zainstalowaniu Linuxa z podstawowym pakietem narzędzi programistycznych włączając w to Qt
Designer. Niezbędne będzie więc pakiet qtdevel, przynajmniej na początek.
Narzędzia
Jak już wspomniałem kod będziemy tworzyć w środowisku programistycznym qt designer.
Oprócz tego podstawowym wręcz narzędziem będzie program qmake, który służy do generowania
skryptu umożliwiającego łatwą kompilację programu. Oczywiście niezbędny będzie również
kompilator gcc. Instalacja wszystkich tych rzeczy w dzisiejszych wersjach Linuxa jest dziecinnie
prosta więc nie będę się (póki co) nad nią rozpisywał. Dodam tylko, że: "damy radę".
Instalacja
Są przypadki, gdzie będziemy musieli coś dograć. Zassać z sieci i doinstalować. Opiszę
więc dwie zasady jakie należy znać żeby przez to przebrnąc.
Instalacja pakietu rpm
Jeśli zassany przez nas plik np. qtdesigner będzie pakietem rpm to, żeby móc go używac
należy sobie instalnąć. Jak ??
rpm Uvh nazwa_pakietu.rpm
Tips:
Jeśli instalujemy z Midnight Commandera to mamy do wyboru dwa skróty:
Do pakietu rpm można wchodzić tak jak do katalogu. Po wejściu pokaże nam się plik
*INSTALL wystarczy go odpalić by rozpocząć instalację.
Jeśli plik rpm jest aktualnie podświetlonym plikiem to można z linii poleceń wpisac
początek polecenia instalacyjnego rpm Uvh , a następnie użyć kombinacji klawiszy CRTL+J żeby
mc automatycznie uzupełnił to czego brakuje w poleceniu.
Jeśli instalacja przebiegnie pomyślnie to OK, ale jeśli nie to program rpm wyświetli nam to
czego brakuje. Niestety trzeba będzie to doinstalować według juz opisanego sposobu.
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
Kompilacja i instalacja ze źródeł
Bardziej hardkorowa opcja, ale wszystko jest dla ludzi. Źródła programu lub biblioteki
przeważnie są rozpowszechniane w postaci pliku *.gz, czyli po prostu spakowanej. Do
rozpakowywania słuźy polecenie tar zxf nazwa_pliku.gz. Jednak o wiele prostsze jest uzycie
Midnight Commandera. Do pliku *.gz wchodzimy jak do katalogu i jego zawartość możamy
skopiowac w dowolne miejsce (F5). Potem według bezproblemowego scenariusza postępujemy tak:
polecenia: ./configure, ./make, ./make install
... a jeśli będą problemy z instalacją znaczy to, że zostaliśmy wybrani do zgłębienia tajemnicy
kompilacji i wogóle budowy systemu operacyjnego Linux TYLKO SIĘ CIESZYĆ.
Wymagania
Nie będę obwijał w gazete wymagania są.... zależne od cierpliwości tego kto będzie się z
tymi wymaganiami zmagał. U mnie na PIII600, 256MB z Riva16MB chodzi to wszystko w miarę,
więc wszystko koło tego będzie dobre.
W dziedzinie softwaru to polecam Linuxa w wersji bardziej idiotoodpornej tj. Aurox może
PLD. Chodzi po prostu o to aby czynności związane z przygotowaniem środowiska nie trwały zbyt
długo i nie były zbyt skomplikowane. Dobry system opracyjny to taki, który pozwoli
użytkownikowi zapomnieć o swoim istnieniu :)))))))).
Autor korzysta obecnie (w chwili powstawania kursu) z systemu Aurox 9.4 z kernelem 2.4.22
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
Qt Designer (3) filozofia pracy
Żeby nie błądzić musimy poznać krok po kroku obsługę tego środowiska tj. np. jak od
formatki przejść do oprogramowania jakiegoś zdarzenia. Nie jest to trudne, ale "trzeba umieć".
Ponadto w lekcji tej napiszemy pierwszy program w Qt Nieśmiertelne "Hello World".
Tips
Opis postępowania zamieszczony poniżej należy wydrukować i powiesić na ścianie,
ewewntualnie nauczyć się na pamięć. Zawiera on opis "krok po kroku" co trzeba zrobić aby powstał
program.
"Krok po kroku"
1. Odpalamy Qt Designera np. poleceniem "designer".
2. W oknie "Qt Designer New/Open" wybieramy C++ Projekt i "OK"
3. W oknie "Project Setting", klikamy "...", tworzymy nowy folder w katalogu domowym o nazwie
takiej jak program, np. helloworld. Jako nazwę pliku tez wpisujemy hellowolrd.pro .
4. Wybieramy "File" > "New" > "Dialog"
5. Klikamy kombinację klawiszy CTRL+E czyli przełączamy widok na edycję kodu źródłowego.
Na pytanie czy chcesz storzyć plik ui.h odpowiadamy "YES".
6. Wybieramy "File" > "Save All" i zapisujemy plik opisujący wygląd formatki (form1.ui) w
folderze przeznaczonym na projekt np. o nazwie "helloworld"
7. Wybieramy "File" > "New" > "C++ Main". "OK".
8. Wybieramy "File" > "Save All". W tym momencie zapisujemy plik "main.cpp".
9. Uruchamiamy terminal w katalogu z programem i wpisujemy z palca po kolei qmake,
make, ./helloworld
Jeśli po wpisaniu tej ostatniej komendy naszym oczkom ukaże się pusta forma znaczy, że
jesteśmy w domu i możemy iść na piwo. Jeśli natomiast polecenie make pokaże jakieś błędy to
najpierw krzyczymy na cały głos, a potem piszemy obraźliwego emaila do autora. Opanowanie
powyższych kroków jest absolutnie najważniejsze, tak więc jeśli jakiś krok jest niezrozumiały
piszcie do mnie postaram się odpowiadać jak najszybciej.
Jeśli polecenie qmake zwórci błędy oznaczać to będzie, że albo nie jesteśmy w katalogu z
programem albo nie ma tam pliku "main.cpp". Normalnie pusty projekt przed kompilacją powinien
się składać z trzech plików: form1.ui, helloworld.pro, main.cpp.
Modyfikacja programu dodajemy bajery
Wybieramy menu "Edit" > "Slots" i w okienku, które sie pojawi klikamy "New function".
Opis wypełnienia okna znajduje się na obrazku:
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
Jako kolejny krok dodajemy przycisk do formy. Jest to stosunkowo proste więc pomijam
zagłębianie się w szczegóły.
Następnie: "Edit" > "Connections" > "New": wypełniamy według następującej kolejości.
Sender: pushButton1
Signal: clicked()
Receiver: Form1
Slot: saycheese()
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
Jeszcze przed kompilacją mała kosmetyka. Klikamy dwukrotnie na przycisku i wpisujemy w
oknie, które się pojawi np. say cheese.
Przechodzimy teraz do edycji kodu. Window > "Edit Form1". W oknie, które się pojawi
powinna już być implementacja funkcji saycheese (jeśli nie ma wróć do początku). Całość kodu
powinna wyglądać następująco:
#include <qmessagebox.h>
void Form1::saycheese()
{
QMessageBox sch ( "My name is first progz", "I say cheese for you",
QMessageBox::NoIcon,
QMessageBox::Ok, 0, 0 );
sch.exec();
}
Po skompilowaniu "make" i odpaleniu "./helloworld" powinien pokazac nam się potworek w
stylu poniższego.
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
Zabawa ze stringami
Zanim zaczniesz czytać ten rozdział upewnij się, że opanowałeś do perfekcji procedury
związane z przygotowaniem programu, tj. stworzenie projektu, pliku głównego programu oraz
kompilowanie stworzonego kodu. Rozdział ten pomija już te fragmenty tworzenia oprogramowania
przy pomocy Qt Designera.
Każdy programista na pewno stanie kiedyś lub już stanął przed trudnym problemem analizy,
zarządzania, rozbijania, scalania i mówiąc bardziej ogólnie modyfikowania stringów. Czym wobec
tego są stringi ? Są to ciągi znaków, które najczęściej są do programu wprowadzane czy to z palca
czy z pliku tekstowego. Cały problem ze stringami polega na tym, że należy je konwertować często
wielokrotnie. Po za tym jest to jeden z najbardziej popularnych formatów danych.
Do obsługi stringów w Qt służą dwie klasy QChar i Qstring, które obudowują standardowy
typ języka C++ char i tablice znakowe. Klasy te zawierają w sobie wiele przydatnych funkcji,
dzięki którym możemy zapomnieć o takich elementach modyfikowania stringów jak wyciągnięcie
jakiegoś znaku/znaków czy też obliczenia długości itd.
Dla prostego przećwiczenia tego co napisałem powyżej proponuję stworzyć nowy projekt
według przepisu z lekcji drugiej i umieścić na nim jedno pole textEdit, jedno pole lineEdit oraz
przycisk pushButton. Następnie stworzyć slota o nazwie np. obliczrozm(). W następnej kolejności
podpinamy tegoż slota do zdarzenia wciśnięcia przycisku i przełączamy widok do edycji kodu
CTRL+E. W edytorze kodu wystarczy wkleić poniższy kod aby obliczyć ilość znaków w polu
tekstowym:
void Form4::napiszcos()
{
QString dl = textEdit3>text();
lineEdit3>setText( QString::number( dl.length() ) );
}
Krótkie wyjaśnienie powyższego kodu:
QString dl = textEdit3>text();
Jak można się domyślić jest to przypisanie do nowo utworzonej zmiennej zawartości pola
textEdit3. Przy okazji wtrącę małą dygresję. Poniższy kod można było z takim samym
powodzeniem umieścić w jednej linii jednak podejście takie utrudnia znajdowanie błędów w
programach.
lineEdit3>setText ( QString::number ( dl.length() );
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
Po pierwsze za pomocą funkcji setText ładujemy do zmiennej text obiektu lineEdit3 to co
jest w nawiasie. W nawiasie mamy użytą funkcję length() pochodzącą z klasy QString, ponieważ
funkcja ta zwraca wartość liczbową int musimy ją skonwertować do postaci znakowej co umożliwia
nam funkcja numer.
Dociekliwy czytelnik powinien w tym miejscu spytać “O CO CHODZI” z tymi zmiennymi
funkcjami i z tym zapisem raz “::”, a innym razem “.”. Zapis z podwójnym dwukropkiem jest dość
prosty do zrozumienia gdyż wskazuje nam na to, że odwołujemy się bezpośrednio do funkcji
znajdującej się wewnątrz klasy – w tym przypadku QString. Zapis z jedną kropką mówi natomiast,
że wywołujemy funkcję z wewnątrz klasy w której się aktualnie znajdujemy.
Jednak program, który oblicza długość pola tekstowego to było by trochę mało musimy więc
go trochę rozbudować. Ponieważ jest to dopiero początek kursu, a szkoda by było pisać kolejnego
“hello worlda”, proponuję zrobić programik, który zakoduje nasz adres email, tak abyśmy mogli go
stosunkowo bezpiecznie umieszczać na naszych stronach WWW (chodzi tu o zabezpieczenie anty
spamowe). Idea programu została zaczerpnięta ze strony internetowej www.nospampl.net polega
ona na tym, że znaki adresu internetowego są konwertowane na swoje reprezentacje
heksadecymalne co nie robi różnicy przeglądarce, a jedynie programom skanującym sieć w
poszukiwaniu zbłąkanych adresów email, na które (i z których) można wysyłać SPAM.
Do programu będziemy potrzebować znów dwóch pól tekstowych jednego textEdit'a i
jednego lineEdit'a no i oczywiście jakiegoś ładnego pushButton'a. Po dodaniu komponentów
musimy utworzyć slota, a następnie podpiąć go no zdarzenia clicked(). Po tym wszystkim
proponuję użyć poniższego kodu:
void Form2::doitnow()
{
textEdit4>setText( doitnow2(lineEdit4>text()) );
// linia ta do pola tekstowego textEdit4 wstawia
// tekst przetworzony przez funkcję doitnow2()
}
QString Form2::doitnow2(const QString & input) const
// pierwsze QString mówi o typie zwracanym przez funkcję
{
QString r = input;
// do zmiennej r przypisujemy wartość podaną jako
// parametr wywołania funkcji
int i, x = r.length();
// deklarujemy zmienne i oraz x
// do zmiennej x przypisujemy długość zmiennej r
// czyli stringa podanego w parametrze wywołania funkcji
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
r = "";
// zerujemy zmienną r, tj. przypisujemy do niej pusty string
for (i=0;i<=x;i++)
{
if ( !(int)input[i] == 0) {
// jeśli znak o numerze [i] ze stringa input jest
// różny od 0, to wykonujemy poniższe
r.append ( "&#" );
// dodajemy do stringa r początek htmlowy
r.append( QString::number( (int)input[i] ) );
// dodajemy do tegoż stringa numeryczną reprezentację znaku
r.append ( ";" );
// zakańczamy stringa
}
}
return r;
}
// wartość zwracana przez tą funkcję trafia do pola tekstowego.
Całość powinna wyglądać tak:
Tekst znajdujący się w polu opisanym jako crypted można skopiować i wstawić do strony
WWW. Przeglądarki zinterpretują to prawidłowo jednak programy spamerskie nie dadzą rady.
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
Program bardziej złożony kalkulator
Za pomocą tego tekstu będzemy mogli przećwiczyć ponownie sposób tworzenia aplikacji w
Qt Designerze, a także przećwiczymy tworzenie GUI. W tym tekście stworzymy sobie prosty
kalkulatorek.
Pierwsze kroki po raz drugi
Opiszę jeszcze raz sposób tworzenia projektu, szerszy opis znajduje się w poprzednim artykule,
tutaj rozpiszę to tylko dla przypomnienia.
1. Gdy Qt Designer się uruchomi w Oknie "New" wybieramy "C++ project"
2. Zapisujemy projekt do osobnego katalogu "kalk", po nazwą "kalk.pro".
3. Wybieramy menu "New" i z okienka wskazujemy Widget
4. Zapisujemy formatkę pod nazwą mFrmCalc.ui
5. Ponownie wybieramy menu New lecz tym razem wskazujemy main.cpp
6. Zapisujemy wszystko i jak na razie wystarczy.
Tworzenie GUI
Aby nasz kalkulator wyglądał jakoś po ludzku, musimy na wstawiać na niego przyciski z
cyferkami. Wykonuje się to w prosty sposób, klikając najpierw na przycisk w Toolboxie Common
Widget, a następnie na formatce. Sugerowany wygląd zamieszczam ponieżej:
Przyciski oznaczone cyframi mają ustawiony parametr width na 50, cała reszta jest
ustawiona na oko. Proponuję jeszcze ustawić parametr ReadOnly komponentu lineEdit na TRUE.
Ponadto ustawimy jeszcze tekst tego komponentu na "0", tak aby zaraz po uruchomieniu nasz
kalkulator się wyzerował. Ustawimy też właściwość hAlign na AlignRight, dla lepszego efektu.
To co przed chwilą stworzyliśmy nie jest jeszcze zbyt dobrym punktem wyjścia do
rozpoczęcia pisania kodu. Musimy jeszcze nadać imiona naszym dzieciom czyli przyciskom, tak
aby nie zginąć zbyt szybko w gąszczu pushButtonX. Nasze przyciski numeryczne nazwiemy
według poniższego klucza przycisk 9 pb9, przycisk 8 pb8 itd. Przyciski działań: podzielić pbDiv,
pomnożyć pbMulti, dodać pbAdd, odjąć pbTa. Przycisk kropki pbDot, zmiany znaku pbChar,
potęga pbPote, pierwiastek pbPierw, no a wynik niech się nazywa pbWynik. Kasowanie niech się
nazywa pbC.
Tworzenie slotów
Jak widać mamy tutaj 19 przycisków, będziemy więc potrzebować 19 slotów. Każdy z nich
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
będzie odpowiadać za kliknięcie wybranego przycisku. Można to oczywiście wykonamy prościej
np. jednym slotem ale dla przećwiczenia lepiej użyć 19. W tym celu wchodzimy w menu edit i
wybieramy slots. W okienku, które sie pojawi dodajemy funkcje i jako nazwy wpisujemy np.
pb1Click(). Powtarzamy tą czynność, aż wszystkie przyciski będą miały swojego slota.
Gdy już wszystkie sloty będziemy mieć potworzone, możemy zabawić się w tworzenie
połączeń między wciśnięciem przycisku, a wywołaniem funkcji slota. W tym celu wykorzystujemy
okienko edit connections. Przy tworzeniu wszystkich połączeń będzie trochę klikologii, aczkolwiek
powyższy sposób jest chyba najszybszym na oprogramowanie takiej ilości funkcji. Gdy uda nam się
stworzyć te połączenia możemy przystąpić do etapu kodowania.
Kodowanie
Na początek zajmiemy się funkcjami do wstawiania cyferek do naszego edita. Posłuży nam
do tego następujący kod, który będzie jednakowy dla wszystkich przycisków oznaczonych
cyferkami:
void Form1::pb9Click()
{
if (lineEdit1>text() != "0") {
lineEdit1>setText( lineEdit1>text() + "9" );
} else {
lineEdit1>setText( "9" );
}
}
Z powyższego kodu widać, że dopisanie nowej cyferki nastąpi tylko wówczas gdy zawartość
pola będzie równa zero, w przeciwnym wypadku cyfra zostanie dopisana do tego co już w tym polu
się znajduje.
Funkcja przycisku kropki będzie za to wyglądać w ten sposób:
void Form1::pbDotClick()
{
QString string( lineEdit1>text() );
int n = string.contains( '.', FALSE );
if (n < 1 ) {
lineEdit1>setText( lineEdit1>text() + "." );
}
}
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
W pierwszych dwóch linijkach sprawdzamy czy nasz lineEdit1 posiada już wpisaną kropę, a
następnie jeśli liczba kropek będzie mniejsza od 1 to dopisujemy nam naszą kropę.
Kasowanie lineEdit1'a
W tym miejscu dochodzimy do zmiennych, o których wcześniej nie wspominałem. Otóż
nasz program będzie sie posługiwał trzema zmiennymi typu float. Deklarację tych zmiennych
proponuję umieścić na samej górze programu, jeszcze przed deklaracją pierwszego slota. Wyglądać
to powinno mniej więcej w ten sposób:
float a,b,c;
Zmienne te będziemy wykorzystywali do operacji matematycznych tj. dzielenie, czy
pierwiastkowanie. Dlatego tez funkcja kasowania musi również wyzerować te zmienne. Wyglądać
to będzie tak:
void Form1::pbCClick()
{
lineEdit1>setText( "0" );
a = 0;
b = 0;
c = 0;
}
Funkcja dodawania znaku
void Form1::pbCharClick()
{
if (lineEdit1>text() != "0") {
QString string( lineEdit1>text() );
int n = string.contains( '', FALSE );
if (n < 1) {
lineEdit1>setText( "" + lineEdit1>text() );
} else {
int a = string.length();
QString t = string.right( a 1 );
lineEdit1>setText( t );
}
}
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
}
Najpierw sprawdzamy czy nasze pole jest czymś innym niż zero, a jeśli jest to musimy się
umieć zachować w zależności od tego czy do pola już został znak zapisany. Jeśli został to musimy
go wykasować, a jeśli jeszcze go tam nie ma to go dodajemy.
Zaczynamy działać
Pierwsze działanie jakie dodamy do naszego programu będzie podnosić liczę z pola
edycyjnego do potęgi. Będzie to wymagało od nas dodania dwóch plików nagłówkowych: stdlib.h i
math.h. Gdy je dodamy wykorzystamy poniższy kod:
// potęgowanie
void Form1::pbPotClick()
{
a = atof(lineEdit1>text());
b = a;
c = a*b;
lineEdit1>setText( QString::number(c) );
}
// no i od razu przy okazji pierwiastkowanie
void Form1::pbPierwClick()
{
a = atof(lineEdit1>text());
c = sqrt(a);
lineEdit1>setText( QString::number(c) );
}
Funkcje bardziej skomplikowane np. Dodawanie
Komplikacje związane z dodawaniem polegają na tym, iż dodawana liczba trafia jakby do
pamięci i czeka na swój dodajnik. Po zapisaniu do programu liczby dodawanej użytkownik
wprowadza rodzaj działania jaki chce wykonać. Gdy te dwie informacje juz mamy potrzebujemy
już tylko drugiej liczby do dodania i możemy zaprezentować wynik. Aby to wszystko miało ręce i
nogi posłużymy się zmienną np. int, która będzie przechowywała typ działania jakie wykonujemy.
Deklaracja tej zmiennej powinna mieć miejsce tuż pod zmiennymi a,b,c.
Wszystkie funkcje tj. dodawanie, odejmowanie, mnożenie i dzielenie będą się odbywać w
funkcji pbWynikClick, natomiast w funkcjach przypisanych do przycisków np. dodawania będą
tylko instrukcje służące zapisaniu wartości pola do zmiennej float. Całość wyglądać będzie tak:
//dodawanie
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
void Form1::pbAddClick()
{
a = atof(lineEdit1>text());
funkcja = 1;
lineEdit1>setText( "0" );
}
// mnożenie
void Form1::pbMultiClick()
{
a = atof(lineEdit1>text());
funkcja = 3;
lineEdit1>setText( "0" );
}
// odejmowanie
void Form1::pbTaClick()
{
a = atof(lineEdit1>text());
funkcja = 2;
lineEdit1>setText( "0" );
}
// dzielenie
void Form1::pbDivClick()
{
a = atof(lineEdit1>text());
funkcja = 4;
lineEdit1>setText( "0" );
}
// wynik
void Form1::pbWynikClick()
{
b = atof(lineEdit1>text());
switch( funkcja ) {
case 1: c = a + b;
break;
case 2: c = a b;
break;
case 3: c = a * b;
break;
case 4: c = a / b;
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
break;
}
lineEdit1>setText( QString::number(c) );
}
jak widać wszystko jest proste i czytelne. Proponuję dla wprawy dodać obsługę błędu dzielenia
przez zero. Na koniec zamieszczam cały kod programu:
#include <stdlib.h>
#include <math.h>
float a, b, c;
int funkcja;
void Form1::Init()
{
pbCClick();
a = 0;
b = 0;
c = 0;
funkcja = 0;
}
void Form1::pb0Click()
{
if (lineEdit1>text() != "0") {
lineEdit1>setText( lineEdit1>text() + "0" );
} else {
lineEdit1>setText( "0" );
}
}
void Form1::pb1Click()
{
if (lineEdit1>text() != "0") {
lineEdit1>setText( lineEdit1>text() + "1" );
} else {
lineEdit1>setText( "1" );
}
}
void Form1::pb2Click()
{
if (lineEdit1>text() != "0") {
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
lineEdit1>setText( lineEdit1>text() + "2" );
} else {
lineEdit1>setText( "2" );
}
}
void Form1::pb3Click()
{
if (lineEdit1>text() != "0") {
lineEdit1>setText( lineEdit1>text() + "3" );
} else {
lineEdit1>setText( "3" );
}
}
void Form1::pb4Click()
{
if (lineEdit1>text() != "0") {
lineEdit1>setText( lineEdit1>text() + "4" );
} else {
lineEdit1>setText( "4" );
}
}
void Form1::pb5Click()
{
if (lineEdit1>text() != "0") {
lineEdit1>setText( lineEdit1>text() + "5" );
} else {
lineEdit1>setText( "5" );
}
}
void Form1::pb6Click()
{
if (lineEdit1>text() != "0") {
lineEdit1>setText( lineEdit1>text() + "6" );
} else {
lineEdit1>setText( "6" );
}
}
void Form1::pb7Click()
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
{
if (lineEdit1>text() != "0") {
lineEdit1>setText( lineEdit1>text() + "7" );
} else {
lineEdit1>setText( "7" );
}
}
void Form1::pb8Click()
{
if (lineEdit1>text() != "0") {
lineEdit1>setText( lineEdit1>text() + "8" );
} else {
lineEdit1>setText( "8" );
}
}
void Form1::pb9Click()
{
if (lineEdit1>text() != "0") {
lineEdit1>setText( lineEdit1>text() + "9" );
} else {
lineEdit1>setText( "9" );
}
}
void Form1::pbDotClick()
{
QString string( lineEdit1>text() );
int n = string.contains( '.', FALSE );
if (n < 1 ) {
lineEdit1>setText( lineEdit1>text() + "." );
}
}
void Form1::pbPotClick()
{
a = atof(lineEdit1>text());
b = a;
c = a*b;
lineEdit1>setText( QString::number(c) );
}
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
void Form1::pbPierwClick()
{
a = atof(lineEdit1>text());
c = sqrt(a);
lineEdit1>setText( QString::number(c) );
}
void Form1::pbAddClick()
{
a = atof(lineEdit1>text());
funkcja = 1;
lineEdit1>setText( "0" );
}
void Form1::pbMultiClick()
{
a = atof(lineEdit1>text());
funkcja = 3;
lineEdit1>setText( "0" );
}
void Form1::pbTaClick()
{
a = atof(lineEdit1>text());
funkcja = 2;
lineEdit1>setText( "0" );
}
void Form1::pbDivClick()
{
a = atof(lineEdit1>text());
funkcja = 4;
lineEdit1>setText( "0" );
}
void Form1::pbWynikClick()
{
b = atof(lineEdit1>text());
switch( funkcja ) {
case 1: c = a + b;
break;
case 2: c = a b;
break;
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
case 3: c = a * b;
break;
case 4: c = a / b;
break;
}
lineEdit1>setText( QString::number(c) );
}
void Form1::pbCharClick()
{
if (lineEdit1>text() != "0") {
QString string( lineEdit1>text() );
int n = string.contains( '', FALSE );
if (n < 1) {
lineEdit1>setText( "" + lineEdit1>text() );
} else {
int a = string.length();
QString t = string.right( a 1 );
lineEdit1>setText( t );
}
}
}
void Form1::pbCClick()
{
lineEdit1>setText( "0" );
a = 0;
b = 0;
c = 0;
}
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
Pliki graficzne
Najprostsza wersja przeglądarki graficznej
1. Tworzymy nowy projekt (“patrz Qt Designer filozofia pracy”)
2. Układamy na formatce pixmapLabel w zakładce “Display” i przycisk pushButton
3. Tworzymy slota np. o nazwie load
4. Tworzymy połączenie między klinięciem przycisku i slotem
Do stworzenia programu wykorzystamy kod, który będzie wykorzystywał okienko
dialogowe Otórz/Zapisz. Za pomocą tego okieka pobierzemy nazwę pliku jaki chcemy załadować
do programu. Funkcja tego okna będzie wyglądała tak:
#include <qfiledialog.h>
void Form1::load()
{
QString s = QFileDialog::getOpenFileName(
"/home",
"Pliki graficzne ( *.jpg *.gif *.png )",
this,
"Otworz plik"
"Wybierz plik" );
pixmapLabel2>setPixmap( s );
}
Chociaż powyższy kod działa powinniśmy jednak wyposażyć go w obsługę błędów:
#include <qfiledialog.h>
void Form1::load()
{
QString s = QFileDialog::getOpenFileName(
"/home",
"Pliki graficzne ( *.jpg *.gif *.png )",
this,
"Otworz plik"
"Wybierz plik" );
if ( s != NULL ) {
pixmapLabel2>setPixmap( s );
}
}
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
Pliki: zapis, odczyt, QFileDialog.
Chyba najbardziej pocieszną rzeczą jaką można wykonać w każdym języku programowania
jest notatnik. Zanim jednak wykonamy prawdziwy notatnik, taki z prawdziwego zdarzenia musimy
poznać dwie klasy przydatne przy obsłudze plików: QFile i QFileDialog. W najprostszym
przykładzie zaczytanie pliku tekstowego będzie wyglądało tak:
#include <qfile.h>
void Form1::zapis()
{
QFile fp("/home/moux/plik.txt");
fp.open( IO_WriteOnly );
fp.writeBlock( textEdit1>text(), qstrlen(textEdit1>text()) );
fp.close();
}
void Form1::odczyt()
{
QFile fp("/home/moux/plik.txt");
char buff[255];
fp.open( IO_ReadOnly );
textEdit1>clear();
while (!fp.atEnd() )
{
fp.readLine(buff, sizeof(buff));
textEdit1>append(buff);
}
fp.close();
}
Oczywiście funkcje odczyt i zapis są slotami połączonymi z przyciskami, tekstEdit1 jest
więc komponentem do przechowywania tekstu. Podobieństwo ze standardowym użyciem jest wiele
jednek widać, że wszystkie funkcje użyte do obsługi plików są metodami klasy QFile. W tym
przykładzie zaczytujemy i zapisujemy cały plik za jednym zamachem. Funkcja writeBlock (uwaga
na wielkości znaków muszą być zachowane) zapisuje tekst z komponentu textEdit1, uzyskany
przy pomocy funkcji text(). Długość tego tekstu jest obliczna za pomocą funkcji qstrlen. Do
odczytania danych używamy funkcji readLine, ponieważ nie wiemy ile znaków ma tekst zapisany w
pliku. Zaczytujemy te dane tak długo jak funkcja atEnd (informująca o tym czy osiągnęliśmy
koniec pliku) zwaraca wartość FALSE (definicja tej funkcji bool QFile::atEnd()). Każda odczytaną
linię zapisujemy do bufora buff, a następnie doklejamy do textEdita.
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
Innym ciekawym sposobem na dostęp do pliku są strumienie, klasy QTextStream i
QDataStream. Odczyt i zapis tego samego tekstu jak w pierwszym przykładzie przy użyciu
QTextStream miałby taką postać:
void Form1::zapis2()
{
QFile fp( "/home/moux/plik.txt" );
fp.open( IO_WriteOnly );
QTextStream stream( &fp );
stream >> textEdit1>text();
fp.close();
}
void Form1::odczyt2()
{
QFile fp( "/home/moux/plik.txt" );
fp.open( IO_ReadOnly );
QTextStream stream( &fp );
textEdit1>setText( stream.read() );
fp.close();
}
Znaczącą różnicą podczas odczytu, w przypadku tych dwóch zastosowań jest to, iż w
drugim przypadku przy odczycie i zapisie tekstów stosowane jest kodowanie lokalne 8bitowe. Dla
nas polskich polaków oznacza to nasze ulubione ISO88592, czyli odczytany plik będzie miał
krzaczki.
QFileDialog
Gdy w obojętnie jakim edytorze napisanym przy użyciu Qt klikamy "Otwórz" albo "Zapisz
jako". Pokazuje się nam okienko, które nie jest niczym innym jak klasą QFileDialog. Do czego to
okienko jest wykorzystywane z punktu widzenia programisty ?? Przede wszystkim do tego, aby dać
użytkownikowi możliwość wskazania pliku (a więc uzyskania jego ścieżki i nazwy) do zapisania
lub otwarcia. Wszystkie metody tej klasy pozwalają sterować wyglądem tego okienka i jego
zawartością. Aby móc korzystać z tej klasy musimy dołączyć plik nagłówkowy qfiledialog.h. W
naszym przykładzie wykorzystanie tej klasy będzie sie przedstawiać następująco:
void Form1::zapis2()
{
QString fname = QFileDialog::getSaveFileName(
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
"/home",
"Pliki tekstowe (*.txt)",
this,
"",
"Zapisz jako" );
if ( fname != NULL) {
QFile fp( fname );
fp.open( IO_WriteOnly );
QTextStream stream( &fp );
stream << textEdit1>text();
fp.close();
}
}
void Form1::odczyt2()
{
QString fname = QFileDialog::getOpenFileName(
"/home",
"Pliki tekstowe (*.txt)",
this,
""
"Otwórz plik" );
if ( fname != NULL) {
QFile fp( fname );
fp.open( IO_ReadOnly );
QTextStream stream( &fp );
textEdit1>setText( stream.read() );
fp.close();
}
}
Zaprezentowane powyżej zastosowanie może być użyte wszędzie tam gdzie konieczne jest
szybkie wykorzystanie tego okienka dialogowego. Gdybyśmy jednak chcieli mieć większą nad nim
kontrolę powinniśmy zadeklarować to okno jako osobną zmienną i wykorzystywać jej właściwości
tak jak to robimy w przypadku textEdit'a. Przykład takiego wykorzystania:
void Form1::odczyt3()
{
QFileDialog *openfd = new QFileDialog( this, "", TRUE );
openfd>setMode( QFileDialog::ExistingFile );
openfd>addFilter( "Pliki tekstowe (*.txt)" );
openfd>setShowHiddenFiles( TRUE );
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
openfd>setDir("/home");
openfd>show();
if ( openfd>selectedFile() != NULL ) {
// jakies operacje
}
}
Jak widać takie zastosowanie pozwoliło nam dodać opcję otwierania plików ukrytych.
Pondto widać tutaj dość ciekawą opcję setMode. Pozwala ona zdecydować o tym w jakim trybie ma
pracować nasze okienko. Do wyboru mamy:
QFileDialog::AnyFile Pozwala wskazanie pliku, który może lecz nie musi istnieć. Ten tryb jest
najbardziej dostosowany do operacji zapisu plików.
QFileDialog::ExistingFile Pozwala na wskazanie pliku juz istniejącego.
QFileDialog::Directory Pozwala na wskazanie katalogu. Wyświetlane są zarówno pliki jak i
katalogi.
QFileDialog::DirectoryOnly Wyświetlane sa tylko katalogi.
QFileDialog::ExistingFiles Pozwala na wskazanie wielu plików.
Opcje te są porównywalne do funkcji operacji, które wykorzystywaliśmy w pierwszym
przykładzie.
Warto jeszcze wspomnieć o tym, iż w funkcji addFiler (podobnie jak w funkcjach do
pobierenia nazw plików) możemy definiowac wiele masek rozszerzeń. Kolejne maski podaje sie w
sposób następujący np.: ("Pliki png (*.png);;Pliki jpg (*.jpg)"), oddzielając je jak widać dwoma
średnikami.
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
Komponenty wyboru, checkBox i radioButton
Zanim przejdziemy do utworzenia projektu słowo wstępu. Co to są komponenty wyboru i
jak z nich korzystać ?? Generalnie w programowaniu okienkowym mamy trzy klasy/komponenty,
które są z tym związane: CheckBox, RadioButton i ButtonGroup. Komponent ButtonGroup
organizuje komponenty jakby w jedną całość. Posiada on zmienną typu integer, która przechowuje
numer ostatniego wciśniętego komponentu. W przypadku gdy na ButtonGroup umieścimy
komponenty RadioButton będziemy dzięki temu mogli w prosty sposób określić, który z nich jest
włączony, a który nie. Komponenty CheckBox pozwalają nam natomiast włączać/wyłączać kilka
opcji. Do grupowania CheckBox'ów lepiej użyć komponentu GroupBox, który jest jedynie ramką z
tytułem.
Do pracy rodacy
Jak zwykle rozpoczynamy od stworzenia projektu, qtcheckb, dodaniu formatki QDialog i
pliku main.cpp. Następnie dodamy komponenty: QButtonGroup, QGroupBox, 3 x QRadioButton,
które umieszczamy na komponencie buttonGroup. Do tego wszystkiego dodamy jeszcze 3 sztuki
QCheckBox, które wcipolimy na QGroupBox.
Kod programu
W tym tekście wykonamy program, który będzie potrafił zmienić kolor swojego tła. Na
początek obsługa QRadioButton. Klasa ta ma w swojej budowie zdefiniowany sygnał clicked(int).
Zmienna int podana przy wywołaniu tej funkcji przechowuje numer wciskanego radioButtona.
Funkcja/slot, którego możemy używać z tą funkcja wygląda tak:
void Form1::rbClick( int x )
{
switch ( x ){
case 0: setBackgroundColor( "#FF0000" );
break;
case 1: setBackgroundColor( "#00FF00" );
break;
case 2: setBackgroundColor( "#0000FF" );
break;
}
setCaption( backgroundColor().name() );
checkBox1>setChecked(FALSE); // zerujemy chceckboxy,
checkBox2>setChecked(FALSE);
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
checkBox3>setChecked(FALSE);
}
Połączenie tej funkcji z sygnałem, oczywiście dokonujemy w okienku Connections.
Z powyższego kodu widać moc wykonawczą zmienne x.
Obsługa CheckBox
W przypadku checkBox nie jest juz tak prosto. Komponenty te mogą pracować oddzielnie, a
co za tym idzie ilość wszystkich kombinacji w przypadku 3 sztuk wynosi 9. Jak dotąd nie
spotkałem się z komponentem grupującym checkBox'y dlatego do ich obsługi wymyśliłem funkcję:
void Form1::edColor()
{
QColor cl( backgroundColor() );
int r, g, b;
r = cl.red();
g = cl.green();
b = cl.blue();
int x = buttonGroup1>selectedId();
if ( checkBox1>isChecked() ) {
r = 127;
} else {
if ( x == 0 ) r = 255; else r = 0;
}
if ( checkBox2>isChecked() ) {
g = 127;
} else {
if ( x == 1 ) g = 255; else g = 0;
}
if ( checkBox3>isChecked() ) {
b = 127;
} else {
if ( x == 2 ) b = 255; else b = 0;
}
setBackgroundColor( QColor( r, g, b ) );
setCaption( QColor( r, g, b ).name() );
}
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
Funkcja ta najpierw rozdziela kolory na rgb, a następnie zmniejsza lub zwiększa kolor o
połowę :))). Funkcję tą musimy podpiąć do każdego checkBox'a do sygnału clicked().
Generalnie sposób działania tej funkcji najlepiej będzie analizować na podstawie działania
programu.
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
Wywołanie programu z parametrem
Do uruchamiania programu w Qt przygotowano, spejalną świetną funkcję. Sprawia ona, że
obsłużenie programu wywołanego z parametrami to naprawde bajka i frajdziowcha :))
Pierwszą i zasadniczą rzeczą od jakiej zaczniemy jest tzw. help. Normalnie jak program
uruchomimy z parametrem help to pojawi nam się pomoc. Jak to zrobić w Qt ? Zaczniemy od
stworzenia projektu, z dwoma formami form1 i form2. Nastepnie zajmiemy się edycją pliku
main.cpp:
#include <qapplication.h>
#include "form1.h"
#include "form2.h"
int main( int argc, char ** argv )
{
QApplication a( argc, argv );
QString opcja = "normal";
if ( argc > 1 ) opcja = argv[1];
if ( qstrcmp(opcja, "normal") == 0 ) {
Form1 w;
w.show();
a.connect( &a, SIGNAL( lastWindowClosed() ), &a, SLOT( quit() ) );
return a.exec();
} else
if ( qstrcmp(opcja, "settings") == 0 ) {
Form2 w2;
w2.show();
a.connect( &a, SIGNAL( lastWindowClosed() ), &a, SLOT( quit() ) );
return a.exec();
} else
if ( qstrcmp(opcja, "help") == 0 ) {
qWarning( "Uzycie \n"
"param [opcja]\n"
"normal \t Uruchamia program w trybie normalnym.\n"
"settings \t Uruchamia program w trybie ustawien.\n"
"help \t\t Wyswietla pomoc."
);
} else {
qWarning( "Nieznana opcja: " + opcja + "\n Sprobuj help" );
}
return 0;
}
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
Jak widać z tego przykładu program posiada cztery możliwości uruchomienia: w trybie
normalnym, gdzie uruchamiane jest normalne okienko programu, w trybie ustawień gdzie
wyświetlane jest okienko form2 z ustawieniami, w trybie pomocy konslowej, dzie wyświetlane sa
wszystkie dostępne opcje oraz w trybie informujacym o złej komendzie. Pobranie parametrów
odbywa się poprzez standardowe argv. Porównanie natomiast jest wykonywane przy pomocy
funkcji porównującej stringi. Warto zwrócić uwagę na zastosowanie qWarning. Za pomocą tej
funkcji wyświetlamy tekst na konsoli. Oprócz tej funkcji w Qt mamy jeszcze dwie wykonujące
podobne operacje. Są to qDebug, która nie różni się niczym od qWarning i prawie niczym od
qFatal, która po wywołaniu od razu zamyka program.
Gdybyśmy chcieli teraz wykorzystać parametr wywołania programu w jakimś pliku
formatki możemy to zrobić w następujący sposób:
#include <qapplication.h>
void Form1::init()
{
if (qstrcmp(qApp>argv()[2], "kill") == 0 )
qFatal( "Koniec zabawy... \n" );
}
Aby przetestować działanie tego programu należy uruchomić go z konsoli wpisając jego
nazwę poprzedzoną "./".
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
Programik wieloformatkowy
Treść tego rozdziału nie jest zbyt skomplikowana, omówione tutaj sposoby wykorzystania
Qt pozwolą na wykorzystywanie w programach kilku form/dialogów. Przedstawione tutaj zostaną
dwa sposoby podejścia do tematu. Pierwszy to tworzenie nowych form przygotowanych wcześniej
w QtDesingerze. Drugi sposób to tworzenie formatek całkowicie w kodzie programu. Ten drugi
sposób z pewnością będzie później przydatny przy innych komponentach takich jak np. buttony. Na
koniec zaprezentuję sposób na przekazywanie danych, zmiennych i funkcji pomiędzy formatkami.
Zaczynamy. Jak zwykle nowy projekt np. o nazwie "2form". Po zapisaniu projektu
tworzymy dwie formatki jedną wyposażamy w dwa SpinBoxy i dwa buttony. Drugą pozostawiamy
pustą, z tym że tworzymy jej pusty plik z kodem, którym zajmiemy sie później. Po utworzeniu
formaki form1, tworzymy slota np. o nazwie frm2show_s(). Slota tego podpinamy do pierwszego
buttona. Do slota wklejamy następujący kod:
#include "form2.h"
void Form1::frm2show_s()
{
Form2 frm2;
frm2.exec();
}
Jak widać nic skomplikowanego. Tworzone w ten sposób okienko jest okienkiem modalnym co
znaczy, że blokuje wykonywanie programu i dostęp do formatki głównej.
Teraz pokażę sposób na tworzenie okienka w sposób dynamiczny. Sposób ten polega na
tym, że okienko jest tworzone w trakcie działania programu po wywołaniu funkcji tworzącej. Takie
podejście pozwala na stworzenie formy na podstawie parametrów, które mogą być różne w różnych
momentach działania programu. Innymi słowy dynamiczne tworzenie obiektów pozwala na dodanie
funkcji do programu, która nie będzie musiała być do niego wkompilowana. W ten sposób działa
np. Quanta gdzie użytkownik ma możliwość dodania własnego przycisku np. wstawiającego jakiś
kod lub wywołującego funkcję do wyboru koloru.
Aby to wszystko uczynić możliwym musimy jak zwykle utworzyć slota frm3show_d() i
podpiąć go do buttona "show dynamic". Funkcja frm3show_d() powinna mieć mniej więcej taki
kod:
void Form1::frm3show_d()
{
QDialog *frm3 = new QDialog ( this, "", TRUE, 0);
frm3>resize(sbWidth>value(), sbHeight>value());
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
frm3>exec();
delete frm3;
}
Dwa fragmenty tego kodu z pewnością wymagają wytłumaczenia:
QDialog *frm3 = new QDialog ( this, "", TRUE, 0 );
to konstruktor klasy QDialog o nazwie *frm. Parametry podane przy wywołaniu konstruktora to po
kolei: rodzic (parent), nazwa, czy okienko ma być modalne, oraz typ formatki. Ponieważ dialog jest
formatką raczej uproszczoną może istnieć tylko jako okienko modalne. Jako typ formatki mamy do
wyboru: WStyle_Customize | WStyle_NormalBorder | WStyle_Title | WStyle_SysMenu.
Testowanie poszczególnych opcji pozostawiam czytelnikowi. Słówko this podane jako parametr
rodzica odwołuje się do programu czyli klasy QApplication.
Funkcja resize jest chyba zrozumiała, jedyne co należy wiedzieć to to, że jest ona funkcją
klasy QWidget, która jest przodkiem klasy QDialog. Szczegółowe omówienie dziedziczenia w Qt
będzie omówione w innym rozdziale. Wskazuję jedynie na istnienie tegoż.
Kolejnym bardzo ważnym elementem tej funkcji jest linia:
delete frm3;
niszczy ona cały obiekt, po zamknięciu okna. Oszczędza to pamięć.
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
Singleton pattern
Nie wiem czy określenie zawarte w tytule ma swój polski odpowiednik, jakkolwiek jest to
jeden z ważniejszych elementów programowania w języku C++ po linuxem. Na czym rzecz
polega ?? Singleton Pattern jest to pewien sposób na stosowanie zmiennych globalnych, czyli takich
widocznych do odczytu i zapisu w całym programie, w każdym jego miejscu. Używanie klas Qt
dość znacznie ogranicza możliwości używania klasycznych zmiennych globalnych. W dodatku
mechanizmy działania Qt Designera nie dają nam zbyt wielkiego pola do manewrów w tym
zakresie. Dlatego też będziemy używać czegoś co po amerykańsku nazywane jest Singleton Pattern.
Określenie pochodzi z książki "Effective C++" Scotta Meyersa. W internecie można znaleźć
setki przykładów użycia tego czegoś, nie ma jednak zbyt wiele na temat użycia Singletona w Qt.
Dlatego też w poniższej części opiszę do bólu prosty przykład.
Zanim to nastąpi przydało by się małe wprowadzenie do tego całego Singletonu. Co to w
ogóle jest i takie tam ?? Więc singleton to klasa, której podstawową cechą jest posiadanie tylko
jednej instancji. W ten sposób jeśli raz zapiszemy tam jakąś wartość będziemy ją mogli zawsze
odczytać. Tak więc pojedynczość tej klasy sprawia, że jest ona idealnie dostosowana do
przechowywania różnych wartości i zmiennych. W bardziej rozbudowanych programach za pomocą
singletonu można zapisywać i odczytywać konfigurację programu.
Wytężamy mózgownice i startujemy:
1.Utwórz projekt o nazwie 2form2.pro
2.Utwórz dwie formatki QDialog o nazwie form1 i form2
3.Utwórz plik main.cpp według opisywanego wielokrotnie wzoru.
4.Utwórz nowy plik nagłówkowy o nazwie zmienne.h
5.Utwórz nowy plik źródłowy o nazwie zmienne.cpp
Zaczniemy od pliku zmienne.h co powinien on zawierać ?? Z pewnością definicję klasy
oraz spis zmiennych jakie będziemy użytkowali. W naszym przykładowym programie
wykorzystamy tylko jedną zmienna, żeby sposób użytkowania singletonów był bardziej widoczny.
Zmienna będzie typu QString więc musimy dodać plik nagłówkowy: qstring.h. Całość kodu tego
pliku będzie wyglądała tak:
#include <qstring.h>
class Singleton {
// definicja klasy o nazwie Singleton jest
// to nazwa wlasna :)
public:
QString ciapek;
// definicja zminnej QString o nazwie ciapek
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
private:
Singleton();
Singleton(const Singleton&);
// konstruktor klasy
friend Singleton& Zmienne();
};
Singleton& Zmienne();
Słówko friend może się wydać dosyć dziwne. Oznacza ono, ze funkcja Zmienne() będzie
miała dostęp do części prywatnej (private) klasy Singleton.
Do dostępu do zmiennych będziemy używać funkcji Zmienne().
Plik zmienne.cpp:
#include "zmienne.h"
Singleton::Singleton() {
// konstruktor klasy nie używany :))
};
Singleton& Zmienne() {
// funkcja glowna singletonu
static Singleton zmienne;
return zmienne;
};
I na tym praktycznie kończy się cała sztuczka niesamowicie sprytna. Pokażę jeszcze tylko
z czym to się wszystko je. Aby to się dało jeść będziemy potrzebowali kilku komponentów
wizualnych, paru slotów i kilku sygnałów wg. poniższej specyfikacji:
Formatka główna form1:
trzy przyciski (QPushButton) o nazwach pb1, pb2 i pb3
jedno pole edycyjne (QLineEdit) o nazwie leCiapek :)
no i jedna labelka (QLabel), która nam powie co znajduje się w leCiapku :)))
Formaka druga form2:
dwie labelki (QLabel) o nazwach: tlCiapek i tlOpisLe
jeden przycisk pb1
i jedno pole edycyjne (QLineEdit) o nazwie leCiapek
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
Koncepcja działania programiku polega na wielokrotnym zapisywaniu różnych wartości do
zmiennej ciapek. Wartości te będziemy pobierać z pól edycyjnych dwóch różnych formatek.
Wybrałem ten sposób na opisanie tematu, ponieważ przy dwóch formatkach mamy do czynienia z
dwoma różnymi klasami. W ten sposób można będzie dość łatwo zauważyć, że zmienna z
singletonu jest ponad klasami.
form1.ui.h
#include "form2.h"
#include "zmienne.h"
void form1::init()
{
Zmienne().ciapek = "1st Step = progz run";
// w ten sposób zapisujemy wartości do zmiennej
// ciapek singletonu
leCiapek>setText( Zmienne().ciapek );
// w ten sposób odczytujemy wartości ze zmiennej
// ciapek singletonu
}
void form1::load_form2()
{
form2 f2;
f2.exec();
}
void form1::ciapek_save()
{
if ( leCiapek>text() != "" ) {
Zmienne().ciapek = leCiapek>text();
} else {
Zmienne().ciapek = "Ciapek byl pusty";
}
}
void form1::ciapek_refresh()
{
leCiapek>setText( Zmienne().ciapek );
}
form2.ui.h
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
#include "zmienne.h"
void form2::init()
{
tlCiapek>setText( "Zmienna globalna ciapek :) wyglada tak: " + Zmienne().ciapek );
}
void form2::ciapek_save()
{
if ( leCiapek>text() != "" ) {
Zmienne().ciapek = leCiapek>text();
} else {
Zmienne().ciapek = "Ciapek byl pusty";
}
tlCiapek>setText( "Zmienna ciapek :) wyglada tak: " + Zmienne().ciapek );
close();
}
Jak widać w ten sam sposób możemy się bawić ciapkiem w zupełnie innej klasie.
PS:
Instrukcja obsługi do programu:
1. W polu edycyjnym wpisz dowolny tekst, np. "My name is tom", wciśnij przycisk zapisz a
następnie nowe okienko
2. Jak widać treść ciapka została zapisana i zaprezentowana na labelce możesz teraz zmienić jej
treść wpisując w biale pole i zapisując :)))
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
Menu, popupmenu
Jak do tej pory tworzone przez nas aplikacje miały raczej charakter małych programików lub
raczej części składowych jakiegoś większego programu. Wszystkie okienka i formatki były
budowane przy użyciu klasy QDialog co ma pewne ograniczenia: nie pozwala na tworzenie menu.
Dlatego też teraz do stworzenia okienka wykorzystamy klasę QMainWindow.
Po uruchomieniu Qt Designera i stworzeniu projektu np. o nazwie menus.pro wybieramy
"File>New", a następnie Main Window. Spowoduje to uruchomienie dość dziwnego kreatora,
który automatycznie utworzy nam edytor tekstu. W omawianym przykładzie wykonamy Menu
własnoręcznie dlatego też proponuję zamknąć kreatora za pomocą przycisku Cancel. Następnie
zapisujemy wszystko.
Teraz trochę klikologii:
Klikamy prawym przyciskiem myszy na formie i wybieramy Add Menu Item:
Zmienimy teraz nazwę pierwszego menu, nadamy mu niezwykle oryginalną nazwę plik.
Aby tego dokonać należy dwukrotnie kliknąć na napis "Menu" i zacząć pisać. Zatwierdzamy
enterem. Po zatwierdzeniu menu powinno już działać przynajmniej w fazie projektowania.
Możemy teraz dwukrotnie kilknąć na napis new item i wpisać np. zakończ. Będzie to pierwsza
opcja jaką damy użytkownikowi programu. Dodanie nowego pola menu zatwierdzamy enterem.
Dodanie akcji
Jak zapewne niektórzy z was zauważyli po utworzeniu okna QMainWidget pojawiło się nam
nowe okienko ActionEditor. Każdy element końcowy naszego menu będzie tu widoczny jako akcja.
(Dobrym pomysłem będzie w tym miejscu zmiana nazw, akcji na pozbawione polskich krzaczków.)
Za pomocą znanego już nam przycisku (tu czerwona strzałka), który wywoła okienko z
połączeniami (connections) dodamy do naszego menusa akcję. Proponuję na sam początek
przypisać funkcję, która bez żadnego "ale" zamknie nasz program. W okienku połączeń
wybierzemy sygnał activated() i slota close().
Dodajemy plik main.cpp i możemy kompilować (qmake > make).
Za pomocą opisanych powyżej metod i kodu z lekcji 5 możemy w prosty sposób utworzyc
edytor tekstu. W tym celu upuszczamy na formę pole textEdit (i zmienimy nazwe na teEdytor) oraz
dodamy do menu Plik opcje otwórz, zapisz i np. nowy. Utworzymy również menu edycja i tu
wstawimy: wytnij, kopiuj, wklej i wstaw date. Aby program wygladał jakoś po ludzku dodamy
również menu pomoc z opcją o programie. Jeśli wszystkie akcje w edytorze akcji mają poprawne
(bez polskich znaków) nazwy to przechodzimy dalej.
Po pierwsze dodamy pliki nagłówkowe: qfiledialog.h, qmessagebox.h oraz qdatetime.h.
Następnie przejdziemy do pisania kodu. Pierwszą akcją jaką mamy w menu plik powinien być
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
nowy. Normalnie funkcja ta tworzy nowy projekt/plik w pamięci, pod warunkiem, że zawartość
pola edycyjnego nie zmieniła się. Tak więc musimy to sprawdzić:
void Form2::nowy()
{
if ( teEdytor>isModified() &&
(QMessageBox::question( this, "Informacja",
"Plik zostal zmieniony. Czy chcesz zapisac ??",
"&Tak", "&Nie",
QString::null, 0, 1 )) == 1
)
{
teEdytor>setText( "" );
teEdytor>setModified(FALSE);
}
}
Akcje (sygnały) otwórz i zapisz proponuję skopiować z lekcji czwartej, aby się za dużo nie
mędzyć. Dobrym pomysłem byłoby dodanie sprawdzania czy zawartość pola edycyjnego się
zmieniła od czasu zapisania, otworzenia lub utworzenia nowego pliku. Ma to olbrzymie znaczenie
gdyż jeśli tego nie zrobimy nasz biedny użytkownik może przez przypadek skasować sobie cały
dzień pracy. Nie będę tutaj opisywał tego z punktu widzenia kodu. Napiszę tylko krótki algorytm
działania naszego programu.
1. Użytkownik otwiera (lub tworzy nowy) plik.
2. W polu edycyjnym dokonuje jakichś zmian.
3. Program pyta się czy zapisać zmiany zanim wykona cokolwiek.
4. Jeśli user odpowie, że chce to program zapisuje zawartośc pola i wykona wskazaną operację.
5. Jeśli user odpowie, że nie chce to program skasuje zmiany wykonując wybraną operację.
6. Po wszystkim ustawimy wskaźnik modyfikacji pola na FALSE
Zabieramy się za menu edycja. Na początek najprostsze: dzisiajsza data:
void Form2::date()
{
QDate date = QDate::currentDate();
QString aDzis = date.toString ( Qt::LocalDate );
teEdytor>append( aDzis );
}
oczywiście, żeby kod zadziałał musimy tego slota podczepić do sygnału kliked pozycji wstaw datę
w menu. Dodanie obsługi copy/paste jest jeszcze prostsze ale polega na wykonaniu czegoś czego
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
jeszcze nie robiliśmy. Chodzi o to, że do tej pory odbiorcą wszystkich sygnałów w naszych
programikach była formartka, tym razem odbiorcą będzie edytor. Okienko ze slotami powinno więc
wyglądać mniej więcej tak:
Na koniec jeszcze dodamy obsługę about. Wstawimy tam, krótkiego messageboxa o autorze
programu:
void Form2::about()
{
QMessageBox::information( this, "Informacja",
"Autorem programu jestem ja GREAT LINUX PROGRAMMER :)",
"&OK",
QString::null, 0, 1 );
}
i nasz programik jest gotowy. Dodamy jeszcze mały bajer polskie znaki :)). Zasada wyświetlania
polskich znaków diakrytycznych polega na wskazaniu kodeka jaki ma zostać użyty przy
konwertowaniu tablicy znaków *char do QString. Wykonujemy to w funkcji init():
#include <qtextcodec.h>
void frm1::init()
{
QTextCodec::setCodecForCStrings( QTextCodec::codecForName("ISO88592") );
}
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
Toolbar
Początki tworzenia toolbara są takie same jak przy menu. W tym przykładzie posłużymy sie
programem z lekcji 10. Po otwarciu projektu w Qt Designerze drugi guzik na formie i Add Toolbar.
Do wykonania toolbara potrzebne będą nam obrazki na ikonki. Proponuje w tym celu
sięgnąc je stąd (images.tar.gz), żeby nie kombinować. Po sciągnięciu należy pliki rozpakować do
katalogu z projekt, tak aby w tym katalogu istniał podkatalog o nazwie .images.
Kolejną czynnością jaką musimy wykonać jest dodanie obrazków do zasobów programu.
Aby to uczynić podświetlamy dowolną akcję w edytorze akcji, najlepiej plikNowyAction i w
edytorze właściwości tej akcji wybieramy opcję iconSet. Klikamy [...] trzy kropki, następnie add.
Wchodzimy do katalogu .images (lub tam gdzie mamy obrazki do akcji toolbara) i zaznaczmy te,
które chcemy dodać. Po dodaniu wskazujemy ikonkę, która ma być przypisana do akcji
plikNowyAction. Postępujemy w ten sposób tak długo, aż wszystkie akcje, które chcemy umieścić
na toolbarze będą miały swoją ikonkę. Gdy wszystkie akcje będą miały ikonki wystarczy
przeciągnąć je na toolbar metodą drag&&drop.
Nasz notatnik już jest najpiękniejszy na świecie prawda ?? Dodamy mu jeszcze dwie opcje:
cofnij i powtórz. W tym celu w Action Edytorze tworzymy dwie nowe akcje inneCofnijAction oraz
innePowtorzAction. Przypisujemy do nich odpowiednie ikonki, a następnie w oknie dialogowym
connection przypisujemy do nich akcje cofnij undo, powtórz redo. W sposób analogiczny do
tego jaki tworzyliśmy funkcje kopiuj/wklej to jest wskazując jako odbiorcę sygnału pole tekstowe
teEdytor.
Dobrym pomysłem była by jeszcze zmiana właściwości text tych aktionsów na takie, które
będą reprezentatywne do pełninoych przez nie funkcji. Mówiąc po polsku cofnij to cofnij.
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
QTimer
Jedna z ciekawszych i przyjemniejszych klas () w programowaniu systemów
wielowątkowych. Użycie tej klasy daje programowi możliwość wykonywania paru czynności
niemalże jednocześnie tzn. wykonując jedną czynność program nie musi czekać na jej zakończenie i
może działać dalej. Do omówienia tego tematu posłużymy się prostym przykładem programu, który
aktualną godzinę. Użyjemy w tym celu: formatki (QDialog) o nazwie frmTime, dwóch przycisków
(QPushButton) pbStart i pbStop oraz jednej labelki tekstowej (QTextLabel) tlTime. Użyjemy także
klasy QTimer co zostanie omówione na przykładzie kodu. Całość powinna wyglądać tak:
Sloty. Tworzymy trzy sloty: starttimer(), stoptimer() i timerDo(). Dwie pierwsze łączymy z
analogicznymi przyciskami. Zapisujemy wszystko. Kod źródłowy tego programu:
#include <qdatetime.h>
#include <qtimer.h>
QTimer *timer;
// deklarowanie zmiennych: można tutaj jak i w
// Class Variabless w sekcji public
// w tym miejscu jest bardziej czytelne
void frmTime::init()
{
timer = new QTimer( this );
// inicjujemy zmienną timer, rodzicem będzie aplikacja
connect( timer, SIGNAL(timeout()), this, SLOT(timerDo()) );
// kodowe stworzenie połączenia między sygnałem, a funkcją
// w przypadku timera oznacza to, że za każdym razem jak timer osiągnie
// timeout czyli zakonczy wywolanie funkcji timerdo() funkcja ta zostanie
// wywolana.
}
void frmTime::timerDo()
{
QTime time = QTime::currentTime();
QString aTime = time.toString ( Qt::LocalDate );
tlTime>setText( aTime );
timer>start( 1000, TRUE );
// powyższa linijka powoduje zapętlenie funkcji timera czyli
// timeout powoduje wywolanie timerdo
}
void frmTime::starttimer()
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
{
timer>start( 1000, TRUE );
// uruchomienie timera
}
void frmTime::stoptimer()
{
timer>stop();
// zatrzymanie timera
}
Przygotowując tą lekcję natknąłem się na pewien problem jaki pewnie każdy będzie miał
przy tworzeniu oprogramowania z użyciem Qt Designera. Chcąc stworzyć jakąś zmienną, która ma
być widoczna w całej klasie formaki naturalnym odruchem jest zainicjowanie jej w funkcji
konstruktora klasy. W przypadku powyższego programu funkcja konstruktora wygląda tak:
frmTime::frmTime( QWidget* parent, const char* name, bool modal, WFlags fl )
: QDialog( parent, name, modal, fl )
{
if ( !name )
setName( "frmTime" );
pbStop = new QPushButton( this, "pbStop" );
pbStop>setGeometry( QRect( 160, 60, 101, 32 ) );
pbStart = new QPushButton( this, "pbStart" );
pbStart>setGeometry( QRect( 30, 60, 101, 32 ) );
tlTime = new QLabel( this, "tlTime" );
tlTime>setGeometry( QRect( 20, 20, 249, 20 ) );
languageChange();
resize( QSize(287, 120).expandedTo(minimumSizeHint()) );
clearWState( WState_Polished );
// signals and slots connections
connect( pbStart, SIGNAL( clicked() ), this, SLOT( starttimer() ) );
connect( pbStop, SIGNAL( clicked() ), this, SLOT( stoptimer() ) );
// tab order
setTabOrder( pbStart, pbStop );
init();
}
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
Funkcja ta znajduje się w pliku form2.cpp w katalogu .ui. Zaglądając do tego pliku
napotkamy na samej górze na napis ostrzegający, że wszystkie zmiany w tym pliku zostaną
utracone. Jak więc tworzyć obiekty widziane w całej klasie ?? Znalazłem dwa sposoby: pierwszy
został opisany w rozdziale pt: wymiana danych, drugi natomiast został przedstawiony tutaj. W obu
tych przypadkach wykorzystujemy funkcję init(), która ma za zadanie zastąpić funkcję konstruktora.
Jeśli natomiast chodzi o destruktora, to istnieje funkcja destroy(), która jest jego zamienimikiem.
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
Grafika od innej strony QPainter
Może się czasami zdarzyć, że będziemy chcieli coś namalować. Namalować linię, wykres,
kółko, obrazek lub może cos bardziej ambitnego np. symulację lotu pocisku. Wszystkie te rzeczy
będziemy mogli wykonać na pomocą bardzo rozbudowanej klasy QPainter. W tym tekście opiszę
jak z niej korzystać.
Na początek, jak zwykle proponuję uruchomić Qt Designera i wykonać wszystkie
standardowe kroki mające na celu utworzenie projektu z formatką klasy QWidget. Do obsłużenia
wszystkich funkcji slotów jakie przewidziałem w tej lekcji potrzebnych nam będzie 7 przycisków.
Jakkolwiek można umieszczać je na formie w momentach gdy będą omawiane poszczególne
elementy klasy QPainter.
DrawEllipse
Teraz będzie kawałek, całkiem przyjemnego kodu. Zaprezentuję również użycie funkcji
random, z małym tips trikiem. Cały ten tips polega na tym iż domyślnie funkcja random, służąca do
generowania liczb pseudolosowych losuje liczby z zakresu 0..RAND_MAX (co
najprawdopodobniej oznacza INT_MAX). Wygląda to tak:
int getRandom(int max)
{
return rand() / (RAND_MAX / max + 1);
}
Co to znaczy i jak działa ?? To co jest widoczne to to, że w parametrze wywołania tej
funkcji podajemy maksymalną liczbę, która uczestniczy w losowaniu. Wynikiem tej funkcji zawsze
będzie liczba mniejsza lub równa liczbie max. Aby móc korzystać z tej funkcji należy zainkludować
plik stdlib.h. Gdy uda nam się zaimplementować funkcję do generowania losowych liczb.
Będziemy mogli nacieszyć wzrok takim oto kodem:
void Form1::rysuj()
{
int x, y;
QPainter paint( this );
for (int i = 0;i<=10000; i++ )
{
x = getRandom( width() );
y = getRandom( height() );
paint.setPen( green );
paint.setPen( SolidPattern );
paint.setBrush( green );
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
paint.drawEllipse ( x, y, 20, 20 );
}
}
Działanie tej funkcji są myślę dość proste do zrozumienia. Druga linia QPainter paint(this)
służy do stworzenia zmiennej typu klasy QPainter. Funkcja konstruktora tej klasy wymaga podania
w swoim parametrze nazwę klasy jakiegoś komponentu wizualnego. W tym przypadku będziemy
rysowali na formie dlatego podałem this co w tym przypadku oznacza Form1. Kolejne linie to
pętelka, zapodanie liczb losowych do zmiennych x i y, przy czym jako wartość maksymalna podana
jest szerokość i wysokość okna. Funkcje setPen, setBrush i drawEllipse są raczej zrozumiałe. Jeśli
funkcję tą podepniesz to sygnału pushButton clicked to na ekranie ujrzysz przepiękny pokaz
rozmnażania się pierwotniaków.
Gradient i skala RGB
Fajnie jest machnąć czasem gradient, program z gradientem wygląda bardziej
profesjonalnie. Widać, że autor się zna na programowaniu. Zrobimy więc teraz parę gradientów:
void Form1::drawrgb()
{
QPainter paint( this);
for (int i=0;i<=255;i++)
{
paint.setPen( QColor (0, 0, i ) );
paint.drawRect ( 10+i, 10, 10, 10 );
paint.setPen( QColor (0, i, 0 ) );
paint.drawRect ( 10+i, 20, 10, 10 );
paint.setPen( QColor (i, 0, 0 ) );
paint.drawRect ( 10+i, 30, 10, 10 );
}
}
Ta sama prosta zasada, która pojawiła się przy elipsie. Tym razem rysujemy kilkadziesiąt
prostokątów, z których każdy ma o 1 stopień jaśniejszy kolor. W ten sposób osiągamy efekt
gradientu. Co jeszcze można wymodzić ?? Gradient na komponencie:
void Form1::rgbnap()
{
QPainter paint( pushButton1 );
for (int i=0;i<=255;i++)
{
paint.setPen( QColor (0, 0, i ) );
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
paint.drawRect ( i, 4, 10, 4 );
paint.setPen( QColor (0, i, 0 ) );
paint.drawRect ( i, 8, 10, 4 );
paint.setPen( QColor (i, 0, 0 ) );
paint.drawRect ( i, 12, 10, 4 );
}
}
Prawie nie różni się to od poprzedniego kodu. Tym razem jednak trzy paski zostaną
namalowane na przycisku.
Szkoła się kłania
Jeśli kiedyś, któraś szkoła lub uczelnia zapragnie nauczać programowania w Qt to na pewno
będzie tam takie zadania: zrób za pomocą Qt zegarek analogowy. Nic prostszego. Użyjemy do tego
celu programu zaprezentowanego w lekcji QTimer. Zamiast jednak wyświetlania czasu w postaci
tekstowej wykorzystamy czas. W moim wykonaniu funkcja timerDo wygląda tak:
void Form1::timerDo()
{
repaint();
QTime time = QTime::currentTime();
int s = time.second();
int m = time.minute();
int h = time.hour();
QPainter paint( this );
paint.setPen( QColor (0, 0, 0 ) );
int x2 = (width() / 2) + lround (240 * sin(s*2*M_PI/60));
int y2 = (height() / 2) + lround (240 * cos(s*2*M_PI/60));
paint.drawLine ( (width() / 2), (height() / 2), x2, y2 );
paint.setPen( QColor (0, 0, 255 ) );
x2 = (width() / 2) + lround (200 * sin(m*2*M_PI/60));
y2 = (height() / 2) + lround (200 * cos(m*2*M_PI/60));
paint.drawLine ( (width() / 2), (height() / 2), x2, y2 );
paint.setPen( QColor (60, 255, 60 ) );
x2 = (width() / 2) + lround (160 * sin((h+m/60)*2*M_PI/12));
y2 = (height() / 2) + lround (160 * cos((h+m/60)*2*M_PI/12));
paint.drawLine ( (width() / 2), (height() / 2), x2, y2 );
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
timer>start( 1000, TRUE );
}
I co ?? Ładnie ??
Lecimy dalej. Grafikujemy pliki.
Za pomocą tej klasy można również wyświetlać pliki. Zabawy jest z tym co nie miara,
ponieważ mamy dość dużą kontrolę nad tym co i jak jest wyświetlane. Dla przykładu
przygotowałem dwie funkcje:
void Form1::drawPix()
{
QPainter paint( this );
QImage *img = new QImage("/home/moux/natalia.jpg");
paint.drawImage(0, 0, *img, 0, 0, 1, 1, OrderedAlphaDither );
}
void Form1::drawpixbig()
{
QPainter paint( this );
QImage *img = new QImage("/home/moux/natalia.jpg");
paint.drawImage(QRect(0, 0, width(), height()), *img);
}
Obie te funkcje wyświetlają zdjęcie z pliku dyskowego. Pierwsza pokazuje to zdjęcie w
oryginalnych rozmiarach, druga dostosowuje je do rozmiarów okna. Oczywiście nie bawiłem się
tutaj w sprawdzanie czy plik istnieje i tego typu sprawy, które normalnie w programie należało by
uczynić.
PS. Rysować linie i kreski możemy w Qt na dwa sposoby ten tu omówiony i za pomocą klasy
QCanvas. Ta druga klasa jest bardziej złożona i zostanie omówiona w dziale Technik bardziej
zaawansowanych.
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
Mysz i klawiatura
W tej lekcji pobawimy się myszką i klawiaturą. Zaprezentuję trzy klasy QKeyEvent,
QMouseEvent i QCursor. Programik, który wykonamy w tej lekcji nic specjalnego nie robi no czym
za chwilę się przekonacie.
Rozpoczynamy programowanie jak zwykle od stworzenia projektu, np. qline.pro i zapisania
go w osobnym katalogu. Następnie dodajemy do programu dialog (QDialog) i dwie labelki
tekstowe.
QCursor
Klasę tą wykorzystamy do określenia pozycji kursora względem pulpitu. Pomiaru
dokonamy w funkcji timerDo() timera. Co zostało już omówione w lekcji poświęconej klasie
QTimer. Funkcja ta powinna wyglądać:
void Form1::timerDo()
{
textLabel2>setText( QString( "%1 , %2 " ).
arg(QCursor::pos().x()).
arg(QCursor::pos().y()) );
timer>start( 100, TRUE );
}
Prezentowany kod może wyglądać trochę szokująco, ale postaram się żeby tak nie było.
QString(%1)::arg(x) jest to funkcja konwertująca. Działa ona na podobnych zasadach do funkcji
printf znanej z języka C++. Dane podane w parametrze funkcji arg mogą mieć różne typy liczbowe
bądź znakowe, zwracana wartość zawsze będzie QString. QCursor::pos().x() tutaj mamy do
czynienia z pięknym kawałkiem kodu. Na czym polega jego piękno ?? Analizując ten kod krok po
kroku widzimy, że wartość zwracana przez funkcję pos() będącej funkcją klasy QCursor jest typu
klasowego QPoint. We wskazanym fragmencie programu interesuje nas tylko jedna część klasy
QPoint (klasa ta składa się z dwóch zmiennych x i y) tj. x. Dlatego też wykorzystujemy funkcję x()
do wyodrębnienia tej zmiennej.
Drugim zastosowaniem klasy QCursor jest zmiana kursora myszy. Wykonanie tego wygląda
następująco:
setCursor( QCursor::CrossCursor );
Zmiany tej dokonamy w dalszej części programu, kiedy będzie to uzasadnione.
QMouseEvent
Klasa QMouseEvent posiada dwie niesamowicie interesujące funkcje: mouseMoveEvent
( QMouseEvent *e ) i mousePressEvent( QMouseEvent *e ). FUNKCJE TE SĄ
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
PREDEFINIOWANE, podobnie jak init() i destroy(). Istnienie tych funkcji w kodzie programu
sprawia, że instrukcje umieszczone w tych funkcjach będą wykonywane zgodnie z ich
przeznaczeniem. Niesie to ze sobą pewne niebezpieczeństwo. Literówka w nazwie takiej funkcji,
nie będzie wykryta przez kompilator co może powodować problemy z odnalezieniem błędu.
Jakkolwiek zostaliście ostrzeżeni. Przykład zastosowania tych funkcji:
#include <qpainter.h>
QPoint startpoint;
QColor color;
void Form1::init()
{
color = black;
}
void Form1::mousePressEvent( QMouseEvent *e )
{
setMouseTracking(TRUE); // przechwytywanie myszy bez wcisnietych klawiszy.
setCursor( QCursor::CrossCursor ); // no i zmienimy cursor na inny
startpoint.setX( e>x() );
startpoint.setY( e>y() );
erase(); // nowe klikniecie = nowy rysunek
}
void Form1::mouseMoveEvent( QMouseEvent *e )
{
textLabel1>setText( QString( "%1 , %2 " ).arg(e>x()).arg(e>y()) );
QPainter paint( this );
paint.setPen(color);
paint.drawLine ( startpoint.x(), startpoint.y(), e>x(), e>y() );
}
Funkcja setMouseTracking pozwala nam na rysowanie po painterze formy bez konieczności
wciśnięcia klawiszy co jest ustawione w Qt domyślnie.
QKeyEvent
Hmmm, nie wiem czy jest sens tłumaczyć:
void Form1::keyPressEvent( QKeyEvent *k )
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
{
switch ( tolower(k>ascii()) ) {
case 'r':
color = red;
break;
case 'g':
color = green;
break;
case 'b':
color = blue;
break;
default:
color = black;
break;
}
}
Jak widać obsługa myszy i klawiatury jest w Qt banalnie prosta. Samo Qt robi wszystko za
nas.
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
QCanvas, a programowanie gier
Ten tytuł nie jest do końca adekwatny do zawartości tego tekstu. W poniższym przedstawię,
lapidarnie i dość skrótowo sposób w jaki posługujemy sie klasą QCanvas. Chciałbym przy okazji
napisać również dlaczego tą klasą się posługujemy ?? Klasa ta ma przede wszystkim jedną
niezastąpioną zaletę, nie jest odświeżana za każdym razem gdy jest przerysowywane okno (jak to
miało miejsce przy QPainter). Do tego dochodzi jeszcze jeden ważny czynnik, klasa tam ma takie
funkcje i takie klasy pokrewne, które sprawiają, że po tej klasie możemy: wędrować myszką
(obsługa zdarzeń myszy), wędrować pikselami i sprawdzać czy piksel jest czy go niema (w zasadzie
to możemy nawet sprawdzić czy w danym konkretnym miejscu na canvasie jest nasz wyrysowany
obiekt czy też nie).
W naszym programie wykonamy najprostszą czynność z wykorzystaniem klasy QCanvas.
Posłużymy się w tym celu Qt Designerem, więc chyba nie musze już tłumaczyć co trzeba zrobić
aby projekt doszedł do skutku. Powiem tylko, że w moim przykładzie wykorzystujemy klasę okna
QMainWindow.
Przy wykorzystaniu klasy QCanvas trzeba wiedzieć jedną rzecz. Klasa ta sama w sobie jest
niewidoczna, tak więc tworzenie na niej malunków będzie bezskuteczne dopóki nie skorzystamy z
innej klasy jaką jest QCanvasView. Wszystko będzie dobrze zobrazowane na poniższym kodzie,
jakkolwiek tą jedną rzecz należy zapamiętać żeby oszczędzić sobie impotenckich frustracji.
"Tako rzecze Zaratustra":
#include <qcanvas.h>
void Form1::init()
{
QCanvas *canvas = new QCanvas( this );
canvas>setBackgroundColor( Qt::black );
canvas>resize( width() 30, height() 30 );
// dostosowujemy do romiaru formatki
QCanvasView *cView = new QCanvasView( canvas, this );
setCentralWidget( cView ); // funkcja jest tylko w QMainWindow
cView>show();
QCanvasRectangle *kwadrat = new QCanvasRectangle( 20, 20, 20, 20, canvas );
kwadrat>setPen( QPen(green, 2) );
kwadrat>show();
QCanvasEllipse *elipsa = new QCanvasEllipse( 100, 100, 0, 4320, canvas );
// 4320 to kat zamkniecia kola = kat prawdziwy(270) * 16
elipsa>setX( 100 );
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
elipsa>setY( 100 );
elipsa>setBrush( QBrush(red, Qt::SolidPattern ) );
elipsa>show();
}
Co mówi nam ten kod ?? Co z tych enigmatycznych haseł powinno się zadomowić w
naszych mózgownicach ?? Przede wszystkim metodyka pracy z QCanvas, którą da się
usystematyzować w poniższych punktach:
1. Tworzymy klasę QCanvas (w moim przykładzie na całej formatce)
2. Tworzymy klasę QCanvasView i w konstruktorze łączymy ją z QCanvas oraz formatką
3. Podpinamy CanvasView do formatki (może to być też Layout wtedy canvasów może być kilka)
4. Tworzymy malunki
5. Robimy show()
Co dalej ?? Dalej jest jeszcze QCanvasItem. Klasa rozbudowująca QCanvas o funkcje takie
jak setActive(), setSelected(). Do tego mamy jeszcze obsługę zdarzeń myszy, o której tu nie
wspomniałem. Czy powinienem ??
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
Open GL w Qt Light Motif
Open GL jest to programowy interfejs do grafiki sprzętowej. Zawiera on około 120
odrębnych poleceń, które są używane do specyficznych obiektów i operacji wykorzystywanych w
interaktywnych trójwymiarowych aplikacjach. Open GL został zaprojektowany jako strumieniowy,
sprzętowoniezależny interfejs, który został zaimplementowany dla większości systemów
operacyjnych i platform sprzętowych. Możliwości tego interfejsu są olbrzymie, umożliwiają
tworzenie obiektów i dodawanie im życia. Jakkolwiek aby poruszać się po poleceniach Open GL,
należy bardzo dobrze znać matematykę. Macierze, kąty, obliczenia przy poruszaniu obiektami,
wszystko to wykracza poza ramy tej strony. W tym tekście zostanie zaprezentowany szablon do
dalszego udoskonalania. Prezentowany program wyświetli za pomocą Open GL prostą figurę i
będzie nią obracał w skali x,y,z.
Do poruszania się w świecie OpenGL stworzono w Qt specjalną klasę QGLWidget. Jest ona
swoistym połączeniem możliwości OpenGL z możliwościami Qt, klasy QWidget. Jak można się
domyślić będziemy reimplementować klase QGLWidget tworząc pod nową podklasę, posiadająca
pożądane funkcje OpenGL.
// glwidg.h
#ifndef GLWIDG_H
#define GLWIDG_H
#include <qgl.h>
#include <qtimer.h>
class GLKwad : public QGLWidget
{
Q_OBJECT
public:
GLKwad( QWidget *parent, const char *name );
~GLKwad();
protected:
void initializeGL();
void paintGL();
void resizeGL( int w, int h );
};
#endif
Zaprezentowane tutaj funkcje są funkcjami Qt. InitializeGL ustawia w OpenGL takie opcje
jak kontekst, listy obiektów, tło itp. Musi być wywołana przed funkcjami paintGL i resizeGL.
Funkcja resizeGL jest wykonywana za każdym razem gdy okno zmienia swój rozmiar. Wielkość
obiektów jest wtedy przeliczana, a ich rozmiar dostosowywany proporcjonalnie. PaintGL renderuje
właściwą scenę grafiki.
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
// glwidg.cpp
#include "glwidg.h"
GLfloat r;
QTimer *Timer;
GLKwad::GLKwad( QWidget *parent, const char *name )
: QGLWidget(parent, name)
{
r = 0.0;
glClearColor(0.0, 0.0, 0.0, 0.0);
initializeGL();
Timer = new QTimer( this, "Timer" );
connect( Timer, SIGNAL( timeout() ), SLOT( updateGL() ) );
Timer>start(10);
}
GLKwad::~GLKwad()
{
}
void GLKwad::initializeGL()
{
glClear(GL_COLOR_BUFFER_BIT);
}
void GLKwad::resizeGL( int w, int h )
{
glViewport( 0, 0, (GLint)w, (GLint)h );
glClear(GL_COLOR_BUFFER_BIT);
glLoadIdentity();
}
void GLKwad::paintGL()
{
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glLoadIdentity();
glRotatef( r, 1.0, 1.0, 1.0 ); // kazdy 1 odpowiada za jedna skale
glColor3f(1.0, 1.0, 1.0);
glBegin(GL_POLYGON);
glVertex2f(0.5, 0.5);
glVertex2f(0.5, 0.5);
glVertex2f(0.5, 0.5);
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
glVertex2f(0.5, 0.5);
glEnd();
glFlush();
r = 0.15;
}
Na powyższym przykładzie widać, że w funkcjach Qt wykonujemy standardowe polecenia
OpenGL. Ustawiamy tryb wyświetlania, czyścimy i rysujemy kwadrat. W przykładzie widać, że
stworzyliśmy Timer, którego sygnał timeout jest połączony ze slotem QGLWidget updateGL(). Slot
ten nie robi nic innego jak tylko wywołuje funkcję paintGL. Zwiększanie wartość "r" w każdym
cyklu Timera powoduje obracanie obiektu o 0.15 stopni w każdym z trzech wymiarów.
Plik formatki do tego programu wygląda tak:
// form.ui.h
#include "glwidg.h"
void Form1::init()
{
GLKwad *glob = new GLKwad(this, "");
setCentralWidget( glob );
}
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
Managery rozmieszczenia, progz bez Designera
Może się czasem zdarzyć tak, ze przy pisaniu aplikacji nie będziemy chcieli korzystać z Qt
Designera np. w sytuacji gdy większość klas będziemy reimplementować lub gdy stosowane klasy
nie są dostępne w Qt Designerze. W takiej sytuacji głównym problemem jest rozmieszenie tych
komponentów tak nie nachodziły na siebie i ich rozmieszczenie było estetyczne. Ponadto może być
problematyczne łączenie sygnałów, funkcji itp. W tym tekście zajmiemy się tego typu sprawami.
Managery rozmieszczenia
Aby ułatwić programistom układanie komponentów (np. QTextEdit, QPushButton) na
formatkach, dopasowywanie ich rozmiarów do zmieniających się rozmiarów formatek mamy w Qt
do dyspozycji trzy zastosowania: rozmieszczenie komponentów poprzez połączenie ich w jeden
widget, rozmieszczenie komponentów poprzez połączenie ich w jeden obiekt, oraz klasę
QSpacerItem pozwalającą na dostosowanie odstępów pomiędzy umieszczanymi komponentami.
Wszystkie te metody używane są z reguły łącznie tworząc tym samym przystępny system
zarządzania interfejsem.
Aby dokładnie wytłumaczyć zasadę ich działania, posłużę się przykładem. Dajmy na to, że
chcemy stworzyć proste okienko, które będzie posiadało pole tekstowe typu QLineEdit, labelkę
tekstową z opisem tego pola i do tego przycisk. Stworzenie takiego okienka w Qt Designerze zajęło
by pięć sekund. Jednak jeśli byśmy koniecznie chcieli mieć dostęp do np. konstruktora formy to
wykorzystanie designera nie będzie możliwe. Dlatego tez wykonamy to w prosty sposób ręcznie.
Wykonamy to wg. poniższego schematu:
Na powyższym schemacie widać co i gdzie rozmieścimy jednak wymaga to zapewne
wytłumaczenie, dlaczego np. w jednym przypadku używamy klasy QVBoxLayout, a w innym
QHBox. Odpowidź polega na tym, że QVBoxLayout jest klasą służącą do rozmieszczania
komponentów, jeśli podłączymy do niej jakieś nowo utworzone obiekty to będą one automatycznie
ustawiane wg. specyfiki QVBoxLayout (QHBoxLayout, naturalnie też). Klasy typu QHBox i
QVBox mają o większe możliwości i są prostsze w obsłudze. Dziedziczą funkcje po QWidget,
przez to mogą być rodzicami dla nowo tworzonych komponentów. Skraca to znacznie proces
tworzenia kodu.
Kodowanie
Zanim utworzymy projekt wg. omówionego schematu muszę podać parę faktów. Na
program do tej lekcji będą się składały trzy pliki. Plik main.cpp z treścią nie wiele (lub wcale), nie
różniącą się od tej tworzonej przez Qt Designera:
#include <qapplication.h>
#include "mform.h"
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
int main( int argc, char ** argv )
{
QApplication a( argc, argv );
mForm w;
w.show();
a.connect( &a, SIGNAL( lastWindowClosed() ), &a, SLOT( quit() ) );
return a.exec();
}
Do tego plik mform.h zawierający deklarację klasy mForm:
#ifndef MFORM_H
#define MFORM_H
#include <qwidget.h>
#include <qlayout.h>
class QLineEdit;
class QLabel;
class QPushButton;
class mForm: public QWidget {
Q_OBJECT
public:
mForm(QWidget * parent = 0, const char * name = 0, WFlags f = 0);
~mForm();
QLabel *textLab1;
QLineEdit *lineEd1;
QPushButton *pushButt1;
protected:
};
#endif
Oraz z pliku mform.cpp, w którym będzie zdefiniowany kod obsługi tworzenia tego okna:
#include "mform.h"
#include <qpushbutton.h>
#include <qlabel.h>
#include <qlineedit.h>
#include <qvbox.h>
#include <qhbox.h>
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
mForm::mForm(QWidget * parent, const char * name, WFlags f):
QWidget(parent, name, f)
{
setCaption( "Layout przykl" );
QVBoxLayout *vboxl_1 = new QVBoxLayout( this, 8, 8, "vboxl_1");
QHBox *hbox1 = new QHBox( this, "hbox1" );
hbox1>setMargin( 8 );
hbox1>setSpacing( 8 );
textLab1 = new QLabel("Tresc labelki", hbox1);
lineEd1 = new QLineEdit("Jakis tekst", hbox1);
vboxl_1>addWidget( hbox1 );
QHBoxLayout *hboxl_1 = new QHBoxLayout( vboxl_1, 8, "hboxl_1" );
QSpacerItem *space1 = new QSpacerItem(20, 20);
hboxl_1>addItem(space1);
pushButt1 = new QPushButton("Tekst przycisku", this);
hboxl_1>addWidget( pushButt1 );
QSpacerItem *space2 = new QSpacerItem(20, 20);
hboxl_1>addItem(space2);
}
mForm::~mForm()
{
}
Z przykładu od razu można dostrzec jedną bardzo istotną rzecz. Gdy rozmieszczamy
komponenty przy użyciu QHBox, korzystamy z tego, że jest to klasa Widget i możemy ją podać
jako rodzica dla okien potomnych (QLabel i QLineEdit to także okna). Natomiast w przypadku
QHBoxLayout wykorzystujemy funkcję addItem. Dodatkowo bardzo interesującą sprawą jest fakt,
że przy tworzeniu tych obiektów praktycznie nie definiujemy żadnych rozmiarów, ani pozycji
(jedyne co podajemy to w konstruktorze QVBoxLayout wartość odstępów pomiędzy
komponentami, a w konstruktorze QSpacerItem rozmiar minimalny). Wszystko robi za nas Qt.
Kompilacja
Utworzenie tego wszystkiego (tych trzech plików) stanowi już program. Jakkolwiek aby go
bezproblemowo skompilować przy pomocy narzędzi Qt musimy utworzyć plik projektu. Tworzymy
go za pomocą polecenia:
qmake project
qmake
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
make
Obsługa zdarzeń
Jeśli przebrnęliśmy przez ręczne tworzeni interfejsu to obsługa sygnałów interakcji z
użytkownikiem nie powinna być dla nas problemem. W deklaracji klasy musimy zadeklarować
slota jakiego będziemy używać:
class mForm: public QWidget {
Q_OBJECT
public:
mForm(QWidget * parent = 0, const char * name = 0, WFlags f = 0);
~mForm();
QLabel *textLab1;
QLineEdit *lineEd1;
QPushButton *pushButt1;
public slots:
virtual void addText();
protected:
};
Jak widać użyliśmy tutaj słowa kluczowego virtual, mówiąc prostymi słowami zapewniamy
sobie w ten sposób, że funkcja slota (przy reimplementacji klasy) będzie wywołana i wykonana
nawet w sytuacji gdy funkcja o takiej nazwie juz istnieje. W pliku mform.cpp w konstruktorze
funkcji (po utworzeniu przycisku) połączymy sygnał kliknięcia przycisku
connect( pushButt1, SIGNAL(clicked()), this, SLOT(addText()) );
z funkcją addText(), którą umieścimy w wyżej wymienionym pliku po definicji konstruktora:
void mForm::addText(){
lineEd1>setText("Hello...");
}
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
Jak napisać fornted ??
Jedną z najciekawszych klas Qt jest klasa QProcess. Pozwala ona na urchamianie poleceń
systemowych, uruchamianie programów i odbieranie wyniku zwracanego przez te programu. Tak
więc dzieki zastosowaniu klasy QProcess możemy w bardzo prosty sposób napisać nakładkę na
jakiś konsolowy program: co w systemie Linux będzie dość przydatne, gdyz wiele jest programów
systemowych, które wymagają od nas pamiętnia nazwy i składni polecenia. Zasotosowanie takie
nazywa się w terminologii komputerowej "Front End" i jest już dość popularne w przypadku takich
programów jak np. nmap.
W tym rozdziale zaprezentuję dwa zastosowania klasy QProcess. Pierwsze pozwoli nam na
uruchomienie i zamknięcie programu KEdit. Drugie na uruchomienie strony manuala i pobranie jej
do pola tekstowego. W obu przypadkach schemat działania jest bardzo zbliżony. Deklarujemy klasę
QProcess, tworzymy polecenie i ewentualną listę argumentów, uruchamiamy process.
#include <qprocess.h>
#include <qtextcodec.h>
#include <qapplication.h>
QProcess *kedProc;
void Form1::init()
{
kedProc = new QProcess( this );
kedProc>addArgument( "kedit" );
}
void Form1::keditrun()
{
if ( !kedProc>start() ) {
qDebug( "Blad podczas uruchamiania procesu" );
}
}
void Form1::keditkill()
{
kedProc>kill();
}
W przykładzie zaprezentowano sposób na uruchomienie zewnętrznego programu jakim w
tym przypadku jest KEdit. Uruchomienie programu konsolowego nie będzie się znacznie różniło,
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
jedyne co dodamy to slot readFromStdout(), który przechwyci wynik działania polecenie.
#include <qprocess.h>
#include <qtextcodec.h>
#include <qapplication.h>
QProcess *proc;
void Form1::doProc()
{
textEdit1>setText("");
proc = new QProcess( this );
proc>addArgument( "man" );
proc>addArgument( "3" );
proc>addArgument( "atoi" );
connect( proc, SIGNAL(readyReadStdout()), this, SLOT(readFromStdout()) );
if ( !proc>start() ) {
qDebug( "Blad podczas uruchamiania procesu" );
}
}
void Form1::readFromStdout()
{
QTextCodec::setCodecForCStrings( QTextCodec::codecForName("ISO88592") );
textEdit1>append( proc>readStdout() );
}
Możliwości operacji wykonywanych przy pomocy QProcess są w systemie Linux
olbrzymie, gdyż ilość aplikacji wykonywanych konsolowo jest w tym systemie ogromna. Z
powyższych dwóch przykładów widać również, że zastsowanie to jest bardzo proste. Tak więc w
tym przypadku dzięki Qt możemy ułatwić sobie życie. Nie tylko programisty, ale również
administratora systemu.
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
Technologia XML w Qt
O możliwościach XML można by napisać niejedną encyklopedią. Główną zaletą tego języka
jest jego olbrzymia skalowalność. Możwmy opisywać struktury i przechowywać dane w dowolny
wybrany przez nas sposób. W jakikolwiek sposób to uczynimy będzie to prawidłowe pod
warunkiem oczywiście, że zachowamy zamknięcie tagów i atrybutów. Dwa najbardziej znane
zastosowania XML to płatnik KEDU i np. RSS. W obu przypadkach standard został stworzony na
bazie XML.
O XMLu
Hmmm, no cóż rozpisywać się nie będę bo nie o to chodzi. Znajdziecie w necie wiele na
temat tego języka. Wiele z tych wiadomości zamota wam w mózgach, ale to nic. W końcu
połapiecie się w tym i wspólnymi siłami rozpracujemy Prokom, tak aby płatnik był nie tylko na
winzawieszacza :)
Aby zacząć cokolwiek na temat XML, musimy wiedzieć dwie rzeczy. Po pierwsze XML tak
jak HTML składa się ze znaczników. Znaczniki to np.:
<znacznik>
<element>wartosc</element>
</znacznik>.
Jakkolwiek to samo moglibyśmy osiągnąć poprzez zapis jednolinijkowy:
<znacznik element="wartosc" />
Różnica jest i jej nie ma :). Specyfikacja XML dopuszcza obie formy od użytkownika
zależy, którą wybierze. Jako programiści musimy umieć posługiwać się i jednym i drugim zapisem.
Do tego wszystkiego dochodzi jeszcze bardzo istotny fakt, specyfikacji i validacji XML. W
pierwszych liniach pliku xmlowego podajemy o jaką specyfikację chodzi. Na przykład w przypadku
RSS jest to:
<?xml version="1.0" encoding="iso88592"?>
<!DOCTYPE rss PUBLIC "//Netscape Communications//DTD RSS 0.91//EN"
"http://my.netscape.com/publish/formats/rss0.91.dtd">
Plik, do którego odnosi się ten adres zawiera opis elementów, ich format, itd. Przy
oprogramowywaniu własnych plików XML nie musimy się tym zajmować.
W obu tych przykładach wykorzystuję formatkę złożoną z QDialog, 4 pól lineEdit
(opisanych przy pomocy QLabel), jednego checkBoxa i trzech pushButtonów. Funkcje
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
wczytaj_next(), wczytaj_prev() i dodanie() są połączone ze slotami clicked() przycisków.
Podejście pierwsze Qt, XML i atrybuty
Na sam początek zajmiemy się obsługą pliku XML, którego struktura złożona jest z
atrybutów. Podejście takie jest prostsze z punku widzenia programisty, ponieważ mamy do
czynienia z mniejszą ilością gałęzi po, których musimy skakać. Do obsługi gałęzi XML
wykorzystamy technologię DOM.
Na samym początku oczywiście będziemy potrzebować plik XML. Proponuję utworzyć coś w
stylu:
<?xml version = '1.0' encoding = 'ISO88592'?>
<baza>
<osoba imie="Tomcio" studiuje="true" nick="moux"
nazwisko="Pielech" miejsce="Gorzów Wlkp." />
<osoba imie="Mariusz" studiuje="true" nick="kazio"
nazwisko="Kaczorek" miejsce="Świnoujście" />
<osoba imie="Monika" studiuje="false" nick="monia"
nazwisko="Kowalski" miejsce="Krakow" />
<osoba imie="Arkafiusza" studiuje="true" nick="moksik"
nazwisko="Nowak" miejsce="Poznań" />
</baza>
Prosta struktura, baza osób. Jak to teraz zaczytać do programu ??
W Qt mamy do dyspozycji takie klasy jak: QDomDocument, QDomElement i
QDomNode.QDomDocument, będzie w tym przypadku całym dokumentem, QDomElement w
pierwszym przypadku będzie odpowiadał za tag baza, a QDomNode to osoba. Zaczytanie tego do
programu będzie wyglądało tak:
#include <qdom.h>
#include <qfile.h>
#include <qtextstream.h>
QDomDocument drzewo;
QDomElement root; // cala baza
QDomNode osoba; // osoba
QDomElement ktos; // cala osoba
void Form1::init()
{
QFile baza( "baza.xml" );
baza.open( IO_ReadOnly );
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
drzewo.setContent( &baza );
baza.close();
root = drzewo.documentElement(); // baza
osoba = root.firstChild();
ktos = osoba.toElement();
wczytanie();
}
void Form1::wczytanie()
{
lineEdit1>setText(ktos.attribute("imie"));
lineEdit2>setText(ktos.attribute("nick"));
lineEdit3>setText(ktos.attribute("nazwisko"));
lineEdit4>setText(ktos.attribute("miejsce"));
if (ktos.attribute("studiuje") == "false" ){
checkBox1>setChecked( FALSE );
} else {
checkBox1>setChecked( TRUE );
}
}
Z tego zapisu (funkcja init()) widać, że ustawiamy osobę na pierwsze dziecko bazy.
Następnie ładujemy to do ktosia i możemy odczytywać atrybuty tych tagów. W ten sposób
zaczytujemy _tylko_ pierwszego luda do programu. W dalszej części programu dodamy opcję
przeglądania następnych osób (funkcje połączone z przyciskami):
void Form1::wczytaj_prev()
{
if ( !osoba.previousSibling().isNull() ) {
osoba = osoba.previousSibling();
ktos = osoba.toElement();
wczytanie();
} else {
osoba = root.lastChild();
ktos = osoba.toElement();
wczytanie();
}
}
void Form1::wczytaj_next()
{
if ( !osoba.nextSibling().isNull() ) {
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
osoba = osoba.nextSibling();
ktos = osoba.toElement();
wczytanie();
} else {
osoba = root.firstChild();
ktos = osoba.toElement();
wczytanie();
}
}
Tutaj podobnie jak w funkcji init(), ustawiamy sobie tego childa, którego chcemy i z niego
robimy wczytanie(). Jedyną nowinką w tym kodzie jest to co mamy w instrukcji if: !
osoba.nextSibling().isNull(). Polecenie takie mówi, że zanim przejdziemy do następnego rekordu
sprawdzamy czy nie jest on pusty i jeśli jest idziemy do pierwszego. Unikamy w ten sposób pustych
pól przy przewijaniu.
Jak dotąd analizując kod możemy zauważyć jedną rzecz. Dreptanie po rekordach odbywa się
za pomocą funkcji nextSibling i previousSibling. W większych programach i przy operacjach na
większych plikach funkcje te mogą być wykonane w pętli, co zautomatyzuje zaczytywanie
zawartości pliku.
Zapisanie/dodanie nowej pozycji. W tym przypadku po prostu dodajemy nowy element do
drzewa, a następnie zapisujemy przy pomocy strumienia.
void Form1::dodanie()
{
QDomElement nowy = drzewo.createElement ("osoba" );
nowy.setAttribute( "imie", lineEdit1>text() );
nowy.setAttribute( "nick", lineEdit2>text() );
nowy.setAttribute( "nazwisko", lineEdit3>text() );
nowy.setAttribute( "miejsce", lineEdit4>text() );
if ( checkBox1>isChecked() )
{
nowy.setAttribute( "studiuje", "true");
} else {
nowy.setAttribute( "studiuje", "false");
}
root.appendChild( nowy );
QFile baza( "baza2.xml" );
baza.open( IO_WriteOnly );
QTextStream ts( &baza );
ts << drzewo.toString();
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
baza.close();
}
Podejście drugie Qt, XML i wartosci
No cóż mamy i drugą opcję. Mocno zadrzewiony plik xml, taki rss dajmy na to. Wygląda on
podobnie do tego:
<?xml version = '1.0' encoding = 'ISO88592'?>
<baza>
<osoba>
<imie>Tomasz</imie>
<nick>moux</nick>
<nazwisko>Pielech</nazwisko>
<miejsce_ur>Gorzów Wlkp.</miejsce_ur>
<student>true</student>
</osoba>
<osoba>
<imie>Kazio</imie>
<nick>kazik</nick>
<nazwisko>Kaczorek</nazwisko>
<miejsce_ur>Warszawa</miejsce_ur>
<student>false</student>
</osoba>
</baza>
Odczyt danych z takiego pliku różni się tym od atrybutów, że skaczemy po QDomNode tak
długo, aż znajdziemy ta gałąź która nas interesuje. W takim przypadku możemy ją zaczytać do
QDomText i użyć w programie. Obsługa takiego pliku to już spory kawałek kodu:
#include <qdom.h>
#include <qfile.h>
#include <qtextstream.h>
QDomDocument drzewo;
QDomElement root; // cala baza
QDomNode osoba; // osoba
QDomElement osobaEl; // cala osoba
QDomNode daneOs; // same dane jedna linia
QDomText daneOsText; // zawartosc
void Form1::init()
{
QFile baza( "baza.xml" );
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
baza.open( IO_ReadOnly );
drzewo.setContent( &baza );
baza.close();
root = drzewo.documentElement(); // baza
osoba = root.firstChild();
osobaEl = osoba.toElement();
// qDebug( osobaEl.text() ); cala linia
wczytanie();
}
void Form1::wczytanie()
{
daneOs = osobaEl.firstChild() ; // imie
daneOsText = daneOs.firstChild().toText();
lineEdit1>setText( daneOsText.nodeValue() );
qDebug( daneOsText.nodeValue() );
daneOs = daneOs.nextSibling(); // nick
daneOsText = daneOs.firstChild().toText();
lineEdit2>setText( daneOsText.nodeValue() );
qDebug( daneOsText.nodeValue() );
daneOs = daneOs.nextSibling(); // nazwisko
daneOsText = daneOs.firstChild().toText();
lineEdit3>setText( daneOsText.nodeValue() );
qDebug( daneOsText.nodeValue() );
daneOs = daneOs.nextSibling(); // miejsce
daneOsText = daneOs.firstChild().toText();
lineEdit4>setText( daneOsText.nodeValue() );
qDebug( daneOsText.nodeValue() );
daneOs = daneOs.nextSibling(); // student
daneOsText = daneOs.firstChild().toText();
if ( daneOsText.nodeValue() == "true" ) {
checkBox1>setChecked( TRUE );
} else checkBox1>setChecked( FALSE );
qDebug( daneOsText.nodeValue() );
}
void Form1::wczytaj_prev()
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
{
if ( !osoba.previousSibling().isNull() ) {
osoba = osoba.previousSibling();
osobaEl = osoba.toElement();
// qDebug( osobaEl.text() ); // cala osoba
wczytanie();
} else {
osoba = root.lastChild();
osobaEl = osoba.toElement();
// qDebug( osobaEl.text() ); // cala osoba
wczytanie();
}
}
void Form1::wczytaj_next()
{
if ( !osoba.nextSibling().isNull() ) {
osoba = osoba.nextSibling();
osobaEl = osoba.toElement();
// qDebug( osobaEl.text() ); // cala osoba
wczytanie();
} else {
osoba = root.firstChild();
osobaEl = osoba.toElement();
// qDebug( osobaEl.text() ); // cala osoba
wczytanie();
}
}
Długie prawda ?? No długie chociaż ten kod nic specjalnego nie robi prócz zaczytywania
drzewa. Można to by było rozpracować za pomocą pętli ale wtedy nie był by tak widoczny
konsensus tego zastosowania. Analizując ten kod, szczególnie funkcję wczytanie() należy zwrócić
ogromną uwagę co jest czym. Sposób w jaki przechodzimy od Node do Text. Bierzemy pierwszy
element DomNode (daneOs) i konwertujemy pierwszy element tej gałęzi do tekstu, który ładujemy
do lineEdit.
Ok, moje Panie. Teraz hardcore zapisywanie tegoż, a właściwie dodawanie nowych
elementów. Dlaczego hardcore ?? Dlatego, że aby zaskutkowało musimy umieć poruszać się po
drzewie i to całkiem nieźle. Dodanie elementu do drzewa jest stosunkowo proste, jednak dodanie do
tej gałęzi a nie innej to jest już nie takie hop*siup.
void Form1::dodanie()
{
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
QDomElement elOsoba = drzewo.createElement( "osoba" ); //
root.appendChild(elOsoba);
QDomElement poz = drzewo.createElement( "imie" ); //
QDomText val = drzewo.createTextNode( lineEdit1>text() );
elOsoba.appendChild( poz );
poz.appendChild( val );
poz = drzewo.createElement( "nick" ); //
val = drzewo.createTextNode( lineEdit2>text() );
elOsoba.appendChild( poz );
poz.appendChild( val );
poz = drzewo.createElement( "nazwisko" ); //
val = drzewo.createTextNode( lineEdit3>text() );
elOsoba.appendChild( poz );
poz.appendChild( val );
poz = drzewo.createElement( "miejsce_ur" ); //
val = drzewo.createTextNode( lineEdit4>text() );
elOsoba.appendChild( poz );
poz.appendChild( val );
QFile baza( "baza5.xml" );
baza.open( IO_WriteOnly );
QTextStream ts( &baza );
ts << drzewo.toString();
baza.close();
}
Hehe, czytam sobie to teraz i ciągle mi się wydaje skomplikowane. No cóż funcka
createElement jest stosunkowo prosta do obczajenia. Chodzi ona w parze z funkcją appendChild. I
to jest właśnie gwóźdź programu, appendChild, które dodaje podgałąź (Node) tam gdzie jej
będziemy kazać. Stworzenie drzewa polega w takiej sytuacji na wywoływaniu funkcji appenChild
dla każdej gałęzi i podgałęzi naszego QDomDocument. Zapis jak w poprzednim przypadku
QTextStream.
Modyfikacja
Hmm, z tego tekstu wcale nie wynika jak dokonywać modyfikacji. No cóż zasada jest
prosta. Tak jak w dwóch powyższych programach dodawaliśmy nową gałąź tak teraz zmienimy
jedynie jej wybrane wartości i to dopiero zapiszemy (zmodyfikowany kod dotyczący XML i
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
atrybutów):
void Form1::dodanie()
{
ktos.setAttribute( "imie", lineEdit1>text() );
ktos.setAttribute( "nick", lineEdit2>text() );
ktos.setAttribute( "nazwisko", lineEdit3>text() );
ktos.setAttribute( "miejsce", lineEdit4>text() );
if ( checkBox1>isChecked() )
{
ktos.setAttribute( "studiuje", "true");
} else {
ktos.setAttribute( "studiuje", "false");
}
QFile baza( "baza2.xml" );
baza.open( IO_WriteOnly );
QTextStream ts( &baza );
ts << drzewo.toString();
baza.close();
}
Qt i SAX !!!
Bardziej szpanerskie podejście do XML o korzeniach podobno gdzieś w JAVIE. Główną
ideją (* special flower) takiego podejścia jest zaczytywanie wszystkiego jak leci z opcją
wykrywania końca znacznika. Jak będzie to wyglądało w naszym kodzie (2 przykład) ?? Hmm, na
początek małe zaciemnienie, aby korzystać z SAX musimy reimplementować klasę
QXmlDefaultHandler, chodzi po prostu o to, żeby wynik zaczytania poszczególnych tagów trafiał
tam gdzie my chcemy. Na poniższym przykładzie pokażę ładowanie do QString chociaż może to
byc oczywiście QListView czy QTable. Przykład składa się z plików parserek.h ("parser"
analizator składni) z deklaracją klasy, parserek.cpp z definicją funkcji i form.ui.h, gdzie to wszystko
wsadzamy do komponentu textEdit1:
// parserek.h
#include <qxml.h>
#include <qstring.h>
class sParser : public QXmlDefaultHandler
{
public:
bool startDocument();
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
bool startElement( const QString&, const QString&, const QString& ,
const QXmlAttributes& );
bool endElement( const QString&, const QString&, const QString& );
bool characters( const QString & ch );
QString daneImie();
private:
QString dane;
};
// parserek.cpp
#include "parserek.h"
bool sParser::startDocument()
{
return TRUE;
}
bool sParser::startElement( const QString&, const QString&,
const QString& ,
const QXmlAttributes& )
{
return TRUE;
}
bool sParser::endElement( const QString&, const QString&, const QString& )
{
return TRUE;
}
bool sParser::characters( const QString & ch )
{
dane += ch ;
return TRUE;
}
QString sParser::daneImie()
{
return dane;
}
// form1.ui.h
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
#include "parserek.h"
void Form1::init()
{
sParser handler;
QFile xmlFile( "baza.xml" );
QXmlInputSource source( &xmlFile );
QXmlSimpleReader reader;
reader.setContentHandler( &handler );
reader.parse( source );
textEdit1>setText( handler.daneImie() );
}
OK, rzucamy teraz okiem na wynik tego co taki program nam poczynił z pliczkiem:
Tłumaczenie tego programu nie jest chyba konieczne. Zaprezentowany tutaj kod to kolejna
pozycja w naszym katalogu "Light Motif". Zaprezentowana klasa sParser nie będzie się wiele
różniła w programach. Jedyne modyfikacje będziemy dokonywać na tych elementach wyjściowych,
zamiast QString dany cos innego. Pewnie też będzie trzeba zmienić obsługę startElement, tak aby
po znalezieniu elementu (3 QString) "xxx" komputer wykonał z nim jaką operację w stylu np.
wczytał dzieci tylko tego elementu.
Źródła programów w pobieralni (mały tips, aby program zaczytał plik baza.xml przy pomocy
ściezki podanej w sposób reletywny, powiniśmy odpalać program z konsoli lub zmienić ścieżkę na
bezwględną).
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
Programowanie w Pythonie z użyciem Qt
Dlaczego o tym piszę ?? Jeśli będziecie kiedyś chcieli używać komputera do jakichś
niestandardowych rzeczy np. w genetyce to bardzo prawdopodobne, że niezbędna wam będzie
znajomość języka Python. Na temat zalet tego języka nie będę się lepiej wypowiadał, są w tej
materii lepsi specjaliści ode mnie. Mój osobisty zachwyt wzbudziło w tym języku podobieństwo do
php, gdzie nie musimy się zbytnio martwić o typy danych i ewentualne konwersje. Ponadto python
ma chyba najwięcej bibliotek, służących do konkretnych zastosowań. Dla samego przykładu można
wskazać zastosowanie Pythona w Gimpie, Qt i zastosowaniach naukowych. Nas jednak w tym
tekście będzie interesować tylko Qt w Pythonie.
Chciałbym aby ten tekst był swoista zachętą do Pythona, dla osób które jeszcze nie miały
okazji się w to pobawić. Przy pisaniu bazowałem na tekście Alexa Fedosova: "Tutorial: Creating
GUI Application in Python with Qt", jakkolwiek nie jest to tłumaczenie słowo w słowo.
Co będzie nam potrzebne ??
Oprócz Qt i Qt Designera niezbędny będzie oczywiście Python i pakiety: PyQt oraz PyQt
devel. Aby dowiedzieć się jakie pakiety pythona posiadamy aktualnie w systemie proponuję
skorzystać z polecenia: "rpm qa | grep i "py"".
Co dalej ??
Podobnie jak w przypadku programowania z użyciem Qt Designera musimy pamiętać o
kolejności pewnych kroków, która jest niezbędna do wykonania programu:
1. Tworzymy GUI (ze slotami i kodem), najlepiej w Qt Designerze
2. Kompilujemy GUI i tworzymy kod Pythona za pomocą programu pyuic
3. Tworzymy plik główny programu, który będzie tworzył formę
4. Odpalamy programik za pomocą python nazwa.py
Pierwsze primo...
Tworzenie aplikacji rozpoczynamy od stworzenia GUI. W tym celu odpalamy Qt Designera.
Aby metodyka pracy z Qt w Pythonie, albo Pythonem w Qt była zrozumiała, program który
napiszemy będzie banalnie prosty. Po raz kolejny w tym kursie napiszemy "hello world". W Qt
Designerze tworzymy nową formatkę QDialog dodajemy do niej pushButtona, lineEdita i slota
witajcie(). Łączymy slota z sygnałem wciśnięcia przycisku. A do wygenerowanej funkcji wklejamy
kod:
void Form1::witajcie()
{
self.lineEdit1.setText( "Witajcie programisty" )
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
}
dla porównania ten sam kod w C++ wyglądał by tak:
void Form1::witajcie()
{
lineEdit1>setText( "Witajcie programisty" );
}
Jak widać pewne różnice są oczywiście, w końcu mamy do czynienia z innym językiem
programowania.
Drugie primo... kompilacja
W katalogu, w którym zapisaliśmy nasze pliki wygenerowane przez Qt Designera wydajemy
polecenie: "pyuic form1.ui > form1.py". Na niektórych Linuksach polecenie to powinno wyglądać
tak: "pyuic form1.ui o form1.py". W ten sposób wygenerowaliśmy sobie obraz kod tworzenia
okienka qt przy pomocy Pythona. Do poprawnego działania aplikacji jest to jednak za mało.
Musimy mieć jeszcze plik główny, który z tego kodu utworzy okienko i je uruchomi.
Trzecie primo... main.py
Na temat tego pliku powiem tylko jedno. W przypadku tworzenia aplikacji za pomocą Qt
Designera jego treść będzie praktycznie nie zmienne. Podobnie jak w przypadku C++, gdzie Qt
Designer tworzył za nas kod tego pliku. Zawsze był on prawie niezmienny. Treść jest następująca:
from qt import *
from form1 import *
import sys
if __name__ == "__main__":
app = QApplication( sys.argv )
f = Form1()
f.show()
app.setMainWidget(f)
app.exec_loop()
zmiany jeśli jakieś będziemy wykonywać to co najwyżej w nazwie pliku form1 i/lub nazwie
formatki. Plik ten jest ostatnim ogniwem w fazie projektowania programów w PyQt. Możemy teraz
zapisać ten plik pod dowolną nazwą i odpalić całość za pomocą polecenia "python main.py"
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
Myślę, że na dobry początek tyle wiadomości wystarczy. W następnych odcinkach napiszę
trochę więcej na temat poruszania się po klasach i slotach w PyQt, być może będzie też trochę na
temat Pythona w zastosowaniach. Any questions ??
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
Programowanie bazodanowe
Zanim zaczne opisywanie baz danych i ich obsługi w Qt, muszę zapodać wstęp na temat
tego co będzie nam potrzbne aby się tego nauczyć. Po pierwsze serwer baz danych zdalny lub
lokalny. Po drugie lib do Qt do obsługi baz danych. Będziemy się uczyć na podstawie MySQL więc
lib do obsługi MySQL w qt będzie w pakiecie qtMySQL. Przykładowa baza danych, którą
będziemy operować w tym tekście została utworzona za pomocą pliku PL/SQL:
# Tworzymy szkoleniowa bazke ludzie i wybieramy ja jako uzytkowa
#
create database `pipole`;
use pipole;
# Tworzymy tabelke lenie w bazie pipole
#
create table `lenie` (
`id` smallint(6) not null default '0',
`imie` varchar(20) not null default '',
`nazwisko` varchar(25) not null default '',
`adres` varchar(30) not null default '',
key `id` (`id`)
) type=MyISAM;
# Wstawimy cosik do tabelki;
#
insert into `lenie` values (0, 'Tomasz', 'Pielech', 'Gorzówek');
insert into `lenie` values (1, 'Wiaczesław', 'Mołotow', 'Moscow');
insert into `lenie` values (2, 'Endriu', 'Pornholio', 'XXX');
Dla tych co wolą klikanie w phpAdmin czy innych programach wspomagających
programowanie baz danych podaje strukturę tego co utworzą te wszystkie polecenia:
mysql> select * from lenie;
+----+------------+-----------+----------+
| id | imie | nazwisko | adres |
+----+------------+-----------+----------+
| 0 | Tomasz | Pielech | Gorzówek |
| 1 | Wiaczes aw | Mo otow | Moscow |
ł
ł
| 2 | Endriu | Pornholio | XXX |
+----+------------+-----------+----------+
3 rows in set (0.00 sec)
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
Oczywiście znajomość podstaw SQL będzie niezbędna do zrozumienia tego tekstu.
Startujemy...
Opisywany tutaj program będzie w dużej mierze bazował na programie opisanym w
dokumentacji do Qt z tą tylko różnicą, że opisany tu program zostanie wykonany metodą wizualną.
Za pomocą myszy i układania komponentów na formatce.
Zaczynamy od stworzenia projektu w Qt Designerze np. o nazwie mysql.pro, dodania pliku
formy (QMainWindow), oraz utworzenia pliku main.cpp. Następnie połozymy na formatce button i
komponent QDataTable (o nazwie table). Po tej ostatniej operacji uruchomi się nam kolejny dziwny
kreator. Proponuję go zamknąć, lub ewentualnie po eksperymentować sobie z nim. W omawianym
przykładzie nie będziemy korzystać z kreatorów. Aby dodać programikowi funkcjonalności
położymy na formatce jeszcze dwie labelki do opisu dwóch pól textEdit (lePassword i leUser).
W poniższym przykładzie przedstawię najprostszy sposób na napisanie aplikacji, która
pobierze sobie cosik z serwera baz danych MySQL. Lecimy po kolei. Na początku inkludujemy:
#include <qsqldatabase.h>
#include <qdatatable.h>
#include <qsqlcursor.h>
#include <qmessagebox.h>
#include <qsqlselectcursor.h>
Obsługa serwera sql ma to do siebie, że trzeba się z nim połączyć, wskazać co chcemy i jak
chcemy to robić. Dlatego też pierwsze linijki kodu wyglądają tak:
QSqlDatabase * db;
void Form1::init()
{
db = QSqlDatabase::addDatabase( "QMYSQL3" );
db>setDatabaseName( "pipole" );
db>setHostName( "localhost" );
table>setAutoEdit( FALSE );
}
kolejne linie oznaczają:
addDatabase wskazuje na sterownik/typ bazy danych jakiego będziemy używać
setDataBaseName to nazwa bazy danych, odpowiednik sqlowego use
setHostName to adres hosta z serwerem MySQL
Kolejnym krokiem będzie utworzenie slota. Proponuję nadać mu nazwę connect() i
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
dokopypastować do niego następujący kod:
void Form1::conenct()
{
db>setUserName( leUser>text() );
db>setPassword( lePassword>text() );
if ( db>open() ) {
QSqlSelectCursor* cur = new QSqlSelectCursor(QString::null, db );
table>setSqlCursor( cur, TRUE, TRUE );
cur>exec("SELECT * FROM lenie");
table>addColumn( "imie", "Imie" );
table>addColumn( "nazwisko", "Nazwisko" );
table>addColumn( "adres", "Adres" );
table>refresh();
} else {
QMessageBox::information( this, "ERROR", "Blad podczas laczenia");
}
}
Do zapytań obsługi sql w Qt przygotowano kilka klas np. QSqlCursor. Jakkolwiek ponieważ
w naszym przykładzie głównym celem jest wejście do bazy i załadowanie do tabelki tego co
chcemy więc wykorzystałem tutaj o wiele prostszą klasę QSqlSelectCursor. Myślę, że kod
powyższy jest zrozumiały i nie wymaga wytłumaczenia. W następnym tekście dodamy do
programu opcję wsadzania do bazy rekordu.
Po wykonaniu program powinien wyglądać mniej więcej:
Dodawanie i usuwanie rekordów wykonujemy poprzez użycie klasy QSqlQuery, do której w
funkcji exec możemy wsadzać dowolne zlecenia SQL. Zlecenia usuwania czy dodawania nie
zwracają żadnego wyniku więc możemy stosować je w połączeniu z funkcjonalnością klasy
QSqlSelectCursor. Zastosowanie obu będzie więc polegało na tym iż najpierw załadujemy dane do
tabeli przez kursor, potem usuniemy/dodamy jakiś rekord poprzez query, a następnie odświeżymy
widok tabeli przy użyciu refresh(). Kod, który to wykona:
void Form1::skasuj()
{
if ( (db>open()) && (lineEdit5>text() != "" ) ) {
QSqlQuery query(QString::null, db);
query.exec( "DELETE FROM lenie WHERE imie='"
+ lineEdit5>text() + "'" );
table>refresh();
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
}
}
void Form1::dodaj()
{
if ( db>open() ) {
QSqlQuery query(QString::null, db);
query.exec("INSERT INTO lenie(imie, nazwisko, adres) VALUES ('"
+ lineEdit6>text() + "','"+
+ lineEdit7>text() + "','"+
+ lineEdit8>text() + "')" );
table>refresh();
}
}
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
Programowanie sieciowe
// podziękowania dla Artanisa za pomoc przy testowaniu
W tym tekście zowstanie omówiona komunikacja między dwoma komputerami
wykorzystująca gniazda. Zanim jednak zacznę opisywac program, chciałbym zwrócić uwagę na
bardzo istotną rzecz. Transmisja gdy już nawiążemy połączenie jest wykonywana przy użyciu
strumieni QDataStream i QTextStream. Oznacza to w pierwszym przypadku gdy na taki sie
zdecydujemy, że możemy odczytywać dowolną ilość bitów i zaczytywać je do dowolnej struktuty
danych (protokół). W drugim przypadku uławia nam konwersje między typami danych.
Jako pierwszy program sieciowy proponuję napisanie prostego komunikatora internetowego.
Wykonaymy go przy pomocy Qt Designera według prezentowanego już wcześniej schematu. W
programie wykorzsyatmi dwa pola QTextEdit jedno z formatem tekstu ustawionym na RichText
gdzie będą się pojawiać nowe wiadomości, drugi z formatem PlainText skąd będziemy wysyłać
wiadomości. Do tego będzie nam potrzebne jeszcze jedno pole LineEdit, w którym będziemy
wpisywać adresy IP. Całość będzie obsługiwana dwoma przyciskami QPushButton, z których
pierwszy połączy nas z serwerem, a drugi wyśle treść jednego z pół textEdit.
Działanie programu będzie polegało na tym, że po uruchomieniu na dwóch różnych
maszynach będą się one mogły ze sobą połączyć i komunikować tj. jedna będzie mogła wysłać do
drugiej treść i na odwrot. Zasada działania mechanizmu odbioru opiera się na stworzeniu klasy
QServerSocket, z którą bądą się łączyć wszyscy klienci. Do połączeń wykorzystamy port 4242.
Połączenie spowoduje utworzenie gniazda ClientSocket (QSocket) odbierającego wszystkie
informacje wpływające na port 4242. Każde połączenie powodować będzie utworzenie nowego
ClientSocket. Obie klasy zadeklarowane zostały w osobynch plikach dla łatwiejszej nawigacji w
kodzie:
// server.h
#include <qserversocket.h>
#include <qapplication.h>
#include <qtextstream.h>
#include "client.h"
class SimpleServer : public QServerSocket
{
Q_OBJECT
public:
SimpleServer( QObject* parent=0 ) :
QServerSocket( 4242, 1, parent )
{
if ( !ok() ) {
qWarning("Otwarcie portu nie mozliwe, aplikacja zostanie zamknieta...");
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
exit(1);
}
}
~SimpleServer()
{
}
void newConnection( int socket )
{
ClientSocket *s = new ClientSocket( socket, this );
emit newConnect( s );
}
signals:
void newConnect( ClientSocket* );
};
Jak widać klasa QServerSocket została tutaj reimplementowana. Obsługujemy dzięki temu
metodę newConnection tej klasy, której to tworzymy nową instancję klienta. Klasa ta będzie miała
następującą postać:
// client.h
// klasa dla kazdego nowego klienta
#include <qsocket.h>
#include <qapplication.h>
#include <qtextstream.h>
class ClientSocket : public QSocket
{
Q_OBJECT
public:
ClientSocket( int sock, QObject *parent=0, const char *name=0 ) :
QSocket( parent, name )
{
line = 0;
connect( this, SIGNAL(readyRead()),
SLOT(readClient()) );
connect( this, SIGNAL(connectionClosed()),
SLOT(deleteLater()) );
setSocket( sock );
qDebug("Przylaczyl sie nowy klient");
}
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
~ClientSocket()
{
}
signals:
void logText( const QString& );
private slots:
void readClient()
{
QTextStream ts( this );
while ( canReadLine() ) {
QString str = ts.readLine();
emit logText( QString("> %1\n").arg(str) );
line++;
}
}
private:
int line;
};
Powyższy przykład prezentuje wykorzystanie funkcji readClient do odczytywania danych
wysłanych do serwera. Funkcja (slot) ta jest połączona z sygnałem klasy ClientSocket przez co jest
automatycznie wywoływana gdy dostaną się do portu dotrą jakieś dane. Po odebraniu danych są one
przesyłan do funkcji logText, którą wykorzystujemy w pliku formatki:
// form1.ui.h
SimpleServer *server; // serwer do odbioru
void Form1::init()
{
textEdit1>setTextFormat( Qt::PlainText );
server = new SimpleServer( this );
connect( server, SIGNAL(newConnect(ClientSocket*)),
SLOT(newConnect(ClientSocket*)) );
}
void Form1::newConnect( ClientSocket *s )
{
textEdit2>append( "Polaczono\n" );
connect( s, SIGNAL(logText(const QString&)),
textEdit2, SLOT(append(const QString&)) );
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)
}
void Form1::wyslij() // przycisk 2
{
QTextStream os(socket);
os << textEdit1>text() << "\n";
textEdit2>append( "<b>" + textEdit1>text() + "</b>");
textEdit1>setText( "" );
}
void Form1::polacz() // przycisk 1
{
socket = new QSocket( this );
socket>connectToHost( lineEdit1>text(), 4242 );
}
UWAGA !!!
Zmienna socket została dodana do pliku form1.h poprzez dopisanie w Object Explorerze w sekcji
Class>protected linii QSocket *socket. Zmienna ta musi być protected. Wykonana aplikacja to:
Kopiowanie kursu w całości lub w kawałku dozwolone pod warunkiem umieszczenia autorstwa
kursu (Tomasz Pielech) i podania adresu email autora (moux@post.pl)