У многих программистов, работающих с Qt4, наверняка возникало навязчивое желание соединить сигнал, посылаемый неким наследником QObject, c простой функцией, не являющейся слотом или даже членом некоторого класса. Однако если весь проект построен на объектах (как обычно и бывает), да и все они наследуются от QObject, то добавить функцию-слот куда надо не составит труда. А если нет? Если вы, например, из экономии памяти (или по другим соображениям) не хотите наследовать ваш класс от QObject, или же действие слота занимает всего 1 строчку и было бы проще и удобнее написать его в виде лямбда-выражения? Или вы по ряду причин хотите по сигналу вызывать одиночную фунцию, не являющуюся членом класса?
Столкнувшись с этой проблемой, я решил написать класс, который позволяет соединять сигнал не только с функцей-слотом, а ещё и с самой обычной функцией, а при поддержке С++11 — ещё и с лямбда-выражением.
Для начала — что должен делать класс? Он должен наследоваться от QObject и хранить ссылку на нашу функцию. Т.е. мы связываем сигнал какого-либо класса с некоторым слотом SmartConnect, а уже SmartConnect хранит ссылку на нашу независимую функцию или лямбда-выражение, и вызывает его по своему слоту.
Самым удобным решением будет перегрузить конструктор SmartConnect — для ссылки на разные функции. Пускай он для начала работает двумя типами сигналов — которые в аргументе передают void, а также те, которые передают QString. Создадим файл smartconnect.h:
Затем — собственно smartconnect.cpp:
Как видите, класс действительно smart — в зависимости от вызванного конструктора сам выбирает нужный внутренний слот и соединяется с ним, а также сохраняет ссылку на нашу функцию. При желании мы можем сделать поддержку сигналов с любыми аргументами — просто добавить ссылку на функцию с этими аргументами, слот, который их принимает и конструктор. А вот внешне использовать класс будет всё также просто.
Теперь создадим файл main.cpp:
Здесь мы создаём 2 кнопки, одна из которых соединяется с функцией не-слотом, другая — с лямбда-выраженим — и при клике по ним выводятся соответствующие сообщения. Теперь давайте создадим pro-файл, не забыв подключить С++11. Сразу скажу, что для этого потребуется новая версия Qt, собранная под такой же компилятор, поддерживающий С++11. Иначе — пример с лямбдой работать не будет. Создадим файл main.pro:
И, наконец скомпилируем и соберём наш пример:
При желании, класс можно усовершенствовать, например добавить в него disconnect, если нужно. Как видите — использование класса просто и удобно, в некоторых случаях он может очень пригодится и реально упростить код.
Столкнувшись с этой проблемой, я решил написать класс, который позволяет соединять сигнал не только с функцей-слотом, а ещё и с самой обычной функцией, а при поддержке С++11 — ещё и с лямбда-выражением.
Пишем класс SmartConnect
Для начала — что должен делать класс? Он должен наследоваться от QObject и хранить ссылку на нашу функцию. Т.е. мы связываем сигнал какого-либо класса с некоторым слотом SmartConnect, а уже SmartConnect хранит ссылку на нашу независимую функцию или лямбда-выражение, и вызывает его по своему слоту.
Самым удобным решением будет перегрузить конструктор SmartConnect — для ссылки на разные функции. Пускай он для начала работает двумя типами сигналов — которые в аргументе передают void, а также те, которые передают QString. Создадим файл smartconnect.h:
#include <QtCore>
class SmartConnect : public QObject
{
Q_OBJECT
void (*pVoidFunc)(void);
void (*pQStringFunc)(QString);
public:
SmartConnect(QObject* sender,const char* signal,void (*pFunc)(void));
SmartConnect(QObject* sender,const char* signal,void (*pFunc)(QString));
private slots:
void voidSlot();
void QStringSlot(QString str);
};
Затем — собственно smartconnect.cpp:
#include <QtCore>
#include "smartconnect.h"
SmartConnect::SmartConnect(QObject *sender, const char *signal, void (*pFunc)()){
pVoidFunc = pFunc;
QObject::connect(sender,signal,this,SLOT(voidSlot()));
}
SmartConnect::SmartConnect(QObject *sender, const char *signal, void (*pFunc)(QString)){
pQStringFunc = pFunc;
QObject::connect(sender,signal,this,SLOT(QStringSlot(QString)));
}
void SmartConnect::voidSlot(){
pVoidFunc();
}
void SmartConnect::QStringSlot(QString str){
pQStringFunc(str);
}
Как видите, класс действительно smart — в зависимости от вызванного конструктора сам выбирает нужный внутренний слот и соединяется с ним, а также сохраняет ссылку на нашу функцию. При желании мы можем сделать поддержку сигналов с любыми аргументами — просто добавить ссылку на функцию с этими аргументами, слот, который их принимает и конструктор. А вот внешне использовать класс будет всё также просто.
Пишем демонстрационный пример
Теперь создадим файл main.cpp:
#include <QtGui>
#include "smartconnect.h"
void onClick(){
qDebug()<<"Hello from void onClick()";
}
int main(int argc, char* argv[]){
QApplication app(argc, argv);
QPushButton button1("button1");
button1.show();
SmartConnect smartConnect1(&button1,SIGNAL(clicked()),onClick);
QPushButton button2("button2");
SmartConnect smartConnect2(&button2,SIGNAL(clicked()),[](){qDebug()<<"Hello from lambda";});
button2.show();
return app.exec();
}
Здесь мы создаём 2 кнопки, одна из которых соединяется с функцией не-слотом, другая — с лямбда-выраженим — и при клике по ним выводятся соответствующие сообщения. Теперь давайте создадим pro-файл, не забыв подключить С++11. Сразу скажу, что для этого потребуется новая версия Qt, собранная под такой же компилятор, поддерживающий С++11. Иначе — пример с лямбдой работать не будет. Создадим файл main.pro:
QT += gui
TEMPLATE = app
CONFIG += release
SOURCES += main.cpp
smartconnect.cpp
HEADERS += smartconnect.h
CONFIG += console
QMAKE_CXXFLAGS += -std=gnu++11
И, наконец скомпилируем и соберём наш пример:
qmake main.pro -o Makefile
make
При желании, класс можно усовершенствовать, например добавить в него disconnect, если нужно. Как видите — использование класса просто и удобно, в некоторых случаях он может очень пригодится и реально упростить код.