Move a window by clicking an internal widget instead of title bar - c++

In Windows when I create a QMainWindow I can move it around the screen by clicking the title bar and dragging it.
In my application I've hidden the title bar by using setWindowFlags(Qt::CustomizeWindowHint) and I'm trying to build a custom title bar using a widget and setting it in the menu space with setMenuWidget(myWidget).
Now I want to reproduce the original behaviour: I want to click on my MyWidget widget inside the QMainWindow and, while mouse is pressed, dragging the mouse moves the window.
Is there a way to do it?

This is an example on how to implement a fake title bar, that has standard buttons (minimize, maximize, close), and can be dragged to move the whole window (this is based on the approach in #Kevin's answer).
#include <QtWidgets>
class FakeTitleBar : public QWidget{
Q_OBJECT
public:
explicit FakeTitleBar(QWidget* parent= nullptr):QWidget(parent){
label.setSizePolicy(QSizePolicy::Expanding,
QSizePolicy::Expanding);
layout.addWidget(&label);
layout.addWidget(&buttonMinimize);
layout.addWidget(&buttonMaximize);
layout.addWidget(&buttonClose);
//connecting buttons' signals to slots
connect(&buttonMinimize, &QPushButton::clicked,
this, &FakeTitleBar::MinimizeWindow);
connect(&buttonMaximize, &QPushButton::clicked,
this, &FakeTitleBar::MaximizeWindow);
connect(&buttonClose, &QPushButton::clicked,
this, &FakeTitleBar::CloseWindow);
//setting vertical fixed size policy
//so that the title bar does not take up any additional space
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
//a bit of styling
setStyleSheet("QPushButton {margin:0px; padding:5px;}"
"QWidget {background-color:blue; color:white;}");
}
public slots:
//slots for corresponding buttons
void MinimizeWindow(){
window()->showMinimized();
}
void MaximizeWindow(){
if(!window()->isMaximized())
window()->showMaximized();
else
window()->showNormal();
}
void CloseWindow(){
window()->close();
}
protected:
void mousePressEvent(QMouseEvent* event){
//save the press position (this is relative to the current widget)
pressPos= event->pos();
isMoving= true;
}
void mouseMoveEvent(QMouseEvent* event){
//isMoving flag makes sure that the drag and drop event originated
//from within the titlebar, because otherwise the window shouldn't be moved
if(isMoving){
//calculate difference between the press position and the new Mouse position
//(this is relative to the current widget)
QPoint diff= event->pos() - pressPos;
//move the window by diff
window()->move(window()->pos()+diff);
}
}
void mouseReleaseEvent(QMouseEvent* /*event*/){
//drag and drop operation end
isMoving= false;
}
//double-clicking on the title bar should maximize the window
void mouseDoubleClickEvent(QMouseEvent* /*event*/){
MaximizeWindow();
}
//in order for the style sheet to apply on this custom widget
//see https://doc.qt.io/qt-5/stylesheet-reference.html#qwidget-widget
void paintEvent(QPaintEvent *)
{
QStyleOption opt;
opt.init(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
}
private:
QHBoxLayout layout{this};
QLabel label{"Fake Title Bar"};
QPushButton buttonMinimize{"-"};
QPushButton buttonMaximize{"M"};
QPushButton buttonClose{"X"};
QPoint pressPos;
bool isMoving{false};
};
//sample usage
class Widget : public QWidget{
public:
explicit Widget(QWidget* parent= nullptr):QWidget(parent){
setWindowFlags(Qt::CustomizeWindowHint);
layout.addWidget(&titleBar);
layout.addWidget(&label);
layout.setContentsMargins(0, 0, 0, 0);
label.setAlignment(Qt::AlignCenter);
//default size for the window
resize(320,240);
}
~Widget(){}
private:
QVBoxLayout layout{this};
FakeTitleBar titleBar;
QLabel label{"this is a sample window"};
};
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
Widget w;
w.show();
return app.exec();
}
#include "main.moc"

You just need to implement the necessary mouse event handling by overwriting MyWidget's mousePressEvent(), mouseMoveEvent() and mouseReleaseEvent() handlers.
Detect the mouse down, get current mouse position
While moving, get current mouse position, calculate difference, save new position, move window by diff
You can get the window (top level widget) from inside MyWidget through the window() method.

Related

QGraphicsView artifacts when updating another views items/viewport in mouse move event handler

We are having an issue with artifacts in an application using multiple QGraphicsScene/QGraphicsViews. Essentially it seems the problem is when when in mousePosChanged event handler for a scene, and call setPos() on an item in a different scene, as well as update a region on the view for the other scene, it leaves artifacts.
I tried to set up a minimal example that hopefully will be easy to spot what is wrong
Essentially I have two scenes (scene 1 and scene 2), each with one ellipse item. When the mouse moves in the first scene, its ellipse item tracks the mouse. It also sends out a signal with mouse position. The second scene is connected to this signal, and updates the position of its ellipse item to the same location. The second graphics view is also connected to the signal, and it updates its viewport in an arbitrary location.
The second graphics view ends up with artifacts as you move the mouse around (see picture below)
This is the code from my minimal example:
class MyView : public QGraphicsView
{
Q_OBJECT
public:
MyView(QGraphicsScene *scene, QWidget *parent = 0);
public slots:
void onMouseMoveEvent();
};
class MyScene : public QGraphicsScene
{
Q_OBJECT
public:
MyScene(QObject *parent = 0);
public slots:
void setTrackerPos(const QPointF &pos);
signals:
void mousePosChanged(const QPointF &pos);
protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent);
private:
QGraphicsEllipseItem *mCursorTracker;
};
MyScene::MyScene(QObject *parent) :
QGraphicsScene(QRectF(0.,0.,1000.,1000.), parent),
mCursorTracker(new QGraphicsEllipseItem(0., 0., 50., 50.))
{
mCursorTracker->setFlag(QGraphicsItem::ItemSendsGeometryChanges);
mCursorTracker->setBrush(QBrush(Qt::red, Qt::SolidPattern));
addItem(mCursorTracker);
}
void MyScene::setTrackerPos(const QPointF &pos)
{
mCursorTracker->setPos(pos);
}
void MyScene::mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
mCursorTracker->setPos(mouseEvent->scenePos());
emit mousePosChanged(mouseEvent->scenePos());
}
MyView::MyView(QGraphicsScene *scene, QWidget *parent) :
QGraphicsView(scene, parent)
{
setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate);
setMouseTracking(true);
}
void MyView::onMouseMoveEvent(const QPointF &pos)
{
viewport()->update(QRect(0,0,250,250));
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget *w = new QWidget;
QHBoxLayout *layout = new QHBoxLayout(w);
MyScene *scene1 = new MyScene(w);
MyScene *scene2 = new MyScene(w);
MyView *view1 = new MyView(scene1, w);
MyView *view2 = new MyView(scene2, w);
layout->addWidget(view1);
layout->addWidget(view2);
QObject::connect(scene1, SIGNAL(mousePosChanged(QPointF)), scene2, SLOT(setTrackerPos(QPointF)));
QObject::connect(scene1, SIGNAL(mousePosChanged(QPointF)), view2, SLOT(onMouseMoveEvent()));
w->show();
return a.exec();
}
I understand that changing to a full update on the view will fix this. However in our real application it is too expensive to repaint the whole scene. The update on the viewport is for a small foreground layer, and only one graphics item position changes (like in this example)

