In a QWidget derived class object with implemented tabletEvent(QTabletEvent *event) and mousePressEvent(QMouseEvent *event), the mousePressEvent gets called every time tabletEvent gets called with type TabletEvent::TabletPress. According to the Qt documentation, this should not happen:
The event handler QWidget::tabletEvent() receives TabletPress, TabletRelease and TabletMove events. Qt will first send a tablet event, then if it is not accepted by any widget, it will send a mouse event.
mainwindow.cpp
#include "mainwindow.h"
#include "tabletwidget.h"
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
TabletWidget* tw = new TabletWidget(this);
setCentralWidget(tw);
}
tabletwidget.h
#ifndef TABLETWIDGET_H
#define TABLETWIDGET_H
#include <QWidget>
class TabletWidget : public QWidget
{
Q_OBJECT
public:
explicit TabletWidget(QWidget *parent = 0);
protected:
void tabletEvent(QTabletEvent *event) Q_DECL_OVERRIDE;
void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
signals:
public slots:
};
#endif // TABLETWIDGET_H
tabletwidget.cpp
#include "tabletwidget.h"
#include <QDebug>
#include <QTabletEvent>
TabletWidget::TabletWidget(QWidget *parent) : QWidget(parent)
{
}
void TabletWidget::tabletEvent(QTabletEvent *event)
{
event->accept();
qDebug() << "tabletEvent: " << event->type();
}
void TabletWidget::mousePressEvent(QMouseEvent *event)
{
qDebug() << "mousePressEvent";
}
The output generated if I use the tip of the pen or press any button of the Wacom Intuos CTH-680S-DEIT is:
tabletEvent: 92
mousePressEvent
tabletEvent: 87
tabletEvent: 87
tabletEvent: 87
tabletEvent: 87
tabletEvent: 93
So first the tabletEvent gets called, and even though I accept the event, mousePressEvent gets called anyway. Every following tabletEvent is of type QTabletEvent::TabletMove and the last one is QTabletEvent::TabletRelease. From the Qt documentation:
QEvent::TabletMove 87
QEvent::TabletPress 92
QEvent::TabletRelease 93
I have tested this on Mac OS 10.10.3 and Windows 7 with the same result. Is this a bug or am I doing it wrong?
This is tested on Qt 5.4.2.
Indeed, according to Qt documentation, the Qt should not be sending mouse events when the tablet is in use. But it seems to do it anyway (I'm using version 5.5).
One way to get around it is to reimplement event() method of QApplication - that's where TabletEnterProximity and TabletLeaveProximity are sent; those functions are not sent to the QWidget's event().
So, whenever the application catches either TabletEnterProximity or TabletLeaveProximity events, you may send a signal to your TabletWidget to change a private bool variable _deviceActive. Then, inside the TabletWidget you add a check for each MousePressEvent (and MouseReleaseEvent) to see if the _deviceActive is true or not; and implement the event only if the flag is false.
To illustrate, the inherited TabletApplication would look like this:
class TabletApplication : public QApplication {
Q_OBJECT
public:
TabletApplication(int& argv, char** argc): QApplication(argv,argc){}
bool event(QEvent* event){
if (event->type() == QEvent::TabletEnterProximity || event->type() == QEvent::TabletLeaveProximity) {
bool active = event->type() == QEvent::TabletEnterProximity? 1:0;
emit sendTabletDevice(active);
return true;
}
return QApplication::event(event);
}
signals:
void sendTabletActive(bool active);
};
And the additional parts inside tabletwidget.h:
class TabletWidget : public QWidget {
// ...
public slots:
void setTabletDeviceActive(bool active){
_deviceActive = active;
}
// ...
private:
bool _deviceActive;
};
Then you check inside the mouse events if the device is active:
void TabletWidget::mousePressEvent(QMouseEvent *event)
{
if (!_deviceActive)
qDebug() << "mousePressEvent";
}
Of course, don't forget to connect the corresponding signal with the slot. Hope it helps.
Reference: TabletApplication from Qt tablet example
Related
C++ Qt newbe here. I work with a QDial object that is intended to be controlled with a mouse wheel, it works fine as such, emitting valueChanged() signals when necessary.
I would like to put a semi-transparent QToolButton on top of it, allowing users to click on the button (and set QDial value to a pre-defined number) while maintaining the ability to use the mouse wheel to control QDial as usual.
I experimented a bit with the TransparentForMouseEvents attribute:
ui->toolButton_Example->setAttribute(Qt::WA_TransparentForMouseEvents);
The problem is - the above code switches off all events, including the ability to emit the clicked() signal.
Is there a way to make a QToolButton transparent selectively for MouseWheelEvents while preserving the ability to respond to MouseClick events? Or would this require rewriting the event filter from scratch?
EDIT: Just to clarify - This question is about making QToolButton transparent to MouseWheel EVENTS while still allowing it to respond to MouseClick EVENTS. It is not about making the button transparent in the graphical sense.
SOLUTION
OK, problem solved the traditional way by subclassing QDial and overriding MousePressEvent and MouseReleaseEvent:
#include <QDial>
#include <QMouseEvent>
class QSuperDial : public QDial {
public:
QSuperDial (QWidget *parent = nullptr) : QDial(parent) {
}
virtual void mousePressEvent (QMouseEvent *event) override {
emit sliderPressed();
}
virtual void mouseMoveEvent (QMouseEvent * event) override {
}
virtual void mouseReleaseEvent (QMouseEvent *event) override {
}
};
Promoting QDial to QSuperDial results in a QDial object that 'behaves' like a button when pressed, emitting sliderPressed signal, while still being responsive to MouseWheelEvent (like a normal QDial).
I think this is the simplest and the most 'Qt-like' solution, but please do correct me if I'm mistaken.
You can use QObject::installEventFilter to have the parent object filter the events before they reach the tool button. Then, override the parent's QObject::eventFilter to handle/ignore the event.
I create an example below:
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QToolButton>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
bool eventFilter(QObject *watched, QEvent *event) override;
private:
QToolButton tool_button_ignored_;
QToolButton tool_button_handled_;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include <QDebug>
#include <QEvent>
#include <QHBoxLayout>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
tool_button_ignored_.setObjectName("tool_button_ignored_");
tool_button_ignored_.setText("Ignored button");
tool_button_ignored_.installEventFilter(this);
tool_button_handled_.setObjectName("tool_button_handled_");
tool_button_handled_.setText("Handled button");
tool_button_handled_.installEventFilter(this);
QWidget *central_widget = new QWidget{this};
QHBoxLayout *layout = new QHBoxLayout{central_widget};
layout->addWidget(&tool_button_ignored_);
layout->addWidget(&tool_button_handled_);
this->setCentralWidget(central_widget);
}
MainWindow::~MainWindow()
{
}
bool MainWindow::eventFilter(QObject *watched, QEvent *event)
{
if (watched != &tool_button_ignored_ || event->type() != QEvent::Wheel)
{
qDebug() << event->type() << watched->objectName() << "handled";
return QMainWindow::eventFilter(watched, event);
}
else
{
qDebug() << event->type() << watched->objectName() << "ignored";
return true; // stop being handled further
}
}
Using Qt to create an application that accepts a file drop. I have an area on my UI that I want to drop the file into, using a Qlabel. I have the function of dragging and dropping the file into the UI working, however I can drop it anywhere on the window, and not just into the Qlabel area.
I thought using
ui->label_drag->setAcceptDrops(true);
would work, however this just removed the functionality all together. What is the best way of handling this? if possible at all.
Thanks
The best way to do this is to override the QLabel class. In the dragEnterEvent be sure to call acceptProposedAction to process the move and leave events. If you don't do that, only the dragEnter event will fire.
Sample code follows. To use this in your project, add the source to your project and then right click on the label on the form and promote the item to QLabelDragDrop.
#ifndef QLABELDRAGDROP_H
#define QLABELDRAGDROP_H
#include <QLabel>
class QLabelDragDrop : public QLabel
{
Q_OBJECT
public:
explicit QLabelDragDrop(QWidget *parent = nullptr);
protected:
void dragEnterEvent(QDragEnterEvent *event);
void dragLeaveEvent(QDragLeaveEvent *event);
void dragMoveEvent(QDragMoveEvent *event);
signals:
public slots:
};
#endif // QLABELDRAGDROP_H
#include "qlabeldragdrop.h"
#include <QDebug>
#include <QDragEnterEvent>
#include <QDropEvent>
QLabelDragDrop::QLabelDragDrop(QWidget *parent) : QLabel(parent)
{
setAcceptDrops(true);
setMouseTracking(true);
}
void QLabelDragDrop::dragEnterEvent(QDragEnterEvent *event)
{
qDebug() << "dragEnterEvent";
event->acceptProposedAction();
}
void QLabelDragDrop::dragLeaveEvent(QDragLeaveEvent *event)
{
qDebug() << "dragLeaveEvent";
releaseMouse();
}
void QLabelDragDrop::dragMoveEvent(QDragMoveEvent *event)
{
qDebug() << "dragMoveEvent";
}
void QLabelDragDrop::dropEvent(QDropEvent *event)
{
qDebug() << "dropEvent";
}
I have a subclass of QGraphicsView to recognize mouse events, and it does. But when those mouse events happen, I need to call some other function in a different class, to handle how the mouse event interacts with the scene.
//Subclass
class Drawspace : public QGraphicsView {
public:
Drawspace(QGraphicsScene * scene, QWidget * parent) : QGraphicsView(scene, parent) {}
Drawspace(QWidget* parent) : QGraphicsView(parent) {}
void mousePressEvent(QMouseEvent * event) {
QMessageBox::information(this, tr("Dialog"), "You clicked the board (from QGraphicsView)");
QWidget::mousePressEvent(event);
}
};
//mainwindow code (my subclass is called "board")
mainwindow::mainwindow(QWidget *parent)
: QMainWindow(parent) {
//Initialize other stuff
ui.setupUi(this);
//Problem here
connect(ui.board, SIGNAL(Drawspace::mousePressEvent(QMouseEvent*)), this, SLOT(on_click(QMouseEvent*)));
}
void mainwindow::on_click(QMouseEvent * event) {
QMessageBox::information(this, tr("Dialog"), "You clicked the screen");
//Do stuff here
}
It builds fine, and when I click the drawspace, I get a dialog that says "You clicked the board (from QGraphicsView)", but I don't get the second from "You clicked the screen".
And yes, the header file for mainwindow has the Q_OBJECT macro
EDIT: Can be fixed by manually defining your own signal, calling that, and changing the connection to use Qt5 syntax. See my answer for code
OP here, the problem can be fixed by defining a signal, calling emit on it and changing the connection to use Qt5 syntax.
//New drawspace: Added signal definition and called emit on it from mousePressEvent
class Drawspace : public QGraphicsView, public QObject {
Q_OBJECT
public:
Drawspace(QGraphicsScene * scene, QWidget * parent) : QGraphicsView(scene, parent) {}
Drawspace(QWidget* parent) : QGraphicsView(parent) {}
void setObjectName(QString str) { QGraphicsView::setObjectName(str); }
void mousePressEvent(QMouseEvent * event) {
QMessageBox::information(this, tr("Dialog"), "Detected click in Drawspace");
emit clicked(event);
}
Q_SIGNALS:
void clicked(QMouseEvent * event);
};
//Used Qt5 syntax, this will automatically pass arguments
connect(ui.board, &Drawspace::clicked, this, &mainwindow::on_click);
mousePressEvent() is not a signal. It is a member function. You cannot connect() a function to a function like you can connect a signal to a signal.
Qt signals are declared as functions, but they get some special treatment. You'd need to use Qt's keywords to signify a signal so it can get the treatment that will make it work.
Make the mousePressEvent virtual, or you are creating a NEW method:
virtual void mousePressEvent(QMouseEvent * event)
and say it is overriden (depending on your compiler:)
virtual void mousePressEvent(QMouseEvent * event) override (Q_DECL_OVERRIDE)
Using Qt I create a QMainWindow and want to call a function AFTER the windows is shown. When I call the function in the constructor the function (a dialog actually) get's called before the window is shown.
If you want to do something while the widget is made visible, you can override QWidget::showEvent like this:
class YourWidget : public QWidget { ...
void YourWidget::showEvent( QShowEvent* event ) {
QWidget::showEvent( event );
//your code here
}
After analyzing the solutions above, it turns that all of them, including the heavily upvoted ones, are faulty.
Many recommend something like this:
class MyWidget : public QWidget {
// ...
};
void MyWidget::showEvent(QShowEvent* event) {
QWidget::showEvent(event);
DoSomething();
}
void MyWidget::DoSomething() {
// ...
}
This works as long as there is no QCoreApplication::processEvents(); in DoSomething. If there is one, it processes all events in the queue, including the QShowEvent which called MyWidget::showEvent in the first place. When it gets to the original QShowEvent, it calls MyWidget::showEvent again, causing an infinite loop.
If this happens, there are three solutions:
Solution 1. Avoid calling processEvents in MyWidget::DoSomething, instead call update or repaint when necessary. If DoSomething calls something else, these functions should avoid processEvents also.
Solution 2. Make DoSomething a slot, and replace direct call to DoSomething() by
QTimer::singleShot(0, this, SLOT(DoSomething()));
Since zero interval timer fires only when after all events in the queue are processed, it will process all events, including the original QShowEvent, remove them from the queue, and only then call DoSomething. I like it the most.
Since only zero interval timer fires only when after all events in the queue are processed, you should not try to "improve" it by lengthening the interval, for instance
QTimer::singleShot(50, this, SLOT(DoSomething())); // WRONG!
Since 50 ms is usually enough time for processing events in the queue, that would usually work, causing an error which is hard to reproduce.
Solution 3. Make a flag which prevents calling DoSomething the second time:
class MyWidget : public QWidget {
// ...
};
void MyWidget::showEvent(QShowEvent* event) {
if (is_opening)
return;
is_opening = true;
QWidget::showEvent(event);
DoSomething();
is_opening = false;
}
void MyWidget::DoSomething() {
// ...
}
Here, is_opening is a boolean flag which should be initialized as false in constructor.
try this:
in mainwindow.h:
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
protected:
void showEvent(QShowEvent *ev);
private:
void showEventHelper();
Ui::MainWindow *ui;
}
in mainwindow.cpp:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
void MainWindow::showEvent(QShowEvent *ev)
{
QMainWindow::showEvent(ev);
showEventHelper();
}
void MainWindow::showEventHelper()
{
// your code placed here
}
Follow Reza Ebrahimi's example, but keep this in mind:
Do not omit the 5th parameter of connect() function which specifies the connection type; make sure it to be QueuedConnection.
I.E.,
connect(this, SIGNAL(window_loaded), this, SLOT(your_function()), Qt::ConnectionType(Qt::QueuedConnection | Qt::UniqueConnection));
I believe that you'd achieve what you need if you do it this way.
There are several types in signal-slot connections: AutoConnection, DirectConnection, QueuedConnection, BlockingQueuedConnection (+ optional UniqueConnection). Read the manual for details. :)
Assuming you want to run your code in the UI thread of the window after the window has been shown you could use the following relatively compact code.
class MainWindow : public QMainWindow
{
// constructors etc omitted.
protected:
void showEvent(QShowEvent *ev)
{
QMainWindow::showEvent(ev);
// Call slot via queued connection so it's called from the UI thread after this method has returned and the window has been shown
QMetaObject::invokeMethod(this, "afterWindowShown", Qt::ConnectionType::QueuedConnection);
}
private slots:
void afterWindowShown()
{
// your code here
// note this code will also be called every time the window is restored from a minimized state
}
};
It does invoke afterWindowShown by name but that sort of thing is fairly common practice in Qt. There are ways of avoiding this but they're a bit more verbose.
Note that this code should work for any QWidget derived class, not just QMainWindow derived classes.
In theory it might be possible for a very quick user to invoke some sort of action on the UI of the displayed window before afterWindowShown can be called but it seems unlikely. Something to bear in mind and code defensively against perhaps.
I found a nice answer in this question which works well, even if you use a Sleep() function.
So tried this:
//- cpp-file ----------------------------------------
#include "myapp.h"
#include <time.h>
#include <iosteream>
MyApp::MyApp(QWidget *parent)
: QMainWindow(parent, Qt::FramelessWindowHint)
{
ui.setupUi(this);
}
MyApp::~MyApp()
{
}
void MyApp::showEvent(QShowEvent *event) {
QMainWindow::showEvent(event);
QTimer::singleShot(50, this, SLOT(window_shown()));
return;
}
void MyApp::window_shown() {
std::cout << "Running" << std::endl;
Sleep(10000);
std::cout << "Delayed" << std::endl;
return;
}
//- h-file ----------------------------------------
#ifndef MYAPP_H
#define MYAPP_H
#include <QtWidgets/QMainWindow>
#include <qtimer.h>
#include <time.h>
#include "ui_myapp.h"
class MyApp : public QMainWindow
{
Q_OBJECT
public:
MyApp(QWidget *parent = 0);
~MyApp();
protected:
void showEvent(QShowEvent *event);
private slots:
void window_shown();
private:
Ui::MyAppClass ui;
};
#endif // MYAPP_H
I solved it without a timer using Paint event. Works for me at least on Windows.
// MainWindow.h
class MainWindow : public QMainWindow
{
...
bool event(QEvent *event) override;
void functionAfterShown();
...
bool functionAfterShownCalled = false;
...
}
// MainWindow.cpp
bool MainWindow::event(QEvent *event)
{
const bool ret_val = QMainWindow::event(event);
if(!functionAfterShownCalled && event->type() == QEvent::Paint)
{
functionAfterShown();
functionAfterShownCalled = true;
}
return ret_val;
}
The best solution for me is count once paint event:
.H
public:
void paintEvent(QPaintEvent *event);
.CPP
#include "qpainter.h"
#include <QMessageBox> // example
int contPaintEvent= 0;
void Form2::paintEvent(QPaintEvent* event)
{
if (contPaintEvent ==0 )
{
QPainter painter(this);
QMessageBox::information(this, "title", "1 event paint"); // example
// actions
contPaintEvent++;
}
}
Reimplement method void show() like this:
void MainWindow::show()
{
QMainWindow::show();
// Call your special function here.
}
I'm trying for half an eternity now overriding QWidgets keyPressEvent function in QT but it just won't work. I've to say i am new to CPP, but I know ObjC and standard C.
My problem looks like this:
class QSGameBoard : public QWidget {
Q_OBJECT
public:
QSGameBoard(QWidget *p, int w, int h, QGraphicsScene *s);
signals:
void keyCaught(QKeyEvent *e);
protected:
virtual void keyPressEvent(QKeyEvent *event);
};
QSGameBoard is my QWidget subclass and i need to override the keyPressEvent and fire a SIGNAL on each event to notify some registered objects.
My overridden keyPressEvent in QSGameBoard.cpp looks like this:
void QSGameBoard::keyPressEvent(QKeyEvent *event) {
printf("\nkey event in board: %i", event->key());
//emit keyCaught(event);
}
When i change QSGameBoard:: to QWidget:: it receives the events, but i cant emit the signal because the compiler complains about the scope. And if i write it like this the function doesn't get called at all.
What's the problem here?
EDIT:
As pointed out by other users, the method I outlined originally is not the proper way to resolve this.
Answer by Vasco Rinaldo
Use Set the FocusPolicy to Qt::ClickFocus to get the keybordfocus by
mouse klick. setFocusPolicy(Qt::ClickFocus);
The previous (albeit imperfect) solution I gave is given below:
Looks like your widget is not getting "focus". Override your mouse press event:
void QSGameBoard::mousePressEvent ( QMouseEvent * event ){
printf("\nMouse in board");
setFocus();
}
Here's the source code for a working example:
QSGameBoard.h
#ifndef _QSGAMEBOARD_H
#define _QSGAMEBOARD_H
#include <QWidget>
#include <QGraphicsScene>
class QSGameBoard : public QWidget {
Q_OBJECT
public:
QSGameBoard(QWidget *p, int w, int h, QGraphicsScene *s);
signals:
void keyCaught(QKeyEvent *e);
protected:
virtual void keyPressEvent(QKeyEvent *event);
void mousePressEvent ( QMouseEvent * event );
};
#endif /* _QSGAMEBOARD_H */
QSGameBoard.cpp
#include <QKeyEvent>
#include <QLabel>
#include <QtGui/qgridlayout.h>
#include <QGridLayout>
#include "QSGameBoard.h"
QSGameBoard::QSGameBoard(QWidget* p, int w, int h, QGraphicsScene* s) :
QWidget(p){
QLabel* o = new QLabel(tr("Test Test Test"));
QGridLayout* g = new QGridLayout(this);
g->addWidget(o);
}
void QSGameBoard::keyPressEvent(QKeyEvent* event){
printf("\nkey event in board: %i", event->key());
}
void QSGameBoard::mousePressEvent ( QMouseEvent * event ){
printf("\nMouse in board");
setFocus();
}
main.cpp
#include <QtGui/QApplication>
#include <QtGui/qmainwindow.h>
#include "QSGameBoard.h"
int main(int argc, char *argv[]) {
// initialize resources, if needed
// Q_INIT_RESOURCE(resfile);
QApplication app(argc, argv);
QMainWindow oM;
QGraphicsScene o;
QSGameBoard a(&oM, 1, 2, &o);
oM.setCentralWidget(&a);
a.show();
oM.show();
// create and show your widgets here
return app.exec();
}
You don't have to reimplement mousePressEvent yourself just to call setFocus. Qt planed it already.
Set the FocusPolicy to Qt::ClickFocus to get the keybordfocus by mouse klick.
setFocusPolicy(Qt::ClickFocus);
As said in the manual:
This property holds the way the widget accepts keyboard focus.
The policy is Qt::TabFocus if the widget accepts keyboard focus by tabbing, Qt::ClickFocus if the widget accepts focus by clicking, Qt::StrongFocus if it accepts both, and Qt::NoFocus (the default) if it does not accept focus at all.
You must enable keyboard focus for a widget if it processes keyboard events. This is normally done from the widget's constructor. For instance, the QLineEdit constructor calls setFocusPolicy(Qt::StrongFocus).
If the widget has a focus proxy, then the focus policy will be propagated to it.
Set the FocusPolicy to Qt::ClickFocus to get the keybordfocus by mouse klick.
setFocusPolicy(Qt::ClickFocus);