Tutorial Qt - 2. Notiuni de baza

Tutorial Qt - 2. Notiuni de baza

Postby morpheus » 24 Jan 2010, 22:34

Tutorial Qt



Lectia 2. Notiuni de baza



1. Concepte

a) Widget
Un widget reprezinta un element ce apartine interfetei grafice.
Clasele corespunzand widget-urilor deriveaza, direct sau indirect, din clasa QWidget

b) Semnale si slot-uri
Semnalele si slot-urile reprezinta un concept central in Qt. Acestea sunt folosite pentru comunicatia intre obiecte.

c) Layout-uri
Layout-urile sunt folosite pentru a specifica in ce fel se pozitioneaza si de dimensioneaza widget-urile copil, in urma redimensionarii widget-ului parinte

d) Evenimente
Toate clasele de evenimente deriveaza din clasa QEvent. Bucla de mesaje (pornita in urma executiei functiei QCoreApplication::exec()), extrage evenimentele native din coada de mesaje si le translateaza in evenimente specifice framework-ului (una din clasele derivate din QEvent). Evenimentele specifice framework-ului sunt transmise apoi catre obiecte a caror clase deriveaza din clasa QObject.

e) Actiuni
Sunt folosite pentru incapsularea comenzilor invocate prin intermediul meniurilor, toolbar-urilor si a shortcut-urilor

2. Hello World

E timpul sa incepem sa scriem putin cod. Presupun ca deja cunoasteti cum se creeaza si cum se compileaza un proiect, din tutorialul precedent.
Iata o versiune a unui program de tip Hello World:

  1.  
  2. #include <QtGui/QApplication>
  3. #include <QLabel>
  4.  
  5. int main(int argc, char *argv[])
  6. {
  7.     QApplication app(argc, argv);
  8.  
  9.     QLabel helloLabel;
  10.     helloLabel.setText(QObject::tr("Hello World"));
  11.     helloLabel.setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
  12.     helloLabel.setGeometry(400, 400, 200, 100);
  13.     helloLabel.setWindowTitle(QObject::tr("Hello World"));
  14.     helloLabel.show();
  15.  
  16.     return app.exec();
  17. }
  18.  


Rezultatul executiei se vede in figura de mai jos:

proiect Hello World Qt

Sa analizam codul:

->QApplication app(argc, argv);
In orice aplicatie grafica Qt (folosind widget-uri) trebuie definit un obiect de tip QApplication.
Pentru aplicatiile in consola, se poate folosi QCoreApplication.
Acesta implementeaza bucla de mesaje a aplicatiei.
Clasa QApplication deriveaza din clasa QCoreApplication.

->QLabel helloLabel;
Declaram un obiect de tipul QLabel. Clasa QLabel este folosita pentru a reprezenta o "eticheta" (label).
QLabel este o clasa de tip widget (vezi sectiunea precedenta). Clasa QLabel deriveaza din clasa QWidget.
Obiectul helloLabel nu are nici un parinte (adica nu e continut in nici un alt widget).

-> helloLabel.setText(QObject::tr("Hello World"));
Setam textul ce va fi continut in helloLabel ca fiind Hello World.

-> helloLabel.setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
Setam aliniamentul textului. Textul va fi aliniat atat pe orizontala cat si pe verticala.

-> helloLabel.setGeometry(400, 400, 200, 100);
Setam coordonatele label-ului.
Coordonatele coltului din stanga-sus vor fi (400, 400) de pixeli. Dimensiunea va fi (200, 100) de pixeli.
Nota: coordonatele sunt relative la coltul din stanga-sus al ecranului.

coordonate widget Qt

-> helloLabel.setWindowTitle(QObject::tr("Hello World"));
Seteaza titlul ferestrei ca fiind Hello World

-> helloLabel.show();
Afiseaza widget-ul.

-> return app.exec();
Porneste bucla de evenimente.

3. Semnale si slot-uri

Semnalele si slot-urile permit comunicatia intre obiecte.

Semnale
Sunt emise la aparitia unor evenimente.
Pot fi emise din codul sursa, folosind emit
Widget-urile dispun de anumite semnale predefinite.
De exemplu, in momentul in care se executa click pe un buton, este emis un semnal.
Programatorul poate si defini propriile sale semnale.

