it's my first question here about Qt (usually internet and doc helps me alot)
I've installed a eventfilter on my app (in main.cpp), and I want this event filter to check the key pressed then redistribute it to lower functions (like moving an item on a QGraphicsScene),
This is working BUT, 5 times..
The qDebug() in filter shows me that the key is pressed 5 times when it was just one.
Event filter is so fast it catches it 5 times.
Can't find a way to debounce that.
Here's the interesting parts of code:
main.cpp
#include "mainwindow.h"
#include "eventfilter.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
xEventFilter *filter = new xEventFilter(&a);
a.installEventFilter(filter);
xMainWindow w;
w.show();
return a.exec();
}
eventfilter.h
#ifndef EVENTFILTER_H
#define EVENTFILTER_H
#include <QObject>
class xEventFilter : public QObject
{
Q_OBJECT
public:
explicit xEventFilter(QObject *parent = nullptr);
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
};
#endif // EVENTFILTER_H
eventfilter.cpp
#include "eventfilter.h"
#include "editor.h"
#include <QKeyEvent>
#include <QDebug>
extern xEditor *editor;
xEventFilter::xEventFilter(QObject *parent) :
QObject(parent)
{ }
bool xEventFilter::eventFilter(QObject *obj, QEvent *event)
{
qDebug() << "Enter filter";
if (event->type() != QEvent::KeyPress)
return QObject::eventFilter(obj, event);
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
qDebug() << keyEvent->text();
switch(keyEvent->key()) {
case Qt::Key_Z: { editor->selection->moveSelection(keyEvent); }
case Qt::Key_S: { editor->selection->moveSelection(keyEvent); }
case Qt::Key_Q: { editor->selection->moveSelection(keyEvent); }
case Qt::Key_D: { editor->selection->moveSelection(keyEvent); }
case Qt::Key_Space: { }
}
return false;
}
Looking up to your answers ;)
The main issue is that even if you handle the event, you return false. According to the documentation, you should return true if you want the event to stop being handled.
In your reimplementation of this function, if you want to filter the
event out, i.e. stop it being handled further, return true; otherwise
return false.
Also, you're missing break in cases of your switch statement.
Related
I am using Qt Creator 4.5.2 (Qt 5.9.5, GCC 7.3.0 64-bit) and running on Ubuntu 18.04.
I just customized the 'QTimeEdit' widget and it worked. Now, I need to intercept the event(s) for the 'Up' and 'Down' arrow (the arrow to change the current time). But, I don't know which event it is.
I already installed the 'evenFilter' and could catch all the events. I printed out all of them. But, I did not see an event trigger when I clicked the 'Up' or 'Down' arrow (The time did get changed). I assumed it was 'keyPressEvent' but apparently it was not since it did not trigger this method when I clicked the 'Up' or 'Down' arrow.
Any idea what events for the 'Up' or 'Down' arrow in the 'QTimeEdit' widget?
void MyTimeEdit::keyPressEvent(QKeyEvent *e)
{
qInfo() << "Key Press";
QTimeEdit::keyPressEvent(e);
}
bool MyTimeEdit::eventFilter(QObject *watched, QEvent *event)
{
if(watched == lineEdit())
qInfo() << "Event Type: " << event->type();
return QTimeEdit::eventFilter(watched, event);
}
If you click with the mouse then the event cannot be caused by a keyboard key.
In this case if you want to detect when you click on the "Up" or "Down" arrow of the QTimeEdit then you must use the mousePressEvent method:
#include <QApplication>
#include <QMouseEvent>
#include <QStyleOptionSpinBox>
#include <QTimeEdit>
#include <QDebug>
class TimeEdit: public QTimeEdit{
public:
using QTimeEdit::QTimeEdit;
protected:
void mousePressEvent(QMouseEvent *event){
QTimeEdit::mousePressEvent(event);
QStyleOptionSpinBox opt;
initStyleOption(&opt);
QStyle::SubControls control = style()->hitTestComplexControl(
QStyle::CC_SpinBox, &opt, event->pos(), this);
if(control == QStyle::SC_SpinBoxUp)
qDebug() << "up";
else if (control == QStyle::SC_SpinBoxDown) {
qDebug() << "down";
}
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
TimeEdit w;
w.show();
return a.exec();
}
But keep in mind that not only the clicks do change the value of what the QTimeEdit shows but also the keyboard, the wheel, etc., so if you want to cover all those cases it is better to track override the stepBy() method
#include <QApplication>
#include <QTimeEdit>
#include <QDebug>
class TimeEdit: public QTimeEdit{
public:
using QTimeEdit::QTimeEdit;
void stepBy(int steps){
qDebug() << steps;
QTimeEdit::stepBy(steps);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
TimeEdit w;
w.show();
return a.exec();
}
I am trying to learn how to make software with GUIs using QtCreator. I've done some programming before, but never anything this involved. So far, I have made a window with 5 items: 2 labels, 1 button, 1 lineEdit, and 1 listWidget.
My goal is to be able to enter text in the lineEdit and make it appear in the listWidget. If you click on the button with the mouse, this works fine.
I would also like to be able to use the enter/return key on a keyboard to activate the button. This is the part I need help with.
I created a new class for handling key events called KeyboardFilter.
I have installed the event filter on the main window object. The eventFilter function should receive any event, check if it's a key event, then check if it's the enter button. If it is, then I want to activate the button.
I can't tell if any of the stuff I've written for the eventFilter is actually working.
// == keyboardfilter.h =======================================================
#ifndef KEYBOARDFILTER_H
#define KEYBOARDFILTER_H
#include <QApplication>
#include <QLineEdit>
#include <QKeyEvent>
class KeyboardFilter : public QObject
{
public:
KeyboardFilter( QObject *parent = nullptr ) : QObject( parent ) {}
//protected:
public:
bool eventFilter( QObject *target, QEvent *event );
};
#endif // KEYBOARDFILTER_H
// == mainwindow.h ===========================================================
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui;
// handles clicking the enter button with the mouse
//private slots:
public slots:
void EnterPressed();
// supposed to handle key presses. doesn't actually work
//protected:
public:
void KeyPressEvent(QKeyEvent *event);
void KeyReleaseEvent(QKeyEvent *event);
bool EventFilter(QEvent *event);
};
#endif // MAINWINDOW_H
// == keyboardfilter.cpp =====================================================
#include "keyboardfilter.h"
bool KeyboardFilter::eventFilter(QObject *target, QEvent *event)
{
if(event->type() == QEvent::KeyPress)
{
QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
if(keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter){
return true;
}
}
if(event->type() == QEvent::KeyRelease){
QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
if(keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter){
return true;
}
}
return false;
}
// == mainwindow.cpp =========================================================
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "QDebug"
#include <QKeyEvent>
#include <iostream>
QString stack[10];
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
stack[1] = "stack";
ui->Display->addItem(stack[1]);
connect(ui->Enter, SIGNAL(released()), this, SLOT(EnterPressed()));
}
MainWindow::~MainWindow()
{
delete ui;
}
//qDebug() << "Debug Message";
//QDebug("text");
void MainWindow::EnterPressed(){
//ui->Input->setText(QString("text"));
ui->Display->clear();
QString input = ui->Input->text();
ui->Input->clear();
ui->Display->addItem(input);
}
// -- keyboardfilter.cpp
#include "keyboardfilter.h"
bool KeyboardFilter::eventFilter(QObject *target, QEvent *event)
{
if(event->type() == QEvent::KeyPress)
{
QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
if(keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter){
return true;
}
}
if(event->type() == QEvent::KeyRelease){
QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
if(keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter){
return true;
}
}
return false;
}
// == main.cpp ===============================================================
#include "mainwindow.h"
#include "keyboardfilter.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
// KeyboardFilter filter;
// a.installEventFilter(&filter);
KeyboardFilter* key = new KeyboardFilter();
w.installEventFilter(key);
if(key){
w.EnterPressed();
}
return a.exec();
}
When I run this code, the window pops up and I can enter text into the lineEdit. If I click the button with the mouse, the text gets moved to the listWidget, as desired. If I enter text and then hit "Enter", nothing happens.
I have tried hitting tab to give focus to the lineEdit, listWidget, and button before hitting enter, but hasn't helped though.
Maybe Qt docs for installEventFilter isn't absolutely clear, you need to install event filter to the object which events you want to filter. In your case it is QLineEdit(Input?) and not the MainWindow. Also you don't need actually extra class for event filter. you could override eventFilte of your MainWindow and install it on QLineEdit. Just be sure to return false if you don't want to stop processing event.
Here is how that could look:
main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QLineEdit>
#include <QKeyEvent>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow() override;
private:
Ui::MainWindow *ui;
public slots:
void EnterPressed();
bool eventFilter(QObject *target, QEvent *event) override;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
#include <QKeyEvent>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
//... your code here
this->ui->Input->installEventFilter(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::EnterPressed(){
//.... your code here
}
bool MainWindow::eventFilter(QObject *target, QEvent *event)
{
if(target != this->ui->Input)return false;
if(event->type() == QEvent::KeyPress)
{
QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
if(keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter){
qDebug() << "eventFilter Enter pressed";
this->EnterPressed(); // call your slot
return true;
}
}
if(event->type() == QEvent::KeyRelease){
QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
if(keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter){
qDebug() << "eventFilter Enter pressed released";
return true;
}
}
return false;
}
A simple way to catch Enter/Return keys globally is using QShortcut and, then, connect the trigger event to button click.
QWidget *widget = new QWidget(this); //The main window central widget
QLineEdit *edit = new QLineEdit(this); //Your edit
QPushButton *button = new QPushButton("Accept", this); //Your button
QLabel *label = new QLabel(this); //Your label
QHBoxLayout *layout = new QHBoxLayout(widget); //Arrange items horizontally
layout->addWidget(edit);
layout->addWidget(button);
layout->addWidget(label);
connect(button, &QPushButton::clicked, this, [=](bool checked){ //The button clicked event
Q_UNUSED(checked)
label->setText(edit->text());
edit->clear();
});
QShortcut *shortcut_return = new QShortcut(Qt::Key_Return, this); //The return shortcut event
connect(shortcut_return, &QShortcut::activated, button, &QPushButton::click);
QShortcut *shortcut_enter = new QShortcut(Qt::Key_Enter, this); //The enter shortcut event
connect(shortcut_enter, &QShortcut::activated, button, &QPushButton::click);
button->setDefault(true); //Set the button as default
setCentralWidget(widget);
I searched for the answers online, but I did not really find one that solved my problem. My question is like: I have a QComboBox, let's say I added three items to this:
ui->comboBox->addItem("First");
ui->comboBox->addItem("Second");
ui->comboBox->addItem("Third");
Then if I press the S on the keyboard, the item will change to Second, if I press T, so item will just change to Third. How can I disable this?
A possible solution is to implement an eventfilter that prevents the letters from being used in the QComboBox:
#include <QApplication>
#include <QComboBox>
#include <QKeyEvent>
class Helper: public QObject{
QComboBox *m_combo;
public:
using QObject::QObject;
void setComboBox(QComboBox *combo){
m_combo = combo;
m_combo->installEventFilter(this);
}
bool eventFilter(QObject *watched, QEvent *event){
if(m_combo){
if(m_combo == watched && event->type() == QEvent::KeyPress){
QKeyEvent *ke = static_cast<QKeyEvent *>(event);
if(!ke->text().isEmpty())
return true;
}
}
return QObject::eventFilter(watched, event);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QComboBox w;
w.addItems({"First", "Second","Third"});
Helper helper;
helper.setComboBox(&w);
w.show();
return a.exec();
}
I was thinking a little about title cause I had a problem how to name it. I am programming in Qt and C++ and I can use clearly basic structure. Now I want to do something more efective but I don't know what can be the best solution. So I have an image in main window. How can I solve that:
If I move cursor of my mouse for the specific area of the image then program should drawn yellow frame around this area. If I move mouse to other section of this image then this frame should disappear. In pseudo-code:
if(mouse_X >= 100 && mouse_X <=150 &&
mouse_Y >= 250 && mouse_Y <= 300)
drawFrame();
Any suggestion?
There are a lot of ways of solving the issue.
One of them could be to use an event filter as #nwp stated in your question. But it is also necessary to track the mouse movement in your image.
I don't know how you are loading the image, so the next code is just an example supposing that you are loading the image in a QLabel. You must to adapt the idea to your own project.
main.cpp
#include "mywindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MyWindow myWindow;
myWindow.show();
return app.exec();
}
mywindow.h
#ifndef _MAINWINDOW_H
#define _MAINWINDOW_H
#include <QMainWindow>
#include <QLabel>
#include <QDebug>
class MyWindow: public QWidget {
Q_OBJECT
public:
MyWindow();
~MyWindow();
bool eventFilter(QObject *object, QEvent *event);
private:
QLabel *imageLabel;
};
#endif
mywindow.cpp
#include "mywindow.h"
#include <QPixmap>
#include <QEvent>
#include <QMouseEvent>
MyWindow::MyWindow()
{
imageLabel = new QLabel(this);
imageLabel->setPixmap(QPixmap(":icon"));
imageLabel->resize(imageLabel->pixmap()->size());
qDebug() << imageLabel->pixmap()->size();
imageLabel->setMouseTracking(true);
imageLabel->installEventFilter(this);
}
MyWindow::~MyWindow()
{
}
bool MyWindow::eventFilter(QObject *object, QEvent *event)
{
if (object == imageLabel && event->type() == QEvent::MouseMove) {
/*
* We know the image is 60x60 px.
*/
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
if(mouseEvent->x() >= 0 && mouseEvent->x() <=30 &&
mouseEvent->y() >= 0 && mouseEvent->y() <= 30)
{
qDebug() << "drawFrame()";
} else {
qDebug() << "!drawFrame()";
}
return false;
}
return false;
}
My example:
main.cpp:
QApplication a(argc, argv);
MainWindow w;
w.start();
return a.exec();
w.start():
if (cf.exec()){
this->show();
} else {
qDebug()<<"Need exit";
//here should be exit
}
At comment place, I tried to do: qApp->exit() and qApp->quit() and this->close() (but 'this' not shown and of cource close() is not working). How can I finish app from any place of code?
Whole code:
main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.start();
return a.exec();
}
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "CadrForm.h"
#include "TeacherForm.h"
#include "DepartmentForm.h"
#include "CategoriesForm.h"
#include "PostForm.h"
#include "RanksAndDegreesForm.h"
#include "TeachersRankAndDegreeForm.h"
#include "ConnectionForm.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
void start();
~MainWindow();
signals:
void widgetChanged(QWidget*);
private slots:
void on_actionCadr_triggered();
void resetMainLayout(QWidget* w);
void on_actionTeacher_triggered();
void on_actionDepartment_triggered();
void on_actionPost_triggered();
void on_actionCategories_triggered();
void on_actionRanksAndDegrees_triggered();
void on_actionTeachersRD_triggered();
private:
ConnectionForm cf;
CadrForm *cadrForm;
TeacherForm *teacherForm;
DepartmentForm *departmentForm;
CategoriesForm *categoriesForm;
PostForm *postForm;
RanksAndDegreesForm *ranksAndDegreesForm;
TeachersRankAndDegreeForm *teachersRankAndDegreeForm;
QWidget *current;
Ui::MainWindow *ui;
void addWidgetToMainLayout(QWidget *w);
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
connect(this, SIGNAL(widgetChanged(QWidget*)), this, SLOT(resetMainLayout(QWidget*)));
ui->setupUi(this);
cadrForm = new CadrForm(this);
teacherForm = new TeacherForm(this);
departmentForm = new DepartmentForm(this);
categoriesForm = new CategoriesForm(this);
postForm = new PostForm(this);
ranksAndDegreesForm = new RanksAndDegreesForm(this);
teachersRankAndDegreeForm = new TeachersRankAndDegreeForm(this);
addWidgetToMainLayout(cadrForm);
addWidgetToMainLayout(teacherForm);
addWidgetToMainLayout(departmentForm);
addWidgetToMainLayout(categoriesForm);
addWidgetToMainLayout(postForm);
addWidgetToMainLayout(ranksAndDegreesForm);
addWidgetToMainLayout(teachersRankAndDegreeForm);
}
void MainWindow::start()
{
if (cf.exec()){
this->show();
} else {
qDebug()<<"Need exit";
qApp->quit();
qDebug()<<"still working";
}
}
void MainWindow::addWidgetToMainLayout(QWidget *w)
{
ui->mainLayout->insertWidget(0, w);
ui->mainLayout->itemAt(0)->widget()->hide();
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::resetMainLayout(QWidget *w)
{
int index;
if (current)
{
index = ui->mainLayout->indexOf(current);
ui->mainLayout->itemAt(index)->widget()->hide();
}
index = ui->mainLayout->indexOf(w);
if (index != -1) ui->mainLayout->itemAt(index)->widget()->show();
else {
addWidgetToMainLayout(w);
resetMainLayout(w);
}
current = w;
setWindowTitle(current->windowTitle());
}
void MainWindow::on_actionCadr_triggered()
{
emit widgetChanged(cadrForm);
}
void MainWindow::on_actionTeacher_triggered()
{
emit widgetChanged(teacherForm);
}
void MainWindow::on_actionDepartment_triggered()
{
emit widgetChanged(departmentForm);
}
void MainWindow::on_actionPost_triggered()
{
emit widgetChanged(postForm);
}
void MainWindow::on_actionCategories_triggered()
{
emit widgetChanged(categoriesForm);
}
void MainWindow::on_actionRanksAndDegrees_triggered()
{
emit widgetChanged(ranksAndDegreesForm);
}
void MainWindow::on_actionTeachersRD_triggered()
{
emit widgetChanged(teachersRankAndDegreeForm);
}
And ConnectionForm - it's just a QDialog with some GUI and without any additional code.
It looks like the problem is you're not allowed to call QApplication::quit() or QApplication::exit() until the QApplication event loop has actually started. The event loop gets started by QApplication::exec(), so you're calling quit()/exit() too soon for it to have an effect.
You can fix this either by moving the quit()/exit() call so that it's in the event loop (e.g. by moving your MainWindow::start() code to the QMainWindow::showEvent() slot), or by changing your MainWindow::start() to return a value, inspect that value in main, and exit (without calling QApplication::exec()`) if it's a value that indicates your process should exit early.
Since you start the dialog event loop "early" - which is blocking -, you do not get to the event loop of the application.
If this is an intentional design, you better call exit(3) and remove the application event loop.
If you want to have the application event loop running, then you will need to make sure that it runs before you get to the point of your dialog execution.
The quick fix would be to start a single shot QTimer right just before the application event loop is started and that would trigger your "start" method call.
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.start();
QTimer::singleShot(200, w, SLOT(start()));
return a.exec();
}
and change the start to a slot, respectively.
In the long run, you may wish to consider a button and so on for bringing your dialog up, however.
The idiomatic way of queuing a method call until the event loop gets a chance to run is:
QMetaObject::invokeMethod(&w, "start", Qt::QueuedConnection);
Thus, your main would become:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
QMetaObject::invokeMethod(&w, "start", Qt::QueuedConnection);
return a.exec();
}