How to send key to parent mainWindow from dialog in QT C++ - c++

I need a virtual keyboard for an embedded linux application (not QML). I couldn't find a better way, so now I am trying to create one. I want a dialog full of buttons that sends keys to parent mainWindow. It runs without errors, but happens nothing in lineEdit.
keyboard.cpp
Keyboard::Keyboard(QWidget *parent) :
QDialog(parent),
ui(new Ui::Keyboard)
{
ui->setupUi(this);
mainWindow = this->parent();
}
void Keyboard::on_btnA_clicked()
{
qDebug() << "Test1";
QKeyEvent event(QEvent::KeyPress, Qt::Key_A, Qt::NoModifier);
qDebug() << "Test2";
QApplication::sendEvent(mainWindow, &event);
qDebug() << "Test3";
}
And in mainWindow.cpp to open keyboard dialog:
keyboard->show();
ui->lineEdit->setFocus();
What is the problem? Thanks in advance.

Several things:
Sending the event to mainWindow requires mainWindow to handle passing the event to the QLineEdit object, without seeing the rest of the code I can't say if this is being done or not; the alternative is sending directly to the QLineEdit like this:
QApplication::sendEvent(lineEdit, &event);
The QKeyEvent constructor also requires a fourth parameter - the string to send, in the example case an "a".
QKeyEvent event(QEvent::KeyPress, Qt::Key_A, Qt::NoModifier);
should be
QKeyEvent event(QEvent::KeyPress, Qt::Key_A, Qt::NoModifier, "a");
to send an "a".
Depending on the exact implementation you may also need to send a QEvent::KeyRelease after the QEvent::KeyPress, i.e.
QKeyEvent event1(QEvent::KeyPress, Qt::Key_A, Qt::NoModifier, "b");
QKeyEvent event2(QEvent::KeyRelease, Qt::Key_A, Qt::NoModifier);
QApplication::sendEvent(edit, &event1);
QApplication::sendEvent(edit, &event2);
As (2) indicates, the key enumeration (i.e. Qt::Key_A) does not send an "a" as you would expect, the string that is sent is instead determined by the fourth parameter in the QKeyEvent constructor, i.e.
QKeyEvent event(QEvent::KeyPress, Qt::Key_A, Qt::NoModifier, "a");
QApplication::sendEvent(lineEdit, &event);
Is equivalent to
QKeyEvent event(QEvent::KeyPress, Qt::Key_B, Qt::NoModifier, "a");
QApplication::sendEvent(lineEdit, &event);
Using QKeyEvent in this manner will probably lead so some unpleasantness in handling backspaces and deletes. It is probably more elegant to simply append the desired character to the QLineEdit text,
lineEdit->setText(lineEdit->text().append("a"));
and use QLineEdit::backspace() and QLineEdit::delete() to handle backspace and delete keys.
Example
#include <QtWidgets/QApplication>
#include <qwidget.h>
#include <qmainwindow.h>
#include <qlineedit.h>
#include <qboxlayout.h>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QMainWindow* main = new QMainWindow;
QWidget* central = new QWidget();
QBoxLayout* layout = new QBoxLayout(QBoxLayout::LeftToRight);
central->setLayout(layout);
QLineEdit* edit = new QLineEdit(central);
edit->setAlignment(Qt::AlignCenter);
layout->addWidget(edit);
edit->setText("sometext");
edit->backspace();
edit->setText(edit->text().append("a"));
main->setCentralWidget(central);
main->resize(600, 400);
main->show();
return a.exec();
}

Related

QT QcoreApplication postEvent() behaviour

