How to recognize QMouseEvent inside child widgets? - c++

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();
}
}

Related

Qt mouse events not working in QGraphicsScene

I am using Qt 5.7 (the latest version). I can't get the mouse events to work in QGraphicsScene, but they work in window outside of my scene. I have followed this question.
So I have overwritten QWidget::mouseMoveEvent() in my main widget's subclass like this:
// header:
class MyWidget {
...
void mouseMoveEvent( QMouseEvent * event );
};
// source:
MyWidget::MyWidget() {
setMouseTracking();
}
void MyWidget::mouseMoveEvent( QMouseEvent * event ) {
}
It doesn't work for: mouseMoveEvent, mouseGrabber, mousePressEvent, mouseReleaseEvent, or mouseDoubleClickEvent. But somehow it only works for mousePressEvent.
Could this be a bug in Qt?
SOURCE CODE:
In objectloader.cpp
ObjectLoader::ObjectLoader(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::ObjectLoader)
{
ui->setupUi(this);
scene=new QGraphicsScene(this);
ui->graphicsView->setScene(scene);
ui->graphicsView->setMouseTracking(true);
setMouseTracking(true);
}
Thats were i set mouse tracking twice
In objectloader.h
Then i define that method in objectloader.h
class ObjectLoader : public QMainWindow
{
Q_OBJECT
public:
explicit ObjectLoader(QWidget *parent = 0);
~ObjectLoader();
private slots:
void mouseMoveEvent(QMouseEvent *event);
protected:
private:
};
#endif // OBJECTLOADER_H
And implementation of that method in objectloader.cpp
void ObjectLoader::mouseMoveEvent(QMouseEvent *event){
qDebug()<<"Mouse moved";
}
When a mouse event is generated by Qt it is generally passed initially to the QWidget that was under the mouse pointer when the event was generated. If that QWidget accepts the event then no further processing will take place. If the event isn't accepted then Qt may propogate the event to that QWidget's parent and so on.
In your particular case the mouse move events you are interested in are being sent to the QGraphicsView/QGraphicsScene conponents where they are being accepted and, hence, no further processing takes place. In a case like that you generally need to install an event filter to intercept and process the events of interest.
Mouse move events will occur only when a mouse button is pressed down, unless mouse tracking has been enabled with QWidget::setMouseTracking().
So, I think you should check whether mouseTracking is really enabled or not, by using `bool hasMouseTracking() const'.

Qt adding child widget in resizeEvent

I have a widget W deriving from QFrame with layout set to an instance of QVBoxLayout. I wonder if the following resizeEvent implementation is correct or is it going to cause an infinite loop:
void W::resizeEvent(QResizeEvent *event) {
for (/* some condition based on the new size of this widget */) {
// Infinite loop or not?
qobject_cast<QVBoxLayout *>(layout())->addWidget(new QWidget());
}
}
So far it worked for me, is this by pure luck?
This is okay. W owns a QLayout which owns QWidget. Adding the QWidget to the QLayout does not change the size of W. You see this all the time. For example, if you place a child widget in a parent and the parent is too small, the child widget will be clipped. Stately differently, the size of the parent does not stretch to accommodate the size of the child. I believe your code would be a typical way to hide or show widgets based on the size of the parent (for example, when the window size changes).
Painting and constructing a hierarchy of widgets are two different things. So, adding QWidgets is just fine, but using QPainter directly in resizeEvent not.
Hierarchy of QWidgets
A hierarchy of QWidgets derivatives (QLineEdit, QPushButton, ...) is a high level specification of how the graphical user interface should look like and may be ordered using QLayout items.
Painting
Painting (using QPainter) is the process of actually drawing something on the screen and is purely done in the virtual function QWidget::paintEvent. Every derivative of QWidget should provide an implementation of this empty base function. The default derivatives (QLineEdit, ...) provide an implementation of paintEvent based on their current state (size of the widget, current text for a QLineEdit, ...) and the current QStyle object, which is typically automatically set based on your OS, but may be changed programmatically using QWidget::setStyle or QApplication::setStyle. A repaint can be requested using QWidget::update.
"Should not/need not" vs "may not"
The sentence "No drawing need be (or should be) done inside this handler." is meant for people implementing a custom QWidget (with a new implementation of paintEvent) to make it clear that you should not implement your painting here, but that a paintEvent will be automatically triggered.
"Should not/need not" is some advice, they do not write "may not". So, if you for some reason (ex. real-time applications) want an immediate screen refreshment, you may invoke a repaint immediately using repaint, resulting in paintEvent being called during resizeEvent. As long as all the QPainter operations on a QWidget are inside a paintEvent (as required by the warning in the QPainter documentation), everything is just fine.
Adding widgets to the layout, using addWidget, within the resizeEvent function is not a problem as it does not instantly trigger a drawing.
You can easily verify this by compiling and executing this simple project:
dialog.h:
#pragma once
#include <QDialog>
class Dialog : public QDialog
{
Q_OBJECT
public:
Dialog(QWidget *parent = 0);
~Dialog();
void resizeEvent(QResizeEvent *event);
void paintEvent(QPaintEvent *event);
private:
bool resizing;
};
dialog.cpp:
#include "dialog.h"
#include <QResizeEvent>
#include <QVBoxLayout>
#include <QPushButton>
#include <QDebug>
Dialog::Dialog(QWidget *parent)
: QDialog(parent),
resizing(false)
{
new QVBoxLayout(this);
}
Dialog::~Dialog()
{
}
void Dialog::resizeEvent(QResizeEvent *event)
{
resizing = true;
if ( event->size().width() == event->size().height() )
{
qDebug() << "Adding widget";
// Infinite loop or not?
layout()->addWidget(new QPushButton());
}
resizing = false;
}
void Dialog::paintEvent(QPaintEvent *event)
{
if ( resizing )
{
qDebug() << "Painting while resizing widget";
}
}
main.cpp:
#include "dialog.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Dialog w;
w.show();
return a.exec();
}
When you run the program, resize the dialog to make it be square (width==height), some buttons are inserted ("Adding widget" is printed to the console), but you'll never see "Painting while resizing widget" message. This is most likely because addWidget sets a dirty display flag that is processed later by the framework. It invalidates the display, but does not repaint it right away.
So what you are doing is fine and does not violate the framework requirement ("No drawing need be (or should be) done inside this handler.").
However, if you are not confident (maybe the painting could be operated right away on different OS, or in future Qt versions....you can't be sure), you can also delay the insertion by emitting a signal connected to a slot using Qt::QueuedConnection, this slot would be executed "later" and then do the call to addWidget, guaranteeing that it's done outside the resizeEvent function.

QDockWidgets merging incorrectly

I have a QDockWidget class and a QMainWindow:
// docker.hpp
class Docker : public QDockWidget
{
Q_OBJECT
public:
Docker(QString title, QWidget* parent = 0);
}
// docker.cpp
Docker::Docker(QString title, QWidget* parent): QDockWidget(title, parent)
{
QWidget* widget = new QWidget(this);
widget.setMinimumSize(200, 200);
setWidget(widget);
widget->setStyleSheet("border:5px solid gray;");
setAllowedAreas(Qt::AllDockWidgetAreas);
}
// mainwindow.hpp
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget* parent);
private slots:
void createDockers();
};
// mainwindow.cpp
MainWindow::MainWindow(QWidget* parent): QMainWindow(parent)
{
setDockOptions(dockOptions() |
QMainWindow::AllowTabbedDocks |
QMainWindow::GroupedDragging);
// The following line of code does not change the situation.
// setTabPosition(Qt::RightDockWidgetArea, QTabWidget::East);
// There are some other codes which connect a button to the void createDockers() method
}
void createDockers()
{
Docker* dock = new Docker("Docker", this);
dock->setFloating(true);
dock->show();
}
I am able to create two Dockers with clicks of the button mentioned above.
However, when I drag one QDockWidget onto the other, the border disappears and no tabs show up:
I am expecting the following to happen: (Achieved by spawning several QDockWidgets)
I am also noticing that one of the QDockWidgets did not vanish. Instead, it merged back to the MainWindow. This only happens if they are the "first two" QDockWidgets.
What caused this problem and how to solve it? I am trying to mimic this project.
I guess it's linked to the QMainWindow::GroupedDragging option. I'm pretty sure it should work well without it (I mean for the not showing tab issue). Do you have restrictions on dock position somewhere else? The documentation implies it could create issues: http://doc.qt.io/qt-5/qmainwindow.html#DockOption-enum
For the style issue, you may need to redefine it on tab event, because once tabbed, the widget may inherit the tab style instead of the dock widget style you defined (not certified at all ^^)
Last guess/thing you can try, is to start with the dock tabbed and not floating to see if you have any new bahaviour, it was what I was doing in a previous project and it was working pretty well.
Sorry but no other ideas for the moment.

Keep QDialog open when parent is minimized?

I have a QMainWindow opening a QDialog (not modal). When I minimize the main window, the dialog is closed as well. Can I somehow keep it open? (the other way round as in Keep QMainWindow minimized when QDialogs show() ).
One thing I have tried is to ignore the event, but to my surprise I never see such a state. Actually I only see ActivationChange (0n99) there.
void CMyDialog::changeEvent(QEvent *evt)
{
QEvent::Type t = evt->type();
if (t == QEvent::WindowStateChange)
{
evt->ignore();
hide();
}
else
{
QDialog::changeEvent(evt);
}
}
Question in Qt center dealing with a similar topic:
http://www.qtcentre.org/threads/24765-Intercept-minimize-window-event
Here I create it as member:
QScopedPointer<MyDialog> m_navigator{new MyDialog(this)}; // this here is the main application window
It is displayed by a public slot:
void MyDialog::toogleNavigator()
{
this->setVisible(!this->isVisible());
}
and is a QDialog derived class:
class MyDialog : public QDialog { ...
---- Edit 2 ------
First Wouter has mentioned it , then Alexander. It is like you guys say, if I pass no parent (as in Alexander`s minimal example), the dialog stays open - with parent it is minimized along with its parent. My apologizes Wouter.
However, in my case it does not work like that. So I did not turn Wouter`s comment without checking or in bad intention. Now it is my duty to find out why. I suspect some utility classes to alter the dialog. I will report back here when I have found the root cause.
Ok, it is the windows flags. If the dialog is a tool window, it is always minimized, as normal window it depends on the parent.
Try to create MyDialog without this(MainApplication) like parent
and may be play with a second parameter of the constructor.
new MyDialog(0/*, ?*/);
Addition It is working code
MainWindow.cpp
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private slots:
void on_pushButton_clicked();
private:
Ui::MainWindow *ui;
QScopedPointer<Dialog> dialog;
};
MainWindow.hpp
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow),
dialog(new Dialog(0))
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_pushButton_clicked()
{
dialog->setVisible(!dialog->isVisible());
}

setVisible return false when QWidget is integrated in QQuickPaintedItem

I was facing one issue with the Widget which is integrated in the QQuickPaintedItem class. When I have Widget integrated in the QQuickPaintedItem, QWidget::isVisible will return false. If I tried to set QWidget::setVisible(true) then it will open another window, which I do not want in my scenario.
Is there any way to get QWidget::isVisible return true so that my child widgets (In my actual scenario, we have 5 layer of parent child hierarchy) will also works fine when I say QWidget::show()?
I have created the scenario similar to it as below.
Header file:
class MyItem: public QQuickPaintedItem{
Q_OBJECT
public:
explicit MyItem(QQuickItem *parent = 0);
void paint(QPainter *painter);
~MyItem();
Q_INVOKABLE void initButton();
protected:
virtual void mousePressEvent( QMouseEvent* event );
private:
QPushButton* bp;
};
source file:
MyItem::MyItem(QQuickItem *parent)
: QQuickPaintedItem(parent)
{
bp = new QPushButton("Hello");
}
MyItem::~MyItem()
{
delete bp;
}
void MyItem::paint(QPainter *painter){
bp->render(painter, QPoint(), QRegion(), QPushButton::DrawWindowBackground | QPushButton::DrawChildren);
}
void MyItem::mousePressEvent( QMouseEvent* event )
{
qDebug() << Q_FUNC_INFO << bp->isVisible();
}
Thanks for help in advance...!!!
I don't know why you want to do this.
Qt do not support to embed a QWidget into a Qt Quick Item in Qt5(Qt Quick 2).
In your code, QWidget is a seperate Window, and you Qt Quick item is in it's own Window.
If you want your Qt Quick item behavior like a Button, you should use Qt Quick's Button control or write one yourself.
If you really want to embed a QWidget into Qt Quick's control tree, you can use Qt Quick 1(Qt4.7/8) instead. Check out QGraphicsProxyWidget's document.