Not able to connect aboutToQuit signal from QApplication - c++

I have a Qt Application which i want to show in the System Tray.
My desired behavior is that if the user clicks the close button of the Application than that application hides in the system tray but does not exit.
My code in main.cpp is :
if (QSystemTrayIcon::isSystemTrayAvailable())
{
QObject *root = engine.rootObjects().at(0);
QQuickWindow *window = qobject_cast<QQuickWindow *>(root);
QAction *showAction = new QAction(QObject::tr("Show"), window);
window->connect(showAction, SIGNAL(triggered()), window, SLOT(show()));
QAction *hideAction = new QAction(QObject::tr("Hide"), window);
window->connect(hideAction, SIGNAL(triggered()), window, SLOT(hide()));
QAction *quitAction = new QAction(QObject::tr("&Quit"), window);
window->connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
QObject::connect(qApp,SIGNAL(aboutToQuit()),window,SLOT(hide()));
QMenu *trayIconMenu = new QMenu();
trayIconMenu->addAction(showAction);
trayIconMenu->addAction(hideAction);
trayIconMenu->addSeparator();
trayIconMenu->addAction(quitAction);
QSystemTrayIcon *trayIcon = new QSystemTrayIcon(window);
trayIcon->setContextMenu(trayIconMenu);
trayIcon->setToolTip("xxx");
trayIcon->setIcon(QIcon("xxx.png"));
trayIcon->show();
}
Now i am not able to connect the aboutToQuit signal and hide the application in the tray i.e
QObject::connect(qApp,SIGNAL(aboutToQuit()),window,SLOT(hide())); line is not correct but i am not getting any errors etc.
Apart from this everything is working correctly.Can someone please tell me what i am doing wrong and how can i achieve my desired behavior.
I would also like to know whether i have got the right signal to connect or whether i should try connecting to some other signal.
Thanks in advance.

You can use :
qApp()->setQuitOnLastWindowClosed(false);
quitOnLastWindowClosed property is true by default which causes your application to quit when the last window is closed. By setting it to false, your application does not terminate when you close the main window.
You can also reimplement closeEvent of your main widget, ignore the close event and just hide your window :
void MainWindow::closeEvent(QCloseEvent * e)
{
e->ignore();
this->hide();
}

Related

QListWidget with custom widgets - not triggering itemClicked signal

