Includes necessaris La funció setupUi() Connexions de Signals i Slots Implementació de les funcionalitats més importants Contingut dels fitxers Zip del projecte
Quan fem el disseny de l'aplicació amb el QT Designer, ell ja ens posa en un fitxer intern tots els includes necessaris per a que a l'hora de compilar tot vagi bé. Però resulta que al fer la implementació ens trobem que fem ús d'altres classes de les quals el QT Designer no té perquè saber-ho, així que en la definició de la nostra classe (mainwindowimpl.h) hi tindrem que incloure les definicions d'aquestes classes.
Si fem un cop d'ull al fitxer mainwindowimpl.h, veiem que s'hi inclouen dos fitxers:
#include <QMainWindow>
#include "ui_mainwindow.h"
El primer (QMainWindow) ve degut a que hem escollit crear un projecte del tipus finestra principal, i el segon ens inclou un fitxer que genera uic i que conté codi en C++ equivalent al fitxer mainwindow.ui, així cada cop que realitzem un canvi (per exemple afegim un nou widget) en el fitxer mainwindow.ui, uic ja s'encarregarà automàticament de fer la conversió a codi C++ i per tant no caldrà que ho implementem nosaltres.
El que haurem de fer és afegir els següents includes al fitxer mainwindowimpl.h:
#include <QFileInfo>
#include <QMessageBox>
#include <QFileDialog>
#include <QTextStream>
#include <QSettings>
#include <QCloseEvent>
Si mirem el fitxer d'implementació de la classe, mainwindowimpl.cpp, veiem que dins el constructor es fa una crida a la funció setupUi(). Aquesta funció és l'encarregada de construir la finestra segons el disseny que haguem fet amb el QT Designer.
És a dir, quan fem el disseny de la finestra i editem per tant el fitxer mainwindow.ui mitjançant el QT Designer, tota aquesta informació s'emmagatzema en format XML dins aquest fitxer. Després quan compilem l'aplicació, internament es realitza un qmake (que alhora es criden dues altres aplicacions: uic i moc) i es genera el Makefile (que després serà compilat amb la comanda make) i també el fitxer ui_mainwindow.h (variarà el nom en altres casos) que és un fitxer intermedi que es genera automàticament en cada compilació i que conté la funció setupUi() que és l'encarregada de generar la finestra de l'aplicació.
Si volguéssim podríem prescindir d'aquesta funció i generar el codi necessari per crear la finestra mitjançant la nostra implementació, però és molt més còmode i precís fer el disseny utilitzant el QT Designer i que les eines esmentades ens generin el codi necessari per crear la finestra.
Abans de tot, veiem el codi del constructor de la classe (al fitxer mainwindowimpl.cpp) que necessitem:
MainWindowImpl::MainWindowImpl( QWidget * parent, Qt::WFlags f)
: QMainWindow(parent, f)
{
setupUi(this);}
connects();
mostraFitxer("");
Podríem ficar totes les connexions aquí dins però per tenir-ho més ordenat, crearem una funció membre que es dirà connects() i que serà l'encarregada de crear totes les connexions necessàries entre els elements implicats.
La funció en qüestió serà:
void MainWindowImpl::connects()
{
connect(actionNou, SIGNAL(triggered()), this, SLOT(newFile()));}
connect(actionObrir, SIGNAL(triggered()), this, SLOT(open()));
connect(actionGuardar, SIGNAL(triggered()), this, SLOT(save()));
connect(actionGuardar_com, SIGNAL(triggered()), this, SLOT(saveAs()));
connect(actionSortir, SIGNAL(triggered()), this, SLOT(close()));
connect(actionRetalla, SIGNAL(triggered()), textEdit, SLOT(cut()));
connect(actionCopia, SIGNAL(triggered()), textEdit, SLOT(copy()));
connect(actionEnganxa, SIGNAL(triggered()), textEdit, SLOT(paste()));
connect(actionAbout, SIGNAL(triggered()), this, SLOT(about()));
connect(actionAboutQT, SIGNAL(triggered()), this, SLOT(aboutQT()));
connect(textEdit, SIGNAL(copyAvailable(bool)),actionRetalla, SLOT(setEnabled(bool)));
connect(textEdit, SIGNAL(copyAvailable(bool)),actionCopia, SLOT(setEnabled(bool)));
connect(textEdit->document(), SIGNAL(contentsChanged()),this, SLOT(documentModificat()));
El que fem és usar la funció connect() de QT, que ens connecta un widget i un signal amb un altre widget i un slot.
Els 10 primers connects corresponent a les 10 possibles accions que hem dissenyat en l'aplicació i s'activen mitjançant el signal tiggered().
El signal tiggered() és emès quan alguna acció és activada per l'usuari.
Els següents 2 connects() ens connecten el Text Edit i el signal copyAvailable() (és cert quan tenim text seleccionat i fals quan no) amb les accions de retallar (actionRetalla) i copiar (actionCopia) respectivament i l'slot setEnabled() que ens permetrà l'acció o no.
És a dir, el que pretenem és permetre executar les accions de copiar i tallar només si hem seleccionat text i desactivar-les altrament.
Finalment l'últim connect() ens connecta el Text Edit amb el signal contentsChanged() (aquest signal és emès quan el contingut ha canviat) amb la pròpia aplicació i l'slot documentModificat().
Implementació de les funcionalitats més importants
A continuació anem a veure les principals funcions que hem creat dins el fitxer mainwindowimpl.cpp.
En primer lloc veurem la implementació de tots els slots que hem definit. Si mirem les connexions de signals i slots al punt anterior, veurem que tenimm 13 connects(), és a dir, que tindríem que implementar 13 funcions corresponents als 13 slots.
Doncs bé, d'aquests 13 només caldrà que n'implementem 7, ja que la resta són propis de la llibreria QT. Abans de veure'n la implementació de 5 d'ells, recordeu que n'hem d'afegir la seva signatura al fitxer mainwindowimpl.h, en concret a l'apartat private slots:
private slots:
void newFile();
void open();
bool save();
bool saveAs();
void documentModificat();
void about();
void aboutQT();void newFile()
void MainWindowImpl::newFile() {
if (potSalvar())}
{
textEdit->clear();}
mostraFitxer("");
És l'slot encarregat de crear un nou fitxer; el que es fa és mirar si hi han dades per salvar i després netejar el Text Edit.
void open()
void MainWindowImpl::open() {
if (potSalvar()) {}
QString fileName = FileDialog::getOpenFileName(this);}
if (!fileName.isEmpty())
carregaFitxer(fileName);
És l'slot encarregat d'obrir un fitxer.
Després de comprovar que el fitxer que volem obrir no estigui buit, el carreguem dins el Text Edit.
bool save()
bool MainWindowImpl::save()
{
if (fitxerActual.isEmpty()) {}
return saveAs();
} else {
return desaFitxer(fitxerActual);
}
És l'slot encarregat de desar un fitxer que ja té nom, és a dir, de desar-ne els canvis.
Primer mirem si el fitxer està buit, és a dir, si és un fitxer nou i en aquest cas cridem a la funció de desar com; si no es tracta d'un fitxer nou en desem els canvis a disc.bool saveAs()
bool MainWindowImpl::saveAs()
{
QString fileName = QFileDialog::getSaveFileName(this);}
if (fileName.isEmpty())
return false;return desaFitxer(fileName);
És l'slot encarregat de desar un fitxer escollint el nom que li volem posar.
El que fem és obrir un quadre de diàleg on se'ns mostra l'explorador de fitxers i escollim el lloc i nom on volem guardar el fitxer.void documentModificat()
void MainWindowImpl::documentModificat()
{
setWindowModified(textEdit->document()->isModified());}
És l'slot encarregat de detectar que el document s'ha modificat.
Quan s'activa fa que es mostri un '*' al títol de la finestra just al costat del nom del fitxer. Això ho fem cridant a la funció setWindowModified(), pròpia de la llibreria QT.La implementació dels slots about() i aboutQT() no la mostrarem ja que l'únic que fan és mostrar un missatge informatiu. En podem veure el codi complet dins el fitxer mainwindowimpl.cpp en l'apartat de contingut dels fitxers.
Fins aquí hem vist la implementació de 5 funcions membre de la classe que corresponen a 5 slots que havíem connectat. Ara anem a veure la implementació de 3 funcions importants dins el codi de l'aplicació. La seva signatura (que hem d'incloure dins el fitxer mainwindowimpl.h) és la següent:
bool potSalvar();
void carregaFitxer(const QString &fitxer);
bool desaFitxer(const QString &fitxer);bool potSalvar()
bool MainWindowImpl::potSalvar()
{
if (textEdit->document()->isModified()) {}
QMessageBox::StandardButton ret;
ret = QMessageBox::warning(this, tr("Editor QT"),
tr("El document ha estat modificat.\n"
"Vols guardar els canvis?"),
QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
if (ret == QMessageBox::Save)
return save();
else if (ret == QMessageBox::Cancel)
return false;
}
return true;
Mitjançant aquesta funció mirem que el document s'hagi modificat. En cas afirmatiu mostrem un missatge informant-ne i demanat si es volen guardar els canvis. Si s'escull l'opció de guardar, es guarden els canvis i sinó no es fa res.
void carregaFitxer()
void MainWindowImpl::carregaFitxer(const QString &fitxer)
{
QFile file(fitxer);}
if (!file.open(QFile::ReadOnly | QFile::Text)) {
QMessageBox::warning(this, tr("Editor QT"),
tr("No puc llegir el fitxer %1:\n%2.")
.arg(fitxer)
.arg(file.errorString()));
return;
}QTextStream in(&file);
QApplication::setOverrideCursor(Qt::WaitCursor);
textEdit->setPlainText(in.readAll());
QApplication::restoreOverrideCursor();mostraFitxer(fitxer);
statusBar()->showMessage(tr("Fitxer carregat"), 3000);
És la funció encarregada de carregar un fitxer.
Després de comprovar que es pugui obir el fitxer, aquest s'obre i es passa a format de text plà per mostrar-lo dins el Text Edit i es mostra un missatge a la barra d'estat de l'aplicació informant que ja s'ha carregat el fitxer.bool desaFitxer()
bool MainWindowImpl::desaFitxer(const QString &fitxer)
{
QFile file(fitxer);}
if (!file.open(QFile::WriteOnly | QFile::Text)) {
QMessageBox::warning(this, tr("Editor QT"),
tr("No es pot escriure el fitxer %1:\n%2.")
.arg(fitxer)
.arg(file.errorString()));
return false;
}QTextStream out(&file);
QApplication::setOverrideCursor(Qt::WaitCursor);
out << textEdit->toPlainText();
QApplication::restoreOverrideCursor();mostraFitxer(fitxer);
statusBar()->showMessage(tr("Fitxer desat"), 3000);
return true;És la funció que s'encarrega de desar el fitxer a disc.
Primer es comprova que es pugui escriure (que no estigui en mode de només lectura), mostrant un missatge d'avís en cas que no es pugui. Un cop feta aquesta comprovació, es crea un fluxe de sortida al qual s'hi volca el contingut del Text Edit. Per finalitzar es mostra un missatge a la barra d'estat informant que el fitxer ha estat desat.
En aquest punt hem vist algunes de les funcions més importants per implementar les funcionalitats de la nostra aplicació, tot i que si fem un cop d'ull al contingut dels fitxers, veurem que n'hi ha algunes més i que al fitxer de capçalera hi hem d'incloure altres definicions.
Només ens resta visualitzar la funció main() que està dins el fitxer main.cpp i que és l'encarregada de crear l'aplicació pròpiament dita. Fem-li un cop d'ull:
int main(int argc, char ** argv)
{
QApplication app( argc, argv );}
MainWindowImpl win;
win.show();
app.connect( &app, SIGNAL( lastWindowClosed() ), &app, SLOT( quit() ) );
return app.exec();
En aquestes 5 línies el que es fa és crear l'aplicació QT i després la finestra que en el nostre cas serà del tipus MainWindowImpl ja que així ho hem triat al crear el projecte. Després es mostra la finestra i es connecta l'aplicació amb el signal que s'activa quan es tanqui la finestra i que realitzarà l'acció de sortir i finalment passem el control a QT per a que processi els events de l'usuari.
A continuació s'inclou tot el contingut dels 3 fitxers de codi:
A continuació s'inclou un fitxer .zip amb el contingut de tot el projecte que hem creat.
És possible que en el primer cop, en comptes de compilar el projecte, el tingueu que recompilar. Per fer-ho aneu al menú dins el QDevelop.
![]() |
![]() |
Josep Ramon Benet Bitrià
2008