In Qt, I can emit a signal, to which I have multiple slots connected, where in the case of direct connections, the connected slots are called on after another.
Let void mySignal(int x) be the signal of the class MyClass.
Depending on the value of x I want to perform a different action, and under the assumption, I want to do exactly one action, I can connect a slot, with a switch-case construct to execute the relevant action.
This implies that I need to know beforehand what kind of values I can get, and what the actions are.
I can also connect a slot for each of my actions, and guard the execution by a if clause. Now I can just connect whatever I want, whenever I want it. But under the assumption that I want to do exactly one action, it would be performance-wise beneficial if I could stop further execution of the slots, when I found the 'match'.
[...]
QObject::connect(this, &MyClass::mySignal, this, [this](int x) {
if (x == 0) {
qDebug() << x; // Stop it now!;
}
});
QObject::connect(this, &MyClass::mySignal, this, [this](int x) {
if (x == 4) {
qDebug() << x; // Stop it now!;
}
});
QObject::connect(this, &MyClass::mySignal, this, [this](int x) {
if (x == 109) {
qDebug() << x; // Stop it now!;
}
});
Is there a way, to tell the signal, to not execute anymore slots, until the signal is emitted again?
One way to do something like this, (ab) using the Qt framework would use the QEvent-system.
The signal handler won't do more, than translating the signal in an QEvent. Instead of all the separate slots, install eventFilter. If the eventFilter accepts the event, return true to stop the propagation to other (filter)objects.
The code is just a quick and dirty test and has no security precautions. It might easily crash hard.
// testevent.h
#ifndef TESTEVENT_H
#define TESTEVENT_H
#include <QObject>
#include <QEvent>
class TestEvent: public QEvent
{
int m_x;
public:
TestEvent(int x = 0);
int x() { return m_x; }
};
class TestEventFilter: public QObject
{
Q_OBJECT
int m_fx;
public:
TestEventFilter(int fx, QObject* parent = nullptr);
bool eventFilter(QObject* obj, QEvent* event) Q_DECL_OVERRIDE;
};
Q_DECLARE_METATYPE(TestEvent)
#endif // TESTEVENT_H
// testevent.cpp
#include "testevent.h"
#include <QDebug>
TestEvent::TestEvent(int x)
: QEvent(QEvent::User)
, m_x(x)
{
}
TestEventFilter::TestEventFilter(int fx, QObject *parent)
: QObject(parent)
, m_fx(fx)
{
}
bool TestEventFilter::eventFilter(QObject *obj, QEvent *event)
{
Q_UNUSED(obj);
TestEvent* e = static_cast<TestEvent*>(event);
qDebug() << "EventFilter for" << m_fx << "got" << e;
if (e->x() == m_fx) {
qDebug() << "accept that event!";
event->accept();
return true;
}
event->ignore();
return false;
}
// run
QObject o;
TestEventFilter* f1 = new TestEventFilter(10);
TestEventFilter* f2 = new TestEventFilter(5);
TestEventFilter* f3 = new TestEventFilter(3);
TestEventFilter* f4 = new TestEventFilter(7);
o.installEventFilter(f1);
o.installEventFilter(f2);
o.installEventFilter(f3);
o.installEventFilter(f4);
qApp->sendEvent(&o, new TestEvent(5));
qApp->sendEvent(&o, new TestEvent(3));
Output:
EventFilter for 7 got 0x369f2fe0
EventFilter for 3 got 0x369f2fe0
EventFilter for 5 got 0x369f2fe0
accept that event!
EventFilter for 7 got 0x369f3250
EventFilter for 3 got 0x369f3250
accept that event!
Related
I connect two signals to same slot. like this:
check = new QCheckBox();
connect(check, SIGNAL(clicked()), this, SLOT(MySlot()));
connect(check, SIGNAL(toggled(bool)),this,SLOT(MySlot()));
I don't want to define an other slot. In MySlot is it possible to recognize which signal callbacks the slot?
How can I do this?
You might be able to use the QMetaObject/QMetaMethod data associated with the sender to get what you want (untested)...
void MyClass::MySlot ()
{
auto index = senderSignalIndex();
if (index == sender()->indexOfSignal("clicked()")) {
/*
* Got here as the result of a clicked() signal.
*/
} else if (index == sender()->indexOfSignal("toggled(bool)")) {
/*
* Got here as the result of a toggled(bool) signal.
*/
}
}
Rather than that, however, if you're using Qt5 I would suggest making use of the new signal/slot syntax along with lambdas...
check = new QCheckBox();
connect(check, &QCheckBox::clicked,
[this]()
{
MySlot(false);
});
connect(check, &QCheckBox::toggled,
[this](bool toggled)
{
MySlot(true, toggled);
});
Along with a change to the signature of MySlot...
/**
* #param from_toggled_signal If true this call was triggered by a
* QCheckBox::toggled signal, otherwise it's
* the result of a QCheckBox::clicked signal.
*
* #param toggle_value If from_toggled_signal is true then this was the
* value passed to QCheckBox::toggled, otherwise unused.
*/
void MyClass::MySlot (bool from_toggled_signal, bool toggle_value = false)
{
.
.
.
}
New slots can be defined on the fly using lambdas :)
class MyClass : public QWidget {
QSomeLayout m_layout{this};
QCheckBox m_check;
enum Signal { Clicked, Toggled };
Q_SLOT void mySlot(Signal);
public:
MyClass( ... ) : ... {
m_layout.addWidget(&m_check);
connect(&m_check, &QCheckBox::clicked, this, [this]{ mySlot(Clicked); });
connect(&m_check, &QCheckBox::toggled, this, [this]{ mySlot(Toggled); });
}
};
You can also add your own context to the signal if that helps. For instance I had a service that downloaded user avatars for multiple windows. I needed the window to only load the user it was interested in something so I would pass in the user's id as the context. Something like:
void UserService::downloadAvatar(const QString& url, const int context = 0) {
...// Make the http request, on finished:
emit onAvatarDownloaded(context, responseBody);
}
I have an overloaded QTreeWidget class, with my SIGNALS: I have promoted it in my UI and when I listen promoted QTreeWidget object with a lambda syntax I have an error.
QObject::connect: signal not found in CustomTreeWidget.
MY CustomTreeWidget looks like:
.h
class CustomTreeWidget : public QTreeWidget
{
Q_OBJECT
public:
explicit CustomTreeWidget(QWidget *parent = 0);
~CustomTreeWidget() {
}
signals:
void currentNodeChanged(QSet<int> uids);
void deleteRequest(QVector<int> uids);
}
.cpp
CustomTreeWidget::CustomTreeWidget(QWidget *parent) : QTreeWidget(parent)
{
setAnimated(true);
connect(this, &CustomTreeWidget::customContextMenuRequested, this, [=](const QPoint &pos) {
this->m_bCustomMenuOpen = true;
const auto &&item = this->itemAt(pos);
QMenu myMenu;
bool ok = !(item) ? false : true;
if (ok) {
//თუ topLevelItem -ია მხოლოდ დამატების action -ი უნდა იყოს ჩართული.
if (item == this->topLevelItem(0) || item == this->topLevelItem(0)->child(0)) {
ok = false;
}
}
QAction *Removecnt = myMenu.addAction(tr("&წაშლა"), this, SLOT(DeleteNode()));
Removecnt->setIcon(QIcon(":/global_res/delete.png"));
Removecnt->setEnabled(ok);
myMenu.exec(this->mapToGlobal(pos));
});
}
void CustomTreeWidget::BFS(QTreeWidgetItem *item, QSet<int> &out)
{
std::queue<QTreeWidgetItem *> Q;
Q.push(item);
while (!Q.empty()) {
QTreeWidgetItem *now = Q.front(); Q.pop();
out.insert(this->m_mapUids[now]);
for (int i = 0; i < now->childCount(); i++) {
Q.push(now->child(i));
}
}
}
QSet<int> CustomTreeWidget::GetCurrentNodeUids()
{
QSet<int> uids;
if (!this->currentItem())
return uids;
this->BFS(this->currentItem(), uids);
return uids;
}
void CustomTreeWidget::DeleteNode()
{
QSet<int> nodes = this->GetCurrentNodeUids();
QVector<int> uids;
for (auto it : nodes) {
uids.push_back(it);
}
emit deleteRequest(uids);
}
My lambda looks like:
connect(ui->productTree, &CustomTreeWidget::deleteRequest, this, [=](QVector<int> uids) {
//logic
});
But this signal works with old syntax.
connect(ui->productTree, SIGNAL(deleteRequest(QVector<int>)), this, SLOT(checkSlot(QVector<int>)));
And this slot is.
void ProductForm::checkSlot(QVector<int> uids)
{
qDebug() << uids.size();
}
So what is problem lambda syntax?
This smells like the violation of the one definition rule (ODR) - perhaps due to a stale build folder. The problem is that the address of deleteRequest passed to the connect method is not the same as the address of the method visible from moc_CustomTreeWidget.cpp. Remove the build folder and try again. If it still doesn't work, start reducing your problem:
Create a minimization branch in the repository (if you're not using version control, you won't go anywhere with minimization).
Copy-paste the contents of the ui_CustomTreeWidget.h file into the CustomTreeWidget.cpp file, remove the #include line.
Inspect the pasted contents for instantiation of the productTree object with the correct type.
If that's correct, then remove everything else from the code, step-by-step, rebuilding and committing to the repository at each step that still reproduces. You should end up with a test case that is 20-30 lines long at most. And it'll be either obvious what's wrong, or you can modify the question with the test case.
In the code above uitablewidget does not update using signal and slot.
It seems as if (ui->tableWidget->setItem(0,0,newItemx);) doesn't work.
Am I doing something wrong or is there a better way to update my qtablewidget from my class B?
Class_A::Class_A(QWidget *parent):QDialog(parent),ui(new Ui::Class_A)
{
ui->setupUi(this);
}
Class_A::~Class_A()
{
delete ui;
}
void Class_A::change_TableWidget(double x,double y) // this is the public slot
{
QTableWidgetItem *newItemx = new QTableWidgetItem(QString::number(x));
ui->tableWidget->setItem(0,0,newItemx);
QTableWidgetItem *newItemy = new QTableWidgetItem(QString::number(y));
ui->tableWidget->setItem(0,0,newItemy);
}
Class_B::Class_B(QWidget *parent) :
QGLWidget(parent)
{
Class_A *t=new Class_A;
connect(this,SIGNAL(mySignal(double,double)),t,SLOT(change_TableWidget(double,double)));
}
void Class_B::mousePressEvent(QMouseEvent *event)
{
double x = event->x();
double y = event->y();
emit mySignal(x,y);
}
You don't have a SLOT(change_TableWidget(double,double)) - yours takes 3 doubles, not two.
You should check that connect() returned true. I like to write
if (!connect(....)) Q_ASSERT(false);
or if (!connect(....)) Q_ASSERT(!"connect");
Also, connect prints out messages to the debug output when it fails to match the signals and slots. You should look for that output.
(Or use the new Qt 5 connect(), which is all checked at compile time.)
I have a CodeEditor class which inherits from QPlainTextEdit. This class has to emit requests when textCursor()'s position or selection changes. The thing is, I don't want 2 calls to assembleIntellisenseRequest and pickListSortRequest signals when both cursorPositionChanged and selectionChanged is emitted. I solved this with adding bool update_ member to CodeEditor, which is set to true in constructor. 500ms delay is just for clarity.
void CodeEditor::makeConnections()
{
auto updateRequest = [this]()
{
if(update_ && !textCursor().hasSelection())
{
update_ = false;
QTimer::singleShot(500, [this]() { update_ = true; });
emit assembleIntellisenseRequest(textCursor().blockNumber());
emit pickListSortRequest(textCursor().positionInBlock());
}
};
connect(this, &CodeEditor::cursorPositionChanged, updateRequest);
connect(this, &CodeEditor::selectionChanged, updateRequest);
}
Is there any better way to accomplish this? Also, why was in this case, when lambda captures by reference this1: printout not equal to this:? It was silent, I just knew, that update_ is still false.
auto updateRequest = [this]()
{
cout << "this: " << this << endl;
if(update_ && !textCursor().hasSelection())
{
update_ = false;
QTimer::singleShot(500, [&]()
{
cout << "this1: " << this << endl;
update_ = true;
});
emit assembleIntellisenseRequest(textCursor().blockNumber());
emit pickListSortRequest(textCursor().positionInBlock());
}
};
Thank you in advance.
You can apply next pattern to your code:
class MyClass : public QObject
{
private slots:
void updateRequest();
private:
QTimer *_timer;
CodeEditor *_editor;
};
MyClass::MyClass()
{
// Init members
// ...
_timer->setSingleShot( true );
_timer->setInterval( 0 );
connect( _editor, &CodeEditor:: cursorPositionChanged, _timer, &QTimer::start);
connect( _editor, &CodeEditor:: selectionChanged, _timer, &QTimer::start);
connect( _timer, &QTimer::timeout, this, &MyClass::updateRequest );
}
In this solution, timer is a "proxy for signals". Each time signal is emited timer will start immedeately (when flow will return to an event loop). Each emitting of signal will call QTimer::start slot. But all calls of start will place only one call of timeout signal to event queue. So, when control flow will return to event loop, your updateRequest slot will be called only once, even if a lot of signals were emited.
QTimer is a "Qt way" to replace your update_ variable, without any timeouts.
Today I encountered a problem with repaint() function from QT libraries. Long story short, I got a slot where I train my neural network using BP algorithm. I had tested the whole algorithm in console and then wanted to move it into GUI Application. Everything works fine except refreshing. Training of neural networks is a process containing a lot of computations, which are made in bp_alg function (training) and licz_mse function (counting a current error). Variable ilosc_epok can be set up to 1e10. Therefore the whole process may last even several hours. Thats why I wanted to display a current progress after each 100000 epochs (the last if contition). wyniki is an object of QTextEdit class used for displaying the progress. Unfortunately, repaint() doesnt work as intended. At the beginning it refreshes wyniki in GUI, but after some random time it stops working. When the external loop is finished, it refreshes once again showing all changes.
I tried to change frequency of refreshing, but sooner or later it always stops (unless the whole training process stops early enough because of satisfying the break condition). It looks like at some moment of time the application decides to stop refreshing because of too many computations. Imo it shouldnt happen. I was looking for a solution among older questions and managed to solve the problem when I used qApp->processEvents(QEventLoop::ExcludeUserInputEvents); instead of wyniki->repaint();. However, Im still curious why repaint() stops working just like that.
Below I paste a part of the code with the problematic part. Im using QT Creator 2.4.1 and QT Libraries 4.8.1 if it helps.
unsigned long int ile_epok;
double mse_w_epoce;
for (ile_epok=0; ile_epok<ilosc_epok; ile_epok++) { //external loop of training
mse_w_epoce = 0;
for (int i=0; i<zbior_uczacy_rozmiary[0]; i++) { //internal loop of training
alg_bp(zbior_uczacy[i], &zbior_uczacy[i][zbior_uczacy_rozmiary[1]]);
mse_w_epoce += licz_mse(&zbior_uczacy[i][zbior_uczacy_rozmiary[1]]);
}
//checking break condition
if (mse_w_epoce < warunek_stopu) {
wyniki->append("Zakończono uczenie po " + QString::number(ile_epok) + " epokach, osiągając MSE: " + QString::number(mse_w_epoce));
break;
}
//problematic part
if ((ile_epok+1)%(100000) == 0) {
wyniki->append("Uczenie w toku, po " + QString::number(ile_epok+1) + " epokach MSE wynosi: " + QString::number(mse_w_epoce));
wyniki->repaint();
}
}
You're blocking your GUI thread, so repaints will not work, it's just plainly bad design. You're never supposed to block the GUI thread.
If you insist on doing the work in the GUI thread, you must forcibly chop the work into small chunks and return to the main event loop after each chunk. Nested event loops are evil, so don't even think you'd want one. All this has a bad code smell, so stay away.
Alternatively, simply move your computation QObject to a worker thread and do the work there.
The code below demonstrates both techniques. It's easy to notice that the chopping-up-of-work requires to maintain loop state inside of the worker object, not merely locally in the loop. It's messier, the code smells bad, again - avoid it.
The code works under both Qt 4.8 and 5.1.
//main.cpp
#include <QApplication>
#include <QThread>
#include <QWidget>
#include <QBasicTimer>
#include <QElapsedTimer>
#include <QGridLayout>
#include <QPlainTextEdit>
#include <QPushButton>
class Helper : private QThread {
public:
using QThread::usleep;
};
class Trainer : public QObject {
Q_OBJECT
Q_PROPERTY(float stopMSE READ stopMSE WRITE setStopMSE)
float m_stopMSE;
int m_epochCounter;
QBasicTimer m_timer;
void timerEvent(QTimerEvent * ev);
public:
Trainer(QObject *parent = 0) : QObject(parent), m_stopMSE(1.0) {}
Q_SLOT void startTraining() {
m_epochCounter = 0;
m_timer.start(0, this);
}
Q_SLOT void moveToGUIThread() { moveToThread(qApp->thread()); }
Q_SIGNAL void hasNews(const QString &);
float stopMSE() const { return m_stopMSE; }
void setStopMSE(float m) { m_stopMSE = m; }
};
void Trainer::timerEvent(QTimerEvent * ev)
{
const int updateTime = 50; //ms
const int maxEpochs = 5000000;
if (ev->timerId() != m_timer.timerId()) return;
QElapsedTimer t;
t.start();
while (1) {
// do the work here
float currentMSE;
#if 0
for (int i=0; i<zbior_uczacy_rozmiary[0]; i++) { //internal loop of training
alg_bp(zbior_uczacy[i], &zbior_uczacy[i][zbior_uczacy_rozmiary[1]]);
currentMSE += licz_mse(&zbior_uczacy[i][zbior_uczacy_rozmiary[1]]);
}
#else
Helper::usleep(100); // pretend we're busy doing some work
currentMSE = 2E4/m_epochCounter;
#endif
// bail out if we're done
if (currentMSE <= m_stopMSE || m_epochCounter >= maxEpochs) {
QString s = QString::fromUtf8("Zakończono uczenie po %1 epokach, osiągając MSE: %2")
.arg(m_epochCounter).arg(currentMSE);
emit hasNews(s);
m_timer.stop();
break;
}
// send out periodic updates
// Note: QElapsedTimer::elapsed() may be expensive, so we don't call it all the time
if ((m_epochCounter % 128) == 1 && t.elapsed() > updateTime) {
QString s = QString::fromUtf8("Uczenie w toku, po %1 epokach MSE wynosi: %2")
.arg(m_epochCounter).arg(currentMSE);
emit hasNews(s);
// return to the event loop if we're in the GUI thread
if (QThread::currentThread() == qApp->thread()) break; else t.restart();
}
m_epochCounter++;
}
}
class Window : public QWidget {
Q_OBJECT
QPlainTextEdit *m_log;
QThread *m_worker;
Trainer *m_trainer;
Q_SIGNAL void startTraining();
Q_SLOT void showNews(const QString & s) { m_log->appendPlainText(s); }
Q_SLOT void on_startGUI_clicked() {
QMetaObject::invokeMethod(m_trainer, "moveToGUIThread");
emit startTraining();
}
Q_SLOT void on_startWorker_clicked() {
m_trainer->moveToThread(m_worker);
emit startTraining();
}
public:
Window(QWidget *parent = 0, Qt::WindowFlags f = 0) :
QWidget(parent, f), m_log(new QPlainTextEdit), m_worker(new QThread(this)), m_trainer(new Trainer)
{
QGridLayout * l = new QGridLayout(this);
QPushButton * btn;
btn = new QPushButton("Start in GUI Thread");
btn->setObjectName("startGUI");
l->addWidget(btn, 0, 0, 1, 1);
btn = new QPushButton("Start in Worker Thread");
btn->setObjectName("startWorker");
l->addWidget(btn, 0, 1, 1, 1);
l->addWidget(m_log, 1, 0, 1, 2);
connect(m_trainer, SIGNAL(hasNews(QString)), SLOT(showNews(QString)));
m_trainer->connect(this, SIGNAL(startTraining()), SLOT(startTraining()));
m_worker->start();
QMetaObject::connectSlotsByName(this);
}
~Window() {
m_worker->quit();
m_worker->wait();
delete m_trainer;
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Window w;
w.show();
return a.exec();
}
#include "main.moc"