Slot-uri
Sunt functii chemate ca urmare a emiterii unui semnal. Widget-urile dispun de slot-uri predefinite, dar programatorul poate defini si propriile sale slot-uri.

Programatorul poate defini conexiuni intre semnale si slot-uri. Adica, poate specifica ce slot-uri vor fi chemate in momentul in care un anumit semnal este emis.
De exemplu, in momentul in care se executa click pe un buton, se emite semnalul clicked(). Programatorul poate sa implementeze un slot si sa conecteze semnalul clicked al butonului la acel slot. In aces fel, programatorul poate specifica ce actiune se va executa in momentul in care se apasa acel buton.

Qt face uz de niste extensii la limbajul C++ standard pentru a defini semnale si slot-uri. Codul este preprocesat cu ajutorul unui tool, numit moc (meta object compiler).

Unul din avantajele importante ale acestui sistem de semnale si slot-uri, fata de sistemele ce folosest callback-uri, este ca sistemul semnale/slot-uri este type safe. Semnatura semnalului trebuie sa fie compatibila cu cea a slot-ului.

Sa luam un exemplu cat mai simplu.
Vom afisa un buton. In momentul in care utilizatorul apasa acel buton, vom incheia executia aplicatiei.

  1.  
  2. #include <QtGui/QApplication>
  3. #include <QObject>
  4. #include <QPushButton>
  5.  
  6. int main(int argc, char *argv[])
  7. {
  8.     QApplication app(argc, argv);
  9.     QPushButton button(QObject::tr("Click to exit"));
  10.     button.setGeometry(400, 400, 200, 100);
  11.     button.setWindowTitle(QObject::tr("Button example"));
  12.     QObject::connect(&button, SIGNAL(clicked()), &app, SLOT(quit()));
  13.     button.show();
  14.  
  15.     return app.exec();
  16. }
  17.  


semnale/slot-uri in Qt

-> QPushButton button(QObject::tr("Click to exit"));
Declaram un obiect, button, de tipul QPushButton. QPushButton este clasa folosita penru a reprezenta un buton.

Pentru a incheia executia programului, putem apela functia QApplication::quit(), care este un slot.
Pentru a incheia executia programului atunci cand utilizatorul apasa butonul button, trebuie sa conectam semnalul clicked al obiectului button la slot-ul quit() al obiectului app .
Facem asta folosind functia connect, statica in clasa QObject.
QObject::connect(&button, SIGNAL(clicked()), &app, SLOT(quit()));

Conceptual, conectarea semnalului clicked la slot-ul quit() poate fi reprezentata ca in figura de mai jos:

Image

4. Organizarea obiectelor Qt

Clasa de baza pentru toate obiectele Qt este QObject.
Obiectele Qt sunt organizate in arbori n-ari, in care un nod poate avea mai multi copii.
In consecinta, fiecare obiect din arborele de obiecte poate avea un obiect parinte si zero sau mai multe obiecte copil.
Aceasta organizare a obiectelor Qt este foarte importanta deoarece usureaza foarte mult sarcina managementului memoriei pentru obiectele Qt. Mai precis, in momentul in care un obiect este distrus, sunt distrusi toti copii lui. Aceasta operatie este efectuata in mod automat de catre framework.
Ca o consecinta, toate obiectele Qt ce au un parinte trebuie alocate in mod dinamic, pe heap, si nu pe stiva.
Obiectele Qt pot fi construite specificand un pointer catre obiectul parinte. Implicit, pointerul catre obiectul parinte are valoarea NULL, insemnand ca obiectul nu are nici un parinte.
Acest concept este foarte important. Neintelegerea lui poate duce la bug-uri greu de reprodus, mai ales cand aplicatia foloseste mai multe fire de executie.

Voi da un exemplu simplu, in care vom recapitula ce am invatat in aceasta lectie.

Exemplul demonstreaza urmatoarele concepte:
- cum putem crea propriul nostru widget, agregand alte widget-uri
- cum putem declara propriul nostru semnal
- cum putem defini slot-uri
- felul in care se face managementul memoriei pentru widget-urile continute