I have written this simple QT mainwindow, only if I pass QString argument to the QKeyEvent, it prints the key, I expect the key to be printed even without QString argument?
section 1 in below code does not seem to work (I don't get the key printed in the QLineEdit field; while section 2 works and "1" is printed! is this normal behavior? what happens to the event when its posted in first section of the code?
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
this->ui->lineEdit->setFocus();
Qt::Key key = Qt::Key_1;
// 1
QKeyEvent *event = new QKeyEvent (QEvent::KeyPress, key ,Qt::NoModifier);
QCoreApplication::postEvent(QWidget::focusWidget(), event); // Does not work! No key is set in the widget
//
//2
QKeyEvent *event2 = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier, QKeySequence(key).toString());
QCoreApplication::postEvent(QWidget::focusWidget(), event2); // this one works!
}
Not all key-events have a text representation (delete, curser movement, shortcuts, ...). For those who have one, the QKeyEvent class stores it in its text. You have to provide that text, otherwise its a "textless" event.
QLineEdit will just add the text, and not deduce it from the event type (as can be seen here)

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: Have parent decide whether or not a child accepts event

I am having an issue trying to get a parent object to filter child events.
In the following example, I set up an event filter on a spin box. The event filter detects mouse press events on the spin box. I would then like the parent object to accept or ignore that event, based on some criteria.
The problem is that it seems to accept the mouse press event and then ignore the mouse release event. This is an issue with mouse wheel events.
How can I have my parent accept/ignore the event?
In the real case, the message has to be passed through more layers, but the behavior is the same. If you click the up arrow on the spin box, the message will popup and then the numbers will start spinning.
Qt version: 5.6.1
#include "mainwindow.h"
#include <QEvent>
#include <QSpinBox>
#include <QHBoxLayout>
#include <QMessageBox>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
QSpinBox* spinner = new QSpinBox;
QHBoxLayout* layout = new QHBoxLayout;
QWidget* widget = new QWidget;
layout->addWidget(spinner);
spinner->installEventFilter(this);
connect(this, SIGNAL(mouse_pressed(QEvent*)),
this, SLOT(handle_event(QEvent*)));
widget->setLayout(layout);
setCentralWidget(widget);
}
MainWindow::~MainWindow()
{
}
bool MainWindow::eventFilter(QObject *watched, QEvent *event)
{
if (event->type() == QEvent::MouseButtonPress
event->type() == QEvent::Wheel)
{
emit mouse_pressed(event);
}
return QMainWindow::eventFilter(watched, event);
}
void MainWindow::handle_event(QEvent* event)
{
event->ignore();
QMessageBox(QMessageBox::Warning, "warning", "ignoring event").exec();
}
Edit 1: I have found a way to partially stop the event cascade. In MainWindow::handle_event(...), rather than calling 'event->ignore()', I call 'event->setAccepted(false)', then check for for 'event->isAccepted()' in the eventFilter. If it is not accepted, I ignore the event.
This solution has worked well for QLineEdit, but it is still not working as expected with QSpinBox and QPushbutton. For QSpinBox, a wheel event still changes the value and clicking the spin buttons result in sustained spinning (no mouse release being detected). For QPushButton, the event is ignored but the button stays depressed.
Edit 2: Returning false after ignoring the event blocks the cascade. Thanks #G.M. for the hint! I will post an answer.
The way to get the parent to decide whether or not a child should handle an event was to call 'event->setAccepted(false)', the check for the in the eventFilter function. If it is false, ignore the event and return true from the function.
Return true from the eventFilter function was counterintuitive to me, but its right there in the documentation. Event filters are much less invasive than subclassing, so I was glad to come to a solution.
#include "mainwindow.h"
#include <QEvent>
#include <QSpinBox>
#include <QHBoxLayout>
#include <QMessageBox>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
QSpinBox* spinner = new QSpinBox;
QHBoxLayout* layout = new QHBoxLayout;
QWidget* widget = new QWidget;
layout->addWidget(spinner);
spinner->installEventFilter(this);
connect(this, SIGNAL(mouse_pressed(QEvent*)),
this, SLOT(handle_event(QEvent*)));
widget->setLayout(layout);
setCentralWidget(widget);
}
MainWindow::~MainWindow()
{
}
bool MainWindow::eventFilter(QObject *watched, QEvent *event)
{
if (event->type() == QEvent::MouseButtonPress
event->type() == QEvent::Wheel)
{
emit mouse_pressed(event);
if (!event->isAccepted())
{
event->ignore();
return true;
}
}
return QMainWindow::eventFilter(watched, event);
}
void MainWindow::handle_event(QEvent* event)
{
event->setAccepted(false);
QMessageBox(QMessageBox::Warning, "warning", "ignoring event").exec();
}

