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'.
Related
Minimal code example:
class Boo : public QPushButton{
public:
Boo(QWidget* w) : QPushButton(w){}
virtual void paintEvent(QPaintEvent* ev){
qDebug()<<__FUNCTION__<<this->objectName();
}
};
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
virtual void paintEvent(QPaintEvent* ev){
qDebug()<<__FUNCTION__;
}
private:
QTimer t;
Ui::MainWindow *ui;
Boo *b1, *b2;
};
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
b1 = new Boo(this);
b1->setObjectName("O1");
b2 = new Boo(this);
b2->setObjectName("O2");
connect(&t, &QTimer::timeout, this, [this](){
b2->repaint();
});
t.start();
t.setInterval(10);
}
Outputs infinitely: MainWindow::paintEvent Boo::paintEvent "O1" Boo::paintEvent "O2"
but i call repaint only for button b2. Using "QWidget::repaint(int x, int y, int w, int h)" or :QWidget::update()" also invoke repaint for main window.
Problem exists on Qt5.12/5.15 and windows11, but looks like general qt bug.
This issue cause high GPU consumption in our more complex GUI application.
Ok. I found the reason myself, after add:
...
b1->setGeometry(15,15, 12, 12);
...
b2->setGeometry(35,35, 14, 14);
...
virtual void paintEvent(QPaintEvent* ev){
qDebug()<<__FUNCTION__<<ev->rect();
}
ouput says, that actual repaint is done only for button region:
MainWindow::paintEvent QRect(35,35 14x14)
Boo::paintEvent "O2" QRect(0,0 14x14)
Important thing: ivoke setGeometry for button, not just add as child. In other case (usable only for toy example, ofcourse) repaint button with unset geometry will lead to repaint every button with unset geometry.
Qt repaints by default all parents too, to allow widgets being partially transparent. If Qt doesn't repaint the parent on an update, some pixels of the previous paintEvent may still be visible.
Qt provides two methods to optimise this behaviour when no transparent background is needed:
Set autoFillBackground. This option is preferred in case of an opaque background color.
Set Qt::WA_OpaquePaintEvent to indicate that you will paint the whole widget (with opaque colors).
More information
Qt documentation: QWidget: Transparency and Double Buffering
I want to make some functions on a dicom serie (with qt and vtk) , and I want to make some connections between the qt window and the mouse.
This is my primary design:
For example, if I click on zoombutton, then I click on my image with the left button of the mouse, I want that the image will be zoomed,
I know that we must use the function mousePressEvent but I have seen that we must use this name for any connection with the mouse, or I want to do 4 or 5 functions like this one, each one for one pushbutton.
How can I do this ?
As you suggested correctly, you should use mousePressEvent to capture a mouse press action. To perform the correct action on a mouse press (zoom, pan, ...), you should remember the last pressed button and call the appropriate method accordingly. This can be implemented as follows:
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow ()
{
connect(ui->panButton, &QPushButton::clicked, this, &MainWindow::onPan)
connect(ui->zoomButton, &QPushButton::clicked, this, &MainWindow::onZoom)
...
}
protected slots:
enum Action {None, Pan, Zoom, ...};
void onPan () {currentAction = Pan;}
void onZoom () {currentAction = Zoom;}
protected:
void mousePressEvent(QMouseEvent *event)
{
switch(currentAction)
{
case Pan:
// perform Pan operation
break;
case Zoom:
// perform Zoom operation
break;
}
}
protected:
Action currentAction;
};
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();
}
}
In example code:
class MyWidget : public QWidget
{
Q_OBJECT
protected:
void mousePressEvent(QMouseEvent *event)
{
qDebug() << event;
event->accept();
QInputDialog::getText(NULL, "", "");
}
};
When I click Right mouse button on widget Input dialog appear on screen. After I click any button on dialog it closed and mousePressEvent call again and again and show dialog. If I click Left mouse button or Ctrl+Left Mouse button on widget all work fine.
This bug apper only on Mac OS (under Windows work fine).
Please help me to avoid this bug.
Those static/synchronous dialog functions always seemed a bit dubious to me -- they are implemented by recursively re-invoking the Qt event loop routine from within the getText() call, and so it's easy to get "interesting" re-entrancy issues when you use them. (For example, if some event in your program were to delete the MyWidget object before the user had dismissed the QInputDialog, then after QInputDialog::getText() returned, the program would be executing from within the mousePressEvent() method of a deleted MyWidget object, which is a situation that is just begging to cause undefined behavior)
In any case, my recommended fix is to avoid the static/synchronous getText() call and use signals instead, something like this:
#include <QWidget>
#include <QInputDialog>
#include <QMouseEvent>
class MyWidget : public QWidget
{
Q_OBJECT
public:
MyWidget() : dialog(NULL) {}
~MyWidget() {delete dialog;}
protected:
void mousePressEvent(QMouseEvent *event)
{
event->accept();
if (dialog)
{
dialog->raise();
dialog->setFocus();
}
else
{
dialog = new QInputDialog;
connect(dialog, SIGNAL(finished(int)), this, SLOT(dialogDismissed()));
dialog->show();
}
}
private slots:
void dialogDismissed()
{
if (dialog)
{
int result = dialog->result();
QString t = dialog->textValue();
printf("Dialog finished, result was %i, user entered text [%s]\n", result, t.toUtf8().constData());
dialog->deleteLater(); // can't just call delete because (dialog) is what is calling this function (via a signal)!
dialog = NULL;
}
}
private:
QInputDialog * dialog;
};
I use MarbleWidget with OpenStreetMap on Qt.
Wheel zoom shows blurry images on the map. Therefore, I want to synchronize the mouse wheel with ZoomIn() and ZoomOut() inorder user to get sharp images on the map.
I want to do something like this:
QObject::connect( MarbleWidget, SIGNAL(??????), this, SLOT(wheelEvent(wheelEvent)) );
void MainWindow::wheelEvent(QWheelEvent *event){
//....
}
Is there any signal or event that I can use from MarbleWidget for ??????? above line?
And, how can I disable the mouse zoom on the MarbleWidget?
You can make your own input handler and tell MarbleWidget to use it. This will allow you to intercept mouse wheel events in the way you are asking.
Create a custom input handler
MarbleWidget uses a default input handler. Inside of MarbleInputHandler.cpp there is a function eventFilter(QObject*, QEvent*) that handles (among other things) the QEvent::Wheel event. Derive from this class and override eventFilter:
class MyMarbleInputHandler : public MarbleWidgetDefaultInputHandler
{
Q_OBJECT
public:
explicit MyMarbleInputHandler(MarbleWidget* mw) :
MarbleWidgetDefaultInputHandler(mw) {}
virtual bool eventFilter(QObject *o, QEvent *e);
signals:
void wheelEvent(QWheelEvent *event);
};
Basically, you want to intercept QEvent::Wheel and emit your own signal. Anything you don't handle yourself should be passed along to the base class.
bool MyMarbleInputHandler::eventFilter(QObject *o, QEvent *e)
{
if (e->type() == QEvent::Wheel)
{
emit wheelEvent(static_cast<QWheelEvent*>(e));
return true;
}
return MarbleWidgetDefaultInputHandler::eventFilter(o, e);
}
Create a custom MarbleWidget
The constructor below shows how you can set the input handler defined above. You'll also have to wire the signal/slot.
class MyMarbleWidget : public MarbleWidget
{
Q_OBJECT
public:
explicit MyMarbleWidget()
{
MyMarbleInputHandler *myMarbleInputHandler = new MyMarbleInputHandler(this);
setInputHandler(myMarbleInputHandler);
connect(myMarbleInputHandler, SIGNAL(wheelEvent(QWheelEvent*)),
this, SLOT(handleWheelEvent(QWheelEvent*)));
}
public slots:
void handleWheelEvent(QWheelEvent *event)
{
if (event->delta() > 0) zoomIn();
else zoomOut();
}
};
handleWheelEvent() provides the code to zoom in/out. Not all scroll wheels work the same, so you'll have to figure out how much movement of the mouse wheel it will take to zoom in/out by one step. In this example, it zooms in/out one step based on each event, paying attention only to the sign of delta() and ignoring its magnitude.
You might also check out MarbleDefaultInputHandler::handleWheel() to see what's going on with the default behavior. They use interpolated/stretched bitmap images between vector layers to provide a smoother animation when zooming. Note that the plus+ and minus- keys on the keyboard will allow you to zoom to non-interpolated map levels, whereas the mouse wheel zooms using animated ("blurry") interpolated layers. This behavior is documented in a bug report.