Qt
Sygnały i automaty
Szukaj…
Wprowadzenie
Uwagi
Oficjalną dokumentację na ten temat można znaleźć tutaj .
Mały przykład
Sygnały i szczeliny służą do komunikacji między obiektami. Mechanizm sygnałów i szczelin jest centralną cechą Qt i prawdopodobnie częścią, która najbardziej różni się od funkcji zapewnianych przez inne frameworki.
Minimalny przykład wymaga klasy z jednym sygnałem, jednym gniazdem i jednym połączeniem:
counter.h
#ifndef COUNTER_H
#define COUNTER_H
#include <QWidget>
#include <QDebug>
class Counter : public QWidget
{
/*
* All classes that contain signals or slots must mention Q_OBJECT
* at the top of their declaration.
* They must also derive (directly or indirectly) from QObject.
*/
Q_OBJECT
public:
Counter (QWidget *parent = 0): QWidget(parent)
{
m_value = 0;
/*
* The most important line: connect the signal to the slot.
*/
connect(this, &Counter::valueChanged, this, &Counter::printvalue);
}
void setValue(int value)
{
if (value != m_value) {
m_value = value;
/*
* The emit line emits the signal valueChanged() from
* the object, with the new value as argument.
*/
emit valueChanged(m_value);
}
}
public slots:
void printValue(int value)
{
qDebug() << "new value: " << value;
}
signals:
void valueChanged(int newValue);
private:
int m_value;
};
#endif
main
ustawia nową wartość. Możemy sprawdzić, jak nazywa się to gniazdo, drukując wartość.
#include <QtGui>
#include "counter.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Counter counter;
counter.setValue(10);
counter.show();
return app.exec();
}
Wreszcie nasz plik projektu:
SOURCES = \
main.cpp
HEADERS = \
counter.h
Nowa składnia połączenia Qt5
Konwencjonalna składnia connect
która korzysta z makr SIGNAL
i SLOT
działa całkowicie w czasie wykonywania, co ma dwie wady: ma pewne obciążenie w czasie wykonywania (co powoduje również obciążenie w postaci binarnej) i nie ma kontroli poprawności czasu kompilacji. Nowa składnia rozwiązuje oba problemy. Przed sprawdzeniem składni w przykładzie lepiej powinniśmy wiedzieć, co się w szczególności dzieje.
Powiedzmy, że budujemy dom i chcemy podłączyć kable. Właśnie to robi funkcja łączenia. Sygnały i gniazda są tymi, które wymagają tego połączenia. Chodzi o to, że jeśli wykonasz jedno połączenie, musisz uważać na kolejne nakładające się połączenia. Za każdym razem, gdy podłączasz sygnał do gniazda, próbujesz powiedzieć kompilatorowi, że za każdym razem, gdy sygnał był emitowany, po prostu wywołaj funkcję gniazda. Tak właśnie się dzieje.
Oto przykładowy main.cpp :
#include <QApplication>
#include <QDebug>
#include <QTimer>
inline void onTick()
{
qDebug() << "onTick()";
}
struct OnTimerTickListener {
void onTimerTick()
{
qDebug() << "OnTimerTickListener::onTimerTick()";
}
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
OnTimerTickListener listenerObject;
QTimer timer;
// Connecting to a non-member function
QObject::connect(&timer, &QTimer::timeout, onTick);
// Connecting to an object member method
QObject::connect(&timer, &QTimer::timeout, &listenerObject, &OnTimerTickListener::onTimerTick);
// Connecting to a lambda
QObject::connect(&timer, &QTimer::timeout, [](){
qDebug() << "lambda-onTick";
});
return app.exec();
}
Wskazówka: stara składnia (makra SIGNAL
/ SLOT
) wymaga, aby metakompilator Qt (MOC) był uruchamiany dla każdej klasy, która ma sloty lub sygnały. Z punktu widzenia kodowania oznacza to, że takie klasy muszą mieć makro Q_OBJECT
(co wskazuje na konieczność uruchomienia MOC na tej klasie).
Z drugiej strony nowa składnia nadal wymaga MOC dla sygnałów do działania, ale nie dla gniazd. Jeśli klasa ma tylko gniazda i nie ma sygnałów, nie musi mieć makra Q_OBJECT
a zatem może nie wywoływać MOC, co nie tylko zmniejsza końcowy rozmiar Q_OBJECT
binarnego, ale także skraca czas kompilacji (brak wywołania MOC i brak kolejnego wywołania kompilatora dla wygenerowanego *_moc.cpp
plik *_moc.cpp
).
Podłączanie przeciążonych sygnałów / gniazd
Choć pod wieloma względami jest lepsza, nowa składnia połączenia w Qt5 ma jedną wielką słabość: łączenie przeciążonych sygnałów i gniazd. Aby kompilator mógł rozwiązać przeciążenia, musimy użyć static_cast
s do wskaźników funkcji członkowskich lub (począwszy od Qt 5.7) qOverload
i przyjaciele:
#include <QObject>
class MyObject : public QObject
{
Q_OBJECT
public:
explicit MyObject(QObject *parent = nullptr) : QObject(parent) {}
public slots:
void slot(const QString &string) {}
void slot(const int integer) {}
signals:
void signal(const QString &string) {}
void signal(const int integer) {}
};
int main(int argc, char **argv)
{
QCoreApplication app(argc, argv);
// using pointers to make connect calls just a little simpler
MyObject *a = new MyObject;
MyObject *b = new MyObject;
// COMPILE ERROR! the compiler does not know which overloads to pick :(
QObject::connect(a, &MyObject::signal, b, &MyObject::slot);
// this works, now the compiler knows which overload to pick, it is very ugly and hard to remember though...
QObject::connect(
a,
static_cast<void(MyObject::*)(int)>(&MyObject::signal),
b,
static_cast<void(MyObject::*)(int)>(&MyObject::slot));
// ...so starting in Qt 5.7 we can use qOverload and friends:
// this requires C++14 enabled:
QObject::connect(
a,
qOverload<int>(&MyObject::signal),
b,
qOverload<int>(&MyObject::slot));
// this is slightly longer, but works in C++11:
QObject::connect(
a,
QOverload<int>::of(&MyObject::signal),
b,
QOverload<int>::of(&MyObject::slot));
// there are also qConstOverload/qNonConstOverload and QConstOverload/QNonConstOverload, the names should be self-explanatory
}
Połączenie gniazda sygnału z wieloma oknami
Prosty przykład wielu okien z wykorzystaniem sygnałów i gniazd.
Istnieje klasa MainWindow, która kontroluje widok okna głównego. Drugie okno kontrolowane przez klasę witryny.
Dwie klasy są ze sobą połączone, więc po kliknięciu przycisku w oknie Witryny coś się dzieje w MainWindow (zmiana etykiety tekstowej).
Zrobiłem prosty przykład, który jest również na GitHub :
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "website.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
public slots:
void changeText();
private slots:
void on_openButton_clicked();
private:
Ui::MainWindow *ui;
//You want to keep a pointer to a new Website window
Website* webWindow;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::changeText()
{
ui->text->setText("New Text");
delete webWindow;
}
void MainWindow::on_openButton_clicked()
{
webWindow = new Website();
QObject::connect(webWindow, SIGNAL(buttonPressed()), this, SLOT(changeText()));
webWindow->show();
}
strona internetowa. h
#ifndef WEBSITE_H
#define WEBSITE_H
#include <QDialog>
namespace Ui {
class Website;
}
class Website : public QDialog
{
Q_OBJECT
public:
explicit Website(QWidget *parent = 0);
~Website();
signals:
void buttonPressed();
private slots:
void on_changeButton_clicked();
private:
Ui::Website *ui;
};
#endif // WEBSITE_H
website.cpp
#include "website.h"
#include "ui_website.h"
Website::Website(QWidget *parent) :
QDialog(parent),
ui(new Ui::Website)
{
ui->setupUi(this);
}
Website::~Website()
{
delete ui;
}
void Website::on_changeButton_clicked()
{
emit buttonPressed();
}
Skład projektu:
SOURCES += main.cpp\
mainwindow.cpp \
website.cpp
HEADERS += mainwindow.h \
website.h
FORMS += mainwindow.ui \
website.ui
Weźmy pod uwagę Uis, które należy skomponować:
- Główne okno: etykieta o nazwie „tekst” i przycisk o nazwie „openButton”
- Okno witryny: przycisk o nazwie „changeButton”
Kluczowymi punktami są zatem połączenia między sygnałami i gniazdami oraz zarządzanie wskaźnikami lub referencjami systemu Windows.