How to synthesize key press events?

I am able to get key value's from HAL through a call back function in Qt. Created event for that key by
QKeyEvent *event = new QKeyEvent (QEvent::KeyPress,
inputKey.keyValue,
Qt::NoModifier);
Note: inputKey.keyValue Key value received from HAL Layer.
Now I need to Register This key event in Qt, So that if any key press happened in IR Remote then in respective form, keyPressEvent(e) or event(e) will get invoke. and based on the key press, specific action will get execute.
Note: More than one form is there, where key press event will trigger And more than one keys are there "Page_Up, Page_Down, Ok Key etc....."
tried to invoke Postevent() and connect(.......) but nothing helped me. KeyPressEvent() is not getting executed.
E.g. like this:
// receiver is a pointer to QObject
QCoreApplication::postEvent (receiver, event);
You can find more info here.
You can reimplement QObject::event() or QWidget::keyPressEvent in your widget to receive key events.
Visit this link or link for more information. See the example code below which consists of two buttons and a label. Clicking pushButton sends 'enter pressed' and pushButton_2 sends 'letter A pressed'. Key events are received in the event() function and label is updated accordingly.
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
connect(ui->pushButton, SIGNAL(clicked()), this, SLOT(sendKeyEvent()));
connect(ui->pushButton_2, SIGNAL(clicked()), this, SLOT(sendKeyEvent()));
}
void MainWindow::sendKeyEvent()
{
QObject* button = QObject::sender();
if (button == ui->pushButton)
{
QKeyEvent *event = new QKeyEvent (QEvent::KeyPress, Qt::Key_Enter, Qt::NoModifier);
QCoreApplication::postEvent (this, event);
}
else if (button == ui->pushButton_2)
{
QKeyEvent *event = new QKeyEvent (QEvent::KeyPress, Qt::Key_A, Qt::NoModifier);
QCoreApplication::postEvent (this, event);
}
}
bool MainWindow::event(QEvent *event)
{
if (event->type() == QEvent::KeyPress)
{
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Enter) {
ui->label->setText("Enter received");
return true;
}
else if (keyEvent->key() == Qt::Key_A)
{
ui->label->setText("A received");
return true;
}
}
return QWidget::event(event);
}
You can create the event on the stack. Then use QCoreApplication::sendEvent to have the event immediately delivered:
QWidget w1, w2;
QKeyEvent event(QEvent::KeyPress, inputKey.keyValue, Qt::NoModifier);
QApplication::sendEvent(&w1, &ev);
QApplication::sendEvent(&w2, &ev);
Each sendEvent will call the widget's event method, that will then invoke the xxxxEvent protected methods as applicable. But don't do it yourself, as you then bypass the application-global event filters and depend on implementation details.
// WRONG!
w1.event(&ev);
// WRONG and won't compile since keyPressEvent is protected
w2.keyPressEvent(&ev);
Or, you can create it on the heap. Qt is managing the event's lifetime as soon as you post it. After you post, the event is not yours anymore and you can't reuse it. You must create multiple events for each post. The simplest way to avoid repetition is to create a local function that produces the events on demand.
QWidget w1, w2;
auto ev = [=]{ return new QKeyEvent(QEvent::KeyPress, inputKey.keyValue, Qt::NoModifier); };
QApplication::postEvent(&w1, ev());
QApplication::postEvent(&w2, ev());
The events will be added to the main thread's event queue. Once the control returns to QApplication::exec, they will be delivered to the widgets one-by-one. The widgets' event method will be called from QApplication::exec, not from postEvent.
The QWidget::event implementation decodes the event types and invokes the protected convenience handlers, such as keyPressEvent, enterEvent, etc. Its implementation follows the following pattern:
bool QWidget::event(QEvent * ev) {
switch (ev->type()) {
case QEvent::KeyPress:
keyPressEvent(static_cast<QKeyEvent*>(ev));
return true;
case QEvent::Enter:
enterEvent(static_cast<QEnterEvent*>(ev));
return true;
...
}
return QObject::event(ev);
}
When implementing handlers for event types that have such xxxxEvent convenience virtual methods, you should reimplement the virtual method, and not the event() itself. Thus your MainWindow should reimplement keyPressEvent:
void MainWindow::keyPressEvent(QKeyEvent * event) {
if (event->key() == Qt::Key_Enter)
ui->label->setText("Enter received");
else if (event->key() == Qt::Key_A)
ui->label->setText("A received");
QMainWindow::keyPressEvent(event);
}
If you wish your key events to be delivered to your window immediately, and that seems to be a reasonable approach, your sendKeyEvent method becomes much simpler:
void MainWindow::sendKeyEvent()
{
if (sender() == ui->pushButton) {
QKeyEvent event(QEvent::KeyPress, Qt::Key_Enter, Qt::NoModifier);
QApplication::sendEvent(this, &event);
}
else if (sender() == ui->pushButton_2) {
QKeyEvent event(QEvent::KeyPress, Qt::Key_A, Qt::NoModifier);
QApplication::sendEvent(this, &event);
}
}
There is a way to simplify things further, though. Recall that the QObject supports dynamic properties. You can thus easily assign the keys as a property of the buttons, and automatically send the event whenever a button with a key property has been pressed:
static const char keyPropKey[] = "key";
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
connect(ui->pushButton, &QPushButton::clicked, this, &MainWindow::sendKeyEvent);
connect(ui->pushButton_2, &QPushButton::clicked, this, &MainWindow::sendKeyEvent);
ui->pushButton.setProperty(keyPropKey, (int)Qt::Key_Enter);
ui->pushButton.setProperty(keyPropKey, (int)Qt::Key_A);
}
void MainWindow::sendKeyEvent()
{
auto key = sender().property(KeyPropKey);
if (key.isValid()) {
QKeyEvent event(QEvent::KeyPress, key.toInt(), Qt::NoModifier);
QApplication::sendEvent(this, &event);
}
}
Finally, as a stylistic nitpick: there's no reason to have the ui member be a pointer. You'll avoid one heap allocation, and plenty of indirections, by using it as a direct member.

