Kapitel 2. KDE- og QT-bibliotekerne

Indholdsfortegnelse

QT-værktøjskassen for grafiske brugergrænseflader
Det første QT-program
Referencedokumentation for Qt
Brugerkommandoer
Interaktion mellem objekt med signaler og slots
Hvad KDE sørger for
KDE 3.x bibliotekerne
Eksempel på et KDE-program

Det norske foretagende TrollTech (http://www.trolltech.com) sørger for en såkaldt GUI-værktøjskasse, som kaldes QT. GUI betyder "grafisk brugergrænseflade", og derfor vises QT-baserede program med knapper, vinduer osv., hvilket muliggør brugerinput ved at synliggøre funktionerne som et program sørger for. En sådan værktøjskasse behøves for at udvikle grafiske programmer som bruger grænsefladen X-windows på Unix-systemer, eftersom X ikke selv indeholder et fordefineret brugergrænseflade. Selv om andre værktøjspakker også er tilgængelige til at oprette en brugergrænseflade, tilbyder QT nogle tekniske fordele som gør programkonstruktion meget enkel. Desuden er QT-værktøjskassen også tilgængelig på Microsoft Windows-systemer, hvilket gør det muligt for udviklerne at lave deres programmer på begge platformene.

KDE-gruppen (http://www.kde.org) dannedes med det formål at gøre det mere brugervenligt at bruge Unix-systemer, og bestemte at QT-værktøjskassen skulle bruges til udvikling af en vinduehåndtering for X-windows, samt en mængde værktøjer som indgår i KDE-pakken. K-desktopmiljøet indeholder derfor hovedkomponenterne vinduehåndteringen kwm, filhåndteringen kfm og startpanelet kpanel samt en mængde førsteklasses værktøjer og programmer. Efter KDE blev udgivet, kiggede mange udviklere på det nye miljø og hvad det havde at tilbyde. KDE-bibliotekerne sørger for væsentlige metoder og klasser som gør at alle programmer som konstrueres med dem ligner hinanden og opfører sig ens, så brugeren har den store fordel kun at behøve at vænne sig til brugen af et bestemt program, ikke til håndtering af dialoger og knapper. Desuden integreres KDE-programmer med desktoppen, kan virke sammen med filhåndteringen via træk og slip, tilbyder sessionshåndtering, og meget mere hvis alle funktioner som tilbydes af KDE-bibliotekerne bruges. Både QT-værktøjskassen og KDE-bibliotekerne er implementerede med programsproget C++. Derfor er de fleste programmer som benytter sig af bibliotekerne også skrevet i C++. I de følgende kapitel tager vi en kort tur gennem bibliotekerne for at se hvad der allerede sørges for, og hvordan QT- og KDE-programmer i almindelighed laves.

Både QT-værktøjskassen og KDE-bibliotekerne er implementerede med programsproget C++. Derfor skrives også programmerne som bruger disse biblioteker oftest i C++. I de følgende kapitel tager vi en hurtig tur gennem bibliotekerne for at se hvad der allerede sørges for, og hvordan QT- og KDE-program laves i almindelighed.

QT-værktøjskassen for grafiske brugergrænseflader

Som sagt, er QT-biblioteket en værktøjskasse som tilbyder grafiske elementer som bruges til at lave programmer med grafiske grænseflader og behøves for programmering for X-windows. Desuden tilbyder værktøjskassen:

  • Et komplet sæt af klasser og metoder klare til at bruge til og med for programmering som ikke berører grafik

  • En god løsning på brugerkommunikationen med virtuelle metoder og mekanismen med signaler og slot

  • Et sæt fordefinerede grafiske grænsefladeselementer, som kaldes "grafiske kontroller" som kan bruges til at oprette synlige elementer

  • Desuden fuldstændige fordefinerede dialoger som ofte bruges i programmer såsom fremgang- og fildialoger

Derfor er det meget væsentligt at kende til QT-klasserne, også selvom du kun vil programmere KDE-programmer. For at få et indblik i grundbegreberne for at oprette og kompilere et program med grafisk grænseflade, tager vi først et kig på et eksempelprogram som kun bruger QT. Derefter udvider vi det til et KDE-program.

Det første QT-program

Som sædvanligt skal programmer i C++ indeholde funktionen main(), som er programkørslens startpunkt. Eftersom vi ønsker at programmet skal være synligt som grafik i vinduer og tilbyde kommunikation med brugeren, skal vi først vide hvordan de kan vises for brugeren. Som et eksempel, tager vi et kig på det første eksempel som indgår i QT's referencedokumentation, og forklarer de grundlæggende skridt i kørslen, inklusive hvorfor og hvordan programmets vindue vises:

#include <qapplication.h>
#include <qpushbutton.h>

int main( int argc, char **argv )
{
QApplication a( argc, argv );

QPushButton hello( "Hello world!", 0 );
hello.resize( 100, 30 );

a.setMainWidget( &hello );
hello.show();
return a.exec();
}

Dette program tegner blot et vindue som indeholder en knap med "Hello world" som tekst. Som for alle Qt-baserede programmer, skal du først oprette en udgave af klassen QApplication, som repræsenteres af variablen a.

Derefter laver programmet en udgave af klassen QPushButton som hedder hello. Dette bliver til knappen. Konstruktøren i hello tager en streng som parameter, som er indholdet af den synlige kontrol, her knappens tekst.

Derefter kaldes metoden resize() for knappen hello. Den ændrer komponenternes normalstørrelse (som i dette tilfælde er QPushButton) som den havde da den oprettedes, til længden 100 billedpunkter og højden 80 billedpunkter. Tilsidst kaldes metoden setMainWidget() og metoden show() for hello. Endelig køres vores QApplication med a.exec(), går ind i hovedbegivenhedsløkken og venter til den skal returnere en heltalsværdi til det omgivende operativsystem for at signalere at programmet er afsluttet.

Referencedokumentation for Qt

Lad os nu tage et hurtigt kig på referencedokumentationen for QT-biblioteket. For at gøre dette, start KDevelop og vælg "Qt" i dokumentationsfanebladets træ. Dokumentationssøgeren åbnes og viser startsiden i QT-referencedokumentationen. Dette er det første sted hvor du kan hente information om QT, dens klasser og tilgængelige funktioner som de sørger for. Desuden er ovenstående program det første som er med i vejledningsafsnittet. For at komme til klasserne vil vi kigge på, QApplication og QPushButton, vælg "Alphabetical Class List" og led efter tilsvarende navne. Følg en af dem for at tage en kig på klassedokumentationen.

Alternativt kan du bruge net-dokumentationen fra TrollTechs QT-dokumentation.

Du vil se konstruktoren og alle andre metoder som klassen QApplication sørger for. Hvis du følger et link, får du mere information om brugen og betydningen af metoderne, hvilket er meget nyttigt hvis du sommetider ikke kan indse den rigtige brug eller vil have et eksempel. Dette gælder også dokumentationen af KDE-bibliotekerne, som bruger en lignende slags dokumentation, derfor er dette næsten alt du behøver at vide om at bruge klassereferencer med dokumentationssøgeren.

Tolkning af eksemplet

Begyndende fra QApplication, finder du alle metoder som bruges i vort første eksempel:

  • konstruktoren QApplication()

  • metoden setMainWidget()

  • metoden exec()

Tolkningen af hvorfor vi bruger disse metoder er meget enkel:

  1. Opret en instans af klassen QApplication med konstruktoren, så vi kan bruge elementer for den grafiske grænsefladen som QT sørger for

  2. Opret en grafisk komponent som bliver indholdet i vort programvindue

  3. Sæt kontrollen som hovedkontrol for en

  4. Kør en instans af QApplication

Det andet objekt i vort program er trykknappen, en instans af klassen QPushButton. Af de to konstruktorer der er til at oprette klassen, bruger vi den anden. Den tager en tekst, som er tekstindholdet i knappen. Her er det strengen "Hello world!". Derefter kalder vi metoden resize() for at ændre størrelse på knappen ifølge dens indhold. Knappen skal være større for at gøre strengen fuldstændigt synlig.

Men hvad gælder for metoden show()? Nu mærker du, at som de fleste andre grafiske komponenter, er QPushButton baseret på enkelt arv. Dokumentationen siger, Arver QButton. Følg linket til klassen QButton. Det viser mange andre kontroller som arves af QPushButton, som vi senere bruger til at forklare signal/slot-mekanismen. Under alle omstændigheder er metoden show() ikke på listen, og derfor skal den være en metode som også sørges for via arv. Klassen som QButton arver er QWidget. Følg kun linket igen, så ser du en hel mængde metoder som klassen QWidget sørger for, inklusive metoden show(). Nu forstår vi hvad der blev gjort i eksemplet med knappen:

  1. Lav en instans af QPushButton, og brug den anden konstruktor til at angive knappens tekst

  2. Ændr størrelsen på kontrollen til dens indhold

  3. Sæt kontrollen som hovedkontrol instansen af QApplication

  4. Fortæl den grafiske kontrol at den skal vises på skærmen ved at kalde show(), en metode som blev arvet fra QWidget

Efter at have kaldet metoden exec(), er programmet synligt for brugeren, og viser et vindue med knappen "Hello world!". Bemærk at programmer med grafiske grænseflader opfører sig noget anderledes sammenlignet med procedurebaserede program. Det vigtigste er at programmet går ind i en såkaldt "hovedbegivenhedsløkke". Det betyder at programmet skal vente på brugerens handlinger og derefter reagere på dem. Det betyder også, for et QT-program, at programmet skal være i hovedbegivenhedsløkken for at starte begivenhedshåndteringen. Næste afsnit beskriver kortfattet hvad det betyder for programmøren og hvad QT tilbyder for at håndtere begivenheder.

Bemærk

For brugere som allerede er avancerede: Knappen har ingen overliggende kontrol deklareret i konstruktoren. Derfor er den en topniveaukontrol og kører med en lokal begivenhedsløkke som ikke behøver at vente på hovedbegivenhedsløkken. Se dokumentationen for klassen QWidget og KDE's biblioteksreferenceguide.

Brugerkommandoer

Efter at have læst foregående afsnit, bør du allerede vide:

  • Hvad QT-biblioteket sørger for for programmer med grafiske grænseflader,

  • Hvordan et program som bruger QT laves, og

  • Hvor og hvordan du finder information om klasser som du vil bruge med dokumentationssøgeren.

Nu fortsætter vi med at give programmet "liv" ved at behandle brugerbegivenheder. I almindelighed har brugeren to måder at kommunikere med et program: musen og tastaturet. En grafisk brugergrænseflade skal sørge for metoder for begge måder, som detekterer handlinger og gør noget som reaktion på handlingerne.

Vinduesystemet sender derfor alle kommunikationsbegivenheder til det tilsvarende program. QApplication sender dem derefter til det aktive vindue som en QEvent, og kontrollerne selv skal bestemme hvad som skal udføres med dem. En kontrol tager imod begivenheden og behandler QWidget::event(QEvent*), som afgør hvilken begivenhed der er sket og hvordan reaktionen skal være. Metoden event() udfører derfor håndteringen af hovedbegivenheden. Derefter sender metoden event() begivenheden til et såkaldt begivenhedfilter som afgør hvad der sker og hvad der skal udføres med begivenheden. Hvis intet filter signalerer at det er ansvarligt for begivenheden, kaldes speciel begivenhedshåndtering. På den måde kan vi skelne mellem:

  • Tastaturbegivenheder: Tabulator og Shift+Tabulator:

    • virtual void focusInEvent(QFocusEvent *)

    • virtual void focusOutEvent(QFocusEvent *)

  • Al øvrig tastaturindtastning:

    • virtual void keyPressEvent(QKeyEvent *)

    • virtual void keyReleaseEvent(QKeyEvent *)

  • Museflytning:

    • virtual void mouseMoveEvent(QMouseEvent *)

    • virtual void enterEvent(QEvent *)

    • virtual void leaveEvent(QEvent *)

  • Handlinger med museknapperne

    • virtual void mousePressEvent(QMouseEvent *)

    • virtual void mouseReleaseEvent(QMouseEvent *)

    • virtual void mouseDoubleClickEvent(QMouseEvent *)

  • Vinduesbegivenheder som indeholder den grafiske kontrol

    • virtual void moveEvent(QMoveEvent *)

    • virtual void resizeEvent(QResizeEvent *)

    • virtual void closeEvent(QCloseEvent *)

Bemærk at alle begivenhedsfunktioner er virtuelle og protected. Derfor kan du implementere begivenhederne som du behøver i egne kontroller og angive hvordan din kontrol skal reagere. QWidget indeholder også nogle andre virtuelle metoder som kan være nyttige i dine programmer. Under alle omstændigheder er det nødvendigt at kende QWidget godt.

Interaktion mellem objekt med signaler og slots

Nu kommer vi til den mest åbenbare fordel ved QT-værktøjskassen: signal/slot-mekanismen. Den tilbyder en meget bekvem og nyttig løsning for kommunikation mellem objekter, som sædvanligvis løses med tilbagekaldsfunktioner i X-windows værktøjskasser. Eftersom kommunikationen kræver strikt programmering og ind imellem gør det meget svært at oprette brugergrænseflader (som beskrevet i QT-dokumentationen og forklaret i Programming with Qt af K. Dalheimer), opfandt TrollTech et nyt system hvor objekter kan sende signaler som kan forbindes til metoder som deklareres som slots. Som programmør af C++, behøver man kun at vide nogen ting om denne mekanisme:

  • klassedeklarationen af en klasse som bruger signaler og slots skal indeholde makroen Q_OBJECT i begyndelsen (uden et semikolon), og skal afledes fra klassen QObject

  • et signal kan sendes med nøgleordet emit, f.eks. emit signal(parametre);, inde i en hvilken som helst medlemsfunktion i en klasse som tillader brug af signaler og slots

  • alle signaler som bruges af klasser som ikke arves skal tilføjes i klassedeklarationen i en signalsektion

  • alle metoder som kan forbindes med et signal deklareres i sektioner med det yderligere nøgleord slot, f.eks. public slots: inde i klassedeklarationen

  • metaobjektoversætteren moc skal køres over deklarationsfilen for at ekspandere makroer og oprette implementeringen (som man ikke behøver kende til). Uddatafilerne fra moc kompileres også af C++ oversætteren.

En anden måde at bruge signaler uden at aflede fra QObject er at bruge klassen QSignal. Se referencedokumentationen for mere information og eksempel på brug. Vi antager at du afleder fra QObject i det følgende.

På denne måde kan din klasse sende signaler hvor som helst og sørge for slots som signaler kan forbindes til. Ved at bruge et signal, behøver du ikke bryde dig om hvem der modtager det. Du behøver kun at sende signalet, og hvilken slot du end forbinder den til kan reagere når den sendes. Desuden kan en slot bruges som en almindelig metode i implementeringen.

For nu at forbinde et signal til en slot, skal du bruge metoderne connect() som varetages af QObject eller, hvis tilgængelige, specielle metoder som objekter sørger for for at angive forbindelsen for et vist signal.

Eksempel på brug

For at forklare hvordan objektinteraktion håndteres, tager vi vort første eksempel igen og udvider det med en enkel forbindelse:

#include <qapplication.h>
#include <qpushbutton.h>

int main( int argc, char **argv )
{
QApplication a( argc, argv );

QPushButton hello( "Hello world!" , 0);
hello.resize( 100, 30 );

a.setMainWidget( &hello );

QObject::connect(&hello, SIGNAL( clicked() ), &a, SLOT( quit() ));

hello.show();
return a.exec();
}

Du mærker at alt du skal tilføje for at give knappen mere kommunikation er metoden connect(): connect (&hello, SIGNAL( clicked() ), &a, SLOT( quit() ));. Hvad er betydningen nu? Klassedeklarationen af QObject siger følgende om metoden connect():

bool connect ( const QObject * afsender, const char * signal, const QObject * modtager, const char * medlem )

Dette betyder at du skal angive en QObject-instanspeger som er signalets afsender, hvilket betyder at den kan sende signalet, som første parameter. Derefter skal du angive signalet som du vil forbinde til. De to sidste parametre er modtagerobjektet som sørger for en slot, fulgt af medlemsfunktionen som er en virkelig slot som vil køres når signalet sendes.

Ved at bruge signaler og slots, kan programmets objekter nemt kommunikere med hinanden uden udtrykkeligt at afhænge af typen af modtagarobjektet. Du vil lære mere om hvordan denne mekanisme bruges produktivt senere i håndbogen. Mere information om signal/slot-mekanismen findes også i KDE's biblioteksreferenceguide og QT's reference på nettet.