I have listwidgetitem's that have a custom widget (step_widget) which contains 3 QPushButtons and a QLabel.
When I press one of the buttons in the widget, it is not triggering itemClicked which i need to see the index of the listwidget that was clicked. If i click the QLabel, the signal is triggered and i am able to obtain the index. How can trigger the signal when one of the buttons is pressed? Why does it not trigger?
QListWidgetItem *item = new QListWidgetItem();
stepWidget *step_widget = new stepWidget();
ui->listWidget->addItem(item);
ui->listWidget->setItemWidget(item,step_widget);
The itemClicked() signal is not emmited because the QPushButton consumes the click event.
There is the simplest solution I've found.
First, in your class stepWidget add a signal that will be emitted when any QPushButton is clicked, for example:
signals:
void widgetClicked();
Then connect the clicked() signal of every QPushButton with that signal. This code is in the constructor of the widget stepWidget:
connect(ui->pushButton1, &QPushButton::clicked, this, &stepWidget::widgetClicked);
connect(ui->pushButton2, &QPushButton::clicked, this, &stepWidget::widgetClicked);
connect(ui->pushButton3, &QPushButton::clicked, this, &stepWidget::widgetClicked);
To test it I added 10 widgets to a QListWidget that reside in the MainWindow class. You must connect that signal to a slot (in this case I use a C++11 Lambda Function, but you can create a slot instead, it's fine) where you establish the current item as the item under the widget. This code is located in the constructor of MainWindow:
for (int i = 0; i < 10; ++i) {
QListWidgetItem *item = new QListWidgetItem;
stepWidget *step_Widget = new stepWidget;
ui->listWidget->addItem(item);
ui->listWidget->setItemWidget(item, step_Widget);
connect(step_Widget, &stepWidget::widgetClicked, [=]() {
ui->listWidget->setCurrentItem(item);
});
}
Now, this will not make the itemClicked() signal to be emitted because the user not clicked the item. But I advice you to connect instead the signal currentRowChanged() as it will be emitted with this code and also it has the advantage that it allows you to directly obtain the row. Once you have the row you can obtain the item and the widget, evidently.
However, this procedure has the inconvenient that the currentRowChanged() signal will be emitted even when the user selects a row with the keyboard or when the user press and slide the mouse over the QListWidget without releasing it.
On workaround could be, for example, using a flag as an attribute for your MainWindow class that is always false except during this connection:
connect(ste_pWidget, &stepWidget ::widgetClicked, [=]() {
buttonPressed = true;
ui->listWidget->setCurrentItem(item);
buttonPressed = false;
});
Then you must manipulate two signals from the QListWidget: clicked() and currentRowChanged(). I choose clicked() instead of itemClicked() because it gives almost directly access to the row:
void MainWindow::on_listWidget_clicked(const QModelIndex &index)
{
ui->statusBar->showMessage(tr("Row clicked: %1").arg(index.row()), 1000);
}
void MainWindow::on_listWidget_currentRowChanged(int currentRow)
{
if (buttonPressed) {
ui->statusBar->showMessage(tr("Row clicked: %1").arg(currentRow), 1000);
}
}
what you can simply do is, make your qpushbutton to qlabel and then qpushbutton will become clickable.
You can then override this method :- on_listWidget_itemClicked(QListWidgetItem *item) for double click on qpushbutton.
I tried it & it worked perfectly, its quite simple workaround.

How to close and exit a QDialog shown with exec() after a timeout?

I am trying to close a QDialog using a timeout from a QTimer.
So far, i have tried to do this :
QDialog dlg;
..
..
myTimer.start(60000); // 60 s
connect(&myTimer, SIGNAL(timeout()),
&dlg, SLOT(close())));
dlg.exec();
qWarning() << "---timer expired or key pressed--";
But when timeout is triggered and the close slot executed the eventloop is not exited. Same behavior with reject slot. I know the done slot should have the expected behavior but as it needs an extra argument (int r), it cannot be directly connected to the timeout() signal.
Of course, i can "relay" the timeout signal to provide the missing argument but is there another more straightforward way to do it ?
Thank you.
dlg.exec(); Is a synchronic, He returns the answer accepted or rejected.
void MainWindow::btnClicked() {
Dialog *dialog = new Dialog();
dialog.exec();
qDebug() << "test";
// while dialog not destroyed (rejected, accepted) Print will not happen never.
}
One way you can use QTimer in your Dialog class:
Dialog::dialog(...) {
//constructor
QTimer::singleShot(60000, this, SLOT(close()));
}
or do not use dialog.exec(); use dialog.show(); if you want dialog let it be modality you can use:
void MainWindow::btnClicked() {
Dialog *dialog = new Dialog();
dialog->setModal(true);
dialog->show();
qDebug() << "test"; //The "test" will be printed, and you can use QTimer :))
}
I suggest to give the dialog its own timer (i.e. instantiate a QTimer locally, before excuting the dialog):
QTimer dlg_timer;
dlg_timer.start(60000); // 60 s
connect(&dlg_timer, SIGNAL(timeout()), &dlg, SLOT(close()));
dlg.exec();
dlg_timer.stop();
As the OP fears in their comment, if the timer timeout signal has been connected to some other slot, before connection with dialog close, and in that slot QTimer::disconnect() is called, the dialog close slot will never be called.

Qt Auto-UI Testing stopping because of messagebox. How to simulate enter on messagebox?

My task is to write an automated UI test for a software being developed. It happens so, that there are radiobuttons triggering a messagebox, which stops my automated test until I manually confirm it (pressing ENTER). The issue is that I do not know how I can call that newly summoned messagebox and then have it confirmed by QTest::keyClick(<object>, QtKey::Key_Enter); and have my automated test continue the run.
I am using QWidgets, QApplication, Q_OBJECT and QtTest.
I will provide a code similar to what I am working with:
void testui1(){
Form1* myform = new Form1();
myform->show();
QTest::mouseClick(myform->radioButton01, Qt::LeftButton, Qt::NoModifier, QPoint(), 100);
// Message box pops up and stops the operation until confirmed
QTest::mouseClick(myform->radioButton02, Qt::LeftButton, Qt::NoModifier, QPoint(), 100);
// again
...
}
How exactly can I script to confirm the message box automatically? The message box is only an [OK] type, so I don't need it to return whether I have pressed Yes or No. A QTest::keyClick(<target object>, Qt::Key_Enter) method needs to know to which object it should press enter to. I tried including myform into the object and it did not work. Googling I did not find the answer. I found the following result as not functioning for what I am looking for
QWidgetList allToplevelWidgets = QApplication::topLevelWidgets();
foreach (QWidget *w, allToplevelWidgets) {
if (w->inherits("QMessageBox")) {
QMessageBox *mb = qobject_cast<QMessageBox *>(w);
QTest::keyClick(mb, Qt::Key_Enter);
}
}
The problem is that once you've "clicked" on your radio button, which results in QMessageBox::exec being called, your code stops running until the user clicks on of the buttons.
You can simulate the user clicking a button by starting a timer before you click on the radio button.
In the callback function for the timer you can use QApplication::activeModalWidget() to obtain a pointer to the message box, and then just call close on it.
QTimer::singleShot(0, [=]()
{
QWidget* widget = QApplication::activeModalWidget();
if (widget)
widget->close();
});
If you want to press a specific key on the message box, you can use QCoreApplication::postEvent to post a key event to the message box
QTimer::singleShot(0, [=]()
{
int key = Qt::Key_Enter; // or whatever key you want
QWidget* widget = QApplication::activeModalWidget();
if (widget)
{
QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
QCoreApplication::postEvent(widget, event);
}
});
So in terms of how this ties into the sample code you gave:
void testui1()
{
Form1* myform = new Form1();
myform->show();
QTimer::singleShot(0, [=]()
{
QWidget* widget = QApplication::activeModalWidget();
if (widget)
widget->close();
else
QFAIL("no modal widget");
});
QTest::mouseClick(myform->radioButton01, Qt::LeftButton, Qt::NoModifier, QPoint(), 100);
}
A sample app putting all the above together:
#include <QApplication>
#include <QMainWindow>
#include <QHBoxLayout>
#include <QPushButton>
#include <QTimer>
#include <QMessageBox>
#include <iostream>
int main(int argc, char** argv)
{
QApplication app(argc, argv);
QMainWindow window;
QWidget widget;
window.setCentralWidget(&widget);
QHBoxLayout layout(&widget);
QPushButton btn("go");
layout.addWidget(&btn);
QObject::connect(&btn, &QPushButton::clicked, [&]()
{
QTimer::singleShot(0, [=]()
{
QWidget* widget = QApplication::activeModalWidget();
if (widget)
{
widget->close();
}
else
{
std::cout << "no active modal\n";
}
});
QMessageBox(
QMessageBox::Icon::Warning,
"Are you sure?",
"Are you sure?",
QMessageBox::Yes | QMessageBox::No,
&window).exec();
});
window.show();
return app.exec();
}
Clicking on Go will look like nothing is happening, as the QMessageBox is closed immediately.
To prove that the message box is shown and then closed you can increase the time in the call to QTimer::singleShot to a value such as 1000. Now it will show the message box for 1 second, and then it will be closed by the timer.

