15
Tags:
c++
Skrevet af
Bruger #5688
@ 07.07.2004
Introduktion
Denne artikel omhandler programmering i Qt-frameworket. Hvad er Qt kan man så spørge, og til det kan man svare at Qt er et fuldt objektorienteret GUI/udviklings-framework implementeret i C++, men med bindings til mange andre sprog (f.eks. Python og Perl).
Dog vil jeg benytte mig af C++ i denne artikel. Qt er et meget kompliceret framework, det danner eksempelvis basis for det meget avancerede KDE-desktopmiljø på UNIX (
http://www.kde.org ), der er kendt for at have meget fart på i udviklingen, og Qt er en af de primære årsager til dette.
Qt er udviklet af det norske firma Trolltech (
http://www.trolltech.com ), der har udgivet Qt/X11 (Qt til GNU/Linux og andre UNIX-systemer der bruger X11R6) under GPL, den frie licens som næsten al fri software er udgivet under. Hvis man vil udvikle proprietære eller closed-source applikationer i Qt, må man dog betale. Ligeledes må man også betale hvis man vil udvikle Qt på Windows. Der er en gammel Qt-Noncommercial udgave til Windows (
http://www.trolltech.com/download/qt/download_noncomm.html ), men den er ret gammel (april 2001), og integrerer kun med Visual Studio 6.
Derfor er Qt klart bedst hvis man udvikler på UNIX/X11 platformen (GNU/Linux går ind under denne), selvom man dog kan downloade en trial af den nyeste Qt-Windows (
http://www.trolltech.com/download/qt/evaluate.html ). Den virker i 30 dage og integrerer med både Borland C++ Builder 5.5, Visual Studio 6 og Visual Studio .NET. En meget vigtig ting at huske er at software der udvikles med Qt Free Editon
skal udgives under en GPL-kompatibel licens, hvis det overhovedet skal udgives.
Denne artikel vil ikke beskæftige sig med at sætte udviklingsmiljøet op, men jeg kan kort nævne hvordan man laver en Qt-projektfil i UNIX (jeg ved ikke om det foregår på samme måde på Windows, men eftersom portabilitet er en af Qt's største fordele vil jeg tro det). Hvis du befinder dig i mappen der indeholder dine .cpp og .h-filer, skriver du bare:
qmake -project
qmake
make
Den første kommando opretter din projektfil (*.pro), den anden opretter en Makefile ud fra projektfil, den sidste kompilerer baseret på makefilen.
Det forudsættes at du har en vis grad af C++-erfaring, i hvert fald mindst en forståelse for pointere, references, objektorienterede teknikker, osv. Derfor vil jeg ikke i denne artikel gennemgå al C++-koden (!"i++; // Inkrementerer i med én"), men blot fokusere på de Qt-relaterede dele. Erfaring med andre grafiske libraries er ikke forudsat.
Denne artikel vil gennemgå programmeringen af en simpel editor, der kan åbne en fil, gemme den, og præsentere et konsistent brugerinterface. Den vil ikke være synderligt avanceret, og vil ikke med det samme gøre en i stand til at producere kæmpestore Qt-applikation, hvilket er et alt for komplekst emne til en artikel af denne størrelse. Istedet vil den introducere Qt og de teknikker man benytter sig af, så man kan danne sig et billede af hvorvidt det er interessant som en fremtidig udviklingsplatform.
Lidt om Qt's virkemåde
Et grafisk Qt-program er opbygget af
widgets - controls i Windows-terminologi. En widget kan være alt, lige fra en knap, over en tekstboks, til en OpenGL-scene. Gennem ActiveQt kan man ligeledes bruge ActiveX-komponenter i sin applikation, dog kun på Windows.
Qt opnår sin store fleksibilitet og styrke gennem MOC - Meta Object Compiler, en compiler der parser dine .cpp-filer før de sendes igennem compileren. Via denne precompiling er det muligt at implementere ganske komplekse makroer der er utroligt læsevenlige og lette at have med at gøre, men samtidigt utroligt kraftfulde. MOC's primære fordel er at den tillader brug af <b>signals og slots</b>.
Signals og slots handler i princippet om at en funktion kan udsende et signal, der så aktiverer et slot. Et signal har ingen effekt i sig selv, men kan kaldes med parametre, der så aktiverer et slot med de pågældende parametre. Et slot er en funktion med en
void-returtype, der både kan kaldes via et signal og som enhver anden funktion. Forbindelsen mellem et signal og et slot gøres i makrofunktionen
connect, med følgende syntaks:
connect(SENDEROBJECT, SIGNAL(signalfunction()), MODTAGEROBJECT, SLOT(slotfunction()))
F.eks:
connect(saveButton, SIGNAL(buttonPressed()), textWidget, SLOT(saveFile()))
Den ovenstående linie vil så forårsage at medlemsobjektet
textWidget kalder funktionen
saveFile() hver gang der trykkes på
saveButton.
connect-funktionen kaldes sædvanligvis i en widgets konstruktor, og omhandler sædvanligvis widget'ens underwidgets (f.eks. knapperne i en menu), selvom både signalet og slot'et kan komme fra et hvilket som helst objekt construktoren kender til. Hvis man er yderligere interesseret i ideen bag signals og slots har Trolltech en fin artikel om det (
http://doc.trolltech.com/3.3/signalsandslots.html ), men det er ikke påkrævet læsning for at gennemføre denne artikel.
Resourcer
http://doc.trolltech.com Indeholder dokumentation for alle Qt's klasser.http://qtforum.org Det største Qt-forum.http://sigkill.dk/code/qed.tar.gz Kildekoden til det program artiklen beskriver. Denne fil er <b>påkrævet</b>, idet der medfølger nogle ikoner (i images-mappen) som vi skal bruge. Koden i denne artikel er blevet renset for alle kommentarer, men koden i dette arkiv er rigt kommenteret..
Koden
Lad os så komme i gang med at skrive noget kode. I alt har vi 5 filer -
main.cpp, qeditfield.h, qeditfield.cpp, qeditor.h, qeditor.cpp.
Vi starter med main.cpp:
#include <qapplication.h>
#include <qstring.h>
#include "qeditor.h"
int main( int argc, char *argv[] )
{
QApplication app( argc, argv );
QString filename;
if ( app.argc() > 1 )
{
filename = app.argv() [ 1 ];
}
QuickEditor *qe = new QuickEditor( filename );
app.setMainWidget( qe );
qe->show();
return app.exec();
}
(Det kan endnu ikke kompileres, forresten, og vil ikke kunne før vi har skrevet det helt færdigt).
Det første vi gør er at inkludere to filer, qapplication.h der er vores indgang til Qt, og qeditor.h, der er vores editors klasse (som vi ikke har lavet endnu). Vi inkluderer også qstring.h, der indeholder Qt's egen string-klasse. Det er ikke noget vi behøver bekymre os om.
I linie 7 erklærer vi en ny QApplication, app, og sender vort programs parametre som parametre til konstruktoren. Dette er vor egentlige applikation.
I linie 10 checker vi om der er mere end ét parameter til programmet, og hvis der er det, gemmes det parameter i en QString og sendes som argument til klassens konstruktor (det gør det også hvis der ikke er nogle argumenter, forresten, men i konstruktoren checker vi om QString'en er tom).
Vi erklærer et nyt objekt af klassen QuickEditor, og sætter vor QApplication, app, til at have den som hovedwidget. Derefter kalder vi en af qe's medlemsfunktioner,
show(), der gør widget'en synlig. Til sidst i funktionen kalder vi vor QApplications
exec()-funktion, og fra da af kontrolleres applikationen af Qt's interne loop.
Vores applikation er koncentreret omkring én widget (der er nedarvet fra QMainWindow, en klasse der er velegnet som "top"-vindue i et program) -
QuickEditor, der er erklæret i qeditor.h og implementeret i qeditor.cpp.
qeditor.h
qeditor.h indeholder blot en klassedeklaration:
#ifndef QEDITOR_H
#define QEDITOR_H
#include <qmainwindow.h>
#include <qstring.h>
#include <qstringlist.h>
#include <qaction.h>
#include <qpopupmenu.h>
#include "qeditfield.h"
class QuickEditor : public QMainWindow
{
Q_OBJECT
public:
QuickEditor( const QString &filename );
private slots:
void fileNew();
void fileOpen();
void fileSave();
void fileSaveAs();
void fileQuit();
void helpAbout();
void helpAboutQt();
void fieldChanged();
protected:
virtual void closeEvent( QCloseEvent * );
private:
bool canWipeFile();
bool canQuit();
void init();
void load( const QString& );
QPopupMenu *fileMenu;
QPopupMenu *helpMenu;
QString currentFile;
QStringList recentFiles;
QuickEditTextField *qfield;
bool changed;
};
#endif
Igen, alle kommentarer er strippet fra koden, det anbefales at hente arkivet der er linket til tidligere. Det mest interessante, og fremmede i denne constructor er formentligt det mærkelige udtryk i toppen -
Q_OBJECT.
Q_OBJECT er en makro der får MOC-preprocessoren til at implementere signals og slots for den pågældende klasse. Reelt betyder det at dine kildefiler bliver kørt igennem preprocessoren, og bliver sat op til at tillade
connect og den slags, noget der er rædselsfuldt at skrive i hånden, men simpelt hvis det gøres gennem en preprocessor.
Derudover er der nogle rimeligt indlysende funktioner, samt en lidt mere obskur en -
closeEvent( QCloseEvent * ). Denne funktion kaldes når man trykker på [X]'et i window manageren. Basalt set når applikationen afsluttes uden at det er en knap i selve programmet der gør det.
En sådan erklæring er imidlertidigt ikke nok, så vi går i gang med at implementere funktionerne i
qeditor.cpp.
qeditor.cpp
Først diverse include-direktiver:
#include "qeditor.h"
#include "qeditfield.h"
#include <qaction.h>
#include <qapplication.h>
#include <qfile.h>
#include <qfiledialog.h>
#include <qmenubar.h>
#include <qmessagebox.h>
#include <qpixmap.h>
#include <qpopupmenu.h>
#include <qsettings.h>
#include <qstatusbar.h>
#include <qtoolbar.h>
#include <qtoolbutton.h>
#include "images/file_new.xpm"
#include "images/file_open.xpm"
#include "images/file_save.xpm"
Standard include-direktiver, sørg blot for at de relevante billeder findes under images-mappen. Billederne kan findes i det arkiv der blev linket til tidligere.
Her er den korte constructor:
QuickEditor::QuickEditor( const QString &filename )
: QMainWindow( 0, 0, WDestructiveClose )
{
init();
if ( !filename.isEmpty() )
load( filename );
}
Constructoren tager en QString som parameter, og vil, hvis strengen ikke er tom, loade den fil som den indeholder navnet på (
load()-funktionen beskrives senere). QuickEditor-konstruktoren kalder, som vi kan se, QMainWindow's konstruktor, hvilket sparer os for en masse manuelt arbejde. Ydermere kalder vi
init(), der er en funktion der sætter GUI'en op, forbinder signals til slots, osv. Dette placeres i en seperat funktion, så vi senere kan tilføje alternative konstruktorer, uden at vi skal genskrive en masse GUI-relateret kode. Her er
init() i
qeditor.cpp:
void QuickEditor::init()
{
QAction *fileNewAction;
QAction *fileOpenAction;
QAction *fileSaveAction;
QAction *fileSaveAsAction;
QAction *fileQuitAction;
fileNewAction = new QAction(
"Ny fil", QPixmap( file_new ),
"&Ny", CTRL + Key_N, this, "new" );
connect( fileNewAction, SIGNAL( activated() ), this, SLOT( fileNew() ) );
fileOpenAction = new QAction(
"Åben fil", QPixmap( file_open ),
"&Åben...", CTRL + Key_O, this, "open" );
connect( fileOpenAction, SIGNAL( activated() ), this, SLOT( fileOpen() ) );
fileSaveAction = new QAction(
"Gem fil", QPixmap( file_save ),
"&Gem", CTRL + Key_S, this, "save" );
connect( fileSaveAction, SIGNAL( activated() ), this, SLOT( fileSave() ) );
fileSaveAsAction = new QAction(
"Gem fil som", QPixmap( file_save ),
"Gem fil &som...", 0, this, "save as" );
connect( fileSaveAsAction, SIGNAL( activated() ),
this, SLOT( fileSaveAs() ) );
fileQuitAction = new QAction( "Afslut", "&Afslut", CTRL + Key_Q, this, "quit" );
connect( fileQuitAction, SIGNAL( activated() ), this, SLOT( fileQuit() ) );
QToolBar* fileTools = new QToolBar( this, "file operations" );
fileTools->setLabel( "Filhandlinger" );
fileNewAction->addTo( fileTools );
fileOpenAction->addTo( fileTools );
fileSaveAction->addTo( fileTools );
fileTools->addSeparator();
fileMenu = new QPopupMenu( this );
menuBar() ->insertItem( "&Fil", fileMenu );
fileNewAction->addTo( fileMenu );
fileOpenAction->addTo( fileMenu );
fileSaveAction->addTo( fileMenu );
fileSaveAsAction->addTo( fileMenu );
fileMenu->insertSeparator();
fileQuitAction->addTo( fileMenu );
helpMenu = new QPopupMenu( this );
menuBar() ->insertItem( "&Hjælp", helpMenu );
helpMenu->insertItem( "Om &Qt", this, SLOT( helpAboutQt() ) );
helpMenu->insertSeparator();
helpMenu->insertItem( "&Om", this, SLOT( helpAbout() ) );
setCaption( "Quick Editor" );
statusBar() ->message( "Parat", 2000 );
qfield = new QuickEditTextField( this, "qfield" );
setCentralWidget( qfield );
connect( qfield, SIGNAL( textChanged() ), this, SLOT( fieldChanged() ) );
}
Det første vi gør er at erklære nogle QActions. Dette er en abtrakt brugerinterfacehandling, der kan tilføjes både som et menupunkt, i en toolbar, osv. Det vil dog være den samme QAction der bliver refereret til, så de vil konstant være synkrone (hvis det f.eks. er en "Bold"-knap i en teksteditor vil knappen blive rykket ind, selv hvis man aktiverer QAction'en på en anden måde end ved at trykke på den, eksempelvis gennem genvejstaster). Det burde være relativt tydeligt ud fra variabelnavnene, hvilke funktioner de forskellige QActions vil have. De har dog ingen funktion før de
connect'es til nogle
slots.
Som det kan ses, er variablerne pointers der umiddelbart ikke peger på noget, derfor opretter vi straks nogle QActions til dem. QActions konstruktor modtager parametre i denne rækkefølge:
[Navn når den optræder i en toolbar], ikon, [Navn når den optræder i en menu], genvejstast, parent widget, internt navnParent Widget er den widget som QObject'et et en underwidget af - sædvanligvis er det
this, men nu og da er det praktisk at erklære en widget som tilhørende en anden end den der skaber den. De fleste klasser i Qt tager
parent widget og
internt navn som de to sidste parametre ( check
http://doc.trolltech.com for detaljer ). Normalt er det ikke værd at bekymre sig om det interne navn.
Efter vi har skabt hvert objekt kalder vi
connect() for at forbinde hver QActions
activated-signal til en medlemsfunktion i klassen der tilsvarer den funktion som vi ønsker den relevante QAction skal have. Disse funktioner vil vi implementere senere.
Når vores QActions er oprettet kan vi lave en QToolBar med de relevante actions. Bemærk at de tilføjes ved at kalde en medlemsfunktion i den QAction vi ønsker at tilføje, med QToolBar'en som parameter. Til sidst tilføjer vi en seperator - bemærk her at det nu er en medlemsfunktion i QToolBar'en der kaldes.
Derefter skal vi oprette en QPopupMenu. Constructoren modtager ét parameter, parent widget, så der er ikke noget besynderligt over det. For at tilføje vores nye QPopupMenu til applikationen, skal vi kalde
menuBar()->insertItem("Navn", QPopupMenu-objekt), hvorefter menuen vil være at finde i applikationen. Derefter kan vi tilføje elementer ganske som i QToolBar. Efter vi har oprettet vor traditionelle "Fil"-menu, opretter vi endnu en klassisk menu - "Hjælp" - på præcist samme måde.
Derefter kalder vi setCaption der sætter programmets titel, hvorefter vi udskriver en besked i statusbaren. Det er meget vigtigt at der skrives til den i konstruktoren, da den ellers ikke vil blive oprettet. Andet argument er tiden (i milisekunder) der går før beskeden forsvinder.
Som noget af det sidste opretter vi et nyt QuickTextEditField - en klasse vi selv opretter der vil blive beskrevet senere, og den sættes til at være programmets centrale widget (programmet består i princippet kun af én widget, da toolbars og statuslinier behandles på en anden måde. Almindelige widgets arrangeres i tabeller der minder en del om dem der benyttes i HTML, men det er ikke relevant for denne artikel).
Den sidste linie i konstruktoren forbinder QuickTextEditField's
textChanged()-signal til denne klasses
fieldChanged()-slot. Dette indikerer at teksten har ændret sig siden filen sidst blev gemt, så programmet vil spørge om filen skal gemmes først, hvis man forsøger at lukke det.
Nu vil vi implementere de andre medlemsfunktioner i klassen, stadigvæk i qeditor.cpp:
void QuickEditor::fileNew()
{
if ( !canWipeFile() )
return;
qfield->setText("");
changed = FALSE;
currentFile = QString::null;
setCaption( "Quick Editor" );
return ;
}
fileNew() er den funktion der kaldes når der trykkes "Ny" i "Fil"-menuen. Hvis filen er ændret spørger den om den skal gemmes (via
canWipeFile(), som vi implementerer senere) samtidigt med at den fjerner teksten fra
qfield, sætter changed til FALSE (når filen er ny er der ingen ændringer), resetter currentFile, den string der indeholder filnavnet på den åbne fil, og sætter vinduestitlen til standard (vi ændrer vinduestitlen når vi åbner en fil, dette vil blive implementeret senere).
void QuickEditor::fileOpen()
{
if ( !canWipeFile() )
return;
QString filename = QFileDialog::getOpenFileName(
QString::null, "Alle filer (*)", this,
"file open", "Quick Editor -- Åbn fil" );
if ( !filename.isEmpty() )
load( filename );
else
statusBar() ->message( "Åbning af fil annulleret", 2000 );
}
Igen starter vi med at kalde en funktion der checker hvorvidt filen har ændret sig siden den sidst blev gemt, og, hvis den har, spørger brugeren om den skal gemmes. Derefter opretter vi en QString som vi giver den værdi
QFileDialog::getOpenGileName returnerer.
Vores argumenter til funktionen betyder at der oprettes en QFileDialog der har en "åben"-knap (og ikke en "gem"-knap), der ikke har nogen fil forvalgt (
QString::null), der kan vælge alle filer (
"Alle filer (*)"), der er en underwidget til
this, hvilket i dette tilfælde betyder at fildialogen centreres over applikationen, hvis interne navn er
"file dialog" og hvis vinduestitel er
"Quick Editor -- Åbn fil".
Der vil formentligt opstå en undtagelse hvis vi forsøger at loade fra et tomt filnavn, så derfor checker vi om filnavnet er tomt (hvilket det er hvis brugeren lukkede fildialogen uden at vælge en fil), før vi loader filen. Selve indlæsninen gøres af funktionen
load(),
fileOpen() er blot en form for frontend. Hvis vi finder at filnavnet er tomt, skriver vi en besked i statusbaren om at åbningen er blevet annulleret.
Nu er det logisk at implementere
load():
void QuickEditor::load( const QString& filename )
{
QFile file( filename );
if ( !file.open( IO_ReadOnly ) )
{
statusBar() ->message( QString( "Kunne ikke åbne \\'%1\\'" ).arg( filename ), 2000 );
return ;
}
QTextStream ts( &file );
qfield->setText( ts.read() );
file.close();
currentFile = filename;
setCaption( QString( "Quick Editor -- %1" ).arg( currentFile ) );
statusBar() ->message( QString( "Åbnede \\'%1\\'" ).arg( currentFile ), 2000 );
changed = FALSE;
}
load() tager ét parameter, en reference til en QString der indeholder et filnavn der vil blive indlæst. Vi starter med at oprette et QFile-objekt, et objekt der faciliterer interaktion med en fil, og sætter den til at åbne filen der er givet i parametret
QString filename. Bemærk at vi ikke foretager nogen checks for at undersøge om strengen er tom.
Vi kalder QFile::open på vores objekt, og specificerer at vi udelukkende ønsker at læse fra den. Hvis dette slår fejl (f.eks. hvis filen ikke eksisterer, eller vi ikke har rettigheder til den) skriver vi en fejlmeddelelse i statusbaren og returnerer fra funktionen.
For at få data fra filen, opretter vi en QTextStream der opererer på den QFile vi definerede tidligere. QTextStream har en række medlemsfunktioner der gør det nemt at behandle teksten i filen, hvorimod QFile i sig selv er mere low-level og behandler selve I/O-processen.
qfield, et objekt af klasse QuickEditTextField, har en medlemsfunktion ved navn
setText(), der, som navnet antyder, sætter teksten i feltet til den QString vi leverer som parameter (QuickEditTextField implementeres senere).
Vi bruger
QTextStream::read() for at få indholdet af filen. Eftersom editoren behandler klar tekst er der ikke behov for at køre filindholdet gennem noget filter.
Vi afslutter vores I/O-operation med
QFile::close(). Vi sætter
currentFile til filename, altså den fil vi lige har indlæst, ændrer vinduestitlen til at reflektere denne ændring i filnavnet (som vi kan se kan man erklære en QString med variabler indsat lidt på samme måde som i
prinf() i C, se evt.
http://doc.trolltech.com/3.3/qstring.html#arg for yderligere detaljer).
Vi skriver en besked i statusbaren og sætter
changed til FALSE for at fuldende indlæsningen.
void QuickEditor::fileSave()
{
if ( currentFile.isEmpty() )
{
return fileSaveAs();
}
QFile file( currentFile );
if ( !file.open( IO_WriteOnly ) )
{
statusBar() ->message( QString( "Kunne ikke gemme \\'%1\\'" ).arg( currentFile ), 2000 );
return ;
}
QTextStream ts( &file );
ts << *qfield;
file.close();
setCaption( QString( "Quick Editor -- %1" ).arg( currentFile ) );
statusBar() ->message( QString( "Gemte \\'%1\\'" ).arg( currentFile ), 2000 );
changed = FALSE;
}
Meningen med denne funktion er at gemme filen under dens eksisterende navn.
Vi starter med at checke om
currentFile er tom - hvilket vil betyde at vi netop har startet programmet eller trykket på "Ny fil", og således ikke har specificeret et filnavn. Hvis dette er tilfældet, returnerer vi samtidigt med at vi kalder funktionen
fileSaveAs(), der viser et vindue hvor brugeren kan vælge hvilket filnavn filen skal gemmes under.
Vi opretter et QFile-objekt der opererer på det filnavn
currentFile indeholder (notér at, i modsætning til
load() ønsker vi her også skriveadgang), hvis vi ikke kan oprette I/O-processen returnerer vi og printer en fejlmeddelelse. Vi opretter en textstream der opererer på filen og overfører indholdet af
qfield til den (vi kan benytte operatoren "<<" da vi har specificeret hvordan det skal fungere på en QTextStream i vor QuickEditTextField-klasse, beskrevet senere). Derefter lukker vi filen, printer en besked i statusbaren, ændrer vinduestitlen og sætter
changed til FALSE. Basalt set er denne funktion i det store hele
load(), men med omvendt fortegn.
void QuickEditor::fileSaveAs()
{
QString filename = QFileDialog::getSaveFileName(
QString::null, "Alle filer (*)", this,
"file save as", "Quick Editor -- Gem som" );
if ( !filename.isEmpty() )
{
int answer = 0;
if ( QFile::exists( filename ) )
answer = QMessageBox::warning(
this, "Quick Editor -- Overskriv fil",
QString( "Overskriv\\n\\'%1\\'?" ).
arg( filename ),
"&Ja", "&Nej", QString::null, 1, 1 );
if ( answer == 0 )
{
currentFile = filename;
fileSave();
return ;
}
}
statusBar() ->message( "Gemning af fil annulleret", 2000 );
}
Ideen med denne funktion er at gemme filen under et nyt navn. Altså skal vi ændre
currentFile og kalde
fileSave(). Vi starter med at oprette en QString og tildele den værdien fra en QFileDialog. Detaljerne vedørerende dette kan findes i forklaringen for
fileOpen(). Vi checker om
filename-variablen indeholder noget (for at se om brugeren har trykket "Annuller"), og hvis den gør, checker vi om det filnavn der er blevet specificeret findes i forvejen. Hvis dette check også er positivt, viser vi en
QMessageBox (
http://doc.trolltech.com/3.3/qmessagebox.html ) der er en child-widget af
this, hvis vinduestitel er "Quick Editor -- Overskriv fil", hvis tekst er "Overskriv <filnavn>?", og hvor der er to knapper - "Ja" og "Nej".
Hvis svaret er "Ja", sætter vi
currentFile til
filename, som brugeren valgte tidligere, og filen gemmes hvorefter der returneres. Hvis ikke, skrives en besked i statusbaren.
void QuickEditor::fileQuit()
{
if ( canWipeFile() )
{
if ( canQuit() )
{
qApp->exit( 0 );
}
}
}
Denne funktion spørger om du vil gemme evt. ugemte filer, spørger en sidste gang om programmet skal afsluttes, og afslutter programmet.
qApp->exit() afslutter altid programmet. Det er også et slot, så det kan connectes til et signal.
bool QuickEditor::canQuit()
{
QString msg;
msg = "Er du sikker på at du vil afslutte Quick Editor?";
int x = QMessageBox::information( this, "Quick Editor -- Afslutning",
msg, "Ja", "&Nej", 0, 1 );
switch ( x )
{
case 0:
return TRUE;
break;
case 1:
default:
return FALSE;
break;
}
return TRUE;
}
Denne funktion returnerer TRUE hvis brugeren vil afslutte programmet, ellers FALSE.
QMessageBox::information ligner meget den QMessageBox vi brugte i
fileSaveAs(), så jeg henfører til den funktions beskrivelse, eller dokumentationen (
http://doc.trolltech.com/3.3/qmessagebox.html#information ) for yderligere information.
void QuickEditor::closeEvent( QCloseEvent* e )
{
fileQuit(); // Vi kalder bare samme funktion som menupunktet "Afslut."
}
closeEvent() er den funktion der kaldes når programmet får besked på at afslutte uden om programmets egne knapper, f.eks. ved at trykke på [x]'et i windowmanageren. I dette tilfælde kalder i bare
fileQuit().
void QuickEditor::helpAbout()
{
QMessageBox::about( this, "Quick Editor -- Om",
"<center><h1><font color=red>Quick Editor<font></h1></center>"
"<center><em><a href=\\"mailto:athas@sigkill.dk\\">athas@sigkill.dk</a></em></center>"
"<p>Quick Editor - Det er det de unge vil ha'.</p>"
);
return ;
}
helpAbout(), der er den traditionelle "Om"-boks, er interessant fordi den viser at det er muligt (til en hvis grænse) at bruge HTML i koden. Ellers er der ikke meget at se, about-boksen implementeres via endnu en af QMessageBox's medlemsfunktioner.
void QuickEditor::helpAboutQt()
{
QMessageBox::aboutQt( this, "Quick Editor -- Om Qt" );
}
Opretter et "Om Qt"-vindue med informationer direkte fra Qt selv, med vinduestitlen "Quick Editor -- Om Qt"
bool QuickEditor::canWipeFile()
{
if ( changed )
{
QString msg;
if ( currentFile.isEmpty() )
msg = "Ikke navngivet ";
else
msg = QString( "Filen '%1'\\n" ).arg( currentFile );
msg += "er blevet ændret. Hvis programmet lukkes går alle ændringer siden sidste gemning tabt.";
int x = QMessageBox::information( this, "Quick Editor -- Ikke gemte ændringer",
msg, "&Gem", "Annuller", "&Smid væk",
0, 1 );
switch ( x )
{
case 0:
fileSave();
break;
case 1:
default:
return FALSE;
break;
case 2:
break;
}
}
return TRUE;
}
Ideen med
canWipeFile() er, at funktionen checker om filen skal gammes (spørger brugeren) - den kaldes før der indlæses nye filer, før programmet afsluttes, osv.
canWipeFile() returnerer TRUE hvis den indlæste fil skal fjernes (det værende gennem indlæsning af en ny fil eller afslutning af programmet), og FALSE hvis den ikke skal.
Den dialogboks der spørger brugeren om han/hun/den/det vil gemme filen, kan enten vise navnet på den indlæste fil (gemt i currentFile-variablen) eller skrive "Ukendt Fil" - eftersom der er to muligheder opretter vi en QString der kan indeholde teksten til dialogboksen, i stedet for at skrive den direkte i funktionskaldet som vi plejer. Hvis currentFile er tom (filen er ikke gemt på disken), skriver vi "Ikke Navngivet."
Vi kalder
QMessageBox::information() (
http://doc.trolltech.com/3.3/qmessagebox.html#information-2 ), der har teksten gemt i
msg-variablen samt tre knapper. De to tal til sidst i funktionskaldet er hhv. standard-knappen og escape-knappen (de knapper der bliver aktiveret hvis brugeren trykker på hhv. ENTER eller ESCAPE).
QMessageBox::information() returnerer en int der tilsvarer hvilken knap der blev trykket på (0, 1 eller 2). Denne værdi behandler vi blot i en switch.
void QuickEditor::fieldChanged()
{
changed = TRUE;
}
Dette er et slot der kaldes når signalet
textChanged() bliver udsendt af
qfield. Det sætter værdien af bool-variablen
changed til TRUE, og indikerer dermed at der er ændret i teksten siden den sidst blev gemt.
Nu har vi implementeret QuickEditor-klassen, der udgør selve vort program. Det eneste vi nu mangler er at definere og implementere selve den widget som vi kan skrive tekst i (
qedit i QuickEditor). Vi nedarver fra QTextEdit, da vi derved har næsten alt den funktionalitet vi har behov for. Ja, faktisk behøvede vi sikkert ikke nedarve overhovedet, men det gør det lettere at udvide funktionaliteten af klassen senere, eksempelvis med syntakshighlight.
qeditfield.h
#ifndef QEDITFIELD_H
#define QEDITFIELD_H
#include <qtextedit.h>
class QuickEditTextField : public QTextEdit
{
Q_OBJECT
public:
QuickEditTextField(QWidget *parent, const char *name);
private:
void init();
};
QTextStream &operator<<( QTextStream&, const QuickEditTextField& );
QTextStream &operator>>( QTextStream&, QuickEditTextField& );
#endif
Der er ikke noget interessant i denne deklaration, vi nedarver fra QTextEdit, indsætter Q_OBJECT-makroen og opretter en init()-funktion (ideen med disse er, at hvis vi har flere konstruktorer kan de kalde denne funktion for at afvikle fælles kode).
Der er ikke meget at implementere i denne klasse:
qeditfield.cpp
#include "qeditfield.h"
QuickEditTextField::QuickEditTextField( QWidget *parent, const char *name )
: QTextEdit( parent, name )
{
init();
}
void QuickEditTextField::init()
{
setMinimumWidth(400);
setMinimumHeight(400);
return;
}
QTextStream &operator<<( QTextStream& s, const QuickEditTextField& field )
{
s << field.text();
return s;
}
QTextStream &operator>>( QTextStream& s, QuickEditTextField& field )
{
field.setText(s.read());
return s;
}
I konstruktoren kalder vi
init(), der så sætter minimumsdimensionerne på widget'en. Vi har også to overstyrede operatorer, der gør at vi let kan udveksle data med en QTextStream (der, f.eks., kan operere på en fil, så vi let kan foretage disk-I/O).
Konklusion
Som jeg håber der er givet indtryk af i denne artikel, er Qt et
ekstremt stort og meget dækkende framework. Det er ikke bar et pænt interface man lige kan smække på en hvilken som helst applikation, Qt kræver sin helt egen programmeringsstil, og udvider ligefrem C++ for at opnå dette.
Beløninngen er let læselig kode, god ydelse, et væld af funktionalitet og et flot brugerinterface.
Når du udvikler dine egne Qt-programmer er det en massiv hjælp at bruge
http://doc.trolltech.com - dokumentationen er rigtig, rigtig god.
Held og lykke.
Hvad synes du om denne artikel? Giv din mening til kende ved at stemme via pilene til venstre og/eller lægge en kommentar herunder.
Del også gerne artiklen med dine Facebook venner:
Kommentarer (6)
Super cool artikel! Mere mere :-)
Der skal nok komme mere, jeg skal bare finde på noget at skrive om.
Screenshots af et program vil være dobbelt sejt, så man ved hvad man kan forvente sig, hvis man bare skimter artiklen igennem
Rigtig dejlig artikel!
Mere er der ikke at sige
Når jeg engang går igang med at lege med linux og KDE igen står det at prøve at programmere i Qt da som nummer 1. på listen!
Et kæmpe arbejde du har lavet der!!
Men som det allerede er sagt, så ville et billede/screenshot af programmet (evt. flere eftersom det udvikler sig) være perfekt... Så du får 4
Du skal være
logget ind for at skrive en kommentar.