I'm beginner in Qt and I want to drag and move Window using my own custom titleBar(QLabel).
The Qt code:
void MainWindow::mousePressEvent(QMouseEvent *event)
{
mpos = event->pos();
}
void MainWindow::mouseMoveEvent(QMouseEvent *event)
{
if (event->buttons() & Qt::LeftButton)
{
QPoint diff = event->pos() - mpos;
QPoint newpos = this->pos() + diff;
this->move(newpos);
}
}
This code allow me to move window by mouse pressed on any QWidget but I want to move window by mouse pressed on QLabel.
I know that its kinda late, but I solved this issue. The code is very similar to the implementation that Farhad suggested, but to solve the "jumping" window, you need to update the current position of the mouse also in the event filter:
if (object == ui->frame_title && event->type() == QEvent::MouseButtonPress)
{
QMouseEvent* mouseEvent = (QMouseEvent*)event;
if (pressed == false){
current = mouseEvent->pos();
}
pressed = true;
return true;
}
Adding this, you get the current mouse location when the user first press the left-click.
Here is the full implementation:
void MainWindow::mousePressEvent(QMouseEvent *event)
{
current = event->pos();
}
void MainWindow::mouseMoveEvent(QMouseEvent *event)
{
if(pressed)
this->move(mapToParent(event->pos() - current));
}
bool MainWindow::eventFilter(QObject *object, QEvent *event)
{
if (object == ui->frame_title && event->type() == QEvent::MouseButtonPress)
{
QMouseEvent* mouseEvent = (QMouseEvent*)event;
if (pressed == false){
current = mouseEvent->pos();
}
pressed = true;
return true;
}
if (object == ui->frame_title && event->type() == QEvent::MouseButtonRelease)
{
pressed = false;
return true;
}
else
return false;
}
Then in your constructor, just add (frame_title is my titlebar):
ui->frame_title->installEventFilter(this);
I suggest you to use eventFilter to get event MousePress and MouseRelease:
void MainApp::mousePressEvent(QMouseEvent *event)
{
current = event->pos();
}
void MainApp::mouseMoveEvent(QMouseEvent *event)
{
if(pressed)
this->move(mapToParent(event->pos() - current));
}
bool MainApp::eventFilter(QObject *object, QEvent *event)
{
if (object == ui->label && event->type() == QEvent::MouseButtonPress)
{
pressed = true;
return true;
}
if (object == ui->label && event->type() == QEvent::MouseButtonRelease)
{
pressed = false;
return true;
}
else
return false;
}
This is a sample project for your question on github download here.
You can re-implement QLabel class and impalement mousePressEvent
Example :
header file
#ifndef MYLABLE_H
#define MYLABLE_H
#include <QEvent>
#include <QObject>
#include <QLabel>
class MyLable : public QLabel
{
Q_OBJECT
public:
explicit MyLable(QWidget *parent = 0);
QPoint mpos;
signals:
public slots:
// QWidget interface
protected:
void mousePressEvent(QMouseEvent *);
};
#endif // MYLABLE_H
.cpp
#include "mylable.h"
#include <QMouseEvent>
MyLable::MyLable(QWidget *parent) : QLabel(parent)
{
}
void MyLable::mousePressEvent(QMouseEvent * event)
{
if (event->buttons() & Qt::LeftButton)
{
QPoint diff = event->pos() - mpos;
QPoint newpos = this->pos() + diff;
this-> parentWidget()->move(newpos);
}
}
Related
I have a scene that extends QGraphicsScene and has an event filter to handle all my scene logic and subsequent hover events etc happen correctly unless the mouse button is held down.
For example I have child elements in groups that have hoverEnterEvent and hoverLeaveEvent that get called correctly unless I'm holding the mouse. I've tried adjusting the code according to some answers and the documentation but I think I may be overlooking something really simple like a scene setting
The element header:
#ifndef IOELEMENT_H
#define IOELEMENT_H
#include <QGraphicsEllipseItem>
#include <QGraphicsItemGroup>
#include <QPen>
class IOElement : public QGraphicsEllipseItem {
public:
IOElement(QGraphicsItemGroup *parent=nullptr);
...
protected:
virtual void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;
virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override;
...
};
#endif // IOELEMENT_H
The element cpp:
#include "ioelement.h"
#include <QBrush>
#include <QDebug>
#include "../nodescene.h"
#include "../nodestyles.h"
IOElement::IOElement(QGraphicsItemGroup *parent) : QGraphicsEllipseItem(parent) {
...
setAcceptHoverEvents(true);
setFlags(QGraphicsItem::GraphicsItemFlag::ItemIsSelectable |
QGraphicsItem::GraphicsItemFlag::ItemSendsScenePositionChanges |
QGraphicsItem::ItemSendsGeometryChanges);
}
void IOElement::hoverEnterEvent(QGraphicsSceneHoverEvent *event) {
NodeScene *nodeScene = dynamic_cast<NodeScene *>(scene());
nodeScene->setChildHover(this);
QPen pen;
pen.setColor(NodeStyles::Color::Pen_Hover_Normie);
pen.setWidth(2);
setPen(pen);
update();
QGraphicsEllipseItem::hoverEnterEvent(event);
}
void IOElement::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) {
NodeScene *nodeScene = dynamic_cast<NodeScene *>(scene());
nodeScene->setChildHover(nullptr);
QPen pen;
pen.setColor(NodeStyles::Color::IO_Pen_Normie);
pen.setWidth(1);
setPen(pen);
update();
QGraphicsEllipseItem::hoverLeaveEvent(event);
}
These hover routines work as expected and I can handle the rest of the mouse logic in the scene with a filter like so
The scene header:
#ifndef NODESCENE_H
#define NODESCENE_H
#include <QGraphicsScene>
class NodeScene : public QGraphicsScene {
Q_OBJECT
public:
using QGraphicsScene::QGraphicsScene;
explicit NodeScene(QObject *parent = nullptr);
...
IOElement * getChildHover() const;
void setChildHover(IOElement * hover=nullptr);
protected:
bool eventFilter(QObject *watched, QEvent *event);
private:
...
IOElement *hoverIO;
...
};
#endif // NODESCENE_H
The scene cpp:
#include "nodescene.h"
...
#include <QGraphicsSceneMouseEvent>
#include <QKeyEvent>
#include <QDebug>
NodeScene::NodeScene(QObject *parent) : QGraphicsScene(parent) {
...
// Events
installEventFilter(this);
}
IOElement * NodeScene::getChildHover() const { return hoverIO; }
void NodeScene::setChildHover(IOElement * hover) {
hoverIO = hover;
}
bool NodeScene::eventFilter(QObject *watched, QEvent *event) {
if(watched == this) {
QGraphicsSceneMouseEvent *mouseSceneEvent;
if(event->type() == QEvent::GraphicsSceneMousePress) { // Mouse Down
if(hoverIO) {
if(hoverIO->IsOutput()) {
activeConnection = new Connection(this, hoverIO->scenePos().x()+6, hoverIO->scenePos().y()+6);
} else {
// Check if connected first
qDebug() << "Grabbing intput";
}
}
}
else if (event->type() == QEvent::GraphicsSceneMouseRelease) { // Mouse Up
delete activeConnection;
activeConnection = nullptr;
}
else if (event->type() == QEvent::GraphicsSceneMouseMove) { // Mouse Move
mouseSceneEvent = static_cast<QGraphicsSceneMouseEvent *>(event);
int x = mouseSceneEvent->lastScenePos().rx();
int y = mouseSceneEvent->lastScenePos().ry();
if(activeConnection) {
activeConnection->DrawTo(x, y);
}
}
QKeyEvent *keyEvent;
if(event->type() == QEvent::KeyPress) { // Key Press
keyEvent = static_cast<QKeyEvent *>(event);
qDebug() << keyEvent->key();
}
}
return QGraphicsScene::eventFilter(watched, event);
}
When the mouse buttons are pressed nothing at all triggers in the hover events
I have QGroupBox with sliderclasses (child class from QLabel)
Picture:
I want to add MouseEvent: when I drag one sliderclasses - another classes will move.
Movement will be as mouse trajectory.
Like this:
Picture:
sliderclass.hpp:
#ifndef SLIDERCLASS_HPP
#define SLIDERCLASS_HPP
#include <QObject>
#include <QLabel>
#include <QMouseEvent>
class sliderclass : public QLabel
{
Q_OBJECT
public:
explicit sliderclass(QWidget *parent = 0);
void mouseMoveEvent(QMouseEvent *ev);
void mousePressEvent(QMouseEvent *ev);
void mouseReleaseEvent(QMouseEvent *ev);
private:
QPoint m_dragPosition;
bool firstCIsNotNull = true;
};
#endif // SLIDERCLASS_HPP
sliderclass.cpp:
#include "sliderclass.hpp"
sliderclass::sliderclass(QWidget *parent) :
QLabel(parent)
{
}
void sliderclass::mouseMoveEvent(QMouseEvent *ev)
{
if ((ev->buttons() & Qt::LeftButton) && firstCIsNotNull){
auto tmp = this->parent()->children();
for(auto obj : tmp) {
auto cbox = static_cast<QLabel*>(obj);
cbox->move(cbox->mapToParent(ev->pos() - m_dragPosition - cbox->geometry().topLeft()));
}
}
}
void sliderclass::mousePressEvent(QMouseEvent *ev)
{
if (ev->button() == Qt::LeftButton) {
m_dragPosition = ev->pos();
firstCIsNotNull = true;
}
}
void sliderclass::mouseReleaseEvent(QMouseEvent *ev)
{
if (ev->button() == Qt::LeftButton) {
firstCIsNotNull = false;
}
}
BUT! This code doesn't work for all QLabels.
All QLabels moves not by trajectory - they all move to final pos of the first QLabel and
all QLabels are layered on top of each other.
By default, QSlider move his thumbtrack by a value belonging to the pageStep() prop on mouse click. To make thumbtrack jump directly at the mouse click point, we need to create a new class inherited by QSlider.
.h
#ifndef QIMPROVEDSLIDER_H
#define QIMPROVEDSLIDER_H
#include <QSlider>
class QImprovedSlider : public QSlider
{
Q_OBJECT
protected:
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
public:
explicit QImprovedSlider(QWidget *parent = 0);
signals:
void clicked(int value) const;
};
#endif // QIMPROVEDSLIDER_H
.cpp
#include <QWidget>
#include <QMouseEvent>
#include <QStyle>
#include <QStyleOptionSlider>
#include <QProxyStyle>
#include "QImprovedSlider.h"
class QImprovedSliderStyle : public QProxyStyle
{
public:
using QProxyStyle::QProxyStyle;
int styleHint(QStyle::StyleHint hint, const QStyleOption* option = 0,
const QWidget* widget = 0, QStyleHintReturn* returnData = 0) const
{
if (hint == QStyle::SH_Slider_AbsoluteSetButtons)
return (Qt::LeftButton | Qt::MidButton | Qt::RightButton);
return QProxyStyle::styleHint(hint, option, widget, returnData);
}
};
QImprovedSlider::QImprovedSlider(QWidget *parent) :
QSlider(parent)
{
setStyle(new QImprovedSliderStyle(this->style()));
}
void QImprovedSlider::mousePressEvent(QMouseEvent *event) {
QStyleOptionSlider opt;
initStyleOption(&opt);
QRect sr = style()->subControlRect(QStyle::CC_Slider,
&opt,
QStyle::SC_SliderHandle,
this);
qDebug() << sr.height() << sr.width();
if (!sr.contains(event->pos()) && event->button() == Qt::LeftButton) {
if (orientation() == Qt::Vertical)
setValue(minimum() + ((maximum()-minimum()) * (height()-event->y())) / height() ) ;
else
setValue(minimum() + ((maximum()-minimum()) * event->x()) / width() ) ;
}
QSlider::mousePressEvent(event);
}
void QImprovedSlider::mouseReleaseEvent(QMouseEvent *event) {
if (event->button() == Qt::LeftButton) {
emit clicked(value());
QSlider::mouseReleaseEvent(event);
}
}
QImprovedSliderStyle make the handle drag more fluid, but in this way, the event is fired even when the click falls inside the handle, while the condition
!sr.contains(event->pos())
should avoid this.
I have borderless form with button like in this question
Problem is that I can't click button. It works only if I perform double-click not changing position over the button. How to check that user i performing click (not drag)?
Here come code:
bool LoginForm::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QMouseEvent::MouseButtonPress)
{
QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
if (obj == ui.loginButton)
{
QPoint absPos = ui.loginButton->mapToParent(QPoint(0, 0));
m_dragPosition = (mouseEvent->pos() + absPos);
return true;
}
}
return false;
}
void LoginForm::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton)
{
m_dragPosition = event->globalPos() - frameGeometry().topLeft();
event->accept();
}
}
void LoginForm::mouseMoveEvent(QMouseEvent *event)
{
if (event->buttons() & Qt::LeftButton)
{
QPoint coord = event->globalPos() - m_dragPosition;
move(coord);
event->accept();
}
}
How to check that user is performing click (not drag)?
It should be easier. Don't steal the event from your button. Such approach is borrowed from Qt example: Music Player. See musicplayer.cpp for details.
void LoginForm::mousePressEvent(QMouseEvent *event)
{
m_dragPosition = event->globalPos() - pos();
event->accept();
}
void LoginForm::mouseMoveEvent(QMouseEvent *event)
{
move(event->globalPos() - m_dragPosition);
event->accept();
}
void LoginForm::mouseReleaseEvent(QMouseEvent *event)
{
m_dragPosition = QPoint();
event->accept();
}
The simpliest way is disable drag clicking on buttonbool
LoginForm::LoginForm(QWidget *parent)
: QWidget(parent)
{
...
m_isDragButton = false;
}
LoginForm::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QMouseEvent::MouseButtonPress)
{
QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
if (obj == ui.loginButton)
m_isDragButton = true;
}
return false;
}
void LoginForm::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton)
{
m_dragPosition = event->globalPos() - frameGeometry().topLeft();
event->accept();
}
}
void LoginForm::mouseMoveEvent(QMouseEvent *event)
{
if (!m_isDragButton)
if (event->buttons() & Qt::LeftButton)
{
QPoint coord = event->globalPos() - m_dragPosition;
move(coord);
event->accept();
}
}
void LoginForm::mouseReleaseEvent(QMouseEvent *event)
{
m_isDragButton = false;
m_dragPosition = QPoint();
event->accept();
}
I want a widget to animate its opacity when it is shown/hidden. I used the below code, but it does not work.
If I animate the property "maximumHeight", it gets animated in show(), but not in hide(). Could someone tell me where I am going wrong?
Header file
byeform.h
#include <QWidget>
#include <QPropertyAnimation>
namespace Ui {
class ByeForm;
}
class ByeForm : public QWidget
{
Q_OBJECT
public:
explicit ByeForm(QWidget *parent = 0);
~ByeForm();
private:
Ui::ByeForm *ui;
QPropertyAnimation *mpTransition;
protected:
bool eventFilter(QObject *obj, QEvent *event);
};
Source file
byeform.cpp
#include "byeform.h"
#include "ui_byeform.h"
#include <QDebug>
ByeForm::ByeForm(QWidget *parent) :
QWidget(parent),
ui(new Ui::ByeForm)
{
ui->setupUi(this);
this->installEventFilter(this);
mpTransition = new QPropertyAnimation(this, "windowOpacity");
mpTransition->setDuration(1000);
mpTransition->setStartValue(0.00);
mpTransition->setEndValue(1.00);
}
ByeForm::~ByeForm()
{
delete ui;
}
bool ByeForm::eventFilter(QObject *obj, QEvent *event)
{
if (this == obj && QEvent::Show == event->type())
{
qDebug() << Q_FUNC_INFO << "in show";
mpTransition->setDirection(QAbstractAnimation::Forward);
mpTransition->start();
}
else if (this == obj && (QEvent::Hide == event->type() ||
QEvent::Close == event->type()))
{
mpTransition->setDirection(QAbstractAnimation::Backward);
mpTransition->start();
}
return false;
}
Does this fix it?
bool ByeForm::eventFilter(QObject *obj, QEvent *event)
{
if (this == obj && QEvent::Show == event->type())
{
qDebug() << Q_FUNC_INFO << "in show";
mpTransition->setDirection(QAbstractAnimation::Forward);
mpTransition->start();
return true; // you might want to remove this line
}
else if (this == obj && (QEvent::Hide == event->type() ||
QEvent::Close == event->type()))
{
mpTransition->setDirection(QAbstractAnimation::Backward);
mpTransition->start();
return true; // you might want to remove this line
}
return QWidget::eventFilter(obj, event);
}
Ofcourse, it doesn't work because it's already hidden when you start the animation. You need to prolong the visibility until your animation has finished.
Like this, maybe:
void ByeForm::setVisible(bool visible)
{
if(isVisible() && !visible) // transition to hide
{
// m_bHideCalled = true;
mpTransition->setDirection(QAbstractAnimation::Backward);
mpTransition->start();
QTimer::singleShot(1000, this, SLOT(hide());
}
if(!isVisible() && visible) // transition to show
{
mpTransition->setDirection(QAbstractAnimation::Forward);
mpTransition->start();
show();
}
// if(m_bHideCalled)
// {
// m_bHideCalled = false;
// hide();
// }
}
Note that you MIGHT need the m_bHideCalled. Set it to false in the constructor. The name could be better though.
It works, but I think that it not good way to do this (I think that you should be do animate before close event will coming)
bool ByeForm::eventFilter(QObject *obj, QEvent *event)
{
if (this == obj && QEvent::Show == event->type())
{
qDebug() << Q_FUNC_INFO << "in show";
mpTransition->setDirection(QAbstractAnimation::Forward);
mpTransition->start();
}
else if (this == obj && (QEvent::Hide == event->type() ||
QEvent::Close == event->type()))
{
mpTransition->setDirection(QAbstractAnimation::Backward);
mpTransition->start();
while (mpTransition->state() == QAbstractAnimation::Running)
{
QApplication::processEvents();
}
}
return false;
}
Other method
Also you can override closeEvent method like this:
void MainWindow::closeEvent(QCloseEvent* e)
{
mpTransition->setDirection(QAbstractAnimation::Backward);
mpTransition->start();
e->ignore();
}
but in this case you should do something after animate will finished (for example connect on signal finished and call some method for closing window/application/etc).
Also you should be check, would manually call close event or by user operations.