I'm coding in c++ with Qt creator. I can get all windows on the screen but I would like to detect their movement directly. Is there any way to detect the movement thanks to a signal emitted by the window ?
You can try creating a QWindow from the target window and then wrap it in a QWidget using QWidget::createWindowContainer().
You can have a look at this QTBUG thread : https://bugreports.qt.io/browse/QTBUG-40320
It needs some effort to make it work properly. The captured window doesn't keep its initial dimensions, and releasing the window behave weirdly. Read the last QTBUG comments to find an improvement.
I added an event filter to this code to capture the window position in real time. But that might be unsatisfying.
class EventFilter : public QObject
{
Q_OBJECT
public:
EventFilter(){}
virtual ~EventFilter(){}
protected:
bool eventFilter(QObject *obj, QEvent *event);
} ;
bool EventFilter::eventFilter(QObject *obj, QEvent *event)
{
qDebug() << event->type() ;
if (event->type() == QEvent::Move) {
QMoveEvent *moveEvent = static_cast<QMoveEvent *>(event);
qDebug() << "position" << moveEvent->pos() ;
return true;
} else {
// standard event processing
return QObject::eventFilter(obj, event);
}
}
I removed part 3 from the QTBUG snippet and installed the event handler on the inner widget. You can also remove the timers.
// From https://bugreports.qt.io/browse/QTBUG-40320
int main(int argc, char *argv[])
{
// Windows: Find HWND by window title
WId id = (WId)FindWindow(NULL, L"Calculator");
if (!id)
return -1;
QApplication a(argc, argv);
// Optional
QTimer t;
t.start(2500);
// Part 1
QWindow* window = QWindow::fromWinId(id);
window->show();
window->requestActivate();
// Optional
QObject::connect(&t, &QTimer::timeout, [=]
{
qDebug() << "=== Inner QWindow ===";
qDebug() << "Geometry:" << window->geometry();
qDebug() << "Active?:" << window->isActive();
qDebug() << "Flags:" << window->flags();
});
// Part 2
QWidget* widget = QWidget::createWindowContainer(window);
widget->show();
// Optional
QObject::connect(&t, &QTimer::timeout, [=]
{
qDebug() << "=== Outer QWidget ===";
qDebug() << "Geometry:" << widget->geometry();
qDebug() << "Active?" << widget->isActiveWindow();
qDebug() << "Flags:" << widget->windowFlags();
});
// Realtime position
EventFilter filter ;
widget->installEventFilter( &filter ) ;
return a.exec();
}
Output:
=== Inner QWindow ===
Geometry: QRect(0,0 640x480)
Active?: true
Flags: QFlags<Qt::WindowType>(ForeignWindow)
=== Outer QWidget ===
Geometry: QRect(2489,29 640x480)
Active? true
Flags: QFlags<Qt::WindowType>(Window|WindowTitleHint|WindowSystemMenuHint|WindowMinMaxButtonsHint|WindowCloseButtonHint)
QEvent::Type(Move)
position QPoint(2484,29)
QEvent::Type(Move)
position QPoint(2481,30)
QEvent::Type(Move)
position QPoint(2478,31)
QEvent::Type(Move)
position QPoint(2474,31)
See also http://blog.qt.io/blog/2013/02/19/introducing-qwidgetcreatewindowcontainer/
Related
Here are 2 answers for capturing the key press event for QTableWidget.
How to create a SIGNAL for QTableWidget from keyboard?
Follow the way above, I can "hook" key press event, When I press space, the background color becomes red.
However, it only works for a selected cell, but not for a in-editing cell.
When it's in editing state, the 2 ways both fail. I can type space freely.
When it's in "editing state" there is editor widget atop QTableWidget (item delegate) which receives key events, and since it's on top you can't see cell content and cell background behind it. But you can access and "hook" this events by setting QAbstractItemDelegate to QTableWidget.
// itemdelegate.h
class ItemDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
explicit ItemDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent)
{
}
bool eventFilter(QObject *object, QEvent *event) override
{
if (event->type() == QEvent::KeyPress) {
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
if (keyEvent->key() == Qt::Key_Space) {
qDebug() << "space pressed";
}
}
return false;
}
};
// main.cpp
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTableWidget* widget = new QTableWidget();
widget->setColumnCount(2);
widget->setRowCount(2);
widget->setItemDelegate(new ItemDelegate(widget));
widget->show();
return a.exec();
}
I have horizontal slider element which is disabled. I want to enable it when I click on it. As the slider is disabled I am unable to capture mouse events on it.
Thanks in advance
I must admit that my first suggestion was wrong:
However, if the slider is disabled the mouse event will be captured probably by something else - I would expect the parent widget.
That expectation was wrong.
I found out about this by trying myself in an MCVE.
For my luck, the other option – using an event filter – does work.
Sample testQClickDisabled.cc:
// Qt header:
#include <QtWidgets>
void populate(QListWidget &qLst)
{
for (int i = 1; i <= 20; ++i) {
qLst.addItem(QString("item %0").arg(i));
}
}
class EventFilter: public QObject {
private:
QListWidget &qLst;
public:
EventFilter(QListWidget &qLst): QObject(), qLst(qLst)
{
qApp->installEventFilter(this);
}
~EventFilter() { qApp->removeEventFilter(this); }
EventFilter(const EventFilter&) = delete;
EventFilter& operator=(const EventFilter&) = delete;
protected:
virtual bool eventFilter(QObject *pQObj, QEvent *pQEvent) override;
};
bool EventFilter::eventFilter(QObject *pQObj, QEvent *pQEvent)
{
if (QScrollBar *const pQScrBar = dynamic_cast<QScrollBar*>(pQObj)) {
if (pQScrBar == qLst.verticalScrollBar()
&& pQEvent->type() == QEvent::MouseButtonPress) {
qDebug() << "Vertical scrollbar hit.";
pQScrBar->setEnabled(true);
}
}
return QObject::eventFilter(pQObj, pQEvent);
}
// main application
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
// setup GUI
QListWidget qLst;
qLst.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
qLst.verticalScrollBar()->setEnabled(false);
qLst.resize(200, 200);
qLst.show();
populate(qLst);
EventFilter qEventFilter(qLst);
// runtime loop
return app.exec();
}
Output:
I would just use a sibling MouseArea, like this:
Slider {
id: slider
enabled: false
}
MouseArea {
anchors.fill: slider
visible: !slider.enabled
onClicked: slider.enabled = true
}
Why QMouseEvent passing multiple events for single movement on QWidget?
I'm implementing simple dragging effect, but the result is not what I expected.
The following code will move the widget to new location but instantly move it back to the original location.
customwidget.h
#ifndef CUSTOMWIDGET_H
#define CUSTOMWIDGET_H
#include <QWidget>
#include <fstream>
class CustomWidget : public QWidget
{
Q_OBJECT
public:
explicit CustomWidget(QWidget *parent = nullptr);
~CustomWidget();
protected:
// define the painting agorithm to see the area of this widget
void paintEvent(QPaintEvent* ev);
// handle the pressing event to initialize the dragging algorithm
// and to track the start of moving event
void mousePressEvent(QMouseEvent* ev);
// implement the dragging algorithm
void mouseMoveEvent(QMouseEvent* ev);
// handle the releasing event to track the end of moving event
void mouseReleaseEvent(QMouseEvent* ev);
private:
std::ofstream fout; // open file "debug.txt"
QPoint prev; // to save the previous point of cursor.
};
#endif // CUSTOMWIDGET_H
customwidget.cpp
#include "customwidget.h"
#include <QMouseEvent>
#include <QPaintEvent>
#include <QPainter>
#include <QBrush>
CustomWidget::CustomWidget(QWidget *parent) : QWidget(parent)
{
// open file for output
fout.open("debug.txt");
// set the widget size and position
setGeometry(0, 0, 100, 100);
}
CustomWidget::~CustomWidget()
{
// close file when program ended
fout.close();
}
void CustomWidget::paintEvent(QPaintEvent *ev)
{
// draw the area with blue color
QPainter painter(this);
QBrush brush(Qt::GlobalColor::blue);
painter.setBrush(brush);
painter.setBackground(brush);
painter.drawRect(ev->rect());
}
void CustomWidget::mousePressEvent(QMouseEvent *ev)
{
ev->accept();
// debug output
fout << "pressed at (" << ev->x() << ',' << ev->y() << ')' << std::endl;
// initialize the dragging start point
prev = ev->pos();
}
void CustomWidget::mouseMoveEvent(QMouseEvent *ev)
{
ev->accept();
// get the cursor position of this event
const QPoint& pos = ev->pos();
// debug output
fout << "moved from (" << prev.x() << ',' << prev.y() << ") to ("
<< pos.x() << ',' << pos.y() << ')' << std::endl;
// calculate the cursor movement
int dx = pos.x() - prev.x();
int dy = pos.y() - prev.y();
// move the widget position to match the direction of the cursor.
move(geometry().x() + dx, geometry().y() + dy);
// update the cursor position for the next event
prev = pos;
}
void CustomWidget::mouseReleaseEvent(QMouseEvent *ev)
{
ev->accept();
fout << "released at (" << ev->x() << ',' << ev->y() << ')' << std::endl;
}
main.cpp
#include "customwidget.h"
#include <QApplication>
#include <QMainWindow>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// generate simple main window.
QMainWindow w;
// set the size of the window.
w.setGeometry(0, 0, 800, 800);
// generate the CustomWidget
CustomWidget *widget = new CustomWidget(&w);
// display the window containing the widget
w.show();
return a.exec();
}
And the result of debug.txt for one single movement of cursor is
CustomWidget pressed at (79,83)
CustomWidget moved from (79,83) to (79,83)
CustomWidget moved from (79,83) to (80,83)
CustomWidget moved from (80,83) to (79,83)
CustomWidget released at (80,83)
The result is moving the widget to new location for a little time then move it back to its original location.
The look of this program will almost looked like the widget is never being moved no mater how you dragging the widget.
My theory is the event manager pass the event when you moved the cursor. But
after the first event is processed, the manager passes another event related to the new location of the widget and the cursor current position. Then the process will move the widget back to where it was.
Although I can change the method of getting location of cursor from
ev->pos()
to
ev->globalPos()
to solve the problem.
But still want to know why the event manager act like that.
You have to do the following:
On mouse press event store the mouse cursor offset relative to the widget,
Move your widget so that the mouse cursor will always preserve the initial non zero offset,
Reset the offset on mouse release event.
The code (draft) might look like:
void CustomWidget::mousePressEvent(QMouseEvent* event)
{
// m_offset is a member variable of CustomWidget
m_offset = event->globalPos() - pos();
QWidget::mousePressEvent(event);
}
void CustomWidget::mouseMoveEvent(QMouseEvent* event)
{
if (!m_offset.isNull()) {
move(event->globalPos() - m_offset);
}
QWidget::mouseMoveEvent(event);
}
void CustomWidget::mouseReleaseEvent(QMouseEvent* event)
{
// Reset the offset value to prevent the movement.
m_offset = QPoint();
QWidget::mouseReleaseEvent(event);
}
I have a lot of signals which all have the same parameters but perform different functions.
The connect and disconnect code for all the signals will be the same, as is the slot handler that the signals connect to.
Instead of writing this code over and over. I would like to use a function pointer or something similar to assign to the signal, then have a common code block which performs the connection or disconnection.
The following code is just to illustrate what I am describing, it isn't valid and will not compile.
void (*pfnSignal)(quint8, QString);
switch( eSigID ) {
case SIGNAL_A:
pfnSignal = signalA;
break;
case SIGNAL_B:
pfnSignal = signalB;
break;
default:
pfnSignal = NULL;
}
if ( pfnSignal != NULL ) {
QObject::connect(pobjRef, pfnSignal, this, SLOT(handler(quint8, QString)));
}
In Qt5, this can be done easily, as it allows connecting using a new pointer to member function syntax.
// Using decltype to avoid figuring out the ugly pointer-to-member-function syntax.
// Assumes all signals have the same arguments.
decltype<&ThatClass::someSignal> pfnSignal = nullptr;
switch( eSigID ) {
case SIGNAL_A:
pfnSignal = &ThatClass::signalA;
break;
case SIGNAL_B:
pfnSignal = &ThatClass::signalB;
break;
}
if (pfnSignal) {
connect(pobjRef, pfnSignal, this, &ThisClass::handler);
}
But actually, this is even possible with Qt4, as the SIGNAL macro is of type const char*.
const char *pfnSignal = nullptr;
switch( eSigID ) {
case SIGNAL_A:
pfnSignal = SIGNAL(signalA(quint8, QString));
break;
case SIGNAL_B:
pfnSignal = SIGNAL(signalB(quint8, QString));
break;
}
if (pfnSignal) {
QObject::connect(pobjRef, pfnSignal, this, SLOT(handler(quint8, QString)));
}
C++11 allows you to write very concise Qt code.
Leverage range-based for loops to iterate over pointers. Those can be pointers to widgets, pointers to methods, etc:
for (auto signal : {&Class::signal1, &Class:signal2})
QObject::connect(sender, signal, receiver, slot);
Leverage lambda expressions to capture constant argument values:
auto const cMySlot = [&](void (Sender::*signal)(int)){
QObject::connect(sender, signal, receiver, slot);
Then:
for (auto signal : {&Class::signal1, &Class:signal2}) cMySlot(signal);
Full example:
// https://github.com/KubaO/stackoverflown/tree/master/questions/signals-simpler-43631464
#include <QtWidgets>
#include <initializer_list>
class Receiver : public QLabel {
Q_OBJECT
public:
Receiver(QWidget * parent = {}) : QLabel{parent} {}
Q_SLOT void intSlot(int val) {
setText(QStringLiteral("int = %1").arg(val));
}
};
class Sender : public QWidget {
Q_OBJECT
QFormLayout m_layout{this};
QPushButton btn1{"Send 1"}, btn2{"Send 5"}, btn3{"Send 10"};
public:
Sender(QWidget * parent = {}) : QWidget{parent} {
m_layout.setMargin(1);
for (auto w : {&btn1, &btn2, &btn3}) m_layout.addWidget(w);
auto const clicked = &QPushButton::clicked;
connect(&btn1, clicked, this, [this]{ emit signal1(1); });
connect(&btn2, clicked, this, [this]{ emit signal2(5); });
connect(&btn3, clicked, this, [this]{ emit signal3(10); });
}
Q_SIGNAL void signal1(int);
Q_SIGNAL void signal2(int);
Q_SIGNAL void signal3(int);
};
using Widgets = std::initializer_list<QWidget*>;
int main(int argc, char **argv)
{
QApplication app{argc, argv};
QWidget win;
QVBoxLayout layout{&win};
Sender sender;
Receiver receiver;
for (auto w : Widgets{&sender, &receiver}) layout.addWidget(w);
// Factor out connection
auto const cIntSlot = [&](void (Sender::*signal)(int)){
QObject::connect(&sender, signal, &receiver, &Receiver::intSlot);
};
// Factor out connection on a list
for (auto signal : {&Sender::signal1, &Sender::signal2, &Sender::signal3})
cIntSlot(signal);
win.show();
return app.exec();
}
#include "main.moc"
Actually, Thomas McGuire was faster than me. (Damn.) Though, I want to add this answer because:
It provides a complete sample.
It uses functors instead of object/member function pointers for signal handlers.
Thus, it may be and add-on to the answer of Thomas McGuire.
Before Qt 5 the signal was described by a char* which should be very simple to handle. Therefore, I assume your question is concerning the new API since Qt 5.
This should work as well if you use the correct method pointer type. I did this for QPushButton and QCheckBox for demonstration because both are derived from QAbstractButton which in turn has two signals with equal signature. IMHO equal signature of signals is mandatory for your solution.
#include <QtWidgets>
enum SigType { None, Click, Toggle };
template <typename FUNCTOR>
void installSignalHandler(
QAbstractButton *pQBtn,
SigType sigType,
FUNCTOR sigSlot)
{
void (QAbstractButton::*pSignal)(bool) = nullptr;
switch (sigType) {
case Click: pSignal = &QAbstractButton::clicked; break;
case Toggle: pSignal = &QAbstractButton::toggled; break;
}
if (pSignal) QObject::connect(pQBtn, pSignal, sigSlot);
}
int main(int argc, char **argv)
{
qDebug() << "Qt Version: " << QT_VERSION_STR;
// main application
QApplication app(argc, argv);
// setup GUI
QWidget qWin;
QVBoxLayout qVBox(&qWin);
QPushButton qBtn1("Button 1 -> Click");
qVBox.addWidget(&qBtn1);
QPushButton qBtn2("Button 2 -> Toggle");
qVBox.addWidget(&qBtn2);
QPushButton qBtn3("Button 3 -> None");
qVBox.addWidget(&qBtn3);
QCheckBox qTgl1("Toggle 1 -> Click");
qVBox.addWidget(&qTgl1);
QCheckBox qTgl2("Toggle 2 -> Toggle");
qVBox.addWidget(&qTgl2);
QCheckBox qTgl3("Toggle 3 -> None");
qVBox.addWidget(&qTgl3);
qWin.show();
// install signal handlers
installSignalHandler(&qBtn1, Click,
[](bool) { qDebug() << "Button 1 received clicked."; });
installSignalHandler(&qBtn2, Toggle,
[](bool) { qDebug() << "Button 2 received toggled."; });
installSignalHandler(&qBtn3, None, // will be actually never called
[](bool) { qDebug() << "Button 3 received none."; });
installSignalHandler(&qTgl1, Click,
[](bool) { qDebug() << "CheckBox 1 received clicked."; });
installSignalHandler(&qTgl2, Toggle,
[](bool) { qDebug() << "CheckBox 2 received toggled."; });
installSignalHandler(&qTgl2, None, // will be actually never called
[](bool) { qDebug() << "CheckBox 3 received none."; });
// run-time loop
return app.exec();
}
Compiled and tested with VisualStudio & Qt 5.6 on Windows 10 (64 bit):
In Qt with C++, I created a window with a small QWidget inside.
The small QWidget show a message every time QEvent::Enter, QEvent::Leave or QEvent::MouseMove is triggered.
When any mouse button is pressed (and holded) outside of the small QWidget, and the mouse is moved on the top of this small QWidget (While holding), QEvent::MouseMove is not triggered for this small QWidget. Additionally, QEvent::Enter is postponed to after the mouse button is released.
In the reverse situation: when the mouse is pressed on the small QWidget (and holded), and then the mouse is moved outside, the QEvent::Leave is postponed to after the mouse button is released.
IS there any solution to retrieve QEvent::MouseMove all the time, even when the mouse button is holded?
Additional data: Yes, setMouseTracking(true) is set.
Testing example:
Widget:
#ifndef MYWIDGET_HPP
#define MYWIDGET_HPP
#include <QWidget>
#include <QStyleOption>
#include <QPainter>
#include <QEvent>
#include <QDebug>
class MyWidget: public QWidget
{
Q_OBJECT
public:
MyWidget( QWidget* parent=nullptr ): QWidget(parent)
{
setMouseTracking(true);
}
protected:
// Paint for styling
void paintEvent(QPaintEvent *)
{
// Needed to allow stylesheet.
QStyleOption opt;
opt.init(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
}
// Show Enter and Leave event for debugging purpose
bool event( QEvent *e)
{
static int counting=0;
if (e->type() ==QEvent::Enter)
{
qDebug() << counting++ << " Enter: " << this->objectName();
}
if (e->type() ==QEvent::Leave)
{
qDebug() << counting++ << " Leave: " << this->objectName();
}
if (e->type() ==QEvent::MouseMove)
{
qDebug() << counting++ << " Move: " << this->objectName();
}
return QWidget::event(e);
}
};
#endif // MYWIDGET_HPP
Main
#include <QApplication>
#include <QDebug>
#include <QWidget>
#include <QTimer>
#include "Testing.hpp"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// Create a main window
QWidget main;
main.setWindowTitle("Cursor blocked for 5s - wait and see");
main.resize(500, 200);
main.move(200, 200);
// Create a MyWidget
MyWidget sub(&main);
sub.setObjectName("sub");
sub.resize(50, 50);
sub.move(50, 50);
// Style the button with a hover
main.setStyleSheet
(
"QWidget#sub{background-color: rgba(0,0,128,0.5);}"
"QWidget#sub:hover{background-color: rgba(128,0,0,0.5);}"
);
// Show the window
main.show();
return a.exec();
}
Project
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
SOURCES +=\
main.cpp
HEADERS +=\
Testing.hpp
RESOURCES +=\
CONFIG += c++11 -Wall
TARGET = Testing
TEMPLATE = app
It is standard behavior. When you press mouse button, widget begin to grab it (make a call of QWidget::grabMouse). I think that you should redesign your behavior, or explain some real use-cases, when you need to track mouse globally.
If you really need to track mouse, you may use event filters.
Pseudo-code (without checks):
QWidget *otherWidget = /*...*/;
QWidget *myWidget = /*...*/;
otherWidget->installEventFilter( myWidget );
// you need to install filter on each widget,
// that you want to track.
// Care with performance
MyWidget : QWidget
{
void handleMouseMove( QPoint pos ) { /*...you code...*/ }
void mouseMove( QMouseEvent *e ) override;
{
handleMouseMove( e->pos() );
QWidget::mouseMove( e );
}
bool eventFilter( QObject *obj, QEvent *e )
{
auto srcWidget = qobject_cast< QWidget * >( obj );
switch ( e->type() )
{
case QEvent::MouseMove:
{
auto me = static_cast< QMouseEvent * >( e );
auto globalPos = srcWidget->mapToGlobal( me->pos() );
auto localPos = this->mapFromGlobal( globalPos ); // Possible, you need to invalidate that poing belongs to widget
handleMouseMove( localPos );
}
break;
};
return QWidget::eventFilter( obj, e );
}
};