Začínáme programovat s Qt

Vítejte ve světe Qt — ve světe multiplatformního GUI toolkitu. V tomto tutoriálu si krok za krokem ukážeme jak naprogramovat aplikaci jako je Notepad, tímto způsobem vás naučíme základních dovedností. Po přečtení tohoto návodu byste měli být připraveni na čtení našich přehledů a API dokumentace a zároveň být schopni nalézt informace, které můžete uplatnit ve vámi vyvíjených aplikacích.

Ahoj Notepade

V tomto úvodním příkladu vytvoříme a zobrazíme oblast pro úpravu textu. Takto vypadá nejjednodušší možný Qt program.

Ahoj Notepade

A toto je jeho zdrojový kód:

  1. #include <QApplication>
  2. #include <QTextEdit>
  3.  
  4. int main(int argv, char **args)
  5. {
  6.     QApplication app(argv, args);
  7.  
  8.     QTextEdit textEdit;
  9.     textEdit.show();
  10.  
  11.     return app.exec();
  12. }

Nyní si tento zdrojový kód řádek po řádku projdeme. V prvních dvou řádcích vkládáme hlavičkové soubory pro QApplication [doc.qt.nokia.com] a QTextEdit [doc.qt.nokia.com], což jsou přávě ty třídy, které pro tento příklad potřebujeme. Názvy všech Qt hlavičkových souborů se shodují s názvy tříd které jsou v nich deklarovány.

Na řádku 6 vytváříme objekt třídy QApplication [doc.qt.nokia.com]. Tento objekt spravuje prostředky celé aplikace a je vyžadován pro funkčnost všech Qt programů mající GUI. Jako parametry potřebuje argv a args, potože Qt samo od sebe přijímá několik argumentů příkazové řádky.

Řádek 8 vytváří objekt třídy QTextEdit [doc.qt.nokia.com]. Oblast pro úpravu textu je vizuální prvek GUI. V Qt takovéto elementy nazýváme widgety. Příklady dalších widgetů jsou například posuvníky, štítky, přepínače, atp. Widget může být zároveň kontejnerem pro další widgety; příkladem budiž hlavní okno aplikace či dialog.

Řádek 9 zobrazí oblast pro úpravu textu na obrazovce ve vlastním okně. Vzhledem k tomu, že widgety mohou složit i jako kontejnery (například QMainWindow [doc.qt.nokia.com], který obsahuje nástrojovou lištu, menu, stavový řádek a několik dalších widgetů), je možné zobrazit widget ve vlastním okně. Widgety jsou implicitně skryty; o zobrazení se stará právě metoda show() [doc.qt.nokia.com]).

Řádek 11 spustí hlavní smyčku eventů aplikace. Dokud aplikace beží, jejím widgetům jsou zasílány eventy. Příkladem eventů jsou stisknutí tlačítek myši a klávesnice. Jakmile napíšete text do dříve zmíněné oblasti pro úpravu textu, ta obdrží eventy informující o stisknutí kláves a reaguje vykreslením příslušného textu.

Pro spuštění aplikace, otevřete příkazovou řádku, přejděte do adresáře kde máte její zdrojový soubor a napište následující příkazy.

  1. qmake -project
  2. qmake
  3. make

Ty vytvoří spustitelný soubor v daném adresáři (na systémech windows bude možná třeba použít nmake namísto make. Také spustitelné soubory se budou nacházet buďto v podadresáři release nebo debug). qmake je Qt nástroj pro sestavování projektů vyžadující konfigurační soubor. Tento konfigurační soubor za nás qmake sám vygeneruje, když ho spustíme s přepínačem -project. Z daného konfiguračního souboru (s příponou .pro) qmake vytvoří makefile, která za vás program sestaví. Na to jak si můžete vytvořit vlastní .pro soubor se podíváme později.

Rozšiřující informace

TémaOdkaz
Widgety a gemoetrie oknaWindow and Dialog Widgets [doc.qt.nokia.com]
Eventy a jejich zpracováníThe Event System [doc.qt.nokia.com]

Přídání tlačítka ‘Quit’

V reálné aplikaci budete běžně potřebovat více než jen jeden widget. Nyní přidáme QPushButton [doc.qt.nokia.com] pod oblast pro úpravu textu. Toto tlačítko bude plnit funkci vypínače aplikace (společně se zavřením okna).

Ahoj Notepade

