Subclassed QLineEdit styleSheet update triggers stack overflow
-
I created a QSuperLine class that inherits from QLineEdit. The idea is for the line to nicely change it's background - flash - when the text is changed either by a user or from within the QApplication. I do have a separate thread (named updater) that emits a freshUpdate signal with 100 msec intervals.
In principle this works as expected on 2 - 3 lines. However, when I have to update a large number of lines in my app (ca. 50), I can see that they 'flash' one by one instead of behaving independently, and - when they do - at some point I am getting a stack overflow exception.
Could someone more experienced with Qt tell me what could be improved in the below code to avoid this problem?
Below is my qsuperline.h content, QLineEdits have been promoted to qSuperLines in the Qt Designer. I use 64-bit MSVC.
#ifndef QSUPERLINE_H #define QSUPERLINE_H #pragma once #include <QLineEdit> #include <QObject> #include "updater.h" #include <QThread> extern void delay(int millisecondsWait); class QSuperLine : public QLineEdit { Q_OBJECT public: QThread * T1 = new QThread(); updater * Up1 = new updater(); bool isBusy; QString InitialContent; QSuperLine (QWidget *parent = nullptr) : QLineEdit(parent) { Up1->moveToThread(T1); InitialContent = ""; isBusy = false; QObject::connect( T1, &QThread::started, Up1, &updater::workNow); QObject::connect( Up1, &updater::freshUpdate, this, &QSuperLine::resetLine); T1->start(); } QSuperLine::~QSuperLine() { } public slots: int QSuperLine::resetLine(int a) { if (InitialContent != this->text() && isBusy == false) { isBusy = true; InitialContent = this->text(); this->setStyleSheet("background-color: rgb(20, 80, 180);border-style: solid;border-width: 2px;border-color: red;border-radius: 9px;color: cyan;"); delay(50); this->setStyleSheet("background-color: rgb(20, 80, 160);border-style: solid;border-width: 2px;border-color: red;border-radius: 9px;color: cyan;"); delay(50); this->setStyleSheet("background-color: rgb(20, 80, 140);border-style: solid;border-width: 2px;border-color: red;border-radius: 9px;color: cyan;"); delay(50); this->setStyleSheet("background-color: rgb(20, 80, 120);border-style: solid;border-width: 1px;border-color: red;border-radius: 9px;color: cyan;"); delay(50); this->setStyleSheet("background-color: rgb(20, 80, 100);border-style: solid;border-width: 1px;border-color: red;border-radius: 9px;color: cyan;"); delay(50); this->setStyleSheet("background-color: rgb(20, 80, 80);border-style: solid;border-width: 1px;border-color: red;border-radius: 9px;color: cyan;"); delay(50); this->setStyleSheet("background-color: rgb(20, 80, 40);border-style: solid;border-width: 0px;border-color: red;border-radius: 9px;color: cyan;"); delay(50); this->setStyleSheet(""); this->isBusy = false; } return 0; } }; #endif // QSUPERLINE_H
The delay function is defined elsewhere as follows:
void delay(int millisecondsWait) { QEventLoop loop; QTimer t; t.connect(&t, &QTimer::timeout, &loop, &QEventLoop::quit); t.start(millisecondsWait); loop.exec(); }
According to the QtDesigner debugger, stack overflow occurs at line 14 of the following asm code:
Qt5Guid!__chkstk [d:\agent\_work\63\s\src\vctools\crt\vcstartup\src\misc\amd64\chkstk.asm @ 67]: 0x7ff802413cc0 sub rsp,10h 0x7ff802413cc4 <+ 4> mov qword ptr [rsp],r10 0x7ff802413cc8 <+ 8> mov qword ptr [rsp+8],r11 0x7ff802413ccd <+ 13> xor r11,r11 0x7ff802413cd0 <+ 16> lea r10,[rsp+18h] 0x7ff802413cd5 <+ 21> sub r10,rax 0x7ff802413cd8 <+ 24> cmovb r10,r11 0x7ff802413cdc <+ 28> mov r11,qword ptr gs:[10h] 0x7ff802413ce5 <+ 37> cmp r10,r11 0x7ff802413ce8 <+ 40> bnd jae Qt5Guid!__chkstk+0x42 (00007ff8`02413d02) 0x7ff802413ceb <+ 43> and r10w,0F000h 0x7ff802413cf1 <+ 49> lea r11,[r11-1000h] 0x7ff802413cf8 <+ 56> mov byte ptr [r11],0 # <- stack overflow here.
Any suggestion would be wellcomed.
-
@BartM
For the overflow exception, you should look at the debugger's stack trace window to see what the trace back to your code is, not just the location of the overflow you show.I happen to have done something very similar to what you describe, but I do it very differently from your approach. You might like to consider whether it would suit you situation better.
I don't use any threads. Nor waiting event loops. (And in your case you are invoking the main event loop many times simultaneously, that may be problematic.) Nor any sub-classes. And all my flashes occur synchronised. And there are no problems.
I have many widgets (mine are actually
QSpinBox
es). The user may change one, and as a result many of the others' values are recalculated and so change. I want the user to see all the changed ones flash for a few seconds. Here is the outline of how I do it:-
I have one module-level
QTimer
. -
I have one module-level
QList
for widgets which are currently flashing. -
The elements in this list are lightweight
struct
s holding a pointer to one widget together with a "counter" for flashing: this is added to the list and loaded with the desired number of flashes when the widget's value is changed, and decrements toward 0 on eachQTimer
tick. When a widget's counter reaches 0 the flashing is stopped and the widget is removed from the flash list. On each tick the color is changed to implement the flash. -
Each time the timer ticks my slot iterates the flash list, decrementing each element's counter, doing the color change, and checking for reaching 0 to remove from the list.
-
If the user changes another spin box while a previous flashing is still on-going, new elements can be added to the list, or if it finds that it needs to flash an existing widget already on the list in can reset the counter to starting number to restart that item flashing.
Since there is only one timer all flashes are synchronised.
It works well for me, and seems a simpler solution. It can be applied to any widget without requiring subclassing. I suspect you have a problem with the multiple delays and threads. Threads are the root of all evil!
-
-
@BartM said in Subclassed QLineEdit styleSheet update triggers stack overflow:
freshUpdate signal with 100 msec intervals.
If you raise this value to say 2 secs. does it still crash ?
-
@BartM said in Subclassed QLineEdit styleSheet update triggers stack overflow:
Could someone more experienced with Qt tell me what could be improved in the below code to avoid this problem?
I have some questions about this code:
- why do you create a new thread for each instance? Is this really necessary?
- is
QSuperLine::resetLine()
slot only called from&updater::freshUpdate
signal? - why not use a
QTimer()
and a counter to handle the stylesheet changes? Something like:
class QSuperLine : public QLineEdit { Q_OBJECT public: QThread * T1 = new QThread(); updater * Up1 = new updater(); QString InitialContent; QSuperLine (QWidget *parent = nullptr) : QLineEdit(parent) { Up1->moveToThread(T1); InitialContent = ""; isBusy = false; QObject::connect( T1, &QThread::started, Up1, &updater::workNow); QObject::connect( Up1, &updater::freshUpdate, this, &QSuperLine::resetLine); T1->start(); connect(&mStyleSheetTmr, &QTimer::timeout, this, &QSuperLine::doBlink); mStyleSheetTmr.setSingleShot(true); mStyleSheetTmr.SetInterval(50); mAnimeState = 0; } inline bool IsBusy() const { return mAnimeState > 0; } private slots: void doBlink() { QString newStyle; switch(mAnimeState) { case 0: newStyle = "background-color: rgb(20, 80, 180);border-style: solid;border-width: 2px;border-color: red;border-radius: 9px;color: cyan;"; break; case 1: newStyle = "background-color: rgb(20, 80, 160);border-style: solid;border-width: 2px;border-color: red;border-radius: 9px;color: cyan;"; break; case 2: newStyle = "background-color: rgb(20, 80, 140);border-style: solid;border-width: 2px;border-color: red;border-radius: 9px;color: cyan;"; break; case 3: newStyle = "background-color: rgb(20, 80, 120);border-style: solid;border-width: 1px;border-color: red;border-radius: 9px;color: cyan;"; break; case 4: newStyle = "background-color: rgb(20, 80, 100);border-style: solid;border-width: 1px;border-color: red;border-radius: 9px;color: cyan;"; break; case 5: newStyle = "background-color: rgb(20, 80, 80);border-style: solid;border-width: 1px;border-color: red;border-radius: 9px;color: cyan;"; break; case 6: newStyle = "background-color: rgb(20, 80, 40);border-style: solid;border-width: 0px;border-color: red;border-radius: 9px;color: cyan;"; break; } this->setStyleSheet(newStyle); if(newStyle.isEmpty()) { mAnimeState = 0; } else { ++mAnimeState; mStyleSheetTmr.start(); } } public slots: void resetLine(int a) { if (InitialContent != this->text() && mAnimeState == 0) { InitialContent = this->text(); doBlink(); } } private: QTimer mStyleSheetTmr; int mAnimeState; };
-
Thanks a lot for all the suggestions. Indeed, as suggested by @JonB and @KroMignon - the main issue was the number of threads failing to run concurently. Changing the delay to 2 seconds did not solve the problem, it just delayed the inevitable.
So what I did was as follows:
- Check which lines change by comparing their ->text() value with a localy stored QString,
- Pointers to the lines that changed were then appended to a QList<QLineEdit*>
- This new list of pointers was then forwarded to another thread running in the background that iterates through the list, and emits a signal to setStyleSheet that is then received in the main thread.
- After the completion of 'flashing' the list is cleared and make availabe for the next cycle.
It works like a charm. Thanks a lot for the example with QSpinBoxes, it looks like the same approach can be indeed applied to other widgets and does not cause any stack problems. I don't know why I haven't thought about it... Though I'm a Qt noob :-)
Again, thank you!
Edit: If someone reads this in the future - the only catch with that method was the necessity to declare
Q_DECLARE_METATYPE(QList<QLineEdit*>) in the header file. Withouth that the side thread was unable to process the QList<QLineEdit*>. -
@BartM
I am glad you changed approach.This new list of pointers was then forwarded to another thread running in the background that iterates through the list, and emits a signal to setStyleSheet that is then received in the main thread.
I still don't know why you need any thread at all, seems more complicated than it needs to be. But if you're happy that's fine :)