Un screenshot:

Widget slider in Qt

In momentul in care utilizatorul trage de controlul slide catre stanga sau catre dreapta, label-ul este actualizat cu valoarea curenta.

Sursele sunt urmatoarele:

  1.  
  2. //sliderwidget.h
  3. #ifndef SLIDERWIDGET_H
  4. #define SLIDERWIDGET_H
  5.  
  6. #include <QtGui/QWidget>
  7.  
  8. class QSlider;
  9. class QLabel;
  10.  
  11. const int MAX_SLIDER_VALUE = 90;
  12.  
  13. class SliderWidget : public QWidget
  14. {
  15.     Q_OBJECT
  16.  
  17. public:
  18.     /**
  19.      *@brief constructor for the slider widget
  20.      *@param parent pointer to the parent widget
  21.      */
  22.     SliderWidget(QWidget *parent = 0);
  23.  
  24.     /**
  25.      *@brief destructor
  26.      */
  27.     ~SliderWidget();
  28.  
  29. signals:
  30.     /**
  31.      *@brief signal emitted when the slider reached MAX_SLIDER_VALUE
  32.      */
  33.     void maximumReached();
  34.  
  35. public slots:
  36.     /**
  37.      *@brief slot called when the value from the slider changed
  38.      *@param newValue the new value
  39.      */
  40.     void valueChanged(int newValue);
  41.  
  42.     /**
  43.      *@brief slot called when maximumReached signal is emitted
  44.      */
  45.     void maximumDetected();
  46.  
  47. private:
  48.     /**
  49.      * the composed slider object
  50.      */
  51.     QSlider *m_slider;
  52.     /**
  53.      * the composed label object
  54.      */
  55.     QLabel  *m_label;
  56. };
  57.  
  58. #endif // SLIDERWIDGET_H
  59.  


Widget-ul nostru se va numi SliderWidget. Clasa SliderWidget deriveaza din clasa QWidget (care, precum va amintiti, este clasa de baza a tuturor widget-urilor).

SliderWidget este compusa din doua widget-uri copil:
- un slider, numit m_slider (de tipul QSlider)
- un label, numit m_label (de tipul QLabel)

Am declarat un semnal, pentru clasa SliderWidget:
void maximumReached();
Acest semnal va fi emis atunci cand valoarea slider-ului va fi mai mare decat MAX_SLIDER_VALUE (egal cu 90)

Am declarat un slot, pentru clasa SliderWidget:
void valueChanged(int newValue);
Acest slot va fi apelat in momentul in care utilizatorul modifica valoarea slider-ului.

De asemenea, am mai declarat un slot:
void maximumDetected();
Semnalul maximumReached va fi conectat la acest slot. Vom afisa o caseta de dialog in care vom specifica ca s-a atins valoarea maxima pentru slider, apoi vom reseta valoarea slider-ului la zero.

Orice clasa care declara semnale si/sau slot-uri trebuie sa foloseasca, in declaratia ei, macroul Q_OBJECT.

Sa trecem la implementarea clasei.

  1.  
  2. //sliderwidget.cpp
  3. #include <QtGui/QSlider>
  4. #include <QtGui/QLabel>
  5. #include <QtGui/QVBoxLayout>
  6. #include <QtGui/QMessageBox>
  7.  
  8. #include "sliderwidget.h"
  9.  
  10. SliderWidget::SliderWidget(QWidget *parent)
  11.     : QWidget(parent)
  12. {
  13.     m_slider = new QSlider(Qt::Horizontal);
  14.     connect(m_slider, SIGNAL(valueChanged(int)), this, SLOT(valueChanged(int)));
  15.  
  16.     m_label = new QLabel;
  17.     m_label->setAlignment(Qt::AlignHCenter);
  18.     m_label->setText(tr("0"));
  19.  
  20.     QVBoxLayout* layout = new QVBoxLayout;
  21.     layout->addWidget(m_label);
  22.     layout->addWidget(m_slider);
  23.  
  24.     connect(this, SIGNAL(maximumReached()), this, SLOT(maximumDetected()));
  25.  
  26.     setLayout(layout);
  27. }
  28.  
  29. SliderWidget::~SliderWidget()
  30. {
  31.  
  32. }
  33.  
  34. void SliderWidget::valueChanged(int newValue)
  35. {
  36.     // if the current value is at least MAX_SLIDER_VALUE,
  37.     // we emit the maximumReached signal
  38.     if (newValue >= MAX_SLIDER_VALUE)
  39.         emit maximumReached();
  40.     else
  41.         m_label->setText(QString::number(newValue));
  42. }
  43.  
  44. void SliderWidget::maximumDetected()
  45. {
  46.     QMessageBox::information(this, tr("Maximum reached"),
  47.                              tr("Maximum reached. Resetting the slider to 0!"));
  48.     // reset the slider value to zero
  49.     m_slider->setValue(0);
  50. }
  51.  


