Miał być w piątek, więc wrzucam dzisiaj. ;) Z tej części kursu poznamy klasę głównego okna w Qt, jak tworzyć własne widgety oraz na czym polega mechanizm sygnałów i slotów. Miłej lektury.
Główne okno w Qt reprezentuje klasa QMainWindow. Jest to chyba najczęściej dziedziczona klasa. Pozwala nam na umieszczanie menu oraz statusu. Najprostszy program z QMainWindow wygląda tak:
mainwindow.hpp:
1 2 3 4 5 6 7 8 9 10 11 12 | #ifndef MAINWINDOW_HPP #define MAINWINDOW_HPP #include <QMainWindow> class MainWindow : public QMainWindow { public: MainWindow(); }; #endif // MAINWINDOW_HPP |
W 4. linijce załączamy nagłówek klasy QMainWindow. W 6. definiujemy własną klasę MainWindow, która dziedziczy po QMainWindow. Natomiast w 9. definiujemy konstruktor naszej klasy, którzy przyda nam się później do tworzenia własnych menu i paska statusu. W ten oto sposób zrobiliśmy nasz pierwszy widget w Qt. :]
mainwindow.cpp:
1 2 3 4 5 | #include "mainwindow.hpp" MainWindow::MainWindow() { } |
main.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #include <QApplication> #include <QMainWindow> #include "mainwindow.hpp" int main (int argc, char *argv[]) { QApplication app(argc, argv); MainWindow window; window.show(); return app.exec(); } |
W 9. linijce tworzymy obiekt klasy naszego okna, a w 11. wyświetlamy to okno. Po kompilacji i uruchomieniu pokazuje się nam okno. Gdy już się pozachwycamy jacy jesteśmy piękni, mądrzy i wspaniali możemy przejść do dalszej zabawy z naszym okienkiem.
Dołożymy przycisk, dzięki któremu (za pomocą sygnałów i slotów) będziemy mogli zamknąć okno (to dla tych, którzy mają problem w trafienie w ‘x’ na belce okna ;)). A wygląda to tak:
mainwindow.hpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #ifndef MAINWINDOW_HPP #define MAINWINDOW_HPP #include <QAainwindow> #include <QTextCodec> #include <QPushButton> class MainWindow : public QMainWindow { private: QPushButton *button; public: MainWindow(); }; #endif // MAINWINDOW_HPP |
W linii 5. i 6. dołączamy nagłówki, pierwszy odpowiada za kodowanie, drugi zawiera klasę przycisku. Natomiast w 11. tworzymy wskaźnik na obiekt klasy przycisku.
mainwindow.cpp:
1 2 3 4 5 6 7 8 9 | #include "mainwindow.hpp" MainWindow::MainWindow() { QTextCodec::setCodecForTr (QTextCodec::codecForName ("UTF-8")); button = new QPushButton (tr("&Wciśnij mnie ;)"), this); button->setGeometry(25, 15, 150, 75); } |
W konstruktorze (linia 5.) ustawiamy kodowania, dla tłumaczeń, na UTF-8, dzięki czemu będziemy widzieć polskie (i nie tylko) krzaczki. Jeżeli mimo ustawienia UTF-8 nadal masz dziwne znaki, to najwyższy czas, żebyś zaczął kodować swoje źródła w UTF-8. :P Oprócz kodowania dla tłumaczeń (o których będzie innym razem), może również ustawić kodowania dla lokali (setCodecForLocale) oraz standardowych stringów (setCodecForCStrings). W linii 7. przypisujemy do naszego wskaźnika przycisku obiekt dynamiczny klasy QPushButton. Pierwszy argument jaki podajemy jest tekst który znajduje się na przycisku. & postawiony przed ‘W’ pozwala nam na wciskanie tego przycisku poprzez skrót Alt-w. Nasz napis przechodzi przez funkcję tr(). Służy ona do tłumaczenia interfejsu aplikacji na inne języki (jak tłumaczyć i wykorzystać do tego Qt Linguist napiszę kiedy indziej). Drugi argument, to wskaźnik na rodzica, na którym ma być wyświetlany nasz widget. Oprócz tego pozwala to, na pomijanie operatora delete, gdyż Qt sam usuwa z pamięci obiekty widgetów, które są „potomkami” (w sensie bycia umieszczonym na formatce rodzica, nie w sensie dziedziczenia) innych widgetów. W następnej linii ustawiamy wymiary i położenie naszego przycisku (kolejno: x, y, szerowość oraz wysokość). Po kompilacji i uruchomieniu pokazuje nam się okno z przyciskiem który możemy sobie poklikać. ;)
Żeby dodać trochę więcej interakcji z programem, podepniemy sygnał kliknięcia przycisku do slotu zamknięcia aplikacji:
mainwindow.hpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #ifndef MAINWINDOW_HPP #define MAINWINDOW_HPP #include <QApplication> #include <QMainWindow> #include <QTextCodec> #include <QPushButton> class MainWindow : public QMainWindow { private: QPushButton *button; public: MainWindow(); }; #endif // MAINWINDOW_HPP |
W mainwindow.hpp dodajemy tylko nagłówek klasy QApplication.
mainwindow.cpp:
1 2 3 4 5 6 7 8 9 10 11 | #include "mainwindow.hpp" MainWindow::MainWindow() { QTextCodec::setCodecForTr (QTextCodec::codecForName ("UTF-8")); button = new QPushButton (tr("&Wciśnij mnie ;)"), this); button->setGeometry(25, 15, 150, 75); connect(button, SIGNAL(clicked()), qApp, SLOT(quit())); } |
W konstruktorze klasy MainWindow dodajemy połączenie między przyciskiem a naszą aplikacją. Przycisk wysyła sygnał kliknięcia do qApp (jest to makro, które reprezentuje obiekt naszej aplikacji), który to wywołuje metodę wyjścia z programu. Taką aplikację możemy już zamknąć przy pomocy naszego przycisku.
Teraz pokażę jak tworzyć własne sloty. Stworzymy dodatkowy przycisk i etykietę, którą będziemy zmieniali tym przyciskiem:
mainwindow.hpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | #ifndef MAINWINDOW_HPP #define MAINWINDOW_HPP #include <QApplication> #include <QMainWindow> #include <QTextCodec> #include <QPushButton> #include <QLabel> class MainWindow : public QMainWindow { Q_OBJECT private: QPushButton *button; QPushButton *butlab; QLabel *label; public: MainWindow(); private slots: void foo(); }; #endif // MAINWINDOW_HPP |
Nową rzeczą jest marko Q_OBJECT, które musi być załączone, w prywatnej sekcji, każdej klasy, która ma własne sygnały lub sloty (lub inne możliwości dostarczane przez meta-object system Qt). Taka klasa musi również dziedziczyć (bezpośrednio lub pośrednio) po klasie QObject. Deklarujemy również prywatny slot. W rzeczywistości jest to metoda, którą możemy normalnie wywoływać, bez łączenia z sygnałami. Jeden sygnał może być podpięty do wielu slotów, tak samo jak jeden slot może przyjmować wiele różnych sygnałów. Jedyne ograniczenie jest takie, że slot nie może przyjmować więcej argumentów niż ma ich sygnał (tworzeniem sygnałów zajmiemy się innym razem).
mainwindow.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #include "mainwindow.hpp" MainWindow::MainWindow() { QTextCodec::setCodecForTr(QTextCodec::codecForName ("UTF-8")); label = new QLabel(tr("Smutna etykieta :(", this); label->setGeometry(5, 5, 150, 30); button = new QPushButton(tr("&Wciśnij mnie ;)"), this); button->setGeometry(5, 35, 100, 30); butlab = new QPushButton(tr("&Zmień napis"), this); butlab->setGeometry(5, 65, 100, 30); connect(button, SIGNAL(clicked()), qApp, SLOT(quit())); connect(butlab, SIGNAL(clicked()), this, SLOT(foo())); } void MainWindow::foo() { label->setText(tr("Wesoła etykieta :)")); } |
Mam nadzieję że kod konstruktora jest w miarę jasny. Na końcu łączymy sygnał przycisku butlab ze slotem foo() obiektu klasy MainWindow. Metoda foo() zmienia tekst naszej etykiety. Jak widać, żadna magia. :)
A na sam koniec nasza aplikacja z prostym menu i paskiem statusu:
mainwindow.hpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | #ifndef MAINWINDOW_HPP #define MAINWINDOW_HPP #include <QApplication> #include <QMainwindow> #include <QTextCodec> #include <QPushButton> #include <QLabel> #include <QMenuBar> #include <QStatusBar> class MainWindow : public QMainWindow { Q_OBJECT private: QPushButton *button; QPushButton *butlab; QLabel *label; QMenu *menu; QAction *quitAction; void createMenus(); void createStatusBar(); public: MainWindow(); private slots: void foo(); }; #endif // MAINWINDOW_HPP |
W nagłówku klasy naszego okna dodajemy wskaźniki na obiekty menu oraz akcji.
mainwindow.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | #include "mainwindow.hpp" MainWindow::MainWindow() { QTextCodec::setCodecForTr (QTextCodec::codecForName ("UTF-8")); createMenus(); createStatusBar(); label = new QLabel(tr("Smutna etykieta :("), this); label->setGeometry(5, 15, 150, 30); button = new QPushButton(tr("&Wciśnij mnie ;)"), this); button->setGeometry(5, 45, 100, 30); butlab = new QPushButton(tr("&Zmień napis"), this); butlab->setGeometry(5, 75, 100, 30); connect(button, SIGNAL(clicked()), qApp, SLOT(quit())); connect(butlab, SIGNAL(clicked()), this, SLOT(foo())); setMinimumSize(200, 200); resize(480, 320); } void MainWindow::foo() { label->setText(tr("Wesoła etykieta :)")); } void MainWindow::createMenus() { menu = menuBar()->addMenu(tr("&Plik")); quitAction = new QAction(tr("&Wyjście"), this); quitAction->setStatusTip(tr("Wyjdź z programu.")); connect (quitAction, SIGNAL(triggered()), qApp, SLOT(quit())); menu->addAction(quitAction); } void MainWindow::createStatusBar() { statusBar()->showMessage(tr("Gotowy")); } |
Konstruktor klasy naszego okna głównego doczekał się ustawień minimalnego rozmiaru okna (linia 22.) oraz zmiany wielkości okna (linia 23.). Znacznie ciekawsze jest to co się dzieje w metodach createMenus() oraz createStatusBar(). Pierwsza metoda tworzy poszczególne menu. Dodajemy menu „Plik” (linia 33.), tworzymy akcję „Wyjście” (linia 35.), której podpowiedź będzie wyświetlana w belce statusu (linia 36.), podłączamy tą akcję do slotu wyjścia z aplikacji oraz dodajemy ją do menu (linie 37. i 38). W metodzie createStatusBar() ustawiamy początkową wartość opisu na belce statusu.
Nie musimy tworzyć głównego menu ani belki statusu, gdyż zawierają się one w klasie QMainWindow. Wskaźniki do nich zwracają metody menuBar() oraz statusBar() (odpowiednio menu programu oraz belka statusu). Jest jeszcze ToolBar, który pozwala trzymać przyciski (chociaż można tam wcisnąć również zwykłe widgety, jak suwaki) ale o tej klasie powiem przy okazji zasobów programu. QActions (jak sama nazwa wskazuje ;)) pozwalają nam przypisać do nich różne akcje (funkcje) poprzez system sygnałów i slotów. Taką akcję możemy później dowolnie umieścić czy to w menu czy na ToolBarze. Na początku mogą się wydać mało intuicyjne, ale po kilku przykładach wszystko staje się jasne i zrozumiałe.
To na tyle, następnym razem wprowadzenie do Qt Creator. W razie pytań, wątpliwości lub jeżeli znaleźliście błąd piszcie w komentarzach, na e-mail lub Jabbera.