How to forward all MouseEvents from QGraphicsView to its QFrame to its QMainWindow? (C++, Qt5)

I have a widget with this structure:
main->QMainWindow->QFrame->QGraphicsView->QScene->QGraphicsPixMapItem->QPixmap
I had to do it this way because im not using Qt creator or QML, just widgets. Anyway I added an event filter to my QMainindow to be movable when clicking and dragging whenever side of the window And it worked. But due to the QGraphicsView implementation if I try to drag it doesnt work but still receives input (a menu opens when i click). What makes QGraphicsview so stubborn and how do i make the window to be draggable when i click and drag on the QGraphics view, Even installing the eventFilter on the view and frame but with no results. Thanks in advance and this is my code.
This is the problem, the red square is the QGrapicsView, the white one is the MainWindow/Qframe. See how i still can make right clic on the red one and the menu still appears. I made a debug and see i get 2 objs when i click, the mainwindow and the Qgraphicview so i dont know why the move functionality just doesn work.
enter image description here
Main.cpp
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
MainWindow w;
w.show();
return QApplication::exec();
}
MainWindow.h
protected:
bool eventFilter(QObject *obj, QEvent *event) override{
if (event->type() == QEvent::MouseButtonPress ) {
qDebug() << event;
auto *ev = (QMouseEvent *) event;
if (ev->button() == Qt::RightButton) {
//Opens menu, it works well when i click on the QGraphics as well
auto *menu = new QMenu(this);
auto *idle = new QAction("Idle", this);
auto *close = new QAction("Quit", this);
menu->addAction(idle);
menu->popup(ev->globalPos());
connect(close, &QAction::triggered, [](){QCoreApplication::quit();});
connect(idle, &QAction::triggered, [this](){changeDance(0);});
}
else{
// "grab" the mainWindow
this->oldPos = ev->globalPos();
}
}
if (event->type() == QEvent::MouseMove) {
//drag the window
auto *ev = (QMouseEvent *) event;
const QPoint delta = ev->globalPos() - oldPos;
move(x() + delta.x(), y() + delta.y());
oldPos = ev->globalPos();
}
}
MainWindow.cpp
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent) {
setWindowTitle("waifu"); //waifu
setWindowFlags(Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint);
setFixedSize(screenWidth, screenHeight);
//setAttribute(Qt::WA_TranslucentBackground, true);
setWindowFlags(Qt::Widget | Qt::FramelessWindowHint);
setWindowFlags(Qt::BypassGraphicsProxyWidget);
//mainWindow->frame->view->scene->pixmap
installEventFilter(this);
frame = new Frame(this);
setCentralWidget(frame);
view = new View(frame);
view->setFixedSize(50, 50);
scene = new Scene(view);
view->setScene(this->scene); // That connects the view with the scene
frame->layout()->addWidget(view);
myItem = new QGraphicsPixmapItem(*new QPixmap());
//scene->setBackgroundBrush(QBrush(Qt::yellow));
scene->addItem(myItem);
view->show();
Frame.h
class Frame : public QFrame {
Q_OBJECT
public:
explicit Frame(QMainWindow *parent = 0) : QFrame(parent) {
setMouseTracking(true);
setStyleSheet("background-color: red;"); //delete
setLayout(new QVBoxLayout);
layout()->setContentsMargins(0, 0, 0, 0);
setWindowFlags(Qt::FramelessWindowHint); //No windowing
setAttribute(Qt::WA_TranslucentBackground); // No background
setStyleSheet("background-color: transparent;");
};
};
QGraphicsView.h
class View : public QGraphicsView {
Q_OBJECT
public:
explicit View(QFrame *parent) : QGraphicsView(parent) {
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setMouseTracking(true);
};
protected:
private:
};
Tried to install the event filter located in the main window, in the Qframe and QGraphicsView class. with the parameter event filter being *parent.

Changing the QPushButton region mask in its subclass to create a RoundButton

I am trying to create a round button by subclassing and setting the region mask so that I can reuse it in my project. I know we can override paintEvent method and draw a circle to show it as a round button. But the problem with this approach is that if user clicks outside the circle (but within button rect) it will be treated as a button click. This problem we don't see when set the region mask.
I tried to set the region by calling setmask method inside resizeEvent/paintEvent. In either of case, button will be blank. I am trying to figure out the place inside the subclass to set the region mask.
RoundAnimatingButton.h ->
#include <QPushButton>
namespace Ui {
class CRoundAnimatingBtn;
}
class CRoundAnimatingBtn : public QPushButton
{
Q_OBJECT
public:
explicit CRoundAnimatingBtn(QWidget *parent = nullptr);
~CRoundAnimatingBtn();
void StartAnimation(QColor r);
void StopAnimation();
public slots:
void timerEvent(QTimerEvent *e);
private:
Ui::CRoundAnimatingBtn *ui;
bool m_Spinning;
// QWidget interface
protected:
void resizeEvent(QResizeEvent *event) override;
void paintEvent(QPaintEvent * e) override;
};
#endif // ROUNDANIMATINGBTN_H
RoundAnimatingButton.cpp
CRoundAnimatingBtn::CRoundAnimatingBtn(QWidget *parent)
: QPushButton (parent)
, ui(new Ui::CRoundAnimatingBtn)
, m_Spinning(false)
{
ui->setupUi(this);
}
CRoundAnimatingBtn::~CRoundAnimatingBtn()
{
delete ui;
}
void CRoundAnimatingBtn::paintEvent(QPaintEvent *e)
{
QPushButton::paintEvent(e);
if(m_Spinning)
{
// Animating code
}
}
void CRoundAnimatingBtn::StartAnimation(QColor r)
{
m_Spinning=true;
startTimer(5);
}
void CRoundAnimatingBtn::StopAnimation()
{
m_Spinning=false;
this->update();
}
void CRoundAnimatingBtn::timerEvent(QTimerEvent *e)
{
if(m_Spinning)
this->update();
else
killTimer(e->timerId());
}
void CRoundAnimatingBtn::DrawRing()
{
}
void CRoundAnimatingBtn::resizeEvent(QResizeEvent *event)
{
// -----------------------------------
// This code didn't work
// -----------------------------------
QRect rect = this->geometry();
QRegion region(rect, QRegion::Ellipse);
qDebug() << "PaintEvent Reound button - " << region.boundingRect().size();
this->setMask(region);
// ----------------------------------
// ------------------------------------
// This code worked
// -------------------------------------
int side = qMin(width(), height());
QRegion maskedRegion(width() / 2 - side / 2, height() / 2 - side / 2, side,
side, QRegion::Ellipse);
setMask(maskedRegion);
}
Qt doc. provides a sample for “non-rectangular” widgets – Shaped Clock Example.
(Un-)Fortunately, I remembered this not before I got my own sample running.
I started in Qt doc. with
void QWidget::setMask(const QBitmap &bitmap)
Causes only the pixels of the widget for which bitmap has a corresponding 1 bit to be visible. If the region includes pixels outside the rect() of the widget, window system controls in that area may or may not be visible, depending on the platform.
Note that this effect can be slow if the region is particularly complex.
The following code shows how an image with an alpha channel can be used to generate a mask for a widget:
QLabel topLevelLabel;
QPixmap pixmap(":/images/tux.png");
topLevelLabel.setPixmap(pixmap);
topLevelLabel.setMask(pixmap.mask());
The label shown by this code is masked using the image it contains, giving the appearance that an irregularly-shaped image is being drawn directly onto the screen.
Masked widgets receive mouse events only on their visible portions.
See also mask(), clearMask(), windowOpacity(), and Shaped Clock Example.
(When reading this, I still missed the link to example.)
At first, I prepared a suitable pixmap for my purpose – dialog-error.png:
for which I converted an SVG from one of my applications.
I tried to apply it to a QPushButton as icon and as mask. This looked very strange. I'm not quite sure what exactly was the problem:
- using the resp. QPushButton as toplevel widget (i.e. main window)
- the fact that QPushButtons icon rendering and the mask may not match concerning position or size.
Without digging deeper, I changed the code and fixed both issues in next try:
making a derived button (like described by OP)
using the button as non-toplevel widget.
This worked soon. I added some code to make the effect more obvious:
a mouse press event handler for main window to show whether shape is considered correctly
a signal handler to show whether clicks on button (in shape) are received correctly.
So, I came to the following sample – testQPushButtonMask.cc:
#include <QtWidgets>
class MainWindow: public QWidget {
public:
explicit MainWindow(QWidget *pQParent = nullptr):
QWidget(pQParent)
{ }
virtual ~MainWindow() = default;
MainWindow(const MainWindow&) = delete;
MainWindow& operator=(const MainWindow&) = delete;
protected:
virtual void mousePressEvent(QMouseEvent *pQEvent) override;
};
void MainWindow::mousePressEvent(QMouseEvent *pQEvent)
{
qDebug() << "MainWindow::mousePressEvent:" << pQEvent->pos();
QWidget::mousePressEvent(pQEvent);
}
class RoundButton: public QPushButton {
private:
QPixmap _qPixmap;
public:
RoundButton(const QPixmap &qPixmap, QWidget *pQParent = nullptr):
QPushButton(pQParent),
_qPixmap(qPixmap)
{
setMask(_qPixmap.mask());
}
virtual ~RoundButton() = default;
RoundButton(const RoundButton&) = delete;
RoundButton& operator=(const RoundButton&) = delete;
virtual QSize sizeHint() const override;
protected:
virtual void paintEvent(QPaintEvent *pQEvent) override;
};
QSize RoundButton::sizeHint() const { return _qPixmap.size(); }
void RoundButton::paintEvent(QPaintEvent*)
{
QPainter qPainter(this);
const int xy = isDown() * -2;
qPainter.drawPixmap(xy, xy, _qPixmap);
}
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
QPixmap qPixmap("./dialog-error.png");
// setup GUI
MainWindow qWin;
qWin.setWindowTitle(QString::fromUtf8("QPushButton with Mask"));
QVBoxLayout qVBox;
RoundButton qBtn(qPixmap);
qVBox.addWidget(&qBtn);
qWin.setLayout(&qVBox);
qWin.show();
// install signal handlers
QObject::connect(&qBtn, &RoundButton::clicked,
[](bool) { qDebug() << "RoundButton::clicked()"; });
// runtime loop
return app.exec();
}
The corresponding Qt project file testQPushButtonMask.pro
SOURCES = testQPushButtonMask.cc
QT += widgets
Compiled and tested on cygwin64:
$ qmake-qt5 testQPushButtonMask.pro
$ make && ./testQPushButtonMask
Qt Version: 5.9.4
MainWindow::mousePressEvent: QPoint(23,22)
MainWindow::mousePressEvent: QPoint(62,24)
MainWindow::mousePressEvent: QPoint(62,61)
MainWindow::mousePressEvent: QPoint(22,60)
RoundButton::clicked()
Concerning the output:
I clicked into the four corners of button.
I clicked on the center of button.