Nyní se podívejme na zdrojový kód

  1. #include <QtGui>
  2.  
  3. int main(int argv, char **args)
  4. {
  5.     QApplication app(argv, args);
  6.  
  7.     QTextEdit textEdit;
  8.     QPushButton quitButton("Quit");
  9.  
  10.     QObject::connect(&quitButton, SIGNAL(clicked()), qApp, SLOT(quit()));
  11.  
  12.     QVBoxLayout layout;
  13.     layout.addWidget(&textEdit);
  14.     layout.addWidget(&quitButton);
  15.  
  16.     QWidget window;
  17.     window.setLayout(&layout);
  18.  
  19.     window.show();
  20.  
  21.     return app.exec();
  22. }

Na řádku 1 vkládáme QtGui [doc.qt.nokia.com], které obsahuje všechny Qt GUI třídy.

Řádek 10 používá Qt systém signálů a slotů a zajišťuje, že stisknutí tlačítka ‘Quit’ ukončí aplikaci. Slot je funkce, která může být vyvolána za běhu použitím jejího jména (jako doslovný řetězec). Signál je funkce, která když zavolána zavolá všechny sloty s ní propojené; tyto procedury nazýváme propojení slotu se signálem (to connect the slot to the signal) a vyslání signálu (to emit the signal).

quit() [doc.qt.nokia.com] je slot třídy QApplication [doc.qt.nokia.com] , který při zavolání ukončí aplikaci. clicked() [doc.qt.nokia.com] je signál třídy, který QPushButton [doc.qt.nokia.com] vyšle poté co je stisknut. Statická funkce QObject::connect() [doc.qt.nokia.com] se stará o propojení signálu se slotem. SIGNAL a SLOT jsou dvě makra, která přebírají prototyp (type-signature) signálu a slotu k propojení.

Řádek 12 vytváří objekt třídy QVBoxLayout [doc.qt.nokia.com]. Jak již bylo zmíňeno, widgety mohou obsahovat další wigety. Je také možné přímo nastavit meze (umístění a velikost) potomků widgetu (widgetů, které daný widget obsahuje). Layout spravuje meze svých potomků automaticky. Například QVBoxLayout [doc.qt.nokia.com] umístí potomky vertikálně pod sebe.

Řádky 13 a 14 přidávájí do layoutu oblast na úpravu textu a tlačítko. Řádek 17 nastavuje nastaví widgetu hlavní layout.

Rozšiřující informace

TémaOdkaz
Signály a SlotySignals & Slots [doc.qt.nokia.com]
LayoutyLayout Management [doc.qt.nokia.com], Widgets and Layouts [doc.qt.nokia.com], Layout Examples [doc.qt.nokia.com]
Widgety které Qt nabízíQt Widget Gallery [doc.qt.nokia.com], Widget Examples [doc.qt.nokia.com]

Odvozování vlastních widgetů od QWidgetu

Možná by bylo hezké dotázat se uživatele před ukončením aplikace, zdali opravdu chce aplikaci opustit. V tomto příkladě vytvoříme podtřídu QWidgetu [doc.qt.nokia.com], které přidáme vlastní slot a následně ho napojíme na naše tlačítko ‘Quit’.

Odvozujeme od QWidgetu

Podívejme se nyní na zdrojový kód

  1. class Notepad : public QWidget
  2. {
  3.     Q_OBJECT
  4.  
  5. public:
  6.     Notepad();
  7.  
  8. private slots:
  9.     void quit();
  10.  
  11. private:
  12.     QTextEdit *textEdit;
  13.     QPushButton *quitButton;
  14. };

Na začátku definice naší třídy (která přirozeně musí být podtřídou třídy QObject [doc.qt.nokia.com]. QObject [doc.qt.nokia.com]) musí být makro Q_OBJECT, to z ní učiní plnohotnotný QObject. To že je třída QObjectem jí dává specielní vlastnosti. Například se můžeme dotázat na název třídy či slotu za běhu programu. Je také možné zjistit parametry slotu a zavolat ho.

Řádek 13 deklaruje slot quit(). Nyní můžeme quit() propojit se signály s odpovídajícím prototypem (libovolným bezparametrickým signálem).

Namísto nastavování GUI a propojení slotu ve funkci main() provádíme tuto práci v konstruktoru třídy Notepad.

  1. Notepad::Notepad()
  2. {
  3.     textEdit = new QTextEdit;
  4.     quitButton = new QPushButton(tr("Quit"));
  5.  
  6.     connect(quitButton, SIGNAL(clicked()), this, SLOT(quit()));
  7.  
  8.     QVBoxLayout *layout = new QVBoxLayout;
  9.     layout->addWidget(textEdit);
  10.     layout->addWidget(quitButton);
  11.  
  12.     setLayout(layout);
  13.  
  14.     setWindowTitle(tr("Notepad"));
  15. }

