custom Qt event is not delivered (event flow: Widget inside -> QMainWindow) - c++

I have a QMainWindow-derived Widget that contains a QGLWidget-derived Widget. I have a custom Event that will get fired from within the GLWidget's paintGL() method that way (the notification shall be asynchonous, that's why I'd like to post an event):
QCoreApplication::postEvent(parent(),new ColpickEvent(rgb, pickPoint));
The event looks like this (very simple, no capsuling of data):
#define COLORPICK_EVENT QEvent::Type(31337)
class ColpickEvent : public QEvent
{
public:
ColpickEvent(QRgb col, const QPoint& pos)
: QEvent(COLORPICK_EVENT), Color(col), PixelPos(pos) {};
QColor Color;
QPoint PixelPos;
};
The MainWindow overrides QObject::event(QEvent*) like this:
#define EVENT_TYPE(ClassName) { ClassName *e = static_cast<ClassName*>(event)
#define END_EVENT } break
#define ACCEPT_EVENT e->accept(); return true
bool MainWindow::event(QEvent* event)
{
switch(event->type())
{
/*... handle other events ...*/
case COLORPICK_EVENT:
EVENT_TYPE(ColpickEvent);
// do something with the data provided by the event ...
ACCEPT_EVENT;
END_EVENT;
/*... handle other events ...*/
}
return QMainWindow::event(event);
}
What happens is, that my ColpickEvent never seems to get dispatched to MainWindow. Any ideas why this happens (or actually doesn't happen)?

OOPS.
As the top level widget was derived from QMainWindow, it needed to have a centralWidget. Somehow it wasn't possible to set the GLWidget as centralWidget, as I needed a Layout to overlay other widgets on top of it.
So the centralWidget of MainWindow is a simple QWidget "w" with a GridLayout. The GLWidget is added to the Layout, that way its parent was changed to "w".
Because of this, the solution is simply enough:
QCoreApplication::postEvent(parent()->parent(),new ColpickEvent(rgb, pickPoint));
(see the double-parent-call).

Related

Is there an easy way to trigger a hoverenter/hoverleave event on children of a QWidget?

For styling purposes, I'm trying to make all the widgets in a set of widgets adopt a certain background if a pointer enteres any of those widgets;
i.e. if the pointer is insides the containing rectangle of widget A, both A and widget B should change its background. Likewise if the pointer enters B, then A should also adopt the same background. The background should, naturally, fall back to the non-hovering background if the pointer is outside both A and B.
QWidget appears to have most of the plumbing to get this done, but its hidden behind protected.
I really want to avoid inheriting from QWidget if I can avoid it and I really would like to not have to change the stylesheets of the widgets too.
[1] In my case, I have three widgets, not two.
Have an event filter class, and let it hold the affected widgets in a vector of pointers:
#include <QMouseEvent>
class EventFilter : public QObject
{
Q_OBJECT
QVector<QWidget*> widgets;
public:
void append(QWidget * w)
{
w->setAttribute(Qt::WA_Hover);
w->installEventFilter(this);
widgets.append(w);
}
bool eventFilter(QObject *watched, QEvent *event)
{
if(event->type() == QMouseEvent::HoverEnter)
{
for(auto w : widgets)
{
w->setStyleSheet("background-color: red;");
}
}
else if(event->type() == QMouseEvent::HoverLeave)
{
for(auto w : widgets)
{
w->setStyleSheet("background-color: white;");
}
}
return false;
}
};
The append method will set up the widget and push it in the vector.
Have a private filter instance in your form class:
class Form : public QWidget
{
Q_OBJECT
//...
private:
EventFilter filter;
//...
In your form constructor:
ui->setupUi(this);
filter.append(ui->widget1);
filter.append(ui->widget2);
filter.append(ui->widget3);
In the example I used setStyleSheet to set the background color, but you can use whatever strategy you prefer, i.e. resetting the widget palette.

How to recognize QMouseEvent inside child widgets?

EDIT and Some Self Critisicm
I tried both given solutions, which both solved my problem, and thus I thank you both! I marked the transparent solution as accepted because I thought it is was the easiest implementation when I only had one child widget, but I wish to share some insight for other beginners:
I first used QLabel, which apperently has enabled Qt::WA_TransparentForMouseEvents by default and thus obviously worked, but I also wanted the text to be selectable, by using QPlainTextEdit instead. Laughably, this is not possible in any way, because if you try to select the text (by clicking) you will close the window! I ended up keeping the transparancy, and neglecting the select-text-feature.
I'm guessing my following question has been answered somewhere before, but after an hour of searching I now post the question myself. I'm grateful if someone can point me to an already answered question that solves my problem.
Anyhow, I'm creating a popup window, using C++ and Qt. I've created the following PopupDialog class which works well and fine for all its purposes. However, I've removed its frame (including the bar containing the close button and window title) to make it look minimalistic, and now I want it to close if the user presses/releases the mouse button anywhere inside the popup window (dialog).
The below code works, however in such a way that I have to click and release the mouse exactly at the QDialog-window itself. It will not close when i click if the mouse hovers over the child widget(s) inside the QDialog, e.g. a QPlainTextEdit, which is displaying text.
Hence, I'm in need of a solution for QDialog to recognize QMouseEvents inside its child widgets. Please, don't hesitate to ask if something is unclear. I have not included my mainwindow.h/.cpp files, or popupdialog.ui file since I believe it would be a little too much to post here, but the .ui extremely simple: Just the QDialog window holding a QBoxLayout, containing a single widget, a QPlainTextEdit. I may posts these on request if it helps.
// popupdialog.h
#ifndef POPUPDIALOG_H
#define POPUPDIALOG_H
#include <QDialog>
#include <QString>
namespace Ui {class PopupDialog;}
class PopupDialog : public QDialog
{
Q_OBJECT
public:
explicit PopupDialog(QWidget *parent = 0, QString msgTxt="");
~PopupDialog();
private:
Ui::PopupDialog *ui;
QString messageText;
void mouseReleaseEvent(QMouseEvent*);
};
#endif //POPUPDIALOG_H
...
// popupdialog.cpp
#include "popupdialog.h"
#include "ui_popupdialog.h"
PopupDialog::PopupDialog(QWidget *parent, QString msgTxt) :
QDialog(parent),
ui(new Ui::PopupDialog),
messageText(msgTxt)
{
ui->setupUi(this);
setWindowFlags(Qt::Window | Qt::FramelessWindowHint);
setModal(true);
ui->message_text_display->setText(messageText);
// The message_text_display is an instance of the class,
// "PlainTextEdit". Using "QLabel" partly solves my
// problem, but does not allow text selection.
}
PopupDialog::~PopupDialog()
{
delete ui;
}
void PopupDialog::mouseReleaseEvent(QMouseEvent *e)
{
this->close();
}
As you already noticed mouse events are handled from child widgets and propagated to parents if not accepted. You can read more about it here
To close your popup window when the click is done inside a child widget you can do two things. You could try looking into installEventFilter and set it up on each child widget to call close().
Another option would require you to have a kind of centralWidget (like the MainWindow usually has) - just to group all your child widgets. This way you could call setAttribute() on it to set Qt::WA_TransparentForMouseEvents property to simply skip handling mouse events on the widget and all of its children.
groupWidget->setAttribute(Qt::WA_TransparentForMouseEvents);
According to Qt docs:
When enabled, this attribute disables the delivery of mouse events to
the widget and its children. Mouse events are delivered to other
widgets as if the widget and its children were not present in the
widget hierarchy; mouse clicks and other events effectively "pass
through" them. This attribute is disabled by default.
Which basically means the event would be passed up the chain to the first widget which can handle the event. In your case it would be the PopupDialog and the already overriden mouseReleaseEvent slot.
in header file
class PopupDialog : public QDialog
{
Q_OBJECT
public:
explicit PopupDialog(QWidget *parent = 0, QString msgTxt="");
~PopupDialog();
//////////////////////////////////
protected:
bool eventFilter(QObject *obj, QEvent *event);
//////////////////////////////////////
private:
Ui::PopupDialog *ui;
QString messageText;
void mouseReleaseEvent(QMouseEvent*);
};
in cpp
PopupDialog::PopupDialog(QWidget *parent, QString msgTxt) :
QDialog(parent),
ui(new Ui::PopupDialog),
messageText(msgTxt)
{
ui->setupUi(this);
setWindowFlags(Qt::Window | Qt::FramelessWindowHint);
setModal(true);
ui->message_text_display->setText(messageText);
// The message_text_display is an instance of the class,
// "PlainTextEdit". Using "QLabel" partly solves my
// problem, but does not allow text selection.
///////////////////////////////////////
foreach (QObject *child, children())
{
child->installEventFilter(this);
}
///////////////////////////////////////
}
///////////////////////////////////////
bool PopupDialog::eventFilter(QObject *obj, QEvent *event)
{
if(event->type() == QEvent::MouseButtonRelease)
{
this->close();
}
}

What is the best way to propagate event to top most Widget/QMainWindow in QT

I have a layered kind of architecture, in which one Widget is inside another.
Top most object is QMainWindow, and there can be any numbers of internal widgets (one inside another).
example:- mainwindow is parent of widget1, widget1 is parent of widget2..so on..
Now I need to handle the close event of the latest child in the top most window.
I want to know the best way of achieving it in QT 5.7.
You may want to subclass the QMainWindow and override the childEvent function:
http://doc.qt.io/qt-5/qobject.html#childEvent
Would allow you to do something like:
class TarunsMainWindow : QMainWindow {
protected:
void childEvent(QChildEvent event) override {
if (event->type() == QEvent::ChildRemoved) {
// do something special
}
QMainWindow::childEvent(event);
}
}

mainwindow.ui and dialog.ui to set/read communication date

I have two windows: mainwindow.ui and dialog.ui. In the mainwindow.cpp I initialise some objects on the heap of another class for Ethernet-communication and use functions of these objects to read date from the bus and show the values in the mainwindow.ui. In dialog.ui I would like to set the values on the bus, but the problem is to access the communication-functions and objects in mainwindow.cpp.
I wanted to define dialog.cpp as friend class, but I make something wrong. Here is some code:
mainwindow.h
class MainWindow : public QMainWindow
{protected: DateReg *myPosReg;...}
mainwindow.cpp
...
myPosReg = new DateReg(DateValue->AddReg());
...
myPosReg->GetValue(a,b,c);
myPosReg->setValue(a,b,c);
...
Can I somehow access setValue() function in dialog.cpp? Should I allways use dialog.cpp for the dialog.ui or there is some possibility work with dialog.ui-date in mainwindow.cpp? To set the values in dialog.ui I use QDoubleSpinBox.
In the mainwindow.ui I call dialog.ui.
Excuse me for my English.
As far as I understand, you have two classes: MainWindow and Dialog. As MainWindow::myPosReg is protected, you can only access it in Dialog, if Dialog derives from MainWindow or if it is a friend.
The following design should work:
class MainWindow : public QMainWindow
{
[...]
protected:
DateReg *myPosReg;
friend class Dialog;
};
class Dialog
{
[...]
MainWindow* myMainWindow;
void someFunction()
{
myMainWindow->myPosReg->setValue([...]);
}
};
As an alternative, I would suggest a more Qt-ish way by using signals and slots. When the spin-boxes in Dialog change, emit a signal to which MainWindow listens.
We start with the dialog:
class Dialog : public QDialog
{
[...]
Dialog()
{
[...init ui...]
connect(ui->spinBoxA, SIGNAL(valueChanged(int)),
this, SLOT(onSpinBoxValueChanged()));
connect(ui->spinBoxB, SIGNAL(valueChanged(int)),
this, SLOT(onSpinBoxValueChanged()));
connect(ui->spinBoxC, SIGNAL(valueChanged(int)),
this, SLOT(onSpinBoxValueChanged()));
}
signals:
void valuesChanged(int a, int b, int c);
private slots:
void onSpinBoxValueChanged()
{
emit valuesChanged(ui->spinBoxA->value(), ui->spinBoxB->value(),
ui->spinBoxC->value());
}
};
So the dialog has an internal slot onSpinBoxValueChanged(), which is called when
one of the spinboxes changes its value. The sole purpose of this slot is to emit
another signal valuesChanged to which we'll connect the main window (i.e. the
dialog's internal slot combines three values into one signal).
The main window could be the following:
class MainWindow : public QMainWindow
{
[...]
DateReg *myPosReg;
public slots:
void setValues(int a, int b, int c)
{
myPosReg->setValues(a, b, c);
}
};
And the code, which connects both
int main()
{
[...]
MainWindow* mainWindow = new MainWindow;
Dialog* dialog = new Dialog;
connect(dialog, SIGNAL(valuesChanged(int,int,int)),
mainWindow, SLOT(setValues(int,int,int)));
}
When the user changes the value in a spinbox, the following chain-reaction happens:
QSpinBox::valueChanged(int) -> Dialog::onSpinBoxValueChanged()
-> Dialog::valuesChanged() -> MainWindow::setValues() -> DateReg::setValues()
The advantage is that you decouple MainWindow and Dialog: Dialog just signals that the values have changed but does not care what happens with them and MainWindow changes its myPosReg but does not care, where the values are coming from.

Propagate events to both QGraphicsView and QGraphicsScene

I have a Qt app with a QGraphicsView and QGraphicsScene. I subclassed them both and want to handle mouseevents in both. I can do this for both classes separately, using
virtual void mousePressEvent(QMouseEvent* event);
But when I handle the events in the GraphicsView, I don't receive them anymore in the GraphicsScene. How do I fix this, i.e. pass the event from the grapchicsview to the graphicsscene?
Just forward mousePressEvent to your GraphicsView's parent, this will then call scene's mousePressEvent :
void MyGraphicsView::mousePressEvent(QMouseEvent * e)
{
// forward to scene (via default view behaviour)
QGraphicsView::mousePressEvent(e) ;
// ...
}
BTW, QGraphicsScene::mousePressEvent has a different kind (class) of event : QGraphicsSceneMouseEvent
Call the base implementation at the end of your overriden function, eg:
void MyView::mousePressEvent(QMouseEvent* event)
{
// do something
QGraphicsView::mousePressEvent(event);
}