bardzo fajny kurs. Brakuje tylko opisu, jak skompilować opisany kod. A tu można natrafić na dwie trudności. Po pierwsze trzeba poinformować kompilator o ścieżce do nagłówków ( -I/usr/include/qt4/QtCore -I/usr/include/qt4/QtGui). Drugą, jest konieczność użycia moc-qt4 do każdego pliku z makrem Q_OBJECT, oraz dołączenie tak powstałego pliku w momencie linkowania
P.S.
tak, widziałem datę wpisu
@Piotr: jak kompilować znajduje się we wstępie do kursu.
po napisaniu komentarza zauważyłem.
Jednak wolę napisać własnego makefile zamiast korzystać z qmake :P
przynajmniej wiem dokładniej co się dzieje podczas kompilacji
@Piotr: no cóż, jak chce Ci się marnować czas na coś co automat i tak zrobi to samo, ale znacznie szybciej. ;)
Trzeba się przyzwyczajać, że coraz więcej zadań będą za naś robić automaty – nawet w programowaniu :D
Fajny ten Twój kurs, ale nie wiem czego u mnie, gdy są dużą litery w includach i brakuje końcówki „.h” to się nie chce skompilować.
PS W mainwindow.hpp masz: #include
mam pytanie, czy konieczne jest łączenie poprzez connect? bo wykonalem prosty kalkulator i bez connect przypisalem poprostu ze jesli klikniety przycisk to wykonaj jakas akcje – czyli w tradycyjny sposob przez if z c++ , wiec kiedy wymagane jest uzywanie wylacznie slotow i sygnalow?
@dmx: pokaż kod, bo nie ogarniam co masz na myśli.
1. zamiast connect(push_button,SIGNAL(clicked()),this,slot(obliczenia())
zrobilem if(on_push_button_clicked())
{
obliczenia();
}
czyli bez uzywania tej funkcji connect, uzywania SIGNAL i SLOT, no i dziala. a moje pytanie dotyczy tego, czy lepiej, zebym zawsze uzywal connect?
2. skoro nie trzeba dlugo czekac na odp (fajnie, ze mozna cie tu szybko napotkac, ze forum nie jest „martwe”) to wykorzystam to i zapytam tez o roznice miedzy qmainwindow, qwidget,qdialod – a mianowicie, rozpoczynajac projekt, najlepiej zaczac od qmainwindow? bo w niektorych przykladach zaczynasz od qwidget a efekt koncowy i tak jest taki sam (przynajmniej dla tych prostych przykladow)
3. tworzac jakis obiekt, np okno lub widget – wpisujemy parent=0 (czyli ze nie ma rodzica) – a co wpisac, jesli tworzymy drugie okno, ktore chcemy wyswietlic na tym pierwszym, w parent= wpisac nazwe tego drugiego okna-dziecka?
mam nadzieje, ze nie zawracam zbytnio glowy, ale wiem, ze troszke upierdliwy potrafie byc, do czasu, az nie poznam odpowiedzi na dreczace mnie pytanie lub az ktos mi nie wyjasni tego tak, abym zrozumial :) a ciesze sie, ze „dorwalem” kogos, kto ma pojecie o qt, bo niedawno zaczalem sie uczyc tej biblioteki
ok pytanie nr 3 juz chyba rozwiazalem, w kostruktorze piszemy THIS, prawda? (czasem tr(nazwa),this)
@dmx: @1: nadal nie specjalnie wiem jak to zrobiłeś (cały kod!), ale co do funkcji on_cos_zdarzenie() to nadal to jest wykorzystywanie sygnałów/slotów (na tym się Qt opiera i przed tym nie uciekniesz), tylko że Qt dopuszcza właśnie używanie takich funkcji. W rzeczywistości są to sloty, tylko że łączone z sygnałami przez meta object compiler… ale nadal mi to nie pasuje do tego co zrobiłeś… weź wrzuć swój kod na jakąś wklejkę. A co do ogólnego pytania to moim zdaniem lepiej używać sygnałów/slotów bo są wygodniejsze i prostsze do późniejszej analizy.
@2: (to nie jest forum, to jest blog!) QMainWindow i QDialog dziedziczą po QWidget i dodają do niego nowe funkcjonalności. A zaczynasz od tego co Ci bardziej jest potrzebne, dla prostych lepszy jest QWidget, dla wykorzystania menu + status bar lepiej użyć QMainWindow.
@3: jeżeli to ma być drugie okno to nic nie wpisujesz.
Witam, kurs kapitalnie się czyta, wszystko byłoby bardzo zrozumiałe gdyby nie jeden szczegół:
To w jaki sposób opisujesz całość wskazuje, że kierujesz kurs do osób mających kiedyś coś wspólnego z programowaniem, a co z takimi co nie mają pojęcia cóż to u licha jest „#include” a z angielskim sa na bakier?
Miałem nadzieję, że własnie trafiłem na stronkę gdzie można od samych podstaw się nauczyć Qt nawet starszemu już facetowi „hodowanym” na Turbo Pascalu :D
Można liczyć na podstawy podstaw czy lepiej tłumaczyć w google anglojęzyczne www?:>
@meff: nie mam jak, cała reszta (znaczy to co nie opisuję) to już jest C++ i średnio robić kurs C++ wewnątrz kursu Qt. Może, kiedyś, jak znajdę czas i chęci to skrobnę kurs C++0x, ale na razie się nie zapowiada.
dobry tutorial. narazie sadze że dobrze mi idzie:P i zgadzam sie ze nie ma co tlumaczyc C/C++ bo to bez sensu.
Docelowo chce zrobić w QT program ktory czyta z zewnatrz sygnal, pokazuje jego przebieg i go zapisuje w jakims pliku. Da radę? co musze sie jeszcze nauczyć?
Te błędy w kodzie to specjalnie?
@lmviii: które błędy w kodzie? Jak to pisałem to raczej działało i chyba też nikt się specjalnie nie skarżył.
w trzecim kodzie od dołu brakuje nawiasu label = new QLabel(tr(„Smutna etykieta :(„, this); a w czwartym od góry jest #include .
We wstępie kod też nie chciał mi się kompilować bo dodawane biblioteki(?) np. #include powinno być #include . albo tutaj trochę myślałem #include .
Niby bzdury więc myślałem, że to takie pułapki, żeby nie kopiować bezmyślnie kodu, a przyznaje że trochę to zniechęca…
no cóż nie można używać ostrych nawiasów więc jeszcze raz :(
w trzecim kodzie od dołu brakuje nawiasu label = new QLabel(tr(„Smutna etykieta :(„, this); a w czwartym od góry jest #include QAainwindow.
We wstępie kod też nie chciał mi się kompilować bo dodawane biblioteki(?) np. #include qapplication powinno być #include QApplication. albo tutaj trochę myślałem #include / qapplication qapplication.
Niby bzdury więc myślałem, że to takie pułapki, żeby nie kopiować bezmyślnie kodu, a przyznaje że trochę to zniechęca…
#ifndef MAINWINDOW_HPP
#define MAINWINDOW_HPP
#include //tu jest błąd :) Powinno być QMainWindow
#include
#include
finddialog.cpp: In member function ‘void FindDialog::createMenus()’:
finddialog.cpp:85: error: ‘menuBar’ was not declared in this scope
finddialog.cpp: In member function ‘void FindDialog::createStatusBar()’:
finddialog.cpp:95: error: ‘statusBar’ was not declared in this scope
make: *** [finddialog.o] Błąd 1
Zrobilem mala modyfikacje i mam takie bledy
Witam po skompilowaniu dwóch ostatnich kodów programu dostaje takiego errora „error: collect2: ld returned 1 exit status”
w czym jest problem ?
zqt, dostaję dokładnie takiego samego errora :( kombinowałem z ustawieniami bibliotek, ale końcówka kompilacji wygląda tak:
/usr/lib/gcc/x86_64-linux-gnu/4.4.3/../../../../lib/crt1.o: In function `_start’:
(.text+0×20): undefined reference to `main’
collect2: ld returned 1 exit status
make: *** [2] Błąd 1
bartek mam ten sam błąd.. Wie ktoś gdzie tkwi błąd w kodzie?
OPX, błąd twki najprawdopodobniej w tym, że do skompilowania potrzebujesz 3 plików, mainwindow.cpp, mainwindow.hpp i main.cpp ! ten ostatni jest na początku tekstu a potem modyfikowane są tylko 2 pozostałe pliki. U mnie pod ubuntu (x64) muszę dodatkowo zamieniać np. #include w #include , inaczej pisze, że nie ma takiego pliku :/ Ehh…
widzę, że nie prawidłowo wyświetla include, no więc muszę zamieniać duże znaki na małe i dodawać .h w nazwach nagłówków :/
dwa ostatnie kody sie nie kompiluja:
undefined reference to `MainWindow::staticMetaObject’ in function ‘MainWindow::tr(char const*, char const*)’
jakies pomysly?
Mi wywalało to „collect2: ld returned 1 exit status” bo miałem plik nagłówkowy z rozszerzeniem *.h jak zmieniłem na *.hpp to i etykieta się zmienia, a nie chciała i menu działa :)
Witam nie chce się tutaj za bardzo mądrzyć ponieważ jestem początkujący i dopiero zaczynam z Qt ale według mnie jest tutaj błąd w przykładnie 4.
Mianowicie w linijce 7 i 8 jest tak :
label = new QLabel (tr(„Smutna etykieta” ,this));
label->setGeometry(5, 5, 150, 30);
a tak się nie da gdy w mainwindow.hpp jest :
Qlabel*label;
Trzeba zmienić na QPushButton * label ;
Albo ominąć to ‘tr’ przy wyświetlaniu ale wtedy to zwykły napis który się zmienia po wywołaniu funkcji a tak był przycisk który zmienia swoja nazwę.
Nie wiem czy dobrze myślę.Nie chce jakoś urazić autora dla którego mam wielki szacunek za podjęcie się napisania tego kursu !!!
:)
A ja mam inny problem. Mianowicie, wszystko mi się pięknie kompiluje, natomiast nie pokazuje mi menu i paska statusu.