1
Leszek Klich
PROGRAMOWANIE OBIEKTOWE W JĘZYKU C++
Multiplatformowa biblioteka QT
LICECJA :
Powielanie lub wykorzystywanie jest możliwe tylko w celach edukacyjnych.
Przy wykorzystywaniu dowolnych algorytmów lub kodów źródłowych z
niniejszego opracowania należy podać imię i nazwisko autora. Wyrażam zgodę
na wykorzystywanie niniejszych kodów do celów komercyjnych pod warunkiem
podania w informacji o programie imienia i nazwiska autora.
Jeśli chcesz wykorzystać dowolny tekst z opracowania na swojej stronie www –
skontaktuj się ze mną
leszek-klich@wp.pl
2
W niniejszym opracowaniu moim głównym celem było zwrócenie uwagi na
alternatywne narzędzie programistyczne. Jednym z najlepszych, według mnie, jest właśnie biblioteka
QT firmy Trolltech. Pisane za pomocą jej programy są praktycznie przenośne pomiędzy wieloma
systemami operacyjnymi (Windows/Linux/Unix/Mac OS). Biblioteka jest bardzo znana w systemie
Linux, lecz w systemie Windows większość programistów wykorzystuje narzędzia typu RAD w stylu
Visual Basic czy Delphi. Nie staram się przez to powiedzieć, że narzędzia te są niepraktyczne. Pragnę
jedynie zwrócić uwagę na istnienie narzędzi, które oferują pracę w zbliżonym do ideału dialekcie
obiektowego języka C++ a to jest w czasie obecnym niemal niespotykane.
3
1
1
.
.
C
C
e
e
l
l
n
n
i
i
n
n
i
i
e
e
j
j
s
s
z
z
e
e
g
g
o
o
o
o
p
p
r
r
a
a
c
c
o
o
w
w
a
a
n
n
i
i
a
a
Praca ta jest skierowana dla wszystkich znających podstawy programowania
proceduralnego w języku C++. Jej treść obejmuje wstępne zagadnienia i teorię
programowania obiektowego oraz praktyczne wykorzystanie wiadomości do budowania
okienkowych aplikacji. W pierwszej kolejności pozwala zapoznać się z mechanizmem i
filozofią programowania obiektowego. W kolejnych rozdziałach opisuje jedną z metod
programowania aplikacji przeznaczonych dla środowisk Windows oraz XWindow.
Krok po kroku opisuje najbardziej przydatne klasy i metody wybranej biblioteki
programowania a opis każdej z nich kończy się praktycznym przykładem. Do każdej z klas
dostępne są także tabele wraz z właściwościami obiektu oraz najważniejszymi metodami.
Dzięki takiemu podejściu niniejsza praca może służyć jako podręczny przewodnik po
elementach biblioteki programisty wykorzystywany podczas pisania programów.
Do niniejszej pracy dołączyłem także płytę CD. Umieściłem na niej bibliotekę QT,
niezbędne narzędzia oraz wszystkie programy w wersji binarnej oraz źródłowej. Muszę w
tym miejscu dodać, że opisywane w niniejszej pracy metody programistyczne nie są
standardowe. Unikam tutaj wykorzystywania nowoczesnych narzędzi typu RAD
1
służących
do budowy aplikacji i interfejsów użytkownika. Zamiast narzędzi tego typu skupiłem się na
„ręcznym” pisaniu szkieletu aplikacji. Takie podejście wymaga od programisty większego
zaangażowania i wiedzy, ale dzięki takiej metodzie można szczegółowo kontrolować rozwój
projektu. Oprócz kwestii typowo praktycznych, takie podejście programisty umożliwia
bezpośredni kontakt z kodem źródłowym aplikacji, co jest nie bez znaczenia dla
programistów wywodzących się z ruchu Open Source
2
i Linuksa.
W niniejszej pracy przedstawiłem bibliotekę języka C++ umożliwiającą pisanie
przenośnych aplikacji opartych o wygodny dialog z użytkownikiem za pomocą myszki oraz
klawiatury. Zdecydowałem się na bibliotekę QT rozprowadzaną wraz z kodem źródłowym,
gdyż w inne tego typu biblioteki, w obecnej fazie rozwoju nie pozwalają na wygodną i
stabilną pracę w systemach Windows. Jako dodatkowe kryterium, którym się kierowałem
była przenośność kodu pomiędzy platformami Windows oraz Linux. Biblioteka QT jest
dostępna bezpłatnie dla wszystkich. Jest standardowo instalowana w większości dystrybucji
1
RAD – (Rapid Application Development) oznacza program służący do szybkiego budowania aplikacji.
Przykładem może być pakiet Borland C++ Builder.
2
Open Source – idea polegająca na dostarczaniu wraz z aplikacją pełnego kodu źródłowego.
4
Linuksa. W tym systemie otrzymujemy ją wraz z kodem źródłowym. W środowisku
Windows biblioteka jest dostępna bezpłatnie pod warunkiem, że nie będzie wykorzystywana
do celów komercyjnych.
Poprzez tę pracę pragnę zgłębić sztukę programowania obiektowego w języku C++
oraz zapoznać się z możliwością jej wykorzystania w przyszłości. Niestety, choć do
produktów takich gigantów jak Borland czy Microsoft dostępne jest wiele wyczerpujących
opisów, książek oraz kursów, to w przypadku pozostałych pozostaje przeszukiwanie sieci
Internet, czytanie list dyskusyjnych i składanie w całość często anglojęzycznej i niepełnej
dokumentacji.
Moje badania dotyczą tylko jednej z wielu dostępnych bibliotek, co było
podyktowane ograniczonym rozmiarem pracy i ogromem niniejszego tematu. Na początku
starałem się przedstawić w skrócie model programowania obiektowego. Następnie
skoncentrowałem się na tworzeniu aplikacji za pomocą wybranej przeze mnie biblioteki.
Opisując ją będę starał się przedstawić jej zalety i wady, przydatność dla programisty oraz
względną stabilność w środowiskach Windows oraz Linux, co jest nie bez znaczenia przy
zastosowaniach komercyjnych. Oczywiście skupię się jedynie na najważniejszych cechach
tej biblioteki i postaram się scharakteryzować najważniejsze z dostępnych klas i metod. W
dalszej części pracy napisałem kilka programów za pomocą opisywanej biblioteki.
5
2
2
.
.
Z
Z
a
a
r
r
y
y
s
s
h
h
i
i
s
s
t
t
o
o
r
r
i
i
i
i
i
i
e
e
w
w
o
o
l
l
u
u
c
c
j
j
i
i
j
j
ę
ę
z
z
y
y
k
k
a
a
C
C
i
i
C
C
+
+
+
+
Od początku powstania komputerów, w wielu ośrodkach badawczych prowadzono
badania nad językiem programowania, który łączył by w sobie cechy języka doskonałego
3
.
Miał to być język jak najwyższego poziomu, w którym łatwo definiować algorytmy oraz
który przejawiał niskie zapotrzebowanie na pamięć i moc obliczeniową. Z drugiej strony
musiał być językiem na tyle niskiego poziomu, aby tworzone algorytmy były wykonywane
szybko i optymalnie. Z uwagi na wiele dostepnych modeli komputerów należało opracować
język uniwersalny, niezależny od maszyny, na którzym jest wykonywany. Uniezależnienie
języka od konkretnej maszyny gwarantowałoby sukces produktu. Niestety w praktyce
szybko przekonano się, że napisanie takiego języka nie jest w ogóle możliwe. W latach
siedemdziesiątych pojawił się jednak język, który bardzo zbliżył się do tego ideału. Językiem
tym był produkt o nazwie „C”. Pierwszy kompilator tego języka powstał w roku 1972.
Zdefiniował go Dennis M. Ritchie
4
z Bell Laboratories w New Jersey. Język ten opierał się
na języku “B” opracowanym dwa lata wcześniej przez Kena Thompsona
5
. Język B zaś
wywodził się z języka BCPL i powstał w University Mathematical Laboratories w
Cambridge w roku 1969. Język B i Asembler były językami, za pomocą których napisano
pierwszy system operacyjny UNIX. Choć język C ewoluował, to jednak pozostał tym samym
językiem programowania, co jego pierwowzór. Język ten znajdziemy w każdej dystrybucji
systemu UNIX oraz jego darmowych klonów takich jak LINUX. Jest to z założenia język
ogólnego przeznaczenia. Początkowo jednak miał służyć do pisania kompilatorów
i systemów operacyjnych. Nowsze kompilatory tego języka były dostarczane z bibliotekami,
które sprawiły, iż C może służyć w wielu dziedzinach informatyki. Choć na rynku były
dostępne inne języki programowania, to jednak przejrzyste struktury danych oraz prostota
wyrażeń sprawiła, że język ten długo dzierżył palmę pierwszeństwa na rynku kompilatorów.
Programowanie w samym języku C było jednak na tyle trudne, że nieliczne grono
programistów, znających język C, było wręcz rozchwytywane i doskonale opłacane przez
wielkie firmy. Inne języki programowania dostępne wówczas na rynku takie jak: Basic,
Fortran czy Pascal, choć prostsze w zastosowaniu, były zbyt mało uniwersalne. Działo się
tak dlatego, że przy ich tworzeniu kierowano się określonym z góry zastosowaniem.
3
B. W. Kernighan, D. Ritchie, Język C, Warszawa 1988, str. 9
4
Tamże, str. 9
5
Tamże, str. 9
6
Zarówno C jak i inne języki wykorzystywały programowanie strukturalne. Ten model
programowania opierał się na dzieleniu programu na funkcjonalne bloki, które miały za
zadanie wykonywanie określonych czynności. Dzięki temu program stawał się bardziej
czytelny w przypadku bardzo dużych i rozbudowanych programów. Programowanie
proceduralne miało jednak wady; uniemożliwiało łatwe przekształcanie już istniejących
programów w inne, a w przypadku gdy zaszła konieczność modyfikacji lub rozbudowy
istniejącego oprogramowania konieczne było pisanie nowych funkcji, zamiast efektywnego
wykorzystywania tych gotowych. Zaczęto więc poszukiwać innych, bardziej efektywnych,
technik programowania.
Istotną zmianę i zwrot w tej dziedzinie wprowadził Bjarne Stroustrup
6
pracujący w
Computer Science Research Center of AT&T Bell Labs in Murray Hill. Opracował on nowy
standard języka C i zmienił tym samym podejście do pisania programów. Tym standardem
było programowanie zorientowane obiektowo (Object Oriented Programming). Swój język
nazwał C++ wskazując, że jest to ulepszony język C. Model obiektowy zrewolucjonizował
pisanie programów komputerowych. Dzięki programowaniu zorientowanemu obiektowo
można było łatwo wykorzystywać stare programy do nowych funkcji przy pomocy
dziedziczenia czy polimorfizmu. Programy stały się bardziej odporne na błędy programisty
i tym samym bezpieczniejsze dzięki wykorzystaniu metod prywatnych. Składnia języka
obiektowego jest co prawda trudniejsza w implementacji, ale prostsza w wykorzystaniu
i dodatkowo bardziej czytelna. Programowanie obiektowe nakłada na programistę
konieczność myślenia o programach jako o typach danych, a później o operacjach
(metodach) specyficznych dla poszczególnych typów danych. Języki proceduralne zaś
odwracają priorytety i stosują szczególny nacisk na procedury, a dopiero potem na dane, na
których te procedury operują.
Sama koncepcja techniki programowania obiektowego pojawiła się w języku Simula
67. Był to język zaprojektowany z myślą o symulacji różnych procesów. Prawdopodobnie
programowanie obiektowe istnieje dzięki grupie naukowców, którzy pracowali nad
symulacjami zachowania się statków. Mieli oni kłopoty z opanowaniem wszystkich
zależności, jakie wywierały na siebie nawzajem wszystkie parametry statków podczas
symulacji. Wtedy wpadli na pomysł, aby pogrupować typy statków w różne klasy obiektów,
a każda z klas sama odpowiadałaby za określanie własnych danych i zachowań. Nowa
6
B. Stourstrup, Język C++, Warszawa 2002
7
koncepcja podejścia do programowania dopracowana w języku Smalltalk, stworzonym w
języku Simula w firmie Xerox PARC. Programiści zaprojektowli jednak w pełni dynamiczny
system, w którym obiekty mogą być tworzone i modyfikowane "w locie", a nie system
oparty na statycznych programach.
Programowanie obiektowe zyskało status techniki dominującej w połowie lat 80-tych,
głównie ze względu na wpływ C++, stanowiącego rozszerzenie języka
C
. Dominacja C++
została utrwalona przez wzrost popularności graficznych interfejsów użytkownika (GUI), do
tworzenia których programowanie obiektowe nadaje się szczególnie dobrze.
Najważniejsze cechy programowania obiektowego to:
Abstrakcja
Każdy obiekt w systemie służy jako model abstrakcyjnego "wykonawcy", który może
wykonywać pracę, opisywać i zmieniać swój stan oraz komunikować się z innymi
obiektami w systemie bez ujawniania, w jaki sposób zaimplementowano dane cechy.
Procesy, funkcje lub metody mogą być również abstrahowane, a kiedy tak się dzieje,
konieczne są rozmaite techniki rozszerzania abstrakcji.
Enkapsulacja (ukrywanie implementacji, hermetyzacja)
Zapewnia, że obiekt nie może zmieniać stanu wewnętrznego innych obiektów w
nieoczekiwany sposób. Tylko wewnętrzne metody obiektu są uprawnione do zmiany
jego stanu. Każdy typ obiektu prezentuje innym obiektom swój "interfejs", który
określa dopuszczalne metody współpracy. Pewne języki osłabiają to założenie,
dopuszczając pewien poziom bezpośredniego (kontrolowanego) dostępu do
"wnętrzności" obiektu. Ograniczają w ten sposób poziom abstrakcji.
Polimorfizm
Referencje i kolekcje obiektów mogą dotyczyć obiektów różnego typu, a wywołanie
metody dla referencji spowoduje zachowanie odpowiednie dla pełnego typu obiektu
wywoływanego. Jeśli dzieje się to w czasie działania programu, to nazywa się to
późnym wiązaniem lub wiązaniem dynamicznym. Niektóre języki udostępniają
bardziej statyczne (w trakcie kompilacji) rozwiązania polimorfizmu – na przykład
szablony i przeciążanie operatorów w C++.
8
Dziedziczenie
Porządkuje i wspomaga polimorfizm i enkapsulację dzięki umożliwieniu
definiowania i tworzenia specjalizowanych obiektów na podstawie bardziej
ogólnych. Dla obiektów specjalizowanych nie trzeba redefiniować całej
funkcjonalności, lecz tylko tą, której nie ma obiekt ogólniejszy. W typowym
przypadku powstają grupy obiektów zwane klasami, oraz grupy klas zwane
drzewami. Odzwierciedlają one wspólne cechy obiektów
7
.
Większość nowoczesnych języków programowania posiada zaimplementowaną
możliwość wspierania programowania obiektowego. Nawet takie produkty, znane głównie
z prostoty i proceduralności jak BASIC czy Pascal, dobrze radzą sobie z obiektami. Niestety
jednak samo dodanie obiektowości do języków, które pierwotnie nie były do niej
przystosowane zrodziło szereg problemów z kompatybilnością. Z kolei "czysto" obiektowym
językom brakowało cech, z których programiści przyzwyczajeni byli korzystać. By zapełnić
tę lukę podejmowano liczne próby stworzenia języków obiektowych dostarczających
jednocześnie "bezpiecznych" elementów proceduralności. Eiffel Bertranda Meyera był
wczesnym przykładem w miarę udanego języka spełniającego te założenia; obecnie został on
w zasadzie całkowicie zastąpiony przez język Java, głównie za sprawą pojawienia się
Internetu, dla którego język ten dostarcza szeregu użytecznych funkcji. Dzisiaj chyba trudno
wyobrazić sobie sieć bez wsparcia tego języka. Muszę dodać, że Java, jak większość
dzisiejszych języków programowania, wywodzi się z języka C, co można zauważyć w ich
bardzo zbliżonej składni.
7
Encyklopedia Wikipedia, pl.wikipedia.org
9
3
3
.
.
N
N
a
a
r
r
z
z
ę
ę
d
d
z
z
i
i
a
a
i
i
k
k
o
o
m
m
p
p
i
i
l
l
a
a
t
t
o
o
r
r
Przygotowanie pakietu MS Visual C++ do współpracy z QT.
Przed kompilacją programów napisanych przy pomocy biblioteki QT, należy
przygotować kompilator do korzystania z dodatkowych plików nagłówkowych oraz
bibliotek. Z menu Tools należy wybrać opcję Options. Z listy rozwijanej Show directories
for: wybrać Include files. Konfiguracja polega na dodaniu do listy katalogów z plikami
nagłówkowymi: C:\Qt\include
Następnie należy podać katalogi bibliotek. Z listy rozwijanej Show directories for: wybrać
Library files i dodać katalogi: C:\Qt\lib Po zakończeniu należy klikniąć klawisz OK, aby
zapisać zmiany. Mamy już możliwość kompilacji programów do pliku pośredniego *.obj.
Aby móc korzystać ze zintegrowanych makr QT należy z menu Tools wybrać opcję
Customize.
Rysunek 1
W oknie Customize należy zaznaczyć opcję QmsDev Developer Studio-Add-In i kliknąć
przycisk Close. Szczegóły konfiguracji ilustruje Rysunek 1.
Rysunek 2
Po zakończeniu konfiguracji na pasku narzędzi pojawią się nowe opcje widoczne na obrazku
nr 2.
10
Kompilator Borland C++
Pakiet instalujemy w wybranym katalogu, np. c:\Bcc55. Następnie do zmiennej
środowiskowej PATH należy dodać ścieżkę dostępu do katalogu c:\Bcc55\bin. W przypadku
systemu Windows 9x, w pliku Autoexec.bat nalezy dopisać na końcu:
set PATH=%PATH%;c:\bcc55\bin
W przypadku Windows 2000/XP we właściwościach systemu (Właściwości systemu ->
Zaawansowane -> Zmienne środowiskowe): należy dodać zmienną środowiskową dla
aktualnego użytkownika:
Nazwa zmiennej: PATH
Wartość zmiennej: %PATH%;c:\bcc55\bin
Konfiguracja pakietu
Aby Borland C++ Compiler 5.5 poprawnie funkcjonował potrzebne są tekstowe pliki
konfiguracyjne: bcc32.cfg i ilink.cfg wskazujące położenie plików nagłówkowych
i bibliotek.
Oto ich przykładowa zawartość:
bcc32.cfg
-I"c:\Bcc55\include"
-L"c:\Bcc55\lib;c:\Bcc55\lib\psdk"
ilink32.cfg
-L"c:\Bcc55\lib;c:\Bcc55\lib\psdk"
Pliki te należy utworzyć w dowolnym edytorze tekstu i zapisać w katalogu bin. Oczywiście
w miejsce przykładowych katalogów należy wstawić właściwe. Standardowo Borland C++
5.5 generuje kod wykonywalny, przeznaczony dla Windows 2000. Z uwagi na istniejące
różnice pomiędzy systemami Windows 95/98/Me a systemem Windows 2000, tak
wygenerowany kod może nie działać pod Windows 95/98/Me. Aby uniknąć tego problemu,
wystarczy przy kompilacji użyć parametrów:
-DWINVER=0x0400
-D_WIN32_WINNT=0x0400
11
Dla ułatwienia pracy najlepiej powyższe linie dopisać do utworzonego pliku
konfiguracyjnego bcc32.cfg.
W przypadku dołączenia dodatkowych bibliotek lub plików nagłówkowych w katalogach
innych niż powyższe, zawartość plików konfiguracyjnych należy oczywiście zmienić. Na
tym kończy się konfiguracja kompilatora - program jest gotowy do pracy.
Standardowa kompilacja wygląda tak:
bcc32 plik1.cpp plik2.cpp plik3.cpp
bcc32 plik1.cpp biblioteka.lib
Poniżej przedstawiam wybrane parametry bcc32:
•-tW - tworzenie pliku docelowego EXE jako aplikacji w API WIN32
•-tWC - tworzenie pliku docelowego EXE jako aplikacji konsolowej
•-tWD - tworzenie biblioteki DLL
•-3 - kompilowanie z użyciem instrukcji 80386 (domyślnie)
•-4 - kompilowanie z użyciem instrukcji 80486
•-5 - kompilowanie z użyciem instrukcji Pentium
•-6 - kompilowanie z użyciem instrukcji Pentium Pro
•-Ixxx - dodanie ścieżki z plikami nagłówkowymi
•-Lxxx - dodanie ścieżki z plikami bibliotek
•-c - tylko kompilacja plików
•-exxx - nazwa docelowego pliku wykonywalnego
•-Ox - wybór poziomu optymalizacji kodu wynikowego
•-P - kompilacja programu w C++, bez względu na rozszerzenia plików źródłowych
12
4
4
.
.
G
G
r
r
a
a
f
f
i
i
c
c
z
z
n
n
y
y
i
i
n
n
t
t
e
e
r
r
f
f
e
e
j
j
s
s
u
u
ż
ż
y
y
t
t
k
k
o
o
w
w
n
n
i
i
k
k
a
a
(
(
G
G
U
U
I
I
)
)
Dzisiejsze
komputery
wyposażone są w doskonałe karty graficzne, dużą ilość
pamięci RAM, duże pojemności dysków twardych, przestrzenny dźwięk, napędy optyczne
oraz szereg innych urządzeń. Jednak nie moc obliczeniowa a coraz łatwiejsza obsługa
komputera sprawiła, że stały się one tak popularne. Już wcześniej budowano
superkomputery, które służyły uniwersytetom i wielkim firmom. Niestety jednak obsługa ich
była na tyle skomplikowana, że informatycy je obsługujący uważani byli za komputerowych
guru. Obsługa takiego komputera odbywała się w trybie znakowym, za pomocą mnóstwa
poleceń wspieranych przez wiele dodatkowych parametrów. Sprawiało to, że praktycznie
nikt z ulicy nie miał dostępu do komputera i nie robiono wiele w celu poprawy tej sytuacji.
Z czasem pojawienia się języków obiektowych, zaczęto tworzyć programy zawierające
przyjazne interfejsy komunikacji z użytkownikiem. Programy dalej działały w trybie
znakowym, ale ich obsługa stawała się intuicyjna i nie trzeba było uczyć się już składni
poleceń. Parametry wywołania programu zastąpiono funkcyjnymi elementami zwanymi
widokami. Widoki zaś umieszczane są na formatkach. Formatki zaś, to po prostu dzisiejsze
okienka.
Widoki można podzielić na kilka tych najważniejszych:
•menu
•paski narzędzi
•pole edycyjne
•etykieta
•pole wyboru
•przełączniki radiowe
•pola tekstowe
Można powiedzieć, że GUI
8
, to wizualne sterowanie programem oraz wizualizacja wyników
tego programu. Gdy zdano sobie sprawę z możliwości i potęgi GUI, wiele firm wyposażało
swoje komputery w systemy operacyjne oparte o tryby graficzne. Korzenie graficznego
interfejsu użytkownika sięgają lat 50. W praktyce został on jednak zbudowany w latach 70.
8
Graficzny Interfejs Użytkownika (ang. Graphical User Interface)
13
przez grupę naukowców z Palo Alto Research Center (PARC) Xeroksa zbudowała pierwszy
komputer o nazwie Alto działający pod kontrolą takiego graficznego interfejsu. W 1979 r.
ośrodek PARC odwiedził Steve Jobs, który w Alto dostrzegł przyszłość komputerów
osobistych. Choć spora część interfejsu w komputerach Lisa i Macintosh wykorzystywała
dokonania dokonano z ośrodka PARC to duża część Mac OS-a powstała jeszcze przed
wizytą Jobsa w laboratoriach Xeroksa. Jako przykład dużej roli jaką w naszym życiu
odgrywa owe tajemnicze GUI przypomnę, że zarówno firma Apple (MacOS), jak i Microsoft
(Windows) zaczerpnęły ideę graficznego interfejsu od Xeroksa. Choć niektórzy pamiętają
jeszcze system operacyjny DOS i jego polecenia, to jednak pamiętanie jego poleceń nie jest
już konieczne, szczególnie w dobie Windows XP, gdzie powłoka DOS jest jedynie
emulowana.
W niniejszej pracy jednak nie będę się skupiał na jednym systemie
operacyjnym, ale zajmę się także systemem Linux, ze względu na to, że właśnie na tę
platformę powstało wiele bezpłatnych lub tanich, co nie oznacza gorszych, bibliotek GUI.
Mnie interesowały biblioteki, które cechuje uniwersalność oraz przenośność kodu pomiędzy
platformami. Poniżej przedstawiam zrzuty ekranu przedstawiające interfejsy użytkownika z
dwóch bibliotek: GTK, QT.
Gtk+
Niezwykle popularną biblioteką w systemie Linuks jest GTK+. Choć aplikacje napisane za
pomocą tej biblioteki standardowo pracują w macierzystym systemie Linux, to jednak, w
obecnej wstępnej co prawda fazie rozwoju, źródła można skompilować w systemie Windows
w języku C. Biblioteka GTK jest napisana w języku C jako obiektowo zorientowane API.
Biblioteka oparta jest na bibliotece GDK (Gimp Drawing Kit), która z kolei operuje na
najniższym poziomie XLib w przypadku XWindow. Biblioteka zyskała rozgłos dzięki
Window Managerowi GNOME (the GNU Network Model Environment), który powstał
na podstawie bibliotki.
14
Rysunek 3
Biblioteka oprócz standardowych funkcji oferuje także narzędziami służącymi do
wizualnego tworzenia interfejsów użytkownika. Jednym z najlepszych jest Glade (rysunek
4), który udostępnia wygodny interfejs.
15
Rysunek 4
Narzędzie Glade pomaga w łatwy i szybki sposób zaprojektować okna
dialogowe. Zastosowane zostały tutaj rozwiązania podobne do tych znanych z wielu narzędzi
typu RAD dla Windows – standardowa paleta z narzędziami, pełna kontrola nad
właściwościami każdego obiektu, edycja każdego parametru, itp. Oprócz tworzenia kodu
źródłowego dla przygotowanych okien dialogowych Glade pozwala też oczywiście na
przygotowanie całego projektu z niezbędnymi dodatkami w rodzaju standardowego pliku
makefile czy configure. Biblioteka GTK ma wiele specyficznych cech. Jedną z nich jest to,
że nie wykorzystuje standardowych bibliotek API z danego systemu operacyjnego
i rodzimych widgetów. Zamiast tego biblioteka oferuje własny interfejs graficzny. Dzięki
temu można dowolnie zmieniać wygląd programu napisanego w GTK poprzez stosowanie
tzw. tematów (ang. themes). Jest to nieco podobne do interfejsu graficznego systemu
Windows XP, z tą różnicą, że GTK jest dużo bardziej elastyczna. Architekturę biblioteki Gtk
przedstawia rysunek 5.
16
Rysunek 5
Biblioteka Gtk jest standardowo przystosowana do obsługi wielu języków.
Dzięki internacjonalizacji pisanie wielojęzycznych aplikacji jest bardzo łatwe. Niestety
posiada bardzo poważną wadę. W obecnej wersji przeznaczonej dla Windows, podczas
kompilacji występuje wiele problemów. Standardowa kompilacja nie powiedzie się, jeśli nie
dołączymy do linkera dodatkowych bibliotek gtk oraz gdk. Dodatkowo, do poprawnej pracy
programu wykonywalnego niezbędny jest rozbudowany pakiet uruchomieniowy (tzw.
runtime), który należy zainstalować przed uruchomieniem aplikacji napisanej przy uzyciu
Gtk. Programowanie przy pomocy tej biblioteki w środowisku Linuks jest niezwykle proste
i przejrzyste.
W przypadku systemu Windows jest na dzień dzisiejszy problematyczne. Jest to
biblioteka, która powstała w środowisku Linuks i dla tego środowiska jest przeznaczona.
Wielu programistów pracuje nad przeniesieniem źródeł na okienka Microsoftu i to z dużym
powodzeniem, czego najlepszym przykładem jest sztandarowy program graficzny GIMP
9
do
niedawna dostępny tylko w systemie Linuks, teraz dostępny także dla użytkowników
Windows. Przyszłość biblioteki wydaje się być stabilna i niezachwiana. Szybki jej rozwój
spowodował opracowanie specjalnych, bibliotek umożliwiających pisanie programów
9
Gimp (GNU Image Manipulation Program). Darmowy program graficzny podobny do Adobe Photoshopa.
17
w C++ czy Perlu.
Biblioteka QT
Biblioteka napisana została przez grupę Trolltech
10
w wersjach dla Windows,
Linuxa, Solarisa, HP-UX oraz innych odmian Unixa. Wśród użytkowników Linuksa zyskała
rozgłos dzięki Windows Menagerowi KDE doskonale emulującemu wizualnie okienka znane
z Windows. Biblioteka jest dostępna na licencji komercyjnej, wymagającej opłat
licencyjnych za jej wykorzystanie. Jednak firma Troll Tech, zainteresowana
rozpowszechnianiem swojego produktu, umożliwiła nieodpłatne wykorzystanie biblioteki w
Wolnym Oprogramowaniu. Dzięki nowoczesnej architekturze, przystępnym warunkom
rozpowszechniania i wsparciu firmy komercyjnej, QT zyskała dużą popularność
w środowisku systemu Linuks. W chwili obecnej można wykorzystywać ją bezpłatnie
w zastosowaniach edukacyjnych lub do tworzenia aplikacji dostępnych wraz z kodem
źródłowym (powyższe kwestie reguluje odpowiednia licencja). Od pewnego czasu, dzięki
naciskowi linuksowej społeczności, dostępny jest kod źródłowy tej biblioteki na platformę
Linuks. Dzięki temu, nie ma realnej groźby konieczności płacenia za tworzenie czy też
używanie aplikacji stworzonych za jej pomocą. Biblioteka QT jest przystosowana
do obiektowego języka C++.
W pakiecie oprócz biblioteki dostajemy do ręki silne narzędzie QT Designer,
które służy do budowy interfejsu użytkownika i zarządzania zdarzeniami opartymi w tym
przypadku o technikę sygnał – slot. Dodatkowo w pakiecie można znaleźć generator plików
konfiguracyjnych dla kompilatora qmake, która zwalnia nas z pisania skomplikowanych
plików Makefile. Biblioteka Qt jest zaprojektowana przy pomocy metod obiektowych
i zaimplementowana w języku C++.
10
www.trolltech.no – strona domowa producenta biblioteki QT
18
Elementy interfejsu graficznego są implementowane jako klasy w języku C++
i komunikują się przez mechanizmy sygnałów i slotów. Sygnały są emitowane przez
elementy interfejsu w związku ze zdarzeniami, jakie w nim zachodzą. Przykładowo,
naciśnięcie przycisku powoduje wyemitowanie sygnału zawierającego informację o tym, jaki
przycisk został naciśnięty.
Sloty są funkcjami (metodami), które są wywołane w przypadku przechwycenia
zdefiniowanych dla danego slotu sygnałów. Wraz z biblioteką otrzymujemy narzędzie
o nazwie QTDesigner (rysunek 6).
Rysunek 6
Aplikacja jest bardzo przydatna do projektowania formatek.
Po zaprojektowaniu metodą przenieś i upuść potrafi w prosty sposób wygenerować kod
XML, z którego można już tworzyć źródła w języku C++.
19
KDevelop
Jest
to
zintegrowane
środowisko programistyczne wzorowane na Visual Studio
(rysunek 7). Wspiera tworzenie standardowych aplikacji konsolowych, aplikacji QT i KDE
11
oraz GTK+. Jest to graficzna nakładka na inne narzędzia, takie jak gcc, make, autoconf, Perl
i inne. Ale niewątpliwie znacznie ułatwia tworzenie programu. Tworząc w KDevelop nowy
projekt wykorzystujemy gotowego wizarda, który tworzy za nas wszystkie podstawowe pliki
(READ-ME, IN STALE, LICENCE, automake, autoconf itp.). Pozwala to uniknąć żmudnej
(i niezbyt łatwej) procedury ręcznego ich tworzenia i zapewnia, że nasz projekt od początku
będzie dobrze przygotowany i zgodny z obowiązującymi w środowisku GNU normami
(skrypt ./configure, podstawowe informacje i pliki). KDevelop potrafi też generować
dokumentację oraz tworzyć dystrybucje naszego projektu (w postaci paczek RPM i TGZ).
Zawiera też wsparcie dla tłumaczeń i wspomaga debugowanie programów.
Rysunek 7
KDevelop zawiera przeglądarkę dokumentacji, do której możemy dołączać
katalogi z dodatkową dokumentacją (wykorzystując programy takie jak htdig można tworzyć
indeksy dla wszystkich dołączonych katalogów). Dokumentacja jest dostępna w postaci
11
KDE – Menadżer okien z systemu Linuks
20
wygodnego panelu z drzewkiem po lewej stronie i tekstem po prawej (podobnie jak np. w
MSDN). Niestety to doskonałe narzędzie jest przeznaczone jedynie na systemy operacyjne
Lunux. Jego najbliższym odpowiednikiem w systemie Windows jest MS Visual C++.
21
5
5
.
.
P
P
r
r
z
z
y
y
k
k
ł
ł
a
a
d
d
y
y
w
w
i
i
d
d
o
o
k
k
ó
ó
w
w
b
b
i
i
b
b
l
l
i
i
o
o
t
t
e
e
k
k
i
i
Q
Q
T
T
W systemie Linuks bardzo popularnym tematem biblioteki QT jest Motif lub
Windows. Wszystkie widoki mają zaimplementowane mechanizmy tematów, które
pozwalają na zmianę wyglądu aplikacji. Można np. sprawić, aby aplikacja wyglądem
przypominała program z systemu Windows lub okno programu upodobniło się do aplikacji
z Mac OS.
Rysunek 8
Rysunek 8 przedstawia okno wywodzące się z QMainWindow zawierające na górze pasek
menu QMenuBar, pasek narzędzi QToolBars z zaimplementowanymi przyciskami
Na pierwszym planie występuje główny widok nazywany przestrzenią roboczą aplikacji
Qworkspace.
Poniżej aplikacja w stylu MDI znanego z wielu edytorów tekstu (rysunek 9). Widok edytor
pochodzi z klasy QMultiLineEdit. Na dole aplikacji umieszczono pasek statusu QStatusBar.
22
Rysunek 9
Rysunek 10 przedstawia jedno z predefiniowanych okien służących do wyboru pliku
QFileDialog.
Rysunek 10
Kolejnym okrem predefiniowanym w bibliotece QT jest okno ustawień wydruku widoczne
na rysunku 11.
23
Rysunek 11
Biblioteka QT dostarcza nam także okna informacyjne QMessageBox. Przykładem jest okno
z rysunku 12 przedstawiające komunikat.
Rysunek 12
Okno postępu zadania QprogressDialog widoczne jest na rysunku 13. Obiekt QProgressBar
może być także użyty jako osobny.
24
Rysunek 13
PopUpMenu, czyli menu podręczne znane z programów pod Windows ilustruje rysunek 14.
Rysunek 14
25
R
R
O
O
Z
Z
D
D
Z
Z
I
I
A
A
Ł
Ł
I
I
P
P
o
o
d
d
s
s
t
t
a
a
w
w
y
y
p
p
r
r
o
o
g
g
r
r
a
a
m
m
o
o
w
w
a
a
n
n
i
i
a
a
o
o
b
b
i
i
e
e
k
k
t
t
o
o
w
w
e
e
g
g
o
o
26
I
I
.
.
1
1
P
P
r
r
o
o
g
g
r
r
a
a
m
m
o
o
w
w
a
a
n
n
i
i
e
e
o
o
b
b
i
i
e
e
k
k
t
t
o
o
w
w
e
e
Programowanie obiektowe ma za zadanie podzielić pisany program na
funkcjonalne bloki. Podobnie jak w programowaniu proceduralnym, z tym, że w podejściu
obiektowym procedury są dodatkowo grupowane i przypisywane obiektom. Procedury te
często deklarowane jako prywatne, przypisane jedynie klasie macierzystej. Dzięki temu
metody i zmienne obiektu są od siebie odseparowane i niewidoczne.
Obiekt jest grupą zawierającą zmienne oraz metody publiczne i prywatne.
Mówiąc obiekt mam na myśli jednolitą, wydzieloną część funkcyjną programu. Na przykład
obiekt: radioodbiornik można traktować jako obiekt. Chodzi o to, że nie trzeba wnikać w
jego budowę ani szczegóły konstrukcji aby go używać. Typowy odbiornik FM składa się
z następujących bloków: głowicy w.cz., mieszacza, wzmacniacza pośredniej częstotliwości,
detektora częstotliwości oraz wzmacniacza m.cz. Jednak jaku użytkownik myślę o nim jako
o całym przedmiocie. Wiem, że przedmiot ten służy do odbierania stacji radiowych.
Mój radioodbiornik posiada opcje dostępne dla użytkownika:
w
strojenie
w
regulację siły głosu
w
wyłącznik.
Jak wynika z wyższego opisu, jest to samodzielna, jednolita jednostka
spełniająca określoną funkcję. Tak też dzieje się w przypadku programowania obiektowego.
Korzystając z gotowych obiektów wystarczy wykorzystać je jako swego rodzaju budulec,
umiejętnie wykorzystując ich metody.
Tak
więc konkretny radioodbiornik nie zaistnieje jeśli nie zostanie wcześniej
złożony z części. Pracownik zakładów produkujących odbiorniki nie myśli o samym
radioodbiorniku jako o tworze zbudowanym z setek części. On widzi w radioodbiorniku
kilkanaście bloków oraz najwyżej kilkadziesiąt podzespołów, które muszą sprawnie działać
jeśli radio ma działać. O tym, że bloki czy podzespoły składają się z jeszcze mniejszych
elementów myśli się dopiero wtedy, gdy radioodbiornik przestaje działać i trzeba go
27
naprawić. Konieczne wówczas staje się poznanie zasady budowy oraz działania
poszczególnych części radioodbiornika. Podobnie postępujemy w programowaniu
obiektowym. Przy programowaniu obiektowym o rozwiązaniu problemu myślimy
w kategoriach obiektów.
Aby
utworzyć obiekt wykorzystujemy klasę. Jest to abstrakcyjna definicja
jeszcze nie istniejącego obiektu, określająca jakie cechy charakteryzują dany obiekt i jakim
operacjom obiekt ten będzie można poddawać. Po zdefiniowaniu klasy, powołujemy
do życia obiekty podając konkretne wartości cech jakie dany egzemplarz wyróżniają. Na
bazie jednej klasy na ogół powołujemy do życia wiele obiektów.
Definicja tworzenia nowej klasy wygląda następująco:
class
nazwa_klasy
{
metoda_1;
zmienna_1;
...
};
Aby utworzyć klasę radioodbiornik napiszemy więc:
class
radioodbiornik
{
char nazwa[30];
int glosnosc;
int wlaczone;
float strojenie;
};
Wewnątrz klasy mogą się znajdować:
w
zmienne typu int, float, char[30]
w
funkcje składowe
w
inne klasy (obiekty)
28
Naszą przykładową klasę radioodbiornik można wyposażyć w takie oto funkcje:
class
radioodbiornik
{
char nazwa[30];
int glosnosc;
int wlaczone;
float strojenie;
void wlacz_radio(int czy_wlaczone);
void zmien_glosnosc(int jaka_glosnosc);
void zmien_czestotliwosc(float jaka_czetotliwosc);
};
Tak
oto
zadeklarowaliśmy niezbędne do działania klasy funkcje składowe oraz
zmienne klasy. Jednak funkcje te są tylko zadeklarowane, co oznacza, że należy jeszcze
je napisać. Deklaracja oznacza jedynie, że takie zmienne czy funkcje są wewnątrz klasy.
Jeśli jednak funkcje te są bardzo krótkie, to w miejscu deklaracji można wstawić od razu
całą definicję.
29
I
I
.
.
2
2
S
S
k
k
ł
ł
a
a
d
d
n
n
i
i
k
k
i
i
k
k
l
l
a
a
s
s
y
y
Sama definicja klasy jest jedynie receptą na obiekt jako na nowy typ danych.
Nie jest to więc samodzielny obiekt. Aby utworzyć nowy obiekt dowolnej klasy należy użyć
deklaracji obiektu. W programach można używać dowolnej ilości takich deklaracji.
radioodbiornik panasonic;
radioodbiornik sony;
Aby
zmodyfikować element składowy obiektu musimy odwołać się do niego
za pomocą nazwy obiektu, kropki rozdzielającej oraz nazwy składowej. Poniżej ustalam
wartość głośności obiektu radioodbiornika panasonic oraz stroję na częstotliwość RMF FM
radioodbiornik o nazwie sony.
panasonic.glosnosc = 40;
sony.stojenie = 88.2;
Jeśli utworzymy wskaźnik do obiektu, stosujemy operator ->
radioodbiornik *moj_odbiorniczek=&panasonic;
moj_odbiorniczek->glosnosc=40;
Do przechowywania składników obiektów w pamięci kompilator przydzieli
odpowiedni jej fragment dla każdego z nich. Funkcje i zmienne znajdujące się wewnątrz
klasy są dostępny tylko w obrębie tej klasy. Jest to właściwość zwana hermetyzacją lub
kapsułowaniem (ang. encapsulation). Jednak niektóre z nich muszą być jednak udostępnione
na zewnątrz klasy, aby można było ich użyć lub po prostu zmienić ich wartości w przypadku
zmiennych.
30
Wszystko to, co znajduje się wewnątrz klasy dzielimy na:
private (prywatne)
zmienne i funkcje składowe klasy, które powinny zostać
niedostępne dla użytkownika, gdyż ich modyfikacja może przynieść
nieobliczalne skutki.
protected
jak wyżej, ale dodatkowo jest dostępny dla innych klas
wywodzących się z tej klasy
public
elementy składowe dostępne dla wszystkich bez ograniczeń. Dzięki
metodom w sekcji public, po odpowiednim zaprogramowaniu
funkcji możemy operować na części private bez ryzyka błędu.
Dzięki takiemu podziałowi możemy określić, do których składników klasy
będzie miał program. Przytaczając jako przykład klasę radioodbiornik, chcemy na pewno,
aby istniał dostęp do metody strojenia. Jednak nie koniecznie chcemy, aby program miał
pezpośredni dostęp do zmiennej strojenie, gdyż zrobi to za nas bezpieczna funkcja
stroj_radio(). Definiując składniki klasy, już na fazie jej projektowania można zdecydować,
które z metod i zmiennych zadeklarować jako prywatne a które udostępnić jako publiczne.
Ogólna zasada podręcznikowa mówi, aby zmiennych publicznych było możliwie
jak najmniej. Sprawi to, że program stanie się bezpieczniejszy, a jego rozbudowa
w przyszłości nie będzie pociągała za sobą ryzyka kolizji zmiennych czy funkcji.
class radioodbiornik
{
private:
char nazwa[30];
int glosnosc, wlaczony;
float strojenie;
31
public:
void wlacz_radio(int jaki_stan);
void stroj_radio(float jaka_czestotliwosc);
};
Aby dobrze zaprojektować klasę, należy pamiętać aby udzielać dostępu typu
public tylko nielicznym funkcjom i zmiennym składowym. Należy tworzyć jak najwięcej
zmiennych typu private, które zapewnią bezpieczeństwo programu.
Funkcje składowe klasy możemy tworzyć dwoma sposobami:
w
w przypadku małych funkcji – wewnątrz definicji klasy
class radioodbiornik
{
...
void stroj(float jaka_czestotliwosc)
{
strojenie = jaka_czestotliwosc;
}
...
}
w
najczęstszą metodą, czyli poza definicją klasy wykorzystując operator zakresu ::
void radioodbiornik::strojenie(float jaka_czestotliwosc)
{
strojenie = jaka_czestotliwosc;
}
W
powyższym przykładzie wykorzystany został operator zakresu. Jest on
przedstawiony w postaci podwójnego dwukropka “::” Nazwa przed dwukropkiem oznacza
nazwę klasy a po prawej stronie nową funkcję składową klasy.
32
I
I
.
.
3
3
M
M
e
e
t
t
o
o
d
d
y
y
o
o
c
c
h
h
r
r
o
o
n
n
y
y
s
s
k
k
ł
ł
a
a
d
d
n
n
i
i
k
k
ó
ó
w
w
k
k
l
l
a
a
s
s
y
y
Jeśli chcemy chronić dostęp do danych występujących w definicji klasy mamy
do wyboru dwie drogi nadawania im wartości początkowych:
w
projektujemy odpowiednie funkcje składowe klasy
void radioodbiornik::inicjuj(char *a_nazwa,
float czestotliwosc,
int glos,
int wylacznik)
{
strcpy(nazwa,a_nazwa);
strojenie = czestotliwosc;
glosnosc = glos;
wlaczony = wylacznik;
}
...
radioodbiornik radyjko;
radyjko.inicjuj("Panasonic",88.2,40.1);
w
Można także utworzyć konstruktor obiektu
radioodbiornik::radioodbiornik(char *jaka_nazwa,
float jaka_czestotliwosc,
int jaka_glosnosc)
{
strcpy(nazwa,jaka_nazwa);
strojenie = jaka_czestotliwosc;
glosnosc = jaka_glosnosc;
}
...
radioodbiornik radio1 = radioodbiornik("SONY",88.2,40);
radioodbiornik radio2("SONY",88.2,40);
33
I
I
.
.
4
4
S
S
k
k
ł
ł
a
a
d
d
n
n
i
i
k
k
i
i
s
s
t
t
a
a
t
t
y
y
c
c
z
z
n
n
e
e
Wewnątrz definicji klasy znajdują się składniki dynamiczne przypisane
do danego obiektu. Składnikiem statycznym może być np. zmienna dowolnego typu.
Zmienna ta jest wspólna dla wszystkich obiektów danej klasy. Jest wiele programów, które
jako niezbędne do swojego działania potrzbują zmiennych typu static. Do składnika tego
typu odwołujemy się jak do standardowego. Warunkiem utworzneia statycznej składowej
klasy jest poprzedzenie jej słowem static oraz utworzeniu zmiennej globalnej,
odpowiadającej dzielonej składowej klasy.
class
radioodbiornik
{
....
static int nazwa_stacji;
....
}
Możemy także odnieść się do składnika poprzez nazwę klasy i operator zakresu
radioodbiornik::nazwa_stacji=”RMF FM”;
34
I
I
.
.
5
5
T
T
a
a
b
b
l
l
i
i
c
c
e
e
o
o
b
b
i
i
e
e
k
k
t
t
ó
ó
w
w
Za
pomocą tablic można tworzyć tablice obiektów danej klasy. Utworzymy
w ten sposób swego rodzaju bazę danych obiektów.
radioodbiornik sony[20];
W momencie definiownia takiej tablicy można też przeprowadzić inicjalizację
radioodbiornik sony[20] = {
radioodbiornik("RMF FM",88.2,40),
radioodbiornik"Radio ZET",105.3,40),
....
};
35
I
I
.
.
6
6
F
F
u
u
n
n
k
k
c
c
j
j
e
e
z
z
a
a
p
p
r
r
z
z
y
y
j
j
a
a
ź
ź
n
n
i
i
o
o
n
n
e
e
Funkcje
zaprzyjaźnione, to funkcje, które choć nie są składowymi danej klasy
mają dostęp do zmiennych prywatnych danej klasy. Aby utworzyć klasę zaprzyjaźnioną,
należy ją zadeklarować w danej klasie.
class radioodbiornik
{
.......
friend void zmien_glos(radioodbiornik&jakie_radyjko);
};
.........
void zmien_glos(radioodbiornik &jakie_radyjko)
{
jakie_radyjko.strojenie=88.2;
}
Z klasą można skojarzyć więcej niż jedną funkcję zaprzyjaźnioną. Mamy wtedy możliwość
dostępu do zmiennych prywatnych oraz funkcji składowych w wielu klasach.
36
I
I
.
.
7
7
K
K
o
o
n
n
s
s
t
t
r
r
u
u
k
k
t
t
o
o
r
r
y
y
i
i
d
d
e
e
s
s
t
t
r
r
u
u
k
k
t
t
o
o
r
r
y
y
Konstruktory są specyficznymi funkcjami składowymi, służącymi do
inicjalizacji i nadawania wartości początkowych obiektom. Konstroktor nosi nazwę klasy
i jego zadaniem jest przypisanie do pamięci początkowych wartości obiektu. Warto
pamiętać, że tworzenie konstruktorów nie jest konieczne. W tym przypadku kompilator
stworzy go za nas w postaci
nazwa_klasy(void)
Konstruktor utworzony przez kompilator nazywamy
konstruktorem domniemanym
Destruktory – funkcje składowe, których zadaniem jest wykonanie jakiegoś
zadania przed usunięciem obiektu z pamięci. Destruktory nazywamy tak jak konstruktory,
z tym, że przed nazwą umieszczamy tyldę ~ Klasa nie musi posiadać destruktora.
Jeśli jednak obiekt wykorzystywał w sobie operator przydziału pamięcie new, to dobrze jest,
gdy destruktor użyje instrukcji delete zwalniającego tę pamięć.
r
r
a
a
d
d
i
i
o
o
o
o
d
d
b
b
i
i
o
o
r
r
n
n
i
i
k
k
:
:
:
:
~
~
r
r
a
a
d
d
i
i
o
o
o
o
d
d
b
b
i
i
o
o
r
r
n
n
i
i
k
k
(
(
v
v
o
o
i
i
d
d
)
)
{
....
}
Obiekt lokalny dynamiczny to obiekt zdefiniowany wewnątrz bloku.
....
{ // otwarcie bloku
...
samochod kr30780; // obiekt powołany do życia
...
} // zamknięcie bloku, obiekt przestaje istnieć
...
w
Konstruktor takiego obiektu jest uruchamiany w momencie, gdy program napotyka
jego definicję.
Destruktor jest uruchamiany, gdy program opuszcza blok.
37
I
I
.
.
8
8
D
D
z
z
i
i
e
e
d
d
z
z
i
i
c
c
z
z
e
e
n
n
i
i
e
e
Dziedziczenie
jest
jedną z największych zalet programowania obiektowego
w C++. Ułatwia programowanie i skraca czas tworzenia programu. Ideą programowania jest
wielokrotne wykorzystywanie tego samego kodu. Technika ta pozwala definiować nowe
klasy przy wykorzystaniu klas już istniejących. Mając napisaną klasę rtv, możemy na jej
bazie utworzyć nową klasę o nazwie telewizor, który odziedziczy cechy i metody klasy –
rodzica. Nowo utworzona klasa, która dziedziczy cechy rodzica, posiada własne cechy oraz
inne składowe, ale oprócz tego posiada także cechy i składowe klasy rodzica. Klasę, z której
dziedziczymy nazywamy rodzicem, klasą bazową lub klasą macierzystą. Klasę dziedziczącą
nazywamy klasą pochodną.
Podsumowując: stosując mechanizm dziedziczenia definiujemy tylko różnice
pomiędzy obiektami, a nie wszystkie obiekty od nowa. Nie musimy także znać kodu klasy
podstawowej, wystarczy gdy wiemy jak funkcjonuje. Oddzielamy też od siebie to, jak dana
klasa jest zrealizowana, od tego, jak się nią posługiwać – nawet w celu dziedziczenia.
Aby użyć klasy do tworzenia klas pochodnych, nie musimy wiedzieć dokładnie za pomocą
jakich kruczków oblicza się w klasie konkretne dane. Programowanie obiektowe umożliwia:
w
przybliżenie sztuki programowania do życia codziennego
w
jest wielkim ułatwieniem w programowaniu zespołowym
w
jest nieocenione przy dużych projektach, bo pozwala pracować lokalnie: nie musimy
przez cały czas pamiętać wszystkich szczegółów i martwić się o nie
Aby utworzyć nową klasę telewizor na bazie starej klasy agd piszemy
class rtv {
private:
float strojenie;
public:
void jaka_czestotliwosc(float czestotliwosc);
}
void
rtv::jaka_czestotliwosc(float czestotliwosc);
{
strojenie = czestotliwosc;
}
class telewizor : public rtv {
38
private:
int glosnosc;
public:
glos_i_stroj(int volume, int freq);
}
void
telewizor::glos_i_stroj(int volume, int freq);
{
glosnosc = volume;
strojenie = freq;
}
Klasa rtv jest dla klasy telewizor klasą podstawową, albo inaczej klasą macierzystą.
Uwaga: konstruktorów i destruktorów w C++ się nie dziedziczy !
Klasa pochodna zawiera wszystkie składniki klasy macierzystej. Ponadto klasa pochodna
może zawierać:
w
dodatkowe dane składowe
w
dodatkowe funkcje składowe
w
nowe definicje funkcji składowych już zdefiniowanych w klasie macierzystej
39
Warto
podkreślić, że dziedziczenie może występować wielokrotnie. Oznacza to,
że klasa, która posiada przodka, sama może być klasą bazową dla innej klasy. W tym
przypadku mamy do czynienia z dziedziczeniem kilkupokoleniowym.
class rtv {
....
}
class radio: public rtv{
....
}
class telewizor: public radio{
....
}
class : dvd telewizor{
....
}
40
R
R
O
O
Z
Z
D
D
Z
Z
I
I
A
A
Ł
Ł
I
I
I
I
P
P
r
r
o
o
g
g
r
r
a
a
m
m
o
o
w
w
a
a
n
n
i
i
e
e
i
i
n
n
t
t
e
e
r
r
f
f
e
e
j
j
s
s
u
u
u
u
ż
ż
y
y
t
t
k
k
o
o
w
w
n
n
i
i
k
k
a
a
41
I
I
I
I
.
.
1
1
O
O
k
k
n
n
o
o
g
g
ł
ł
ó
ó
w
w
n
n
e
e
p
p
r
r
o
o
g
g
r
r
a
a
m
m
u
u
Programowanie za pomocą biblioteki QT przypomina budowę programu z klocków.
Z dostępnych w bibliotece elementów GUI wybieramy niezbędne nam elementy formatki
i dołączamy je do projektu. Poniżej znajduje się przykład prostego programu napisanego za
pomocą języka C++ oraz biblioteki QT.
Pierwszym programem jest pusty formularz. Jest to zarazem najprostszy program,
jaki można napisać w QT. Na nim w dalszej części będziemy umieszczać dodatkowe
elementy aplikacji. Nazywany oknem głównym (ang. main window), stanowi szkielet
dla innych, bardziej rozbudowanych projektów.
// LISTING 1
1.
#include
<qapplication.h>
2.
#include
<qwidget.h>
3.
class
MojeOkno :
public
QWidget
{
4.
public
: MojeOkno();
};
5.
MojeOkno::MojeOkno()
{
6.
setGeometry(300,200,300,200);
}
7.
int
main(
int
argc,
char
**argv)
{
8.
QApplication
okienko(argc,argv);
9.
MojeOkno
plum;
10.
okienko.setMainWidget(&plum);
11.
plum.show();
12.
return
okienko.exec();
}
Aby
przykład był bardziej czytelny, zastosowałem numerowanie linii.
Oczywiście podczas przepisywania programu numerowanie należy pominąć.
W liniach 1 oraz 2 dołączamy do projektu niezbędne pliki nagłówkowe. QApplication jest
przodkiem wszystkich klas w QT. QWidget jest głównym widokiem, z którego pochodzą
wszystkie inne klasy – widoki będące jego potomkami.
42
W linijce 3 opisujemy klasę i tworzymy składowe klasy. Nie musimy już deklarować
wszystkich metod, gdyż są one automatycznie dziedziczone z klasy QWidget. Linia 4
zawiera udostępnienie klasy MojeOkno jako publiczne.
W linii 5 tworzymy konstruktor klasy, w którym definiujemy różne właściwości klasy.
Tu w następnych programach deklarowane będą gniazda niestandardowe.
Linia 6 jest odpowiedzialna za rozmieszczenie i rozmiar okna aplikacji. W miejsce tej linii
można wstawić wpis: setMinimumSize(X,Y) oraz setMaximumSize(X,Y), co spowoduje,
że okno będzie można powiększać w określonym zakresie wartości pomiędzy Min oraz Max.
Główna pętla programu zaczyna się od linii 7. Tworzy ona obiekt QApplication, ustawia
obiekt QApplication jako główny widok programu (linia 10), pokazuje widok na ekranie
(linia 11) oraz przekazuje sterowanie do mechanizmu obsługi zdarzeń QT. Uruchomiony
program przedstawiony jest na rysunku 15.
Rysunek 15
43
I
I
I
I
.
.
2
2
P
P
r
r
z
z
y
y
c
c
i
i
s
s
k
k
i
i
Postaram
się teraz rozbudować program z listingu 1 o dodatkowy element –
przycisk (ang. push button). Przyciski z regóły nie wymagają wyjaśnienia a ich stosowanie
jest niezbędne niemal w każdym przypadku. Warto jedynie nadmienić, że wyróżniamy
3 typy przycisków:
•standardowe (ang. Push button)
•radiowe (ang. Radio button)
•pola wyboru (ang. Check button)
Przyciski standardowe to zwykłe przyciski spotykane w większości programów.
Przyciski radiowe są używane wtedy, gdy trzeba umożliwić użytkownikowi wybranie jednej
z kilku opcji. Przyciski tego typu mają dwa stany – zaznaczony i odznaczony. Przyciski
radiowe są podobne, ale umożliwiają wybranie tylko jednego stanu. Przyciski radiowe
można dowolnie grupować.
Poniżej przedstawiam przykład przycisku zwykłego. Jest to nieco rozbudowany
program z listingu 1 uzupełniony o dodatkowy element – przycisk. Choć przykład ten nie
rożni się wiele od poprzedniego, to niektóre polecenia wymagają dodatkowego objaśnienia.
Sam schemat działania poczynając od tego przykładu będzie podobny. Dołączenie nowego
obiektu do projektu wymaga:
•dołączenia plików nagłówkowych do projektu
•zarezerwowania pamięci dla wskaźnika obiektu
•utworzenie nowego obiektu
•ewentualne zabiegi zmieniające niektóre właściwości obiektu
// LISTING 2
#include
<qapplication.h>
#include
<qwidget.h>
#include
<qpushbutton.h>
#include
<qfont.h>
class
MojeOkno :
public
QWidget
{
public
: MojeOkno();
1.
private
: QPushButton *przycisk_koniec;
};
MojeOkno::MojeOkno()
{
setGeometry(300,200,300,160);
44
2.
przycisk_koniec = new QPushButton("Koniec programu",this);
3.
przycisk_koniec->setGeometry(20,20,260,100);
4.
przycisk_koniec->setFont(QFont("Times",18,QFont::Bold));
}
int main(
int
argc,
char
**argv)
{
QApplication okienko(argc,argv);
MojeOkno plum;
okienko.setMainWidget(&plum);
5.
plum.show();
return
okienko.exec();
}
Opis
działania programu przedstawia się następująco: linia oznaczona jako
1 lokalizuje pamięć dla nowego przycisku. Jako że przyciski i wszelkie inne elementy
formularza są częścią wewnętrzną klasy, to nie wymagają ingerencji z zewnątrz. Wszystkie
tego typu zmienne (w tym przypadku QpushButon) ustalamy jako prywatne.
Zaczynając od linii 2, tworzymy nowy przycisk. Jako parametry dołączamy etykietę
przycisku oraz wskaźnik do widoku macierzystego. Wskaźnik this w tym przypadku
wskazuje właśnie na widok nadrzędny w stosunku do przycisku.
Linia 3 definiuje miejsce położenia obiektu w oknie macierzystym oraz jego rozmiary.
Metoda setGeometry występuje niemal w każdym wizualnym widgecie komunikacji
z użytkownikiem. Linia 4 jest dodatkiem opcjonalnym i niekoniecznym, ale bardzo
praktycznym. Dzięki metodzie setFont można dowolnie ustawiać czcionkę widoku, co czyni
go bardziej czytelnym. Ciekawostką jest linia 5. W poprzednim przykładzie wyświetlała ona
widok na ekranie. W tym przypadku jednak nie jest to konieczne, gdyż obiekt macierzysty
robi to automatycznie. Dzięki temu nie musimy „ręcznie” wyświetlać każdego z elementów
na ekranie. Rysunek 16 przedstawia skompilowany i uruchomiony kod z listingu 2.
Rysunek 15
45
Nazwa opis
clicked()
Naciśnięto przycisk
pressed() Przycisk
wciśnięty
released() Zwolniono
przycisk
Qpixmap pixmap(“piksmapa.xmp”);
setPixmap(pixmap)
Etykieta graficzna przycisku
setText(“Tekst”) Definiuje
etykietę przycisku
setAccel( CTRL + 'P'
Przypisuje skrót klawiaturowy do przycisku
text() Zwraca
etykietę przycisku
setDefault()
Przycisk domyślny. Jest to przycisk, który zostanie
wciśnięty, gdy użytkownik naciśnie klawisz Enter.
Tabela 1 - Najważniejsze właściwości i metody widgetu button
Przyciski radiowe
Przyciski radiowe tworzymy z klas QButtonGroup oraz QRadioButton.
Aby skorzystać z przycisków radiowych należy skorzystać z biblioteki QradioButton oraz
Qbutton group deklarując je w sekcji include:
#include <qradiobutton.h> // dla przycisków
#include <qradiogroup.h> // dla ramki grupującej (opcjonalnie)
oraz w sekcji private zadeklarować pamięć dla obiektów:
private
:
QButtonGroup
*grupa;
QRadioButton *wybor1;
QRadioButton
*wybor2;
QRadioButton *wybor3;
lub po prostu:
private
:
QButtonGroup
*grupa;
QRadioButton *wybor1, *wybor2, *wybor3;
następnie w definicji widgetu tworzymy obwódkę oraz przyciski:
grupa = new QButtonGroup("Wybierz opcje", this);
grupa->setGeometry(
10,10,180,180
);
wybor1 = new QRadioButton("Wybieram 1", grupa);
wybor1->move(
10,20
);
wybor2 = new QRadioButton("Wybieram 2", grupa);
wybor2->move(
10,50
);
wybor3 = new QRadioButton("Wybieram 3", grupa);
wybor3->move(
10,80
);
//Poniżej definiujemy przycisk pola wyboru
wybor5 = new QCheckBox("Pole wyboru !", grupa);
wybor5->setChecked(true);
wybor5->move(
10,130
);
46
grupa->insert(wybor1);
grupa->insert(wybor2);
grupa->insert(wybor3);
grupa->insert(wybor5);
Dodatkowo
powyżej dodałem jeden przycisk pola wyboru – CheckButton.
Nazwałem go przycisk 5 a w linii setChecked(true) sprawiłem, że jest on domyślnie
włączony. Geometria przycisków jest ustalana za pomocą funkcji move.
Celowo zrezygnowałem z opcji setGeometry, gdyż nigdy nie wiemy, jaką szerokość
i wysokość powinny mieć te przyciski. Na rysunku 16 przedstawiłem gotowy program
demonstrujący użycie przycisków radiowych oraz pól wyboru. Dodatkowym elementem jest
ramka grupująca obiekty. Ramka oprócz roli czyto estetycznej sprawia, że program staje się
bardziej czytelny.
Rysunek 16
nazwa opis
clicked()
Naciśnięto przycisk
pressed() Przycisk
wciśnięty
released() Zwolniono
przycisk
setPixmap(const QPixmap)
Etykieta graficzna przycisku
setText(“Tekst”) Definiuje
etykietę przycisku
setAccel( CTRL + 'P'
Przypisuje skrót klawiaturowy do przycisku
text() Zwraca
etykietę przycisku
isChecked() Zwraca
true,
jeśli jeśli przycisk jest włączony
setChecked() Włącza lub wyłącza przycisk
Tabela 2 – Najważniejsze właściwości oraz metody przełączników oraz przycisków radiowych
47
I
I
I
I
.
.
3
3
E
E
t
t
y
y
k
k
i
i
e
e
t
t
y
y
t
t
e
e
k
k
s
s
t
t
o
o
w
w
e
e
Etykiety tekstowe (ang. labels) są niezbędną częścią każdego programu. Pełnią one
zwykle funkcję informacyjną. Mogą być statyczne lub zmieniać się w trakcie wykonywania
programu. Etykiety najczęściej służą do opisywania działania czy przeznaczenia innych
widoków. Za zawartość etykiety odpowiada metoda setText(„tekst”), w której jedynym
argumentem jest stała lub zmienna tekstowa. W poniższym przykładzie dodatkowo
zastosowałem wyrównywanie tekstu (linia 2). Zamiast AlignCenter można posłużyć się inną
zdefiniowaną stałą. Ich lista znajduje się w tabeli nr 3.
Funkcja Opis
działnia
AlignTop
Wyrównanie do góry obiektu QLabel
AlignBottom
Wyrównanie do dołu obiektu QLabel
AlignLeft
Wyrównanie do lewej obiektu QLabel
AlignRight
Wyrównanie do prawej obiektu QLabel
AlignHCenter
Tekst dodany w poziomej pozycji środkowej obiektu QLabel
AlignVCenter
Tekst dodany w pionowej pozycji środkowej obiektu QLabel
AlignCenter
AlignHCenter – AlignVCenter razem wzięte
WordBreak Automatyczne
dzielenie
wyrazów
ExpandTabs
QLabel rozwija tabulatory
Tabela 3 – Właściwości etykiety
funkcję setAlignment można uzywać łącząc powyższe zmienne:
setAlingment(AlignHCenter | AlignVCenter);
// LISTING 3
#include
<qapplication.h>
#include
<qwidget.h>
#include
<qlabel.h>
#include
<qfont.h>
class
MojeOkno :
public
QWidget
{
public
: MojeOkno();
private
: QLabel *moja_etykieta;
};
MojeOkno::MojeOkno()
{
setGeometry(300,200,350,160);
moja_etykieta = new QLabel(this);
moja_etykieta->setGeometry(20,20,300,80);
1.
moja_etykieta->setText("To jest etykieta (ang. Label)");
moja_etykieta->setFont(QFont("Times",18,QFont::Bold));
2.
moja_etykieta->setAlignment(AlignCenter);
}
48
int
main(
int
argc,
char
**argv)
{
QApplication
okienko(argc,argv);
MojeOkno
plum;
okienko.setMainWidget(&plum);
plum.show();
return
okienko.exec();
}
Efekt działania programu z listingu 3 możemy podziwiać na rysunku 17
Rysunek 17
Etykieta specjalna LCD
Poniżej przedstawiam jeszcze jedną etykietę dostępną w bibliotece QT –
LCDNumber. Jest to wyświetlacz umożliwiający efektowne wyświetlanie wyników
lub parametrów na ekranie.
W praktyce jest on bardzo przydatny do obrazowania wszelkich procesów. Poniżej znajduje
się cały kod źródłowy oraz zrzut ekranu (rysunek 18) obrazujący wygląd widoku.
//LISTING 4
#include
<qapplication.h>
#include
<qwidget.h>
#include
<qlcdnumber.h>
class
MojeOkno :
public
QWidget
{
public
: MojeOkno();
p
rivate
: QLCDNumber *numerek;
};
MojeOkno::MojeOkno()
{
setGeometry(300,200,180,100);
numerek = new QLCDNumber(this);
numerek->setGeometry(10,10,150,80);
49
numerek->display(2004);
}
int
main(
int
argc,
char
**argv)
{
QApplication
okienko(argc,argv);
MojeOkno
plum;
okienko.setMainWidget(&plum);
plum.show();
return
okienko.exec();
}
Rysunek 18
Obiekt LCDNumber posiada także interesujące funkcje składowe. W tabeli nr 4 zawarłem
najważniejsze z nich.
Funkcja Opis
działnia
setNumDigits() Ustawia
liczbę cyfr do wyświetlania
SetBinMode() Tryb
binarny
setDecMode() Domyślny tryb dziesiętny
setSegmentStyle() Wygląd cyfr (przekazując jak parametr: Outline, Filled, Flat)
checkOverFlow() Przekroczenie zakresu to sygnał jaki emituje obiekt w przypadku
przekroczenia zakresu.
Tabela 4 – właściwości i metody etykiety LCD
50
I
I
I
I
.
.
4
4
P
P
o
o
l
l
e
e
w
w
e
e
j
j
ś
ś
c
c
i
i
o
o
w
w
e
e
Pole
wejściowe (ang. input text) jest jednowierszowym komponentem służącym
do pobrania linii tekstu. Za jego pomocą można pobrać od użytkownika dowolną zmienną
tekstową lub numeryczną. W drugim przypadku jednak niezbędna będzie dodatkowa
konwersja zmiennych, gdyż pole tekstowe zawsze zwraca łańcuch znakowy. Dzięki
niewielkiej modyfikacji może służyć jako dyskretne pole do wpisywania hasła.
// LISTING 5
#include
<qapplication.h>
#include
<qwidget.h>
#include
<qlineedit.h>
#include
<qlabel.h>
#include
<qfont.h>
class
MojeOkno :
public
QWidget
{
public
: MojeOkno();
private
:
QLineEdit *linia_edycyjna;
QLabel *etykieta;
};
MojeOkno::MojeOkno()
{
setGeometry(
300,200,300,100
);
etykieta = new QLabel(this);
etykieta->setGeometry(
10,20,60,30
);
etykieta->setText("Wpisz dane:");
linia_edycyjna = new QLineEdit("
Tu wpisz teks
t",this);
linia_edycyjna->setGeometry(
72,25,160,20
);
}
int
main(
int
argc
,
char
**argv
)
{
QApplication
okienko(argc,argv);
MojeOkno
plum;
okienko.setMainWidget(&plum);
plum.show();
return
okienko.exec();
}
51
Rysunek 19
Obiekt pole tekstowe ma także kilka ciekawych właściwości.
Wpisując linia_edycyjna->setMaxLenght(8); ograniczymy ilość wpisanego tekstu.
Z kolei linia_edycyjna->setEchoMode(QLineEdit::Password); możemy sprawić, że zamiast
wpisywanego tekstu pojawią się gwiazdki. Ta opcja jest niezbędna w przypadku wpisywania
haseł dostępu. Aby pobrać wpisany tekst z okna wystarczy wpisać: linia_edycyjna->text().
Aby ograniczyć ilość wpisywanych znaków można zastosować metodę:
linia_edycyjna->maxLength(ilosć_znaków);
Linia edycyjna emituje przydatne sygnały: returnPressed() i textChanged().
Sygnał returnPressed() jest emitowany, gdy przyciśnięty zostanie przycisk Enter. Sygnał
textChanged() zaś, jest emitowany za każdym razem, gdy wpisany tekst się zmienia. Sygnał
ten zawiera również nowy tekst. Rysunek 19 obrazuje przykład użycia pola tekstowego.
52
I
I
I
I
.
.
5
5
L
L
i
i
s
s
t
t
y
y
Listy (ang. lists) to skomplikowane komponenty języka C++. Stanowią one
bardzo ważny element w programowaniu. Na szczęście QT dostarcza nam gotowe obiekty
gotowe do użycia a ich wykorzystanie jest niezwykle proste. Listę stosujemy zawsze, jeśli
trzeba dać użytkownikowi wybór jednej lub wielu opcji. W listach wyświetlamy także
np. zawartośc bazy danych czy elementy wyboru. Pełne zastosowanie list ograniczone jest
tak naprawdę tylko wyobraźnią programisty. Warto więc przyjrzeć się bliżej temu obiektowi.
//Listing 6
#include
<qapplication.h>
#include
<qwidget.h>
#include
<qlistbox.h>
class
MojeOkno :
public
QWidget
{
public
: MojeOkno();
private
: QListBox *lista;
};
MojeOkno::MojeOkno()
{
setGeometry(
300,200,180,100
);
lista = new QListBox(this);
lista->setGeometry(
10,10,150,80
);
lista->insertItem("Opcja nr 1");
lista->insertItem("Opcja nr 2");
lista->insertItem("Opcja nr 3");
}
int main(int
argc
, char
**argv
)
{
QApplication
okienko(
argc
,
argv
);
MojeOkno
plum;
okienko.setMainWidget(&plum);
plum.show();
return
okienko.exec();
}
53
Rysunek 20
Aktualnie wybraną pozycję listy można pobrać przy pomocy funkcji
lista->currentItem();
uzyskany w ten sposób index elementu można przekazać do funkcji
lista->test();
Pozwoli to otrzymać aktualnie zaznaczony tekst lub rysunek w przypadku
lista->pixmap();
54
I
I
I
I
.
.
6
6
P
P
o
o
l
l
a
a
k
k
o
o
m
m
b
b
i
i
Pola kombi są bardzo podobne do list i mogą je w pewnych przypadkach
zastępować gdy na formatce brakuje miejsca. Także sam sposób działania jest bardzo
podobny
12
.
//LISTING 7
#include
<qapplication.h>
#include
<qwidget.h>
#include
<qcombobox.h>
class
MojeOkno :
public
QWidget
{
public
: MojeOkno();
private
: QComboBox *moje_combo;
};
MojeOkno::MojeOkno()
{
setGeometry(
300,200,180,100
);
moje_combo = new QComboBox(this);
moje_combo->setGeometry(
10,10,150,20
);
moje_combo->insertItem("
Opcja nr 1
");
moje_combo->insertItem("
Opcja nr 2
");
moje_combo->insertItem("
Opcja nr 3
");
}
int
main(
int
argc
,
char
**argv
)
{
QApplication
okienko(argc,argv);
MojeOkno
plum;
okienko.setMainWidget(&plum);
plum.show();
return
okienko.exec();
}
Rysunek 21
12
Daniel Solin, Programowanie przy użyciu biblioteki QT w 24 godziny, Warszawa 2001, str. 119
55
I
I
I
I
.
.
7
7
R
R
a
a
m
m
k
k
i
i
g
g
r
r
u
u
p
p
u
u
j
j
ą
ą
c
c
e
e
Ramki
grupujące (ang. group box), jak sama nazwa wskazuje służą
do grupowania przeróżnych elementów. Ramki powodują, że formatka staje się bardziej
czytelna a program nabiera estetyki. Staje się rónież łatwiejszy i bardziej przejrzysty.
//LISTING 7
#include
<qapplication.h>
#include
<qwidget.h>
#include
<qlabel.h>
#include
<qgroupbox.h>
class
MojeOkno :
public
QWidget
{
public
: MojeOkno();
private
: QGroupBox *moja_ramka;
QLabel *etykietka;
};
MojeOkno::MojeOkno()
{
setGeometry(
300,200,180,100
);
moja_ramka = new QGroupBox(this);
moja_ramka->setGeometry(
10,10,150,80
);
moja_ramka->setTitle("
Ramka grupujaca
");
etykietka = new QLabel(this);
etykietka->setGeometry(
30,30,120,20
);
etykietka->setText("
To jest przyklad ramki
");
etykietka->setAlignment(AlignCenter);
}
int
main(
int
argc
, c
har
**argv
)
{
QApplication
okienko(
argc
,
argv
);
MojeOkno
plum;
okienko.setMainWidget(&plum);
plum.show();
return
okienko.exec();
}
Rysunek 22
56
7
7
.
.
8
8
S
S
u
u
w
w
a
a
k
k
i
i
i
i
p
p
o
o
l
l
a
a
p
p
r
r
z
z
e
e
w
w
i
i
j
j
a
a
n
n
e
e
Suwak (ang. slider) służy do ustalania wartości numerycznych określonych
zmiennych lub stanów wewnętrznych innych widgetów. Operacje zmiany wartości
przeprowadza się poprzez przeciąganie gałki za pomocą myszy. Tę samą rolę pełni pole
przewijane (ang. spin box) jednak jego obsługa polega na przyciskaniu klawiszy góra/dół
13
.
#include
<qapplication.h>
#include
<qwidget.h>
#include
<qslider.h>
#include
<qspinbox.h>
class
MojeOkno :
public
QWidget
{
public
: MojeOkno();
private
: QSlider *suwaczek;
QSpinBox *pole;
};
MojeOkno::MojeOkno()
{
setGeometry(
300,200,180,100
);
suwaczek =
new
QSlider(
0,100,10,50
,Horizontal,this);
suwaczek->setGeometry(
10,10,150,20
);
suwaczek->setTickmarks(QSlider::Below);
pole =
new
QSpinBox(
0,100,1
,this);
pole->setGeometry(
10,60,50,20
);
}
int
main(int
argc
, char
**argv
)
{
// Pętla główna…
}
Rysunek 23
13
Tamże, str. 127
57
I
I
I
I
.
.
9
9
T
T
w
w
o
o
r
r
z
z
e
e
n
n
i
i
e
e
m
m
e
e
n
n
u
u
p
p
r
r
o
o
g
g
r
r
a
a
m
m
u
u
Przez lata rozwijania GUI programiści opracowali standard wyglądu i sposobu
działania graficznych aplikacji. Standard ten dotyczy szczególnie systemu Windows, ale
ostatnio jest on coraz częściej stosowany również na platformach UNIX
14
. Menu jest
komponentem umieszczanym niemal w każdym programie. Z nieocenioną pomocą
przychodzi klasa QmainWindow, która pozwala w łatwy i przystępny sposób dodawać do
programu nie tylko menu, ale również paski narzędzi
15
.
Procedura tworzenia menu polega na dołączeniu dwóch plików nagłówkowych
niezbędnych przy definiowaniu linii zawierającej menu.
#include
<qmenubar.h>
#include
<qpopupmenu.h>
Następnie w deklaracji klasy musimy zdefiniować elementy:
private
:
QPopupMenu *pliki, *inne_menu;
QMenuBar
*menu;
pliki = new QPopupMenu();
pliki->insertItem("
Komunikat numer 1
",this,SLOT (funkcja()));
pliki->insertItem("
Komunikat numer 2
");
pliki->insertItem("
Zakonczenie programu
",qApp,SLOT(quit()));
...tutaj umieszczamy pionowe opcje (popupy)
menu = new QMenuBar(this);
menu->insertItem("
Menu rozwijane
",pliki);
menu->insertItem("
Zakoncz program
",qApp,SLOT(quit()));
...tutaj umieszczamy poziome grupy opcji
14
Tamże, str. 85
15
Tamże, str. 85
58
Należy pamiętać, że dla każdego nowego popup-a w menu trzeba zadeklarować nową
zmienną QPopupMenu *nowypopup;
Rysunek 24
Nowo utworzone menu widoczne jest na obrazku o numerze 24. Choć program
tak na prawdę nie robi nic, to jednak, doskonale ilustruje używanie paska menu. Posiada
także zaimplementowane zdarzenie na opcji Zakoncz program, które kończy działanie
naszego przykładu.
59
I
I
I
I
.
.
1
1
0
0
P
P
r
r
e
e
d
d
e
e
f
f
i
i
n
n
i
i
o
o
w
w
a
a
n
n
e
e
o
o
k
k
n
n
a
a
d
d
i
i
a
a
l
l
o
o
g
g
o
o
w
w
e
e
Biblioteka QT jest wyposażona w kilka przydatnych okien dialogowych.
Znajdują się tutaj okna do wybierania pliku, definicji koloru, okna wyboru fontu oraz okno
postępu.
Okno wyboru koloru
Klasa QColor służy do wyboru koloru. Aby skorzystać z okna dialogowego
wystarczy jedynie dołączyć nagłówek poprzez
#include
<qcolordialog.h>
#include
<qcolor.h>
zadeklarować pamięć dla obiektów
private:
QColorDialog *moj_dialog;
QColor kolor;
oraz wywołać funkcję składową getColor(). Funkcja ta zwraca obiekt QColor reprezentujący
ten kolor.
kolor = moj_dialog->getColor(QColor(0,0,0));
w zmiennej kolor otrzymamy obiekt QColor. Rysunek 25 przedstawia okno wyboru koloru.
60
Rysunek 25
61
I
I
I
I
.
.
1
1
1
1
O
O
k
k
n
n
a
a
i
i
n
n
f
f
o
o
r
r
m
m
a
a
c
c
y
y
j
j
n
n
e
e
Niezwykle przydatnym komponentem jest komunikat (ang. message box).
Jest to okienko zawierające ważną informację dla użytkownika, lub ostrzegające go
o zaistniałej sytuacji. Dodatkowo okienko z komunikatem może być wyposażone
w predefiniowaną piksmapę. Okna komunikatów mogą także wymagać od użytkownika
działania w postaci naciśnięcia przycisku.
QMessageBox::information(this, "Tytul okna",
"To jest informacja dla Ciebie\n"
"A to jest druga linia" );
Rysunek 26
istnieje także możliwość zdefiniowania własnych przycisków:
informacja =
new
QmessageBox("Informacja",
"To jest informacja dla Ciebie !",
QMessageBox::Information,
QMessageBox::Yes | QMessageBox::Default,
QMessageBox::No | QMessageBox::Escape
QMEssageBox::Cancel);
informacja->show();
Rysunek 27
switch
(QMessageBox::information(this,"Nazwa okna",
"Czy chcesz dowiedziec sie wiecej?\n","Nacisnij odpowiedni przycisk",
"&Chce", "&Nie chce", "&Anuluj",
0, // Enter == klaisz 0
2 ) ) { // Escape == klawisz 2
case
0: // Naciśnięto Chcę, skrót Alt-C lub Enter.
// Tutaj operacje odnośnie chcę
62
break;
case
1: // kliknięto Nie lub skrót Alt-D
// tutaj operacje nie chcę
break;
case
2: // Kliknięto Anuluj albo klawisz Ecs
// Tutaj nie wychodzimy
break
;
}
Rysunek 28
Funkcja Opis
działnia
QMessageBox::NoIcon
Brak jakiejkolwiek ikony
QMessageBox::Information Ikonka
informacji
QMessageBox::Warning Ikonka
ostrzegawcza
QMessageBox::Critical Wyświetla ikonę krytycznej decyzji lub informacji
Tabela 5 – Właściwości okna komunikatów
Funkcja Opis
działnia
QMessageBox::Ok
Przycisk OK.
QMessageBox::Cancel
Przycisk zrezygnuj z operacji
QMessageBox::Yes
Tak
QMessageBox::No
Nie
QMessageBox::Abort
Anuluj operację
QMessageBox::Retry
Ponów operację
QMessageBox::Ignore
Ignoruj
Tabela 6 – predefiniowane przyciski okna komunikatów
63
R
R
O
O
Z
Z
D
D
Z
Z
I
I
A
A
Ł
Ł
I
I
I
I
I
I
Z
Z
d
d
a
a
r
r
z
z
e
e
n
n
i
i
a
a
64
I
I
I
I
I
I
.
.
1
1
M
M
e
e
c
c
h
h
a
a
n
n
i
i
z
z
m
m
o
o
b
b
s
s
ł
ł
u
u
g
g
i
i
z
z
d
d
a
a
r
r
z
z
e
e
ń
ń
Programowanie GUI jest aspektem, w którym jednym z najbardziej znaczących
elementów są obiekty i ich stany cząstkowe. Nie jest ich wiele mniej w innych rodzajach
programów, ale: po pierwsze, wiele programów ma jednak jakiś interfejs użytkownika, a po
drugie nawet programy, które go nie mają, mają też jakąś strukturę złożoną z odpowiedniej
ilości stanów cząstkowych.
Ponieważ jednak synchronizacja stanu i w ogóle stany cząstkowe są chyba
najpoważniejszym problemem w programowaniu GUI, zatem porządniejsze biblioteki
graficzne, takie jak Qt, zostały wyposażone w najlepszą technologię do synchronizacji
stanów cząstkowych. Takim rozwiązaniem zaimplementowanym w QT jest technologia
sygnałów i slotów.
Opiszę tutaj, na czym ten mechanizm polega. Otóż jeden obiekt definiuje sobie
jakieś sygnały. Następnie na rzecz obiektu wywołuje się jakieś metody, które dokonują
zmiany jego stanu. Na przykład: system przekazał informację obiektowi o zdarzeniu, które
go dotyczy i jest ważnym zdarzeniem czy też również spowodowało zmianę jego stanu.
Metoda obsługująca zdarzenie na znak, że coś takiego się stało, wysyła sygnał. Co się wtedy
dzieje? Z punktu widzenia obiektu nic. Dopóki nie podłączymy pod taki sygnał konkretnego
gniazda, też nic się nie wydarzy.
Biorąc dla przykładu inny obiekt, który definiuje w sobie slot. Slot to nic innego
jak po prostu funkcja, czy też metoda, której przeznaczeniem jest wykonać się na
odpowiednie zawołanie. Slot służy do podłączenia do niego sygnału. Załóżmy więc, że ten
obiekt ma slot i podłączył sobie do niego sygnał. W takim przypadku gdy następuje
"wysłanie" (emisja) sygnału, to w odróżnieniu od poprzedniej sytuacji, coś konkretnego 1
się już stanie. Wykona się "slot".
65
I
I
I
I
I
I
.
.
2
2
Z
Z
a
a
l
l
e
e
t
t
y
y
m
m
e
e
c
c
h
h
a
a
n
n
i
i
z
z
m
m
u
u
s
s
y
y
g
g
n
n
a
a
ł
ł
–
–
g
g
n
n
i
i
a
a
z
z
d
d
o
o
Mechanizm
obsługi zdarzeń wykorzystywany w bibliotece QT jest tym,
co odróżnia tę bibliotekę od innych. Programiści, tworzący tę bibliotekę już podczas
projektowania położyli szczególny nacisk na łatwość interakcji z użytkownikiem oraz
elastyczność i prostotę programowania. Inne biblioteki GUI do tworzenia zdarzeń
wykorzystują funkcje zwrotne (ang. callback function). Mechanizm ten jest jednak
skomplikowany oraz niezwykle trudny do zrozumienia. Biblioteka QT realizuje mechanizm
zdarzeń poprzez tak zwany system sygnał – gniazdo (ang. signal – slot). Jest to jasny i prosty
w implementacji system, polegający na przyporządkowaniu do każdego sygnału
emitowanego przez widget – gniazda będącego zwykłą funkcją. Dzięki tej metodzie
połączenie emitowanego zdarzenia z kodem wykonywalnym można wykonać w jednej linii
programu. Do połączenia sygnału z gniazdem służy funkcja connect. Poniżej wyjaśnię użytą
tu terminologię oraz opiszę wykorzystywanie predefiniowanych oraz niestandardowych
gniazd.
connect(obiekt_emitujący,SIGNAL(zdarzenie)),obiekt_docelowy,SLOT(stan_w)));
Makra SIGNAL i SLOT nie są skomplikowanymi funkcjami. Zamieniają one
podany argument tekst, poprzedzony odpowiednio cyframi 1 lub 2. W Qt istnieje wyraźnie
zaznaczona zasada, że sygnały to metody PRYWATNE klasy. Oznacza to, że nikt, poza
samym obiektem, nie ma prawa wywoływać sygnałów (nawet makro signals jest aliasem
do private).
Zdarzenie – jest to sygnał emitowany przez widgety. Zwykle odnosi się
do tego, co aktualnie robi program. Na przykład, gdy klikniesz jakiś przycisk, ten
wygeneruje zdarzenie clicked(). Problem polega na tym, aby w prosty sposób to zdarzenie
mogło poinformować program, że ów przycisk został wciśnięty. I tutaj przychodzi z pomocą
mechanizm sygnał – gniazdo.
Stan
wewnętrzny – jeśli mowa o sygnałach, to należy tutaj wspomnieć także o
wewnętrznych stanach widgetów. Oznacza on, że pewne cechy widgetu uległy zmianie pod
wpływem określonego zdarzenia.
66
Gniazdo – jest zwykłą funkcją połączoną z sygnałem za pomocą specjalnego
polecenia. Wykonuje kod przypisany do sygnału. Na przykład, jeśli chcemy, aby
po kliknięciu przycisku program się zakończył, to musimy przypisać sygnał clicked()
do gniazda quit().
Sygnał – to także funkcja składowa. Jeśli coś wewnątrz widgetu ulegnie
zmianie, to obiekt ten może wyemitować sygnał. Jeśli sygnał połączymy z gniazdem,
otrzymamy w pełni funkcjonalny mechanizm obsługi zdarzeń. Można połączyć wiele gniazd
z jednym sygnałem. Wówczas wykonywanie gniazd odbywa się jedno po drugim,
w przypadkowej kolejności.
67
I
I
I
I
I
I
.
.
3
3
G
G
n
n
i
i
a
a
z
z
d
d
a
a
p
p
r
r
e
e
d
d
e
e
f
f
i
i
n
n
i
i
o
o
w
w
a
a
n
n
e
e
Niemal wszystkie widgety posiadają swoje wewnętrzne stany oraz emitują
określone sygnały. Na przykład wspomniany wcześniej przycisk (butt1) posiada sygnał
clicked(). Poniższa linia sprawi, że po kliknięciu (wygenerowaniu) sygnału clicked() program
wykorzysta predefiniowaną metodę quit() aby się zakończyć.
connect(butt1, SIGNAL(clicked(), qApp, SLOT(quit()));
Rozpatrzmy bardziej rozbudowany przykład. Jest to prosta aplikacja
demonstrująca wykorzystanie gniazd predefiniowanych zawartych w obiektach. Przesuwając
suwak, zmienia się wartość wyświetlacza LCD.
Rysunek 29
suwak=
new
QSlider(Vertical,this);
suwak->setGeometry(5,5,30,90);
wyswietlacz =
new
QLCDNumber(2,this);
wyswietlacz->setGeometry(50,15,140,70);
connect
(suwak,SIGNAL(valueChanged(
int
)),wyswietlacz, SLOT(display(
int
)));
W tym przypadku funkcja connect() łączy sygnał valueChanged() z gniazdem wyświetlacza
LCD. Gniazdo to nosi nazwę display() i jest wbudowaną funkcją obiektu LCDNumber.
Pomiędzy sygnałem a gniazdem jest przesyłana wartość typu integer. Dzięki temu wartość
suwaka jest bezpośrednio wyświetlana na wyświetlaczu z każdym wyemitowaniem
zdarzenia valueChanged(), czyli przy każdej zmianie wartości. Nie jest przy tym konieczne
określanie klasy, w której znajduje się gniazdo.
68
Jeśli wewnątrz definicji klasy wywołujemy funkcję bez określenia z jakiej klasy ona
pochodzi, to domyślnie C++ zakłada, że pochodzi ona z klasy bieżącej.
W kolejnych rozdziałach niniejszej pracy przedstawię najważniejsze widgety
oraz ich właściwości, gniazda predefiniowane oraz emitowane przez nie sygnały.
69
I
I
I
I
I
I
.
.
4
4
G
G
n
n
i
i
a
a
z
z
d
d
a
a
n
n
i
i
e
e
s
s
t
t
a
a
n
n
d
d
a
a
r
r
d
d
o
o
w
w
e
e
Poprzedni podpunkt odnosił się do gniazd standardowych. W trakcie pisania
programów niezbędne jest jednak wykorzystanie gniazd niestandardowych. Na przykład
wygenerowane zdarzenie clicked() będzie wywoływać funkcję niezaimplementowaną
z innym obiektem. Standardowe połączenie sygnału z gniazdem spowoduję, że kompilator
zwróci błąd. Jest to wada mechanizmu sygnał – gniazdo.
deklaracja przycisku;
connect
(przycisk,SIGNAL(clicked()),wyswietlacz,SLOT(funkcja()));
}
obiekt::gniazdo_niestandardowe()
{
jakiś kod;
{
Powyższy przykład ilustruje wymóg wykorzystania gniazda niestandardowego.
Niestety próba kompilacji takiego programu zakończy się błędem. Niestety C++ nie rozumie
takiego odwołania do gniazda. W tym przypadku musimy skorzystać z narzędzia MOC. Jest
to prekompilator generujący właściwy kod dla kompilatora. Przed tym jednak należy
utworzyć nagłówek aplikacji definiujący gniazdo.
Nagłówek najlepiej jest zapisać jako osobny plik o rozszerzeniu *.h
class
obiekt:
public
QWidget
{
Q_OBJECT
public
: obiekt();
private
: QPushButton *przycisk;
protected slots:
void
gniazdo_niestandardowe();
};
Wszyskie gniazda niestandardowe w bibliotece QT muszą pochodzić z klasy
Q_OBJECT, aby były poprawnie rozpoznawane przez prekompilator MOC.
MOC – prekompilator META OBJECT COMPILER służący do generowania poprawnego
kodu dla kompilatora. Narzędzie to przechwytuje słowa kluczowe specyficzne dla QT (na
przykład emit) i tworzy z nich prawidłowy kod C++.
70
Użycie narzędzia MOC:
moc plik.h plik.moc
Gotowy plik *.moc można już kompilować. Oczywiście w środowiskach typu MS Visual
C++ czy KDevelop prekompila gniazd odbywa się automatycznie podczas uruchomienia
opcji kompilacji. Także program TMAKE automatycznie uaktywnia MOC-a.
W przypadku “ręcznej” kompilacji trzeba wykonać kilka kroków:
moc deklaracja.h deklaracja.moc
następnie należy dołączyć plik moc do kodu źródłowego cpp klasy
#include”deklaracja.moc”
Kolejną czynnością jest kompilacja wraz z głównym plikiem main
g++ -lqt deklaracja.cpp main.cpp -o gotowy
Inną metodą jest prekompilacja do pliku object a następnie konsolidacja z plikami cpp.
moc deklaracja.h deklaracja.moc
g++ -c deklaracja.moc -o deklaracja.o
g++ -c main.cpp -o main.o
g++ -lqt deklaracja.o main.o -o gotowy
71
R
R
O
O
Z
Z
D
D
Z
Z
I
I
A
A
Ł
Ł
I
I
V
V
P
P
r
r
z
z
y
y
k
k
ł
ł
a
a
d
d
y
y
p
p
r
r
o
o
g
g
r
r
a
a
m
m
ó
ó
w
w
w
w
b
b
i
i
b
b
l
l
i
i
o
o
t
t
e
e
c
c
e
e
Q
Q
T
T
72
I
I
V
V
.
.
1
1
G
G
e
e
n
n
e
e
r
r
a
a
c
c
j
j
a
a
i
i
s
s
o
o
r
r
t
t
o
o
w
w
a
a
n
n
i
i
e
e
e
e
l
l
e
e
m
m
e
e
n
n
t
t
ó
ó
w
w
l
l
i
i
s
s
t
t
y
y
Program demonstruje budowę listy elementów oraz jej sortowanie.
Bezpośrednio po uruchomieniu programu, nalezy ustalić suwakiem liczbę generowanych
elementów listy w zakresie od 2 do 50. Domyślnie ilość elementów ustaliłem na 10.
Następnie należy kliknąć przycisk Generuj liczby. Na liście liczby losowe zostanie
wygenerowanych losowo ustalona wcześniej lista liczb. Po wygenerowaniu ciągu liczb,
można przejść do sortowania. W tym celu należy wcisnąć przycisk Sortowanie elementów.
W prawej liście wyświetlone zostaną wszystkie wygenerowane wcześniej liczby
posortowane według wartości.
Rysunek 30
Program pomimo swej prostoty ilustruje wiele interesujących kwestii języka
C++ oraz samej biblioteki QT. Znaleźć tu można wykorzystanie widoku progress dialog
służącego do przedstawiania postępu dowolnego procesu, generację liczb losowych
oraz konwersję liczb na łańcuchy i odwrotnie. Program korzysta także z niestandardowych
gniazd. Wynik działania programu jest pokazany na rysunku 30.
73
Źródło programu: Plik nagłówkowy main.h
//-----------------------------------------------------------
// Program sortujący listę typu QMultiLineEdit
// autor: Leszek Klich
// ----------------------------------------------------------
#include
<qapplication.h>
#include
<qwidget.h>
#include
<qlistbox.h>
#include
<qfont.h>
#include
<qlcdnumber.h>
#include
<qlabel.h>
#include
<qslider.h>
#include
<qpushbutton.h>
#include
<qmessagebox.h>
#include
<qprogressdialog.h>
#include
<qstring.h>
#include
<
qmultilineedit.h>
class
Sortowanie :
public
QWidget
{
Q_OBJECT
public
:
Sortowanie();
int
tablica[
50
];
private
:
QMultiLineEdit
*lista;
QMultiLineEdit
*lista2;
QLabel
*etykieta1;
QLabel
*etykieta2;
QLabel
*etykieta3;
QLCDNumber
*numer;
QSlider
*suwak;
QPushButton
*przycisk_generuj;
QPushButton
*przycisk_sortuj;
QPushButton
*przycisk_zakoncz;
QProgressDialog
*pasek_postepu;
QLabel *info1, *info2, *info3;
protected slots
:
void
generacja();
void
Sortowanie1();
};
74
Źródło programu: plik main.cpp
#include
"main.h"
#include
"
stdlib.h"
Sortowanie::Sortowanie()
{
setMaximumSize(
500,350
);
setMinimumSize(
500,350
);
lista = new QMultiLineEdit(this);
lista->setGeometry(
10,35,100,300
);
lista->setReadOnly(TRUE);
lista2 = new QMultiLineEdit(this);
lista2->setGeometry(
110,35,100,300
);
lista2->setReadOnly(TRUE);
etykieta2 = new QLabel(this);
etykieta2->setGeometry(
20,1,80,30
);
etykieta2->setText("
Liczby losowe
");
etykieta2->setFont(QFont("Times",10,QFont::Bold));
etykieta3 = new QLabel(this);
etykieta3->setGeometry(
110,1,100,30
);
etykieta3->setText("
Lista posortowana
");
etykieta3->setFont(QFont("Times",10,QFont::Bold));
numer = new QLCDNumber(2,this);
numer->setGeometry(
380,10,70,30
);
numer->display(10);
etykieta1 = new QLabel(this);
etykieta1->setGeometry(
230,10,150,30
);
etykieta1->setText("
Ilosc elementow listy:
");
etykieta1->setFont(QFont("Times",12,QFont::Bold));
suwak = new QSlider(
2,50,10,10
,Horizontal,this);
suwak->setGeometry(
230,60,220,20
);
connect(suwak,SIGNAL(valueChanged(int)),numer,SLOT(display(int)));
przycisk_generuj = new QPushButton("
Generuj liczby
",this);
przycisk_generuj->setGeometry(
230,100,100,30
);
connect(przycisk_generuj,SIGNAL(clicked()),this,SLOT(generacja() ));
przycisk_generuj = new QPushButton("
Sortowanie
",this);
przycisk_generuj->setGeometry(
360,100,130,30
);
connect(przycisk_generuj,SIGNAL(clicked()),this,SLOT(Sortowanie1() ));
przycisk_zakoncz = new QPushButton("
Koniec progr.
",this);
przycisk_zakoncz->setGeometry(
280,150,150,30
);
connect(przycisk_zakoncz,SIGNAL(clicked()),qApp,SLOT(quit()));
info1 = new QLabel(this);
info1->setGeometry(
230,190,270,30
);
info1->setText("
Demonstracja sortowania elementów
");
info1->setFont(QFont("Times",12,QFont::Bold));
info2 = new QLabel(this);
75
info2->setGeometry(
265,215,270,30
);
info2->setText("
autor: Leszek Klich (2004)
");
info2->setFont(QFont("Times",12,QFont::Normal));
}
void Sortowanie::generacja()
{
lista->clear();
pasek_postepu = new QProgressDialog("
Trwa generowanie
",
"
Anuluj wykonywanie
",suwak->value(),this, "pasek_postepu",TRUE);
pasek_postepu->show();
int licznik, liczba = 0;
for(licznik=1; licznik<=suwak->value(); licznik++) {
liczba = rand() %
1000
;
QString pozycja;
pozycja = pozycja.setNum(liczba);
tablica[licznik-1]=liczba;
lista->insertLine(pozycja);
pasek_postepu->setProgress(licznik);
} }
void Sortowanie::Sortowanie1()
{
if (lista->numLines()<2)
QmessageBox::warning(this,"
Niepowodzenie
","
Najpierw wygeneruj liczby
");
else {
lista2->clear();
int i,n;
n=suwak->value();
for (i=0; i<n; i++){
for (int j=0; j<n-1; j++){
if (tablica[j]>tablica[j+1]){
int tmp;
tmp = tablica[j];
tablica[j] = tablica[j+1];
tablica[j+1] = tmp; }}}
int liczba;
for (i=0; i<suwak->value(); i++){
liczba=tablica[i];
QString pozycja;
pozycja = pozycja.setNum(liczba);
lista2->insertLine(pozycja);
}}}
int
main(int
argc
, char
**argv
)
{
QApplication okienko(
argc
,
argv
);
Sortowanie start;
okienko.setMainWidget(&start);
start.show();
return
okienko.exec();
}
}
76
I
I
V
V
.
.
2
2
P
P
r
r
o
o
g
g
r
r
a
a
m
m
o
o
b
b
l
l
i
i
c
c
z
z
a
a
j
j
ą
ą
c
c
y
y
s
s
i
i
l
l
n
n
i
i
ę
ę
Poniżej przedstawiam prosty program obliczający silnię. Zmienna n oznacza
liczbę naturalną. Program silnia jest często wykorzystywany do nauki programowania
i stanowi prosty przykład algorytmu.
n! = 1
⋅ 2 ⋅ 3 ⋅ ... ⋅ ⋅ (n - 1) ⋅ n, n > 1
Iloczyn kolejnych liczb naturalnych od 1 do n,
0! = 1 oraz 1! = 1
n
n!
0
1
1
1
2
2
3
6
4
24
5
120
6
720
7
5 040
8
40 320
9
362 880
10
3 628 800
11
39 916 800
.....
....................
20
2430
⋅10^15
Tabela 7 – Tablica wartości silni
Rysunek 31
77
Oto kod źródłowy aplikacji z rysunku 31.
Źródło programu: plik main.h
//-----------------------------------------------------------
// Program przedstawiający obliczanie silni
// autor: Leszek Klich
// ----------------------------------------------------------
#include
<qapplication.h>
#include
<qwidget.h>
#include
<qlistbox.h>
#include
<qfont.h>
#include
<qlabel.h>
#include
<qpushbutton.h>
#include
<qmessagebox.h>
#include
<qgroupbox.h>
#include
<qspinbox.h>
#include
<qstring.h>
class
silnia : public QWidget
{
Q_OBJECT
public
:
silnia();
private
:
QLabel *info1, *info2, *info3;
QGroupBox *ramka1, *ramka2, *ramka3;
QSpinBox
*regulacja;
QButton
*przycisk_oblicz;
QLabel
*etykieta_wynik;
QButton
*przycisk_koniec;
protected slots
:
void
oblicz_silnie();
};
Źródło programu: plik main.cpp
#include
"main.h"
silnia::silnia(){
setMaximumSize(
200,260
);
setMinimumSize(
200,260
);
ramka1=new QGroupBox(this);
ramka1->setGeometry(
2,2,194,70
);
info1 = new QLabel(this);
info1->setGeometry(
35,10,140,30
);
info1->setText("
Obliczanie SILNI
");
info1->setFont(QFont("Times",12,QFont::Bold));
info2 = new QLabel(this);
78
info2->setGeometry(
20,33,170,30
);
info2->setText("
autor: Leszek Klich
");
info2->setFont(QFont("Times",12,QFont::Normal));
ramka2=new QGroupBox(this);
ramka2->setGeometry(
2,74,194,70
);
ramka2->setTitle("
Ustaw parametr
");
regulacja= new QSpinBox(
2,33,1
,this);
regulacja->setGeometry(
37,100,120,30
);
regulacja->setPrefix("
n =
");
przycisk_oblicz = new QPushButton("
Oblicz silnie
",this);
przycisk_oblicz->setGeometry(
26,150,150,30
);
connect(przycisk_oblicz,SIGNAL(clicked()),this,SLOT(oblicz_silnie()));
ramka3=new QGroupBox(this);
ramka3->setGeometry(
2,185,194,30
);
etykieta_wynik = new QLabel(this);
etykieta_wynik->setGeometry(
50,188,100,20
);
etykieta_wynik->setText("
Wynik n!=
");
etykieta_wynik->setFont(QFont("Times",12,Qfont::Bold));
etykieta_wynik->setAlignment(AlignCenter);
przycisk_koniec = new QPushButton("
Zakoncz program
",this);
przycisk_koniec->setGeometry(
24,222,150,30
);
connect(przycisk_koniec,SIGNAL(clicked()),qApp, SLOT(quit()));
}
void silnia::oblicz_silnie(){
if (regulacja->value()<1)
QmessageBox::information(this,"
Niedobrze
","
Musisz podac n wieksze od 1
");
else
{
int i, wynik=1;
for (i=2; i<=regulacja->value(); i++)
wynik=wynik*i;
QString wyn;
wyn = wyn.setNum(wynik);
etykieta_wynik->setText(wyn);
}}
int main(int
argc
, char
**argv
)
{
QApplication
okienko(
argc
,
argv
);
silnia
start;
okienko.setMainWidget(&start);
start.show();
return
okienko.exec();
79
I
I
V
V
.
.
3
3
S
S
y
y
m
m
u
u
l
l
a
a
c
c
j
j
a
a
r
r
u
u
c
c
h
h
u
u
p
p
o
o
p
p
a
a
r
r
a
a
b
b
o
o
l
l
i
i
Poniższy przykład ilustruje ruch punktu po krzywej. Najbardziej zbliżonym do
rzeczywistości torem jest ruch po paraboli. Piksel poruszający się wzdłuż paraboli porusza
się także w ruchu ukośnym. Znając prędkość początkową S i kąt A można obliczyć zasięg R
i największą wysokość HT toru lotu jako:
R = S * S * SIN(2 * A)/G
HT = (S * SIN(A))^2)/2 * G
G jest przyśpieszeniem ziemskim wynoszącym 980 cm/s
2
. Warto dodać, że największy
zasięg otrzymamy przy kącie 45 stopni. Wysokość HT jest największa, gdy obiekt
wyrzucimy w górę, czyli A = 90
o
. Pozycję punktu wzdłuż krzywej otrzymujemy zmieniając
X od 0 do R i obliczając wartości Y z równania:
Y = C1 * X^ 2 + C2 * X + Y0
Y0 jest w tym przypadku wybranym przez nas punktem ekranu. Stała C1 oraz C2 są
określone jako:
C1 = G/(2 * (S * COS(A))^ 2)
C2 = – TAN(A)
W poniższym programie zastosowałem oscylacje zanikającej amplitudy. Wykorzystałem
w tym przypadku funkcję SIN. W celu obliczenia zmieniającej się amplitudy zastosowałem
funkcję EXP.
Po uruchomieniu programu należy skorygować wartości startowe: wysokość, z jakiej upada
punkt oraz odległość.
80
Rysunek 32
Źródło programu: plik main.h
//----------------------------------------------------------------
// Program ilustrujący ruch upadającej piłki wg ruchu po paraboli
// autor: Leszek Klich
// ---------------------------------------------------------------
#include
<qapplication.h>
#include
<qwidget.h>
#include
<qlistbox.h>
#include
<qfont.h>
#include
<qlabel.h>
#include
<qpushbutton.h>
#include
<qgroupbox.h>
#include
<qspinbox.h>
#include
<qstring.h>
#include
<qpainter.h>
#include
<qmessagebox.h>
#include
<math.h>
#include
<stdlib.h>
class
Parabola : public QWidget
{
Q_OBJECT
public
:
Parabola();
81
private
:
QSpinBox *wys, *tlum;
QGroupBox *ramka1, *ramka2;
QLabel *info1, *info2;
QButton
*przycisk_start;
QPainter
*paint;
QLabel *about1, *about2;
protected slots
:
void
paintEvent(QPaintEvent*);
void
Rysuj();
};
Źródło programu: plik main.cpp
#include
"main.h"
void Parabola::paintEvent(QPaintEvent*)
{
paint
=
new
QPainter;
paint->begin(this);
paint->setPen(QPen(black, 4, QPen::SolidLine));
paint->drawLine(
10,370,490,37
0);
paint->end();
}
Parabola::Parabola()
{
setMaximumSize(
500,400
);
setMinimumSize(
500,400
);
ramka2=new QGroupBox(this);
ramka2->setGeometry(
2,5,498,50
);
ramka2->setTitle("
Parametry wstepne
");
info1 = new QLabel(this);
info1->setGeometry(
10,23,70,20
);
info1->setText("
Wysokosc:
");
wys= new QSpinBox(
50,400,30
,this);
wys->setGeometry(
70,26,60,20
);
wys->setPrefix("
H =
");
info2 = new QLabel(this);
info2->setGeometry(
150,23,70,20
);
info2->setText("
Odleglosc:
");
tlum= new QSpinBox(
30,60,5
,this
);
tlum->setGeometry(
205,26,60,20
);
tlum->setPrefix("
O =
");
przycisk_start = new QPushButton("
Rysuj animacje
",this);
przycisk_start->setGeometry(
280,26,100,20
);
connect(przycisk_start,SIGNAL(clicked()),this,SLOT(Rysuj()));
about1 = new QLabel(this);
about1->setGeometry(
300,63,170,15
);
82
about1->setText("
Symulacja ruchu upadajacej pilki.
");
about2 = new QLabel(this);
about2->setGeometry(
300,78,100,10
);
about2->setText("
autor: Leszek Klich
");
}
void Parabola::Rysuj()
{
float
W=3.14159/tlum->value();
float
D=90*3.14159/180;
float
K=0.01;
int
XM=490;
int
YM=370;
int
XN,YN,Y,X;
int
H=wys->value();
paint->begin(this);
paint->setPen(QPen(rand()%200, 2, QPen::SolidLine));
for (XN=0; XN<=XM-10; XN=XN+1) {
YN=H*sin(W*XN+D) * exp(-K * XN);
YN=YM-abs(YN)-3;
paint->drawLine(XN+10,YN,XN+11,YN+1);
X=XN+10;
Y=YN; }
paint->end();
}
int
main(int
argc
, char
**argv
)
{
QApplication okienko(
argc
,
argv
);
Parabola start;
okienko.setMainWidget(&start);
start.show();
return
okienko.exec();
83
I
I
V
V
.
.
4
4
S
S
z
z
y
y
f
f
r
r
o
o
w
w
a
a
n
n
i
i
e
e
m
m
e
e
t
t
o
o
d
d
ą
ą
X
X
O
O
R
R
Szyfrowanie jest procesem przetwarzającym dane wejściowe w określony
sposób. W ten sposób czynimy je nieczytelne dla osób niepowołanych. Istnieje wiele
sposobów kodowania danych. Jednym z prostszych, co nie oznacza nieskutecznych jest
metoda XOR. Do kodowania potrzebny jest ciąg znaków będący kluczem. Użycie do
kodowania funkcji XOR znacznie upraszcza kod programu, ponieważ funkcja kodująca
to także XOR.
(XOR(A,XOR(A,X)=X)
Ponowne “przepuszczenie” zakodowanych danych przez funkcję XOR –
odkoduje ciąg danych. Jakość i bezpieczeństwo kodowania jest liniowe i zależy od długości
łańcucha hasła – klucza. Największe niebezpieczeństwo deszyfrowania danych pojawia się
wtedy, gdy znana jest przynajmniej część danych wejściowych. Analizując zależności
pomiędzy ciągiem zakodowanym i zdekodowanym, można określić klucz kodujący.
Niebezpieczeństwo takie jest realne, ponieważ różne pliki tego samego typu mają np. takie
same nagłówki. Tak więc gdy wiemy, że ktoś szyfruje plik XLS, odzyskanie hasła nie jest
trudne. Program przedstawiony jest na rysunku 33. Po uruchomieniu należy załadować plik
do zakodowania. W tym celu należy kliknąć przycisk Plik do zakodowania / dekodowania.
Domyślnie można szyfrować pliki typu TXT, ale po wpisaniu w miejsce typu
pliku *.*, można szyfrować dowolne typy plików, nie wyłączając plików wykonywalnych.
84
Rysunek 33
Źródło programu: plik main.h
#include
<qapplication.h>
#include
<qwidget.h>
#include
<qfont.h>
#include
<qlabel.h>
#include
<qpushbutton.h>
#include
<qmessagebox.h>
#include
<qgroupbox.h>
#include
<qstring.h>
#include
<qfiledialog.h>
class
szyfrowanie : public QWidget
{
Q_OBJECT
public
:
szyfrowanie();
private
:
QGroupBox
*obwodka;
QLabel *tytul, *info, *autor;
QButton *przycisk_koniec, *przycisk_koduj;
QFileDialog
*fdialog;
QString
plik;
char
*key;
protected slots
:
int
szyfruj();
};
85
Źródło programu: plik main.cpp
#include
"main.h"
szyfrowanie::szyfrowanie() {
key="
LeSzEkKlIcHtOjestKlucz
";
setMaximumSize(
350,180
);
setMinimumSize(
350,180
);
obwodka=new QGroupBox(this);
obwodka->setGeometry(
2,2,343,170
);
tytul = new QLabel(this);
tytul->setGeometry(
48,10,250,30
);
tytul->setText("
Kodowanie/dekodowanie tekstu XOR
");
tytul->setFont(QFont("Times",12,QFont::Bold));
info = new QLabel(this);
info->setGeometry(
10,80,330,30
);
info->setText("
Informacja: oczekiwanie...
");
autor = new QLabel(this);
autor->setGeometry(
58,140,250,30
);
autor->setText("
autor: Leszek Klich IwZ III
");
autor->setFont(QFont("Times",11,QFont::Bold));
przycisk_koniec = new QPushButton("
Zakoncz program
",this);
przycisk_koniec->setGeometry(
24,112,300,30
);
connect(przycisk_koniec,SIGNAL(clicked()),qApp,SLOT(quit()));
przycisk_koduj = new QPushButton("
Plik do zakodowania/odkodowania
",this);
przycisk_koduj->setGeometry(
34,50,280,30
);
connect(przycisk_koduj,SIGNAL(clicked()),this, SLOT(szyfruj()));
}
int
szyfrowanie::szyfruj() {
plik = fdialog->getOpenFileName("","*.txt");
if(plik.isNull()==false) {
FILE *in,*out;
unsigned int i_k,i_p;
char bufor;
info->setText(plik);
if((out=fopen(plik,"rb"))==NULL) {
QmessageBox::critical(this,"
Niedobrze
","
Nie moge otworzyc pliku.
");
fclose(in); }
if((in=fopen("
wynik.txt
","wb"))==NULL) {
QmessageBox::critical(this,"
Niedobrze
","
Nie moge otworzyc pliku.
");
fclose(out); }
i_k=0;
fseek(out,0,SEEK_END);
i_p=ftell(out);
fseek(out,0,SEEK_SET);
86
while(i_p>0){
fread(&bufor,1,1,out);
bufor=(bufor^key[i_k]);
i_k++;
if(i_k==strlen(key)) i_k=0;
fwrite(&bufor,1,1,in);
i_p--;
}
QmessageBox::information(this,"Informacja",
"ZOKONCZONO. Zak./Odk. tekst jest w pliku wynik.txt");
} else
QmessageBox::information(this,"Anulowanie","Anulowales wybieranie");
return 1;
}
int
main(int
argc
, char
**argv
)
{
QApplication okienko(argc,argv);
szyfrowanie start;
okienko.setMainWidget(&start);
start.show();
return
okienko.exec();
}
87
Z
Z
A
A
K
K
O
O
Ń
Ń
C
C
Z
Z
E
E
N
N
I
I
E
E
88
W pracy tej nie opisałem wszystkich możliwości i zalet biblioteki QT, gdyż jest
to niemożliwe z racji ogromu niniejszego tematu. Opisałem tylko te komponenty, które
umożliwiają napisanie prostego programu. Dodatkowo, na końcu opracowania dołączyłem
kilka programów przykładowych, z których można zauważyć pewną analogię co do
wykorzystania poszczególnych klas i widoków. Ich inicjalizacja, użycie w praktyczne jest
bardzo do siebie podobne, co może być pomocne w nauce programowania.
Mottem przewodnim niniejszej pracy była jednak przenośność oraz
uniwersalność biblioteki. To właśnie te cechy przeważyły, że zdecydowałem się właśnie na
ten produkt. Warto wspomnieć, że biblioteka świetnie radzi sobie z bazami danych. Posiada
pełne wsparcie dla różnych serwerów SQL. Jest to jednak temat na kolejne opracowanie,
jeszcze bardziej obszerne.
Opracowanie to przekształciłem także w stronę internetową, co ma
spowodować, że temat ten przybliżę większemu gronu programistów, którzy pragną pisać
programy dla systemu Linuks, ale nie chcą rezygnować z programowania w komercyjnych
systemach operacyjnych.
Zapraszam na stronę www.qtlibrary.glt.pl, gdzie znajduje się elektroniczna wersja tego
dokumentu.
Leszek Klich © 2004
89
W
W
y
y
k
k
a
a
z
z
s
s
k
k
r
r
ó
ó
t
t
ó
ó
w
w
API
– Application Programming Interface
CD
– Compact Disc
OOP
– Object Oriented Programming
GUI
– Graphical User Interface
GTK
– GIMP Toolkit Kit
GNOME – GNU Network Object Model Evironment
GDK
– GIMP Drawing Kit
XLib
– XWindow Library
DOS
– Disc Operating System
RAD
– Rapid Application Development
GNU
– GNU's Not Unix
B
B
i
i
b
b
l
l
i
i
o
o
g
g
r
r
a
a
f
f
i
i
a
a
m
m
e
e
r
r
y
y
t
t
o
o
r
r
y
y
c
c
z
z
n
n
a
a
:
:
1. E. Harlow, Linux. Tworzenie Aplikacji, Wrocław 1999.
2. D. Hearn, M. Baker, Grafika mikrokomputerowa, Warszawa 1988.
3. K. Jamsa, C++, Warszawa 1996.
4. K. Jakubczyk, Turbo Pascal i Borland C++ przykłady, Gliwice 2002.
5. B. W. Kernighan, D. M. Richie, Język C, Warszawa 1988.
6. J. Liberty, C++ w 24 godziny, Warszawa 1998.
7. N. Matthew, R. Stones, Linux. Programowanie, Łódź 1999.
8. D. Solin, Programowanie przy użyciu biblioteki QT, Warszawa 2001.
9. B. Stourstrup, Język C++, Warszawa 2002.
10. J. Surażski, Linuksowe C++, “Chip Specjal – Linux”, 1999, nr 14.
11. K Walczak, Nauka programownia obiektowego w języku C++, Warszawa 2002.
12. P. Wróblewski, Algorytmy, struktury danych i techniki programowania, Gliwice 2003.
13. M. Wiącek, Radosna twórczość, “Chip Special – Linux”, 2002, nr 7.
14. M. Wiśniewski, Biblioteka QT 2, “Linux Plus”, 2000, nr 6.
I
I
n
n
n
n
e
e
m
m
a
a
t
t
e
e
r
r
i
i
a
a
ł
ł
y
y
(
(
S
S
t
t
r
r
o
o
n
n
y
y
i
i
n
n
t
t
e
e
r
r
n
n
e
e
t
t
o
o
w
w
e
e
)
)
:
:
www.troltech.no
www.qtlibrary.glt.pl
www.intercon.pl/~sektor/cbx/
http://free.of.pl/q/qtmoux/index.php