I would like to show a label and execute a function after displaying the label. Unfortunately, the label is always displayed after the function is executed.
void MainWindow::showLabel(){
myLabel->show();
doSomething();
}
void MainWindow::doSomething(){
QThread::msleep(3000);
myLabel->hide();
}
So, when i execute my code, the programm waits for three seconds and does show me an empty window afterwards (since it directly hides the label before even showing it; if I comment the hide function, the label is shown after waiting three seconds).
What I've tried to do is modifying the showEvent like this:
void MainWindow::showEvent(QShowEvent *event) {
QMainWindow::showEvent(event);
doSomething();
}
Am I doing something wrong by modifying the method or is there any other way to show the label before executing the followed function?
I would solve your problem in the following way:
void MainWindow::showLabel()
{
myLabel->show();
// Wait for 3sec. and hide the label.
QTimer::singleShot(3000, myLabel, SLOT(hide()));;
}
i.e. you don't need the second function and to block the current thread with QThread::msleep(), which is the reason why your label appears after the timeout is fired.
Update
If you need to do more than just hiding a label, define a slot and call it like:
void MainWindow::showLabel()
{
myLabel->show();
// Wait for 3sec. and call a slot.
QTimer::singleShot(3000, this, SLOT(doSomething()));
}
// This is a slot
void MainWindow::doSomething()
{
myLabel->hide();
[..]
// some more stuff
}
QThread::msleep(3000); is blocking the main thread where event loop is processed. So it prevent to show myLabel until sleep time is end. The solution is either to use QTimer as vahancho recomended or call event loop processing manualy by calling QEventLoop::exec() after myLabel->show();.
Related
I have a simple class based on QTreeWidget. In some cases (when value one of columns updated), I need to repaint it. I have a function which invokes when I need to update my widget:
void TreeWidget::updated()
{
/* some actions with cells */
/* here need to repaint widget */
this->update();
/* also I'm tried this->repaint(); */
}
But line this->update(); (or this->repaint();) gave no results. Widget repaint only when I click on it.
So how can I repaint my widget?
The classes that inherit from QAbstractScrollArea as QTreeWidget have viewport() which is the widget that must be updated, so in your case the solution is:
viewport()->update();
If you want to call update from another thread you can use QMetaObject::invokeMethod():
QMetaObject::invokeMethod(viewport(), "update", Qt::QueuedConnection)
This is the solution:
viewport()->update();
I learned one interesting thing. As it turned out, you can update widgets in Qt only from the main thread. My function updated() was called by another thread, so this->update() did not work. However, all slots in Qt are executed just in the main thread, wherever they are called from. In this case, the correct solution would be to wrap this->update() inside the slot. Like this:
TreeWidget::TreeWidget()
{
/* ... */
connect(this, SIGNAL(signal_update()), this, SLOT(slot_update()));
/* ... */
}
void TreeWidget::updated()
{
/* some actions with cells */
emit signal_update();
}
void TreeWidget::slot_update()
{
this->update();
}
Yeah, it's a less beautiful solution than this->viewport()->update() but more correct.
Here I am explaining my problem statement in detail and the efforts I have put so far
A) Problem Statement : During printing if 'Stop Printing' pushbutton is pressed, the printing should stop at that moment!
B) My Work :
1. StartPrinitng_Pressed :
void MainWindow :: on_StartPrinitng_Pressed()
{QSqlquery studentList;
studentList("SELECT Name, address FROM class WHERE Roll No = some variable")
while(studentList.next())
{
Name=studentList.value(0).toString();
address=studentList.value(1).toString();
QTimer:: singleShot(1000,this,SLOT(StopNow())); //calling stopNow function
if(StopPrintingNow==0)
{ //** I am printing the fetched data (in a string) by setting GPIO pins HIGH **// }
}
}
2. StopPrinting_Pressed :
void MainWindow::on_StopPrinting_Pressed()
{StopPrintingNow=1;}
3. StopNow Function Declaration :
void MainWindow::StopNow()
{
if(StopPrintingNow==1)
{ //** I have reset all serials ports; Break; **// }
else if(StopPrintingNow==0)
{ QTimer::singleShot(1000,this,SLOT(on_startPrinting_pressed())); }
}
C) Flow of program execution : As and when "StartPrinting" pushbutton is pressed, the query shown in my question executes which fetches data from database and perform simultaneous printing.
D)Problem Faced -
1.GUI is getting hanged while printing, hence StopPrinting button doesn't respond.
Qtimer is not calling "StopNow function " while printing (though I have called it at correct position)enter image description here
Handling of timers and button presses is both covered by the Qt event loop -- which is blocked while you are looping over that SQL query. You have two options:
1) Periodically dispatch events in your while loop.
This is as simple as
qApp->processEvents();
But you have to be careful, however: any events you trigger due to user interaction (or a timer) will block and your while loop will not run until the event is finished. In your case especially, you could end up running a second copy of your on_StartPrinitng_Pressed function.
2) Do the printing on a separate thread.
This involves some more code, but the gist of it is that you create a SqlPrinter object with a startPrinting slot and stopPrinting slot. You then create a QThread and change its owner thread to that thread. Slot invocations will happen across the thread boundary and all will be fine.
class SqlPrinter : public QObject {
Q_OBJECT
public:
SqlPrinter(QObject* parent = nullptr) : QObject(parent) {}
public slots:
void startPrinting();
void stopPrinting();
};
In your main code, then do something like this, assuming that you have the two buttons named MainWindow_StartButton and MainWindow_StopButton:
QThread* printerThread = new QThread(qApp);
SqlPrinter* printer = new SqlPrinter;
printer->moveToThread(printerThread);
printerThread->start();
QObject::connect(MainWindow_StartButton, &QPushButton::clicked, printer, &SqlPrinter::StartPrinting);
QObject::connect(MainWindow_StopButton, &QPushButton::clicked, printer, &SqlPrinter::StopPrinting);
Don't forget to clean up SqlPrinter afterwards!
I have one problem which I can't solve using the Internet. I have label and I set pixmap on it. I put it on main window (widget) where is button (QPushButton) too. I want to do that:
If I click on the button then on this pixmap will be drawn circles continuously
If I click this button for second then drawing must be stopped by function pause()
The second one is easy, it's empty slot:
void pause() {}
But at first I've tried to use loop
while(true)
draw();
but it crashed a program (loop).
Any idea how to solve it?
You should never block the main thread. This will cause the OS to consider your application has hanged. In fact it is a good practice to move any code, whose execution takes more than 50 milliseconds to another thread to keep the main thread responsive, especially in the case of Qt, where it is also the GUI thread.
You should use an event driven approach, which will not block the thread.
class YourClass : public QObject { // QObject or derived
Q_OBJECT
public:
YourClass() { connect(&timer, &Timer::timeout, this, &YourClass::draw); }
public slots:
void start() { timer.start(33); }
void pause() { timer.stop(); }
private:
QTimer timer;
void draw() { ... }
};
When start() is invoked, draw() will be called every 33 miliseconds. pause() will effectively stop that until start() is invoked again. You can control the rate at which draw() is invoked by adjusting the timer's interval, by default it is 0, which in the case of drawing is overkill, you should adjust for a desired framers per second. In the example above, the it is 33 milliseconds, or roughly 30 FPS.
You should then call draw() with some time interval, instead of halting the whole GUI thread with it.
For that, there's QTimer:
QTimer timer; // should be a member, a pointer optionally - you then do new Qtimer(this);
connect(&timer, &QTimer::timeout, draw);
timer.start(500); // in milliseconds
// assuming you are calling this from member function of QObject-deriving class
// and draw is a non-member function
If you know to do the connections, you can connect it to anything...
The same can be done with QThread and putting it to sleep in that loop.
Anyhow, I don't get how an empty pause() stops the drawing. Aren't you halting your application again? Just do timer.stop();.
I have a class in the main thread which initiates another thread. From the created thread, I send a signal to main thread to get the current documentElement of the QWebView and main thread replies to to the thread and sets the local QWebElement variable in the thread. So, here is some code:
Thread has:
...
public slots:
void setCurrentElement(QWebElement aElement);
signals:
void sgGetCurrentElement(void);
private:
QWebElement currentElement;
Main thread has:
...
public slots:
void onGetCurrentElementReceived(void);
signals:
void sgResponseToGetElement(QWebElement aElement);
After creating the thread, I connect the signals to slots like:
connect(insExtractor, SIGNAL(sgGetCurrentElement()), this, SLOT(onGetCurrentElementReceived()));
connect(this, SIGNAL(sgResponseToGetElement(QWebElement)), insExtractor, SLOT(setCurrentElement(QWebElement)));
Once the main thread receives a signal from the thread, it does the following:
void targetClass::onGetCurrentElementReceived(void)
{
emit sgResponseToGetElement(insWebView->page()->mainFrame()->documentElement());
}
The setter in the thread does the following:
void createdThread::setCurrentElement(QWebElement aElement)
{
currentElement = aElement;
}
Here is the problem:
My target is to find the first form element whose id is submitForm. There is such a form in the element, no problem there.
If I write the following line before emitting the signal in the main thread:
QWebElement form_found = currentElement.findFirst("form[id='submitForm']");
form_found doesn't return null and it finds the form element. No problem.
If I write the same code right after I set the currentElement in setCurrentElement in the thread, it does the same and it finds the form. However, if I do the same right after I emit the very first signal again in the thread, it returns null. Here is the code:
void createdThread::startExtracting(void)
{
sgGetCurrentElement();
QThread::msleep(2000);
QWebElement form_found = currentElement.findFirst("form[id='submitForm']");
}
So, form_found is coming null. Could anyone tell me why it is returning null? I put logs everywhere and it is being called after it is set in the setter. What is wrong with this?
My suspect is that you are stopping the event loop of the thread doing the sleep.
Threads in Qt are a bit tricky. Take a look at this, it makes all these questions very clear.
I'd say you should use the Element when you are sure you got it and that's on setCurrentElement. Why don't you start the extraction on that method ?
I have a Qt application that uses a QMainWindow-derived class for the main UI. On startup I want to make some security checks and, if they fail, display a message to the user and close the main window. Currently I make these checks in the QMainWindow constructor, but if I call the close method, nothing happens and the application continues to run. For example:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
setupUi(this);
...
if (checkFails())
{
QMessageBox::warning(this, tr("Error"), tr("You cannot run this app"));
// This has no effect
close();
}
}
Alternatively I could make the checks in the main function but then I lose the ability to display a language-specific message box (the tr macro only works in a QObject-derived class by the looks of things.)
Any ideas on how to close the main window on startup or make the tr macro work outside of a QObject derived class?
The event loop needs to be running before you can successfully close the main window. Since you probably first construct a window, and then start the event loop the close() call has no effect. Try the following solution instead:
QTimer::singleShot(0, this, SLOT(close()));
The QTimer::singleShot() will fire as soon as an event loop has been started, and subsequently calls the close() method on your application main window.
The above solution will probably cause your main application window to be visible for a short period of time, causing unwanted flickering.
A cleaner solution should perform the security checks prior to constructing the main window. Since tr() is also available as a static method on QObject, this can be done from the main function.
Most applications start up in three steps: 1) construct the window; 2) show the window; 3) start the event loop. You can make steps 2 and 3 conditional on the success of step 1 by adding a flag, whose value is set by the window constructor, to the window class:
Window class:
class myMainWindowClass : public QMainWindow
{
Q_OBJECT
public:
myMainWindowClass()
: isFinished_(false) { if (error) isFinished_ = true; } // constructor
bool isFinished() const { return isFinished_; }
private:
bool isFinished_;
}
Application code:
int main()
{
myMainWindowClass main_window(); // Step 1
// Finish early if isFinished flag is set
if (main_window.isFinished())
return 0;
main_window.show(); // Step 2
return a.exec(); // Step 3
}
This should also avoid any flicker as the application will end before the window is show()n.
tr is a public static member of QObject. You should be able to call QObject::tr("Error") in your main function.
Have you tried first hide()ing the window (this should occur anyway when close() is called) to see if this then allows close() to destroy the window.
If this does not work, you could always try destroy(true, true)ing the window along with any sub-windows.