-> m_slider = new QSlider(Qt::Horizontal);
Cream obiectul de tip slider si specificam ca va fi pozitionat pe orizontala

-> connect(m_slider, SIGNAL(valueChanged(int)), this, SLOT(valueChanged(int)));
In momentul in care utilizatorul modifica valoarea slider-ului, se va emite (de catre framework) semnalul valueChanged
Noi conectam semnalul respectiv la slotul SliderWidget::valueChanged()

-> m_label = new QLabel;
Cream obiectul de tip label

-> m_label->setAlignment(Qt::AlignHCenter);
Specificam faptul ca label-ul va fi centrat pe orizontala, in interiorul widget-ului nostru

-> m_label->setText(tr("0"));
Setam textul initial pentru label ca fiind 0

-> QVBoxLayout* layout = new QVBoxLayout;
Cream un obiect de tip vertical box layout. Vom adauga obiectul de tip slider si cel de tip label in acest layout.
Aceasta asigura faptul ca aceste doua obiecte vor fi asezate pe verticala, in widget-ul nostru

-> layout->addWidget(m_label);
Adaugam m_label in layout

-> layout->addWidget(m_slider);
Adaugam m_slider in layout

-> connect(this, SIGNAL(maximumReached()), this, SLOT(maximumDetected()));
Conectam semnalul maximumReached la slot-ul maximumDetected()

-> setLayout(layout);
Setam layout ca fiind layout-ul widget-ului nostru
In urma acestei actiuni, obiectul de tip SliderWidget va deveni parintele obiectelor m_label si m_slider
Asta inseamna ca m_label si m_slider vor fi distruse atunci cand obiectul care le contine (de tip SliderWidget ) va fi distrus.

->In slot-ul SliderWidget::valueChanged(), actualizam text-ul afisat in label daca valoarea slider-ului este mai mica decat MAX_SLIDER_VALUE
Daca valoarea slider-ului este mai mare sau egala decat MAX_SLIDER_VALUE, se emite semnalul maximumReached
Parametrul newValue va contine valoarea curenta a slider-ului.

-> in slot-ul SliderWidget::maximumDetected(), afisam un message-box, avertizand utilizatorul ca s-a atins valoarea maxim permisa pentru slider. Apoi, valoarea slider-ului este setata la zero.

  1.  
  2. // main.cpp
  3. include <QtGui/QApplication>
  4. #include "sliderwidget.h"
  5.  
  6. int main(int argc, char *argv[])
  7. {
  8.     QApplication app(argc, argv);
  9.     SliderWidget widget;
  10.     widget.show();
  11.     return app.exec();
  12. }
  13.  


->In functia main(), declaram obiectul app de tip QApplication . Precum am spus mai sus, orice aplicatie Qt, de tip GUI, are nevoie sa declare un obiect de tip QApplication.

->Declaram un obiect de tip SliderWidget , pe stiva. Obiectul va fi distrus in mod automat, in momentul in care functia main() se va termina.

-> widget.show();
Afisam widget-ul

-> return app.exec();
Pornim bucla de evenimente

Surse, proiect si executabil -

Lectia 3. Widget-uri simple
7p / 3 votes
Curiosity killed the cat
User avatar
morpheus
Word
 
Joined: 30 Dec 2009
Location: Bucharest, Romania
Status: 54.84

Return to Tutoriale QT

Who is online

Users browsing this forum: No registered users and 0 guests