How can I edit the default window title and frame in a Qt application?

I want to create a personalized UI with my Qt application. For this reason I want to edit the default window frame that you can see at any type of window application.
The application that contains the default window title and frame:
http://0000.2.img98.net/out.php/i12977_with-default-title-and-frame.jpg
The application that edit the window title and frame:
http://0000.2.img98.net/out.php/i12978_without-defualt-title-andframe.jpg
You need to create a new class derived from QWidget, and pass Qt::FramelessWindowHint argument to QWidget constructor, like this:
class MyWidget : public QWidget {
public:
MyWidget(QWidget* parent) : QWidget(parent, Qt::FramelessWindowHint) {...}
After it you need to reimplement QWidget::paintEvent (QPaintEvent * event) and draw any design you want.
For example, you have main window design as a PNG image.
class MyWidget : public QWidget {
Q_OBJECT
private:
QPushButton* button;
QLabel* label;
QComboBox* combobox;
QPixmap pixmap;
public:
explicit MyWidget(QWidget *parent = 0) : QWidget(parent, Qt::FramelessWindowHint) {
// Create some controls
button = new QPushButton();
label = new QLabel();
combobox = new QComboBox();
QVBoxLayout* l = new QVBoxLayout();
l->addWidget(button);
l->addWidget(label);
l->addWidget(combobox);
setLayout(l);
resize (500, 500);
setAttribute(Qt::WA_TranslucentBackground); // enable translucent background
pixmap = QPixmap("./1.png"); // load design picture
};
protected:
virtual void paintEvent (QPaintEvent* event) {
painter.setPen(Qt::NoPen);
painter.setBrush(QColor(0, 0, 0, 0));
painter.drawRect(this->rect());
painter.drawPixmap(this->rect(), pixmap, pixmap.rect());
};
Since we don't see a titlebar, we need to implement window drag operation:
private:
bool pressed;
QPoint mousePressPoint;
protected:
virtual void mousePressEvent ( QMouseEvent * event ) {
QWidget::mousePressEvent(event);
if (!pressed) {
pressed = true;
mousePressPoint = event->pos();
}
}
virtual void mouseMoveEvent ( QMouseEvent * event ) {
QWidget::mouseMoveEvent(event);
if (pressed) {move(event->globalPos() - mousePressPoint);}
}
virtual void mouseReleaseEvent ( QMouseEvent * event ) {
QWidget::mouseReleaseEvent(event);
if (pressed) {pressed = false;}
}

QGraphicsView possible bug?

The example code is from my project. I've tried to make it as short as possible and to the point.
The overlay is used to draw over all the other widgets in the app. This works for most widgets, but today I've started to notice that QAbstractScrollArea subclasses are giving me a hard time. The problem is that the overlay appears not on top, and whatever drawing that happens is blocked.
#include <QtGui/QApplication>
#include <QtGui/QVBoxLayout>
#include <QtGui/QGraphicsView>
#include <QtGui/QPushButton>
class View : public QGraphicsView{
public:
View(){
//delete viewport(); setViewport(new QWidget);
}
};
class Widget : public QWidget{
QWidget* overlay_;
public:
Widget(){
resize(512, 512);
QVBoxLayout* layout = new QVBoxLayout;
QPushButton* button = new QPushButton(" Click Me! ");
layout->addWidget(button);
layout->addWidget(new View);
overlay_ = new QWidget(this);
overlay_->installEventFilter(this);
connect(button, SIGNAL(clicked()),
overlay_, SLOT(show()));
overlay_->hide();
setLayout(layout);
}
bool eventFilter(QObject* target, QEvent* event){
if(target == overlay_){
if(event->type() == QEvent::Paint && overlay_->isVisible()){
overlay_->resize(size());
QPainter painter(overlay_);
painter.setPen(QPen(QColor(1, 102, 192, 255), 1, Qt::SolidLine,
Qt::FlatCap, Qt::MiterJoin));
painter.drawRect(rect().adjusted(60, 0, -60, 0));
return true;
}
}
}
};
int main(int argc, char *argv[]){
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
To fix this in this example and have overlay go on top of View, you'll need to uncomment the commented line at the top. So my question is this: why do I need to delete and assign a new viewport widget in the constructor in order for overlay not get overdrawn?
This isn't a bug with QGraphicsView, it will happen if you use a standard QScrollArea as well.
The issue, I think, is the order in which Qt draws child widgets. Sibling widgets are drawn in the order they are added to the parent (although you can't rely on this).
The reason that resetting the viewport "solved" the problem is because when you do that you create a new QWidget that has no background to be the viewport. The QGraphicsView is still being drawn over the overlay_, it just has a transparent viewport. Notice how it's still drawn behind the pushbutton, however.
If you want to draw an overlay only over the QGraphicsView, you can override QGraphicsView::paintEvent() and do it there. If you want to draw the overlay over your entire widget, I would embed your layout inside a second QWidget and then try using QWidget::raise() to force the overlay visually to the top.