Jak jste mohli vidět v definici třídy, používáme ukazatele na naše QObjekty [doc.qt.nokia.com] (textEdit a quitButton). Zpravidla byste měli QObjekty [doc.qt.nokia.com] vždy alokovat na heapu a nikdy je kopírovat.

Viditelné stringy “obalujeme” do funkce tr() [doc.qt.nokia.com] a to z toho důvodu, kdybychom se v budoucnu rozhodli poskytovat naší aplikaci ve více jazykových mutacích. Nebudeme zacházet do detailů, ovšem pokud vás téma zajímá, můžete se podívat na odkaz Qt Linguist z následující tabulky.

Rozšiřující informace

TémaOdkaz
tr() a internacionalizaceQt Linguist Manual [doc.qt.nokia.com], Writing SOurce Code for Translation [doc.qt.nokia.com], Hello tr() [doc.qt.nokia.com] Example, Internationalization with Qt [doc.qt.nokia.com]
QObjekty a objektový model Qt (Základ pro porozumění Qt)Object Model [doc.qt.nokia.com]
qmake a systém překladu Qtqmake Manual [doc.qt.nokia.com]

Vytváření .pro souboru

Na ukázku si vytvoříme vlastní .pro soubor namísto toho, který za nás vygenerovalo qmake -project.

  1. HEADERS =  notepad.h
  2. SOURCES =  notepad.cpp \
  3.   main.cpp

Následující příkazy přeloží náš ukázkový projekt.

  1. qmake
  2. make

Používání třídy QMainWindow

Mnoho aplikací bude mít prospěch z používání QMainWindow [doc.qt.nokia.com], které má svůj vlastní layout kam můžete přidat menu, dokovací widgety, nástrojovou lištu a stavovou lištu. QMainWindow [doc.qt.nokia.com] má centrální oblast která může obsahovat libovolný widget. V Našem případě tam umístíme naši oblast pro úpravu textu.

Používání třídy QMainWindow

Nyní se podívejme na novou definici třídy Notepad

  1. #include <QtGui>
  2.  
  3. class Notepad : public QMainWindow
  4. {
  5.     Q_OBJECT
  6.  
  7. public:
  8.     Notepad();
  9.  
  10. private slots:
  11.     void open();
  12.     void save();
  13.     void quit();
  14.  
  15. private:
  16.     QTextEdit *textEdit;
  17.  
  18.     QAction *openAction;
  19.     QAction *saveAction;
  20.     QAction *exitAction;
  21.  
  22.     QMenu *fileMenu;
  23. };

Přidáváme další dva sloty pro otevření a uložení dokumentu. Budou implementovány v následující sekci.

Často chceme, aby bylo možné vyvolat jediný slot několika widgety. Typický příklad představují položky menu a tlačítka na nástrojové liště. Pro usnadnění Qt nabízízí QAction [doc.qt.nokia.com], která může být předáná několika widgetům a být propojena se slotem. Například jak QMenu [doc.qt.nokia.com] tak i QToolBar [doc.qt.nokia.com] mohou vytvářet položky menu a nástrojová tlačítka z identických QAction [doc.qt.nokia.com] objektů. Za moment se podíváme jak to funguje.