Problems with QStatusBar->showMessage()

I'm writing a Qt GUI Application, but there's a strange error i can't figure out;
Here's the whole code:
main.cpp
#include "LevelEditor.h"
int main(int argc, char* argv[])
{
LevelEditor editor(argc, argv);
editor.go();
return 0;
}
LevelEditor.h
#ifndef LEVELEDITOR_H
#define LEVELEDITOR_H
#include <QtGui>
class LevelEditor
{
public:
LevelEditor(int argc, char* argv[]);
~LevelEditor();
void go();
protected:
QApplication* app;
QMainWindow* main_window;
QMenuBar* menu_bar;
QStatusBar* status_bar;
QWidget* central;
QMenu* menu_entry[3];
QFrame* about_frame;
QList<QAction*> file_actions;
QList<QAction*> edit_actions;
QList<QAction*> help_actions;
private:
};
#endif // LEVELEDITOR_H
LevelEditor.cpp
#include "LevelEditor.h"
#include <QStatusBar>
LevelEditor::LevelEditor(int argc, char* argv[])
{
//initialise main objects
app = new QApplication(argc, argv);
main_window = new QMainWindow();
menu_bar = main_window->menuBar();
status_bar = main_window->statusBar();
central = main_window->centralWidget();
about_frame = new QFrame();
//initialise menu entries and actions
menu_entry[0] = new QMenu(); //file
menu_entry[1] = new QMenu(); //edit
menu_entry[2] = new QMenu(); //about
//creating and connecting events to action
menu_entry[0]->setTitle("File");
file_actions.append(new QAction("New", menu_entry[0]));
file_actions.append(new QAction("Open", menu_entry[0]));
file_actions.append(new QAction("Save", menu_entry[0]));
file_actions.append(new QAction("Quit", menu_entry[0]));
QObject::connect(file_actions.back(), SIGNAL(triggered()), app, SLOT(quit()));
QObject::connect(file_actions.back(), SIGNAL(hovered()), status_bar, SLOT(showMessage("Quit this App"));
menu_entry[0]->addActions(file_actions);
menu_bar->addMenu(menu_entry[0]);
//edit menu
menu_entry[1]->setTitle("Edit");
edit_actions.append(new QAction("Cut", menu_entry[1]));
edit_actions.append(new QAction("Copy", menu_entry[1]));
edit_actions.append(new QAction("Paste", menu_entry[1]));
menu_entry[1]->addActions(edit_actions);
menu_bar->addMenu(menu_entry[1]);
//help menu
help_actions.append(new QAction("About", menu_entry[2]));
QObject::connect(help_actions.back(), SIGNAL(triggered()), about_frame, SLOT(show()));
menu_entry[2]->setTitle("Help");
menu_entry[2]->addActions(help_actions);
menu_bar->addMenu(menu_entry[2]);
about_frame->resize(400,300);
}
LevelEditor::~LevelEditor()
{
//dtor
}
void LevelEditor::go()
{
//nothing
main_window->showMaximized();
menu_bar->show();
status_bar->show();
app->exec();
}
This code compiles fine without errors.
Anyway, the debug console gives me a warning
QObject::connect : NO such slot &QStatusBar::showMessage("Quit this App")
The problem seems related to this line:
QObject::connect(file_actions.back(), SIGNAL(hovered()), status_bar, SLOT(showMessage("Quit this App"));
I've searched in "QStatusBar.h" for the showMessage function and it is declared, but can't be called neither with "." nor "->" (even if it's public). Also tried this:
QObject::connect(file_actions.back(), SIGNAL(hovered()), status_bar, SLOT(showMessage("Quit this App", 0));
and this:
QObject::connect(file_actions.back(), SIGNAL(hovered()), status_bar, SLOT(QStatusBar::showMessage("Quit this App"));
But to no avail, it just won't recognise the function.
Am i missing something here?
Thanks in advance.
EDIT: Solved, i was taking the hard way to show a status text instead of using QAction::setStatusTip, my bad.
You can't connect a signal to a slot with different signature. And you are not even using the proper connect syntax. The SLOT part should be:
SLOT(showMessage(const QString &))
It's to tell the meta object system what type(s) of parameters to send to the slot, not what concrete data to send.
In your case, you can't connect a signal with no parameter to a slot that expects one. You can achieve that by connecting the signal to your own slot and then call QStatusBar::showMessage from there.
You could use QSignalMapper to do what you want:
QSignalMapper * mapper = new QSignalMapper(this);
QObject::connect(file_actions.back(), SIGNAL(hovered()), mapper, SLOT(map()));
mapper->setMapping(file_actions.back(), "Quit this app");
connect(mapper, SIGNAL(mapped(const QString &)), statusBar, SLOT(showMessage(const QString &));
Using QSignalMapper allows you, to simply add another "hovered" messages without creating new slots for each. Simply for all other cases just use:
mapper->setMapping(yourAction/Button/Whater, "Your status message");
QObject::connect(yourAction/Button/Whater, SIGNAL(hovered/Other signal()), mapper, SLOT(map()))