Qt - connect menuBar and QWidget

So I have menu bar like this:
this->layout = new QGridLayout;
QMenuBar* menuBar = new QMenuBar();
QMenu *fileMenu = new QMenu("File");
menuBar->addMenu(fileMenu);
fileMenu->addAction("Exit");
this->layout->setMenuBar(menuBar);
And I am wonderig how to connect thie action "Exit" with some slot my QWidget, I tryed something like this:
connect(menuBar,SIGNAL(menuBar->actions),this,SLOT(exitGame()));
But it is not working, can you tell me what I am doing wrong? And yes I have read manual about QMenuBar bud there are no examples of connecting. I have read about some connecting in Qt Designer but I am not using it.
You need to connect the QAction pointer returned from QMenuBar::addAction to the slot...
auto *exit_action = fileMenu->addAction("Exit");
connect(exit_action, &QAction::triggered,
[this](bool checked)
{
exitGame();
});

Qt, QDialog buttons not Respondig

The dialog opens with two buttons, OK and Cancel. None of the buttons responds to user click. I have to press the X on the top right to cancel the dialog.
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (obj == mTabWidget && event->type() == QEvent::MouseButtonDblClick)
{
// query and set tab(s) names
QTabWidget *tab = qobject_cast<QTabWidget *>(obj);
if(tab)
{
QDialog dlg;
QVBoxLayout la(&dlg);
QLineEdit ed;
la.addWidget(&ed);
QDialogButtonBox bb(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
la.addWidget(&bb);
dlg.setLayout(&la);
if(dlg.exec() == QDialog::Accepted)
{
tab->setTabText(0, ed.text());
return true;
}
}
}
// Standard event processing
return QObject::eventFilter(obj, event);
}
Am I missing any connect() line or signals? I tried to read the Qt documentation, but from what I understood, calling QDialogButtonBox::OK gets processed as Accepted.
UPDATE :
New Dialog Function
OK, i have created a new function that takes care of the Dialog box, i am calling it from the event function. it is still not responding, now on the terminal, i see an error that says, : no such slot MainWindow::accept() and another for reject. I know that i have no slots for these two in the .h file. i tried to find how to build the slots but i couldnt, any help would be great. thank you
void MainWindow::initializeBOX()
{
QDialog dlg;
QVBoxLayout la(&dlg);
QLineEdit ed;
la.addWidget(&ed);
//QDialogButtonBox bb(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
//btnbox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
la.addWidget(buttonBox);
dlg.setLayout(&la);
if(dlg.exec() == QDialog::Accepted)
{
mTabWidget->setTabText(0, ed.text());
}
}
Rather than launching the dialog from event filer you should trap QWidget::mouseDoubleClickEvent by overloading that virtual function in your code. And as long as it is a callback already you can do more stuff immediately from there including the dialog. Or maybe send the signal to slot that does the dialog (a bit cleaner). I would do the signal from mouseDoubleClickEvent event handler and make the parent QWidget::mouseDoubleClickEvent to consume the event to avoid possible complications especially when porting the code to other platform.
Event filters are for non-standard event processing. There is nothing non-standard in your case.