Stejně jako v předchozí definici, nastavujeme GUI v konstruktoru třídy Notepad

  1. Notepad::Notepad()
  2. {
  3.     saveAction = new QAction(tr("&Open"), this);
  4.     saveAction = new QAction(tr("&Save"), this);
  5.     exitAction = new QAction(tr("E&xit"), this);
  6.  
  7.     connect(openAction, SIGNAL(triggered()), this, SLOT(open()));
  8.     connect(saveAction, SIGNAL(triggered()), this, SLOT(save()));
  9.     connect(exitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
  10.  
  11.     fileMenu = menuBar()->addMenu(tr("&File"));
  12.     fileMenu->addAction(openAction);
  13.     fileMenu->addAction(saveAction);
  14.     fileMenu->addSeparator();
  15.     fileMenu->addAction(exitAction);
  16.  
  17.     textEdit = new QTextEdit;
  18.     setCentralWidget(textEdit);
  19.  
  20.     setWindowTitle(tr("Notepad"));
  21. }

Naše QAction [doc.qt.nokia.com] objekty jsou vytvořeny s textem který chceme zobrazit na widgetech kterým je předáme (v našem případě se jedná o položky v menu). Pokud bychom je zároveň chtěli přidat do nástrojové lišty, mohli bychom jim nastavit i ikony [doc.qt.nokia.com].

Pokud nyní stiskneme položku v menu, spustí to danou akci a ta vyvolá příslušný slot.

Rozšiřující informace

TémaOdkaz
Hlavní okna a jejich třídyApplication Main Window [doc.qt.nokia.com], Main Window Examples [doc.qt.nokia.com]
MDI aplikaceQMdiArea [doc.qt.nokia.com], MDI Example [doc.qt.nokia.com]

Ukládání a načítání

V tomto příkladě naimplementujeme funkčnost slotů open() a save(), které jsme přidali v předchozí definici.

Ukládání a načítání

Začneme se slotem open():

  1. QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"), "",
  2.     tr("Text Files (*.txt);;C++ Files (*.cpp *.h)"));
  3.  
  4. if (fileName != "") {
  5.     QFile file(fileName);
  6.     if (!file.open(QIODevice::ReadOnly)) {
  7.         QMessageBox::critical(this, tr("Error"),
  8.    tr("Could not open file"));
  9.         return;
  10.     }
  11.     QString contents = file.readAll().constData();
  12.     textEdit->setPlainText(contents);
  13.     file.close();
  14. }

Prvním krokem je dotázání se uživatele na název souboru, který chce otevřít. Qt nabízí QFileDialog [doc.qt.nokia.com], což je dialog ve kterém uživatel může vybrat soubor. Obrázek výše zobrazuje tento dialog na Linuxové distribuci Kubuntu. Statická funkce getOpenFileName() [doc.qt.nokia.com] zobrazí modální souborový dialog a neskončí dokud uživatel nevybere soubor. Vrací cestu k vybranému souboru, případně prázdný řetězec pokud uživatel dialog zrušil.

Pokud máme cestu k souboru, pokusíme se ho otevřít funkcí open() [doc.qt.nokia.com]. Ta vrací true pokud bylo otevření úspěšné. Nebudeme zacházet do detailů co se zpracování chyb týče, ovšem můžete si přečíst články odkázané v sekci “Rozšiřující informace”. Pokud soubor nemůže být otevřen, zobrazíme QMessageBox [doc.qt.nokia.com] a zobrazíme dialog s chybovým hlášením (pro více informací si přečtěte dokumentaci třídy QMessageBox [doc.qt.nokia.com]).
Čtení dat je díky funcki readAll() [doc.qt.nokia.com] triviální, ta vrátí veškerá data obsažená v souboru v QByteArray [doc.qt.nokia.com]. Funkce constData() [doc.qt.nokia.com] vrací veškerá data jako const char* jenž QString [doc.qt.nokia.com] přebírá v jednom ze svých konstruktorů. Poté již obsah souboru snadno zobrazíme v oblasti pro úpravu textu. Po načtení soubor uzavřeme za použití close() [doc.qt.nokia.com], tím předáme souborový deskriptor zpět operačnímu systému.

Nyní se podívejme na slot save().

  1. QString fileName = QFileDialog::getSaveFileName(this, tr("Save File"), "",
  2.     tr("Text Files (*.txt);;C++ Files (*.cpp *.h)"));
  3.  
  4. if (fileName != "") {
  5.     QFile file(fileName);
  6.     if (!file.open(QIODevice::WriteOnly)) {
  7.         // error message
  8.     } else {
  9.         QTextStream stream(&file);
  10.         stream << textEdit->toPlainText();
  11.         stream.flush();
  12.         file.close();
  13.     }
  14. }

Při ukládání obsahu oblasti pro úpravu textu do textového souboru používáme třídy QTextStream [doc.qt.nokia.com], která zaobaluje objekt třídy QFile [doc.qt.nokia.com]. Tento textový proud je schopen zapisovat QString [doc.qt.nokia.com] přímo do souboru; QFile [doc.qt.nokia.com] přijímá pouze surová data (typu char*) funkcí write() [doc.qt.nokia.com] třídy QIODevice [doc.qt.nokia.com]

Rozšiřující informace

TémaOdkaz
Soubory a zařízení vstupu a výstupuQFile [doc.qt.nokia.com], QIODevice [doc.qt.nokia.com]