I have encountered the following issue:
When I double click one of the QGLWidgets, what I want to do is to expand that clicked widget to full parent widget size, and if the full size widget is double cliked again, it restores back to show all widgets, so the code is like this:
display_widgets_ stores all created QGLWidgets.
bool MainWindow::eventFilter(QObject *watched, QEvent *event)
{
if(event->type() == QEvent::MouseButtonDblClick)
{
QMouseEvent* mouseEv = (QMouseEvent*)event;
if(mouseEv->buttons() == Qt::LeftButton)
{
display_widgets_[0]->parentWidget()->setUpdatesEnabled(false);
if(!showAll_){
for(auto& it:display_widgets_)
{
it->show();
}
showAll_ = true;
}else{
for(auto& it: display_widgets_)
{
if(watched == it)
{
selected_widget_ = it;
showAll_ = false;
selected_widget_->show();
}else
{
it->hide();
}
}
}
display_widgets_[0]->parentWidget()->setUpdatesEnabled(true);
}
return true;
}
return QMainWindow::eventFilter(watched, event);
}
BUT, in the process of Double Click, I got this effect:
And it will finally resize to normal size, but I always got this ugly transformation.
More Information:
I subclassed the QGLWidget to create my own QGLWidget so that I can move each QGLWidget context to a thread to fast rendering.
After several trial and error, I changed the QGLWidget to QOpenGLWidget and this ugly effect disappeared. My Qt Version is 5.8-msvc2015. This problem only occurs when using QGLWidget.
Related
I'm working on a way of creating QWidgets that respond to touch, running as a plugin to a proprietary Qt 5.2 application. The application provides touch and gesture events (QTouchEvent, QGestureEvent), but no mouse events.
I have no control over the application itself, apart from what can be done at runtime, and I'm trying to be as minimally invasive as I can.
EDIT: To clarify the above paragraph, my code is being loaded automatically by Qt as a 'fake' image plugin. The host application appears to not have set Qt::AA_SynthesizeMouseForUnhandledTouchEvents, and when I tried to do it... things did not go well. The application has it's own set of touch and gesture handlers and Widgets it uses.
One option is to subclass each QWidget type and add custom event handling for touch events. I would rather not do this, however, as it would be tedious, and I don't really need advanced touch/gesture functionality for the most part.
Another option, which I've been trialing is to install an eventFilter on the widgets, which creates mouse events from the touch events, and sends the mouse event to the widget instead. This is working quite well for many simpler widgets, and even complex widgets like QFileDialog mostly work.
My problem comes with certain widget types (I've come across two so far), which either partially work, or don't work at all, and I'm not sure where I'm going wrong.
An example of a partially working widget with the eventFilter technique is the QComboBox. The combo box itself responds to the touch, and opens the dropdown list. However, the dropdown list itself just doesn't seem to work with the touch.
QDialogButtonBox is the most recent widget I've discovered that doesn't work. The eventFilter seems to trigger on the QDialogButtonBox itself, but not the button(s) for some reason.
Here are the relevant bits of the current (very) WIP experimental code:
From NDBWidgets.h
void setTouchFilter(QWidget *w);
class TouchFilter : public QObject
{
Q_OBJECT
public:
TouchFilter(QWidget* parent = nullptr);
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
};
And from NDBWidgets.cc
static void installTouchFilter(QWidget *w)
{
if (!w->property(touchFilterSet).isValid()) {
w->setProperty(touchFilterSet, QVariant(true));
w->setAttribute(Qt::WA_AcceptTouchEvents);
auto ef = new TouchFilter(w);
w->installEventFilter(ef);
}
}
void setTouchFilter(QWidget *w)
{
if (!w) {
return;
}
auto children = w->children();
nh_log("installing eventFilter on the following children:");
for (int i = 0; i < children.size(); ++i) {
if (auto wi = qobject_cast<QWidget*>(children.at(i))) {
nh_log(" Class: %s Name: %s", children.at(i)->metaObject()->className(), children.at(i)->objectName().toUtf8().constData());
installTouchFilter(wi);
// if (auto cb = qobject_cast<QComboBox*>(wi)) {
// setTouchFilter(cb->view());
// }
}
}
installTouchFilter(w);
}
TouchFilter::TouchFilter(QWidget *parent) : QObject(parent) {}
bool TouchFilter::eventFilter(QObject *obj, QEvent *event)
{
// Only care about widgets...
auto widget = qobject_cast<QWidget*>(obj);
if (!widget) {
return false;
}
auto type = event->type();
if (type == QEvent::TouchBegin || type == QEvent::TouchUpdate || type == QEvent::TouchEnd) {
event->accept();
auto tp = static_cast<QTouchEvent*>(event)->touchPoints().at(0);
nh_log("eventFilter triggered for class: %s name: %s", widget->metaObject()->className(), widget->objectName().toUtf8().constData());
if (type == QEvent::TouchBegin) {
nh_log("event TouchBegin captured");
QMouseEvent md(QEvent::MouseButtonPress, tp.pos(), tp.screenPos(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QCoreApplication::sendEvent(widget, &md);
} else if (type == QEvent::TouchUpdate) {
nh_log("event TouchUpdate captured");
QMouseEvent mm(QEvent::MouseMove, tp.pos(), tp.screenPos(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QCoreApplication::sendEvent(widget, &mm);
} else if (type == QEvent::TouchEnd) {
nh_log("event TouchEnd captured");
QMouseEvent mu(QEvent::MouseButtonRelease, tp.pos(), tp.screenPos(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QCoreApplication::sendEvent(widget, &mu);
}
return true;
}
return false;
}
An example usage:
void NDBDbus::dlgComboBox() {
NDB_DBUS_USB_ASSERT((void) 0);
auto d = new QDialog();
d->setAttribute(Qt::WA_DeleteOnClose);
auto vl = new QVBoxLayout();
auto cb = new QComboBox(d);
cb->addItem("Item 1", QVariant(1));
cb->addItem("Item 2", QVariant(2));
vl->addWidget(cb);
auto bb = new QDialogButtonBox(QDialogButtonBox::Close, d);
connect(bb, &QDialogButtonBox::rejected, d, &QDialog::reject);
vl->addWidget(bb);
d->setLayout(vl);
setTouchFilter(d);
d->open();
}
I'm still very new to Qt event handling, and eventFilters, and I've gotten to the point where I'm rather stumped, so if anyone has any suggestions or ideas, I would very much appreciate them!
So, TLDR, and the final question: Is it possible to transform touch events to mouse events inside an eventFilter for widgets such as QComboBox or QDialogButtonBox? And if so, how?
Using the mouse events of QWidget it is possible to catch the user's interaction with the widget. However, Qt does not deliver only the resulting action (a click or a double click), but provides the whole sequence of events as it is. Thus, the action should be recognized manually and my question is: How? QMouseEvent::flags() doesn't help much, since the only flag Qt::MouseEventCreatedDoubleClick is never set, as reported here.
In other words, how to properly emit those two signals, defined in the header file of MyWidget (derived from QWidget):
void clicked();
void doubleClicked();
having those slots:
void MyWidget::mousePressEvent(QMouseEvent *event)
{
...
}
void MyWidget::mouseReleaseEvent(QMouseEvent *event)
{
...
}
void MyWidget::mouseDoubleClickEvent(QMouseEvent *event)
{
...
}
By properly I mean that if the user double clicks MyWidget only MyWidget::doubleClicked should be emited instead of MyWidget::clicked followed by MyWidget::doubleClicked;
I met the same problem before. I think it is by design of Qt. This may not be a best solution, but what you can do is to create an event filter and 'wait' for a while:
bool m_bClicked = false;
bool eventFilter(QObject* object, QEvent* event)
{
if(event->type() == QEvent::MouseButtonPress)
{
// Wait for 400 milliseconds
m_bClicked = true;
QTimer::singleShot(400, this, SLOT(eventMousePressTimer()));
return true;
}
else if(event->type() == QEvent::MouseButtonDblClick)
{
// Double-clicked
m_bClicked = false;
return true;
}
return false;
}
void eventMousePressTimer()
{
if(m_bClicked)
{
// Single-clicked
}
}
i'm trying to take screenshot with Paint event (WM_Paint) event in QT but i dont know how,
i used this code to take screenshot but it is not really good for do this
it must do screenshot when some changes in desktop not take screenshot every 1000 ms with timer
void MainWindow::shootScreen()
{
originalPixmap = QPixmap(); // clear image for low memory situations
// on embedded devices.
originalPixmap = QPixmap::grabWindow(QApplication::desktop()->winId());
//emit getScreen(originalPixmap);
updateScreenshotLabel();
}
void MainWindow::updateScreenshotLabel()
{
this->ui->label_2->setPixmap(originalPixmap.scaled(this->ui->label_2- >size(),
Qt::KeepAspectRatio,
Qt::SmoothTransformation));
}
Use QObject::installEventFilter on the widgets you are interested in, then check for the proper events. For example, in your MainWindow ui initialization:
void MainWindow::yourUiInitFunc()
{
exampleWidget = new QWidget;
...
exampleWidget->installEventFilter(this);
...
}
Then re-implement eventFilter:
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (obj == exampleWidget && event->type() == QEvent::KeyPress)
shootScreen();
return QMainWindow::eventFilter(obj, event);
}
tank you Jon Harper your codes was useful but i changed somethings there
your code just work when some key press event on my project form but i added paint method to your if and now it work on windows events too
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (obj == this && QEvent::Paint == event->type()) {
shootScreen();
}
return false;
}
In Delphi I often made an OnAfterShow event for the main form. The standard OnShow() for the form would have little but a postmessage() which would cause the OnafterShow method to be executed.
I did this so that sometimes lengthy data loading or initializations would not stop the normal loading and showing of the main form.
I'd like to do something similar in a Qt application that will run on a desktop computer either Linux or Windows.
What ways are available to me to do this?
You can override showEvent() of the window and call the function you want to be called with a single shot timer :
void MyWidget::showEvent(QShowEvent *)
{
QTimer::singleShot(50, this, SLOT(doWork());
}
This way when the windows is about to be shown, showEvent is triggered and the doWork slot would be called within a small time after it is shown.
You can also override the eventFilter in your widget and check for QEvent::Show event :
bool MyWidget::eventFilter(QObject * obj, QEvent * event)
{
if(obj == this && event->type() == QEvent::Show)
{
QTimer::singleShot(50, this, SLOT(doWork());
}
return false;
}
When using event filter approach, you should also install the event filter in the constructor by:
this->installEventFilter(this);
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;
}
I have a control with several QSpinBox objects inside a QScrollArea. All works fine when scrolling in the scroll area unless the mouse happens to be over one of the QSpinBoxes. Then the QSpinBox steals focus and the wheel events manipulate the spin box value rather than scrolling the scroll area.
I don't want to completely disable using the mouse wheel to manipulate the QSpinBox, but I only want it to happen if the user explicitly clicks or tabs into the QSpinBox. Is there a way to prevent the QSpinBox from stealing the focus from the QScrollArea?
As said in a comment to an answer below, setting Qt::StrongFocus does prevent the focus rect from appearing on the control, however it still steals the mouse wheel and adjusts the value in the spin box and stops the QScrollArea from scrolling. Same with Qt::ClickFocus.
In order to solve this, we need to care about the two following things:
The spin box mustn't gain focus by using the mouse wheel. This can be done by setting the focus policy to Qt::StrongFocus.
The spin box must only accept wheel events if it already has focus. This can be done by reimplementing QWidget::wheelEvent within a QSpinBox subclass.
Complete code for a MySpinBox class which implements this:
class MySpinBox : public QSpinBox {
Q_OBJECT
public:
MySpinBox(QWidget *parent = 0) : QSpinBox(parent) {
setFocusPolicy(Qt::StrongFocus);
}
protected:
virtual void wheelEvent(QWheelEvent *event) {
if (!hasFocus()) {
event->ignore();
} else {
QSpinBox::wheelEvent(event);
}
}
};
That's it. Note that if you don't want to create a new QSpinBox subclass, then you can also use event filters instead to solve this.
Try removing Qt::WheelFocus from the spinbox' QWidget::focusPolicy:
spin->setFocusPolicy( Qt::StrongFocus );
In addition, you need to prevent the wheel event from reaching the spinboxes. You can do that with an event filter:
explicit Widget( QWidget * parent=0 )
: QWidget( parent )
{
// setup ...
Q_FOREACH( QSpinBox * sp, findChildren<QSpinBox*>() ) {
sp->installEventFilter( this );
sp->setFocusPolicy( Qt::StrongFocus );
}
}
/* reimp */ bool eventFilter( QObject * o, QEvent * e ) {
if ( e->type() == QEvent::Wheel &&
qobject_cast<QAbstractSpinBox*>( o ) )
{
e->ignore();
return true;
}
return QWidget::eventFilter( o, e );
}
edit from Grant Limberg for completeness as this got me 90% of the way there:
In addition to what mmutz said above, I needed to do a few other things. I had to create a subclass of QSpinBox and implement focusInEvent(QFocusEvent*) and focusOutEvent(QFocusEvent*). Basically, on a focusInEvent, I change the Focus Policy to Qt::WheelFocus and on the focusOutEvent I change it back to Qt::StrongFocus.
void MySpinBox::focusInEvent(QFocusEvent*)
{
setFocusPolicy(Qt::WheelFocus);
}
void MySpinBox::focusOutEvent(QFocusEvent*)
{
setFocusPolicy(Qt::StrongFocus);
}
Additionally, the eventFilter method implementation in the event filter class changes its behavior based on the current focus policy of the spinbox subclass:
bool eventFilter(QObject *o, QEvent *e)
{
if(e->type() == QEvent::Wheel &&
qobject_cast<QAbstractSpinBox*>(o))
{
if(qobject_cast<QAbstractSpinBox*>(o)->focusPolicy() == Qt::WheelFocus)
{
e->accept();
return false;
}
else
{
e->ignore();
return true;
}
}
return QWidget::eventFilter(o, e);
}
My attempt at a solution. Easy to use, no subclassing required.
First, I created a new helper class:
#include <QObject>
class MouseWheelWidgetAdjustmentGuard : public QObject
{
public:
explicit MouseWheelWidgetAdjustmentGuard(QObject *parent);
protected:
bool eventFilter(QObject* o, QEvent* e) override;
};
#include <QEvent>
#include <QWidget>
MouseWheelWidgetAdjustmentGuard::MouseWheelWidgetAdjustmentGuard(QObject *parent) : QObject(parent)
{
}
bool MouseWheelWidgetAdjustmentGuard::eventFilter(QObject *o, QEvent *e)
{
const QWidget* widget = static_cast<QWidget*>(o);
if (e->type() == QEvent::Wheel && widget && !widget->hasFocus())
{
e->ignore();
return true;
}
return QObject::eventFilter(o, e);
}
Then I set the focus policy of the problematic widget to StrongFocus, either at runtime or in Qt Designer.
And then I install my event filter:
ui.comboBox->installEventFilter(new MouseWheelWidgetAdjustmentGuard(ui.comboBox));
Done. The MouseWheelWidgetAdjustmentGuard will be deleted automatically when the parent object - the combobox - is destroyed.
Just to expand you can do this with the eventFilter instead to remove the need to derive a new QMySpinBox type class:
bool eventFilter(QObject *obj, QEvent *event)
{
QAbstractSpinBox* spinBox = qobject_cast<QAbstractSpinBox*>(obj);
if(spinBox)
{
if(event->type() == QEvent::Wheel)
{
if(spinBox->focusPolicy() == Qt::WheelFocus)
{
event->accept();
return false;
}
else
{
event->ignore();
return true;
}
}
else if(event->type() == QEvent::FocusIn)
{
spinBox->setFocusPolicy(Qt::WheelFocus);
}
else if(event->type() == QEvent::FocusOut)
{
spinBox->setFocusPolicy(Qt::StrongFocus);
}
}
return QObject::eventFilter(obj, event);
}
With help from this post we cooked a solution for Python/PySide. If someone stumbles across this. Like we did :]
class HumbleSpinBox(QtWidgets.QDoubleSpinBox):
def __init__(self, *args):
super(HumbleSpinBox, self).__init__(*args)
self.setFocusPolicy(QtCore.Qt.StrongFocus)
def focusInEvent(self, event):
self.setFocusPolicy(QtCore.Qt.WheelFocus)
super(HumbleSpinBox, self).focusInEvent(event)
def focusOutEvent(self, event):
self.setFocusPolicy(QtCore.Qt.StrongFocus)
super(HumbleSpinBox, self).focusOutEvent(event)
def wheelEvent(self, event):
if self.hasFocus():
return super(HumbleSpinBox, self).wheelEvent(event)
else:
event.ignore()
This is my Python PyQt5 port of Violet Giraffe answer:
def preventAnnoyingSpinboxScrollBehaviour(self, control: QAbstractSpinBox) -> None:
control.setFocusPolicy(Qt.StrongFocus)
control.installEventFilter(self.MouseWheelWidgetAdjustmentGuard(control))
class MouseWheelWidgetAdjustmentGuard(QObject):
def __init__(self, parent: QObject):
super().__init__(parent)
def eventFilter(self, o: QObject, e: QEvent) -> bool:
widget: QWidget = o
if e.type() == QEvent.Wheel and not widget.hasFocus():
e.ignore()
return True
return super().eventFilter(o, e)
Just to help anyone's in need, it lacks a small detail:
call focusInEvent and focusOutEvent from QSpinBox :
void MySpinBox::focusInEvent(QFocusEvent* pEvent)
{
setFocusPolicy(Qt::WheelFocus);
QSpinBox::focusInEvent(pEvent);
}
void MySpinBox::focusOutEvent(QFocusEvent*)
{
setFocusPolicy(Qt::StrongFocus);
QSpinBox::focusOutEvent(pEvent);
}