How to draw a point (on mouseclick) on a QGraphicsScene? - c++

I have the following code to set up a QGraphicsScene. I wish to click on the scene and draw a point at the location I've clicked. How could I do this? This is my current code:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
QGraphicsScene *scene;
QGraphicsView *view = new QGraphicsView(this);
view->setGeometry(QRect(20, 50, 400, 400));
scene = new QGraphicsScene(50, 50, 350, 350);
view->setScene(scene);
}

UPDATE: There is a new class called QGraphicsSceneMouseEvent that makes this a little easier.
I just finished an example using it here:
https://stackoverflow.com/a/26903599/999943
It differs with the answer below in that it subclasses QGraphicsScene, not QGraphicsView, and it uses mouseEvent->scenePos() so there isn't a need to manually map coordinates.
You are on the right track, but you still have a little more to go.
You need to subclass QGraphicsView to be able to do something with mouse presses or with mouse releases using QMouseEvent.
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsEllipseItem>
#include <QMouseEvent>
class MyQGraphicsView : public QGraphicsView
{
Q_OBJECT
public:
explicit MyQGraphicsView(QWidget *parent = 0);
signals:
public slots:
void mousePressEvent(QMouseEvent * e);
// void mouseReleaseEvent(QMouseEvent * e);
// void mouseDoubleClickEvent(QMouseEvent * e);
// void mouseMoveEvent(QMouseEvent * e);
private:
QGraphicsScene * scene;
};
QGraphicsView doesn't natively have dimension-less points. You will probably want to use QGraphicsEllipse item or simply, scene->addEllipseItem() with a very small radius.
#include "myqgraphicsview.h"
#include <QPointF>
MyQGraphicsView::MyQGraphicsView(QWidget *parent) :
QGraphicsView(parent)
{
scene = new QGraphicsScene();
this->setSceneRect(50, 50, 350, 350);
this->setScene(scene);
}
void MyQGraphicsView::mousePressEvent(QMouseEvent * e)
{
double rad = 1;
QPointF pt = mapToScene(e->pos());
scene->addEllipse(pt.x()-rad, pt.y()-rad, rad*2.0, rad*2.0,
QPen(), QBrush(Qt::SolidPattern));
}
Note the usage of mapToScene() to make the pos() of the event map correctly to where the mouse is clicked on the scene.
You need to add an instance of your subclassed QGraphicsView to the centralWidget's layout of your ui if you are going to use a form.
QGridLayout * gridLayout = new QGridLayout(ui->centralWidget);
gridLayout->addWidget( new MyQGraphicsView() );
or if your ui has a layout already it will look like this:
ui->centralWidget->layout()->addWidget( new MyGraphicsView() );
If you don't use a QMainWindow and a form, you can add it to a QWidget if you set a layout for it and then add your QGraphicsView to that layout in a similar manner. If you don't want a margin around your QGraphicsView, just call show on it and don't put it inside a different layout.
#include <QtGui/QApplication>
#include "myqgraphicsview.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MyQGraphicsView view;
view.show();
return a.exec();
}
And that's it. Now you are dangerous with QGraphicsView's and their interaction with the mouse.
Be sure to read and study about Qt's Graphics View Framework and the related examples to be effective when using QGraphicsView and QGraphicsScene. They are very powerful tools for 2D graphics and can have a bit of a learning curve but they are worth it.

Related

How to make invisible button on widget with background image?

I want to make a simple application with invisible button.
I set background image for my widget by UI property styleSheet and Resources -
border-image:url(:/image.jpg).
I always get something like this
and then I try to add button on it
I was trying with
ui->pushButton->setStyleSheet("QPushButton{background: transparent;}");
ui->pushButton->setStyleSheet("background-color: rgba(255, 255, 255, 0);");
and it works with buttons on default background, but not in my case.
Every button that I add takes default parent background image. I dont want to see any hints of a button, but when I click on an area to be able to perform some functionality.
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->centralWidget->setStyleSheet("background-image:url(:image.jpg)");
ui->pushButton->setStyleSheet("QPushButton{border:none;}");
}
Code an above makes button flat, but it duplicate background image from parent widget anyway.
Have you any idea how to resolve it?
Cause
A common misconception is that when a stylesheet without a selector is applied to an element, then it is used only for that element. In fact all element's children are styled as well. Thus a selector should be used to achieve the expected result.
Solution
I would suggest you to change this line in your code
ui->centralWidget->setStyleSheet("background-image:url(:image.jpg)");
to
ui->centralWidget->setStyleSheet(".QWidget { background-image:url(:image.jpg) }");
Important: Note the dot before QWidget. It means style the QWidget, but exclude the subclasses. This is necessary because QPushButton is a subclass of QWidget and otherwise would be affected as well.
Then you can set the pushButton's backgroung color to transparent as you do with
ui->pushButton->setStyleSheet("QPushButton{background: transparent;}");
Example
Here is a simple example I have prepared for you in order to demonstrate the proposed solution (requires cat.png in the resource file under pix/images):
#include <QMainWindow>
#include <QWidget>
#include <QPushButton>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr) :
QMainWindow(parent) {
auto *widget = new QWidget(this);
auto *button = new QPushButton(widget);
widget->setStyleSheet(".QWidget {"
" background-image:url(':/pix/images/cat.png');"
" background-repeat: no-repeat;"
"}");
button->setStyleSheet(".QPushButton {"
" background-color: transparent"
"}");
button->move(100, 100);
button->resize(100, 100);
connect(button, &QPushButton::clicked, [](){
qDebug("clicked");
});
setCentralWidget(widget);
resize(600, 480);
}
};
Result
The given example produces a window with a background and a 100x100px invisible clickable area positioned at (100, 100):
I think it's better to answer here than in comments.
You just have to set the following stylesheet for your QPushButton to make it invisible:
QPushButton
{
border: none;
}
I've made the test and it worked well.
For the tests, I have set the wrapping widget's background-image property. I also did another test with the background-color property instead. It worked in both cases (whether the background is a plain color or a picture/photo).
I hope it helps.
EDIT:
I have written a widget that performs what you want. And I also provided a windows in order to make the below example minimal and complete so that you can reproduce it.
I have tested it and it worked well.
test.h:
#ifndef TEST_H
#define TEST_H
#include <QMainWindow>
#include <QPushButton>
class WidgetWithHiddenButton : public QWidget
{
Q_OBJECT
protected:
QPushButton * invisible_button;
public:
WidgetWithHiddenButton(QWidget * parent = nullptr);
QPushButton * getButton();
protected:
void paintEvent(QPaintEvent *) override;
};
class TestWindow final : public QMainWindow
{
Q_OBJECT
private:
WidgetWithHiddenButton * widget;
public:
TestWindow();
};
#endif // TEST_H
test.cpp:
#include "test.h"
#include <QApplication>
#include <QStyleOption>
#include <QPainter>
#include <QVBoxLayout>
WidgetWithHiddenButton::WidgetWithHiddenButton(QWidget * parent) : QWidget(parent)
{
// build your widget as you want.
invisible_button = new QPushButton("Here is a button", this);
QVBoxLayout * lay = new QVBoxLayout;
QHBoxLayout * inner_lay = new QHBoxLayout;
inner_lay->addStretch();
inner_lay->addWidget(invisible_button);
inner_lay->addStretch();
lay->addLayout(inner_lay);
this->setLayout(lay);
this->setStyleSheet("WidgetWithHiddenButton {background-image: url(path_to_image/image.jpg);}");
invisible_button->setStyleSheet("QPushButton {border: none;}");
}
QPushButton * WidgetWithHiddenButton::getButton()
{
return invisible_button;
}
void WidgetWithHiddenButton::paintEvent(QPaintEvent *)
{
QStyleOption opt;
opt.init(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
}
TestWindow::TestWindow()
{
resize(500, 300);
widget = new WidgetWithHiddenButton;
this->setCentralWidget(widget);
connect(widget->getButton(), &QPushButton::clicked, qApp, &QApplication::quit);
}
int main(int argc, char ** argv)
{
QApplication app(argc, argv);
TestWindow tw;
tw.show();
return app.exec();
}
Feel free to adapt it (especially by changing the class name because WidgetWithHiddenButton is very ugly :) ).
Notes:
I have written a text in the button in order to make it visible (for tests purposes) but you can remove it if you want the button completely invisible.
I connected the QPushButton::clicked() signal to the QApplication::quit() slot in order to perform an action when we click on the area of the button.
I redefined the paintEvent() method because it is needed when using Q_OBJECT macro alongside stylesheets over a custom QWidget as the documentation mentioned.
Feel free to modify the way I build the widget in the constructor (layouts, sizes, ...) to make it fit your requirements.

QMenu not execing at correct position first time

I have this very strange issue regarding a QMenu and its position when execing.
Here is the code for my subclassed QMenu:
DockItemContextMenu::DockItemContextMenu(QWidget *parent) : QMenu(parent){
style = qApp->style();
QPointer<QAction> restoreAction = new QAction(QIcon(style->standardIcon(QStyle::SP_TitleBarMaxButton)), "Restore", this);
QPointer<QAction> minimizeAction = new QAction(style->standardIcon(QStyle::SP_TitleBarMinButton), "Minimize", this);
QPointer<QAction> maximizeAction = new QAction(style->standardIcon(QStyle::SP_TitleBarMaxButton), "Maximize", this);
QPointer<QAction> stayOnTopAction = new QAction("Stay On Top", this);
stayOnTopAction->setCheckable(true);
QPointer<QAction> closeAction = new QAction(style->standardIcon(QStyle::SP_TitleBarCloseButton), "Close", this);
this->addActions({restoreAction, minimizeAction, maximizeAction, stayOnTopAction, closeAction});
connect(restoreAction, &QAction::triggered, parent, [this](){ emit restoreTriggered();}, Qt::QueuedConnection);
connect(minimizeAction, &QAction::triggered, parent, [this](){ emit minimizeTriggered();}, Qt::QueuedConnection);
connect(maximizeAction, &QAction::triggered, parent, [this](){ emit maximizeTriggered();}, Qt::QueuedConnection);
connect(stayOnTopAction, &QAction::triggered, parent, [this](){ emit stayOnTopTriggered();}, Qt::QueuedConnection);
connect(closeAction, &QAction::triggered, parent, [this](){ emit closeTriggered();}, Qt::QueuedConnection);
}
Okay, so essentially I have another widget who holds an instance of this DockItemContextMenu as a field. In this owning class, called Titlebar, I made it such that doing a right click will emit the customContextMenuRequested(QPoint) signal.
TitleBar::TitleBar(QString title, QWidget *parent){
...
this->setContextMenuPolicy(Qt::CustomContextMenu);
contextMenu = new DockItemContextMenu(this);
connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint)), Qt::QueuedConnection);
...
}
After this, this widget is essentially inserted into a QGraphicsScene and is converted implicitly into a QGraphicsItem. When I do the FIRST right click event on my Titlebar it will not exec at the correct screen position if I dragged the MainWindow of the entire QApplication anywhere other than its starting position on screen. In addition to being in a QGraphicsScene, this scene itself is always stored in a QSplitter. Now I would understand if this always had some sort of issue, but it turns out, every time I call the slot for that signal, ONLY the first time will it exec in the incorrect position in the QGraphicsScene. No matter how I manipulate the size of the Titlebar widget itself, move commands or maximize commands to the MainWindow, or even edit the splitter size for the QGraphicsView that affects the size of the QGraphicsScene, it will always be in the correct position afterwards. here is the function for execing:
void TitleBar::showContextMenu(QPoint point){
qDebug() << point;
contextMenu->exec(point);
emit _parent->focusChangedIn();
}
I printed the point at which it is calling the exec. The strangest part is that both times I right click in the same location, it will print the SAME value for the slot's positional parameter both the first exec and second exec, but be in the correct location every time other than the first. Did I forget to set some other flag when I added the context menu to the Titlebar class? Does it have anything to do with setting the QMenu's parent to the Titlebar? I'm just dumbfounded how the same QPoint could exec at two different screen locations given the same value. Does anybody have a clue what may or may not be happening on the first call to the Titlebar's slot for execing the QMenu?
EDIT: The issue stemmed from doing this line of code in the Titlebar constructor:
contextMenu = new DockItemContextMenu(this);
Changing it to:
contextMenu = new DockItemContextMenu;
fixed the issue. Does anyone know why, or is this possibly a bug? I rather not accept this as an answer because it does not explain why it happened in the first place.
Here is a minimal example with the same effect.
MainWindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QWidget>
#include <QGraphicsView>
#include <QSplitter>
#include <QHBoxLayout>
#include <QGraphicsScene>
#include <QPointer>
#include <QTreeWidget>
#include "titlebar.h"
class MainWindow : public QMainWindow{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
private:
};
#endif // MAINWINDOW_H
MainWindow.cpp:
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent){
QPointer<QWidget> widgetArea = new QWidget;
QPointer<QHBoxLayout> hLayout = new QHBoxLayout;
widgetArea->setLayout(hLayout);
QPointer<QSplitter> splitter = new QSplitter;
hLayout->addWidget(splitter);
QPointer<QTreeView> tree = new QTreeView;
splitter->addWidget(tree);
QPointer<QGraphicsView> view = new QGraphicsView;
splitter->addWidget(view);
splitter->setStretchFactor(0, 1);
splitter->setStretchFactor(1, 4);
QPointer<QGraphicsScene> scene = new QGraphicsScene;
view->setScene(scene);
QPointer<Titlebar> blue = new Titlebar;
blue->setObjectName("blue");
blue->setStyleSheet(QString("#blue{background-color: rgb(0,0,255)}"));
blue->resize(250,250);
scene->addWidget(blue);
this->setCentralWidget(widgetArea);
this->resize(1000,750);
}
MainWindow::~MainWindow(){
}
Titlebar.h:
#ifndef TITLEBAR_H
#define TITLEBAR_H
#include <QMenu>
#include <QWidget>
#include <QPointer>
#include <QDebug>
#include <QMouseEvent>
class Titlebar : public QWidget{
Q_OBJECT
public:
explicit Titlebar(QWidget *parent = nullptr);
QPointer<QMenu> menu;
QPoint currentPos;
protected slots:
void mousePressEvent(QMouseEvent* event);
void mouseMoveEvent(QMouseEvent* event);
void showContextMenu(QPoint point);
};
#endif // TITLEBAR_H
Titlebar.cpp:
#include "titlebar.h"
Titlebar::Titlebar(QWidget *parent) : QWidget(parent){
setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint)), Qt::QueuedConnection);
menu = new QMenu(this);
menu->addAction("Test");
}
void Titlebar::showContextMenu(QPoint point){
qDebug() << point;
menu->exec(mapToGlobal(point));
}
void Titlebar::mouseMoveEvent(QMouseEvent *event){
if (event->buttons() && Qt::LeftButton){
QPoint diff = event->pos() - currentPos;
move(pos() + diff);
}
}
void Titlebar::mousePressEvent(QMouseEvent * event){
currentPos = event->pos();
}
main.cpp:
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
So this runs and reproduces the error accordingly. If you change the line in Titlebar.cpp from
menu = new QMenu(this);
to:
menu = new QMenu;
Then it works correctly. ONLY the first right click to open the context menu will spawn in the incorrect location on screen. All subsequent right clicks will now follow either the widget/window/splitter in any combination. I don't get it, can someone tell me if this is actually a bug or not.
You need to add one line of code because your using a QGraphicsProxyWidget which is part of a QGraphicsScene. The scene is represented by a QGraphicsView which inherits QAbstractScrollArea. This causes the context menu to be shown via the viewport and not the widget itself. Therefore adding this one line of code will override the title bar to not be embedded in the scene when it's parent was already embedded in the scene. Effectively making it reference the widget again and not the viewport.
In the MainWindow.cpp right after line 26 add
blue->setWindowFlags(Qt::BypassGraphicsProxyWidget);

move QGraphicsView around QGraphicsScene

I'm making a side view drag racing game in QT c++. I want to move my view around my scene from left to right. I have the scene set to 3600x800, but i want the view the be at the far left of my scene not at the center at the start. When i press W on my keyboard I want the view to move to the left for 1px. How do I do that? I can't find anything online
scene=new QGraphicsScene(this);
view = new QGraphicsView;
scene->setSceneRect(0,0,3600,800);
view->setScene(scene);
You will never find something so particular on the internet, you should look for each part separately:
If you want it to appear on the left side then you must use horizontalScrollBar() of the GraphicsView when it is displayed, we can do that with the showEvent method.
if you want to do an action when you press any key you could overwrite the keyPressEvent method.
To move the sceneRect() you must make a copy, move it and set it again.
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QKeyEvent>
#include <QScrollBar>
class GraphicsView: public QGraphicsView{
public:
using QGraphicsView::QGraphicsView;
protected:
void keyPressEvent(QKeyEvent *event){
if(event->key() == Qt::Key_W){
if(scene()){
QRectF rect = scene()->sceneRect();
rect.translate(1, 0);
scene()->setSceneRect(rect);
}
}
}
void showEvent(QShowEvent *event){
QGraphicsView::showEvent(event);
if(isVisible()){
horizontalScrollBar()->setValue(0);
}
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene;
GraphicsView view;
scene.setSceneRect(0,0,3600,800);
view.setScene(&scene);
scene.addRect(0, 200, 400, 400, Qt::NoPen, Qt::red);
view.show();
return a.exec();
}

QGraphicsView::NoViewportUpdate doesn't work

I have a very simple window with a QGraphicsView, a QGraphicsScene inside, and a simple QPushButton. When user clicks button, a line should be added to the scene. However, since I set QGraphicsView::NoViewportUpdate, the line shouldn't be displayed. On the opposite, the line gets displayed.
According to the documentation, QGraphicsView will never update its viewport when the scene changes; the user is expected to control all updates. This mode disables all (potentially slow) item visibility testing in QGraphicsView, and is suitable for scenes that either require a fixed frame rate, or where the viewport is otherwise updated externally.
How do I solve this problem?
Here is the code:
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QWidget>
#include <QPushButton>
class MainWindow : public QWidget
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
private:
QGraphicsView* view;
QGraphicsScene* scene;
QPushButton* b;
public slots:
void start();
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include <QVBoxLayout>
MainWindow::MainWindow(QWidget *parent)
: QWidget(parent)
{
scene = new QGraphicsScene(0, 0, 400, 400);
view = new QGraphicsView(scene);
view->setViewportUpdateMode(QGraphicsView::NoViewportUpdate);
b = new QPushButton("Start");
connect (b, &QPushButton::clicked, this, &MainWindow::start);
QVBoxLayout* layout = new QVBoxLayout;
layout->addWidget(view);
layout->addWidget(b);
setLayout(layout);
}
MainWindow::~MainWindow()
{
}
void MainWindow::start()
{
scene->addLine(0, 0, 200, 200);
}
I "solved" that. I discovered the viewport doesn't get updated if you do NOT hover (for example) it with the mouse. So, if you do not interact, viewport does not update. However, viewport does not update if you scroll with the mouse wheel inside the qGraphicsView.

Qt: Custom widget in QScrollArea

I am attempting to create a custom widget. My Widget renders itself unless it is inside a scroll area. The code below works. If I change the if(0) to an if(1) inside the MainWindow constructor, it will not render the "Hello World" string. I assume that I must (re)implement some additional methods, but so far I have not been able to find the correct ones with trial and error.
// hellowidget.h
#ifndef HELLOWIDGET_H
#define HELLOWIDGET_H
#include <QtGui>
class HelloWidget : public QWidget
{
Q_OBJECT
public:
HelloWidget(QWidget *parent = 0);
void paintEvent(QPaintEvent *event);
};
#endif // HELLOWIDGET_H
// hellowidget.cpp
#include "hellowidget.h"
HelloWidget::HelloWidget(QWidget *parent)
: QWidget(parent)
{
}
void HelloWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.drawText(rect(), Qt::AlignCenter, "Hello World");
}
// mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QtGui>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
private:
};
#endif // MAINWINDOW_H
// mainwindow.cpp
#include "mainwindow.h"
#include "hellowidget.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
HelloWidget *hello = new HelloWidget;
QWidget *central = hello;
if( 0 )
{
QScrollArea *scroll = new QScrollArea ;
scroll->setWidget(hello);
central = scroll;
}
setCentralWidget( central );
}
MainWindow::~MainWindow()
{
}
// main.cpp
#include <QtGui/QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
You just have to give your HelloWidget a size and place.
Add this line to your code.
hello->setGeometry(QRect(110, 80, 120, 80));
Or if you want to fill the scroll area with your widget:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
QScrollArea *const scroll(new QScrollArea);
QHBoxLayout *const layout(new QHBoxLayout(scroll));
HelloWidget *const hello(new HelloWidget);
hello->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
layout->addWidget(hello);
setCentralWidget( scroll );
}
Per Qt docs, "When using a scroll area to display the contents of a custom widget, it is important to ensure that the size hint of the child widget is set to a suitable value. If a standard QWidget is used for the child widget, it may be necessary to call QWidget::setMinimumSize() to ensure that the contents of the widget are shown correctly within the scroll area."
Does it work right if you follow these instructions?
I was pulling my hair out over this also, but eventually found QScrollArea's setWidgetResizable, which made the QScrollArea allow my widget to expand to take up the available space.