Custom QGraphicItem and Repaint Issues - c++

All, I implemented a QGraphicsItem which is a polygon. I sped up development by using QGraphicsEllipseItem as the points (for the dragging capabilities.) However, I am now having difficulty in update() functionality. My code is posted at the end, my questions are:
Am I taking the right approach here? I am starting to doubt myself
Am I supposed to be calling QGraphicsItem::update() in my implementation? I call it alot
Some other info:
I did a dirty little hack. In my actual code I also inherit from QObject. This allows me to install an eventFilter on the scene() (which I know has been set using itemChange). From the scene I filter QGraphicsSceneMouseEvent during mouse moves and call QGraphicsItem::update() otherwise my lines wouldn't be redrawn.
When I remove my InteractivePolygon from the scene now, my lines are not removed! I have to call scene()->update. I feel like something's not right.
declaration:
class InteractivePolygon : public QGraphicsItem
{
public:
//Only important methods
QRectF boundingRect() const;
void paint(bla bla bla);
bool eventFilter(QObject *, QEvent *);
private:
QList<QGraphicsEllipseItem *> m_points;
void AddPolygonPoint(QPointF);
QGraphicsEllipseItem * MakeNewPoint(QPointF);
}
implementation:
QRectF InteractivePolygon::boundingRect() const
{
return childrenBoundingRect();
}
void InteractivePolygon::paint(QPainter painter.. otherstuf)
{
QPen line_pen(QColor(255,0,0));
painter->setPen(line_pen);
if(m_points.count() > 1)
{
for(int i = 1; i < m_points.count(); ++i)
painter->drawLine(m_points[i-1]->pos(), m_points[i]->pos());
}
}
void AddPolygonPoint(QRectF point)
{
QGraphicsEllipseItem * new_item = MakeNewPoint(point);
new_item->setParent(this);
m_points->push_front(new_item);
update();
}
QGraphicsEllipseItem * InteractivePolygon::MakeNewPoint(QPointF & new_point)
{
QGraphicsEllipseItem * result = 0;
result = new QGraphicsEllipseItem();
result->setPos(new_point);
result->setRect(-4, -4, 8, 8);
result->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable)
return result;
}
//Lets pretend this method is correctly setup/exists
bool InteractivePolygon::eventFilter(QObject *object, QEvent *event)
{
if(event->type() == QEvent::QEvent::GraphicsSceneMouseMove)
{
QGraphicsSceneMouseEvent * mouse_move = (QGraphicsSceneMouseEvent *)event;
//Selected index is set else, let's assume it works
if(selected_index)
{
update(); //If I don't do this, my lines in my paint() are not redrawn.
}
}
}

The solution was prepareGeometryChange() everytime I changed anything in my item that would change its bounding box. Everything is redrawn correctly and updated as necessary.
This allowed me to remove all update() calls.

Related

Qt GraphicsScene XOR Line or Line in separate layer?

I have started to learn Qt, and try to improve my basic C++ skills.
In GraphicsScene, I try to draw a line by using the mouse (mouse events).
When I start drawing a line in GraphicsScene, a thin dashed line is drawn from the origin, where I clicked first to the current mouse position and moves with the mouse, before the second point is clicked. To erase it, I draw it in black. If I hover over already draw lines, you will see the black drawn lines on them. To undraw it without leaving marks, an XOR operation on GraphicsScene would come in handy, or if I could draw in a different layer and not touching the other layer could be handy. But I couldn't yet figure how to do it. The example is on https://github.com/JackBerkhout/QT_Draw001
In line.cpp is the function setLineP2(int x, int y), which draws and erases that thin dashed line.
Can anybody help me with this, please?
The major misconception is thinking of a QGraphicsScene as some sort of a bitmap: it's not! It is a collection of items that can render themselves, and a spatial index for them. In a scene, if you wish to delete/hide something, you must not overpaint it - instead simply delete/hide the item in question as desired. The scene will handle all the details - that's what it's for
You must forget about GDI-anything at this point. You're not painting on the raw DC here. Even when using raw GDI, you do not want to paint on the window's DC as that flickers, you should paint on a bitmap and blit the bitmap to the window.
For example, your eraseScene method adds a rectangle on top of the scene, wasting memory and resources as all the previous items are retained (you can iterate through them), whereas all it should do is to clear the scene (or its equivalent):
void MainWindow::eraseScreen(void)
{
[...]
scene->addRect(0, 0, width()+1000, height()+1000, pen, brush);
}
vs. the correct:
void MainWindow::eraseScreen(void)
{
scene->clear();
}
Below is a complete example that approximates what you presumably meant to do in your code. It is 120 lines long. It was somewhat hard to figure out what exactly you meant to do as your code is so convoluted - it's useful to describe the exact behavior in simple terms in the question.
The example uses QPainterPath to keep a list of MoveTo and LineTo elements that a QPainterPathItem renders. It also uses a QGraphicsLineItem to display the transient line.
The MyScene::PathUpdater is used to enclose the context where a path is modified, and ensure that proper pre- and post-conditions are maintained. Namely:
Since QPainterPath is implicitly shared, you should clear the path held by QGraphicsPathItem to avoid an unnecessary implicit copy. That's the precondition necessary before modifying m_path.
After m_path has been modified, the path item must be updated, as well as a new status emitted.
The following other points are worth noting:
Holding the members by value leads to a notable absence of any memory management code (!) - the compiler does it all for us. You won't find a single new or delete anywhere. They are not necessary, and we're paying no additional cost for not doing this manually. Modern C++ should look exactly like this.
The clear split between the display MainWindow and MyScene. The MainWindow knows nothing about the specifics of MyScene, and vice-versa. The code within main acts as an adapter between the two.
The leveraging of C++11.
Succinct style necessary for SO test cases and examples: for learning it's best to keep it all in one file to easily see all the parts of the code. It's only 120 lines, vs. more than twice that if split across files. Our brains leverage the locality of reference. By splitting the code you're making it harder for yourself to comprehend.
See also
Another demo of interactive item creation.
A more advanced example of status notifications.
// https://github.com/KubaO/stackoverflown/tree/master/questions/scene-polygon-7727656
#include <QtWidgets>
class MainWindow : public QWidget
{
Q_OBJECT
QGridLayout m_layout{this};
QPushButton m_new{"New"};
QPushButton m_erase{"Erase All"};
QLabel m_label;
QGraphicsView m_view;
public:
MainWindow() {
m_layout.addWidget(&m_new, 0, 0);
m_layout.addWidget(&m_erase, 0, 1);
m_layout.addWidget(&m_label, 0, 2);
m_layout.addWidget(&m_view, 1, 0, 1, 3);
m_view.setBackgroundBrush(Qt::black);
m_view.setAlignment(Qt::AlignBottom | Qt::AlignLeft);
m_view.scale(1, -1);
connect(&m_new, &QPushButton::clicked, this, &MainWindow::newItem);
connect(&m_erase, &QPushButton::clicked, this, &MainWindow::clearScene);
}
void setScene(QGraphicsScene * scene) {
m_view.setScene(scene);
}
Q_SIGNAL void newItem();
Q_SIGNAL void clearScene();
Q_SLOT void setText(const QString & text) { m_label.setText(text); }
};
class MyScene : public QGraphicsScene {
Q_OBJECT
public:
struct Status {
int paths;
int elements;
};
private:
bool m_newItem = {};
Status m_status = {0, 0};
QPainterPath m_path;
QGraphicsPathItem m_pathItem;
QGraphicsLineItem m_lineItem;
struct PathUpdater {
Q_DISABLE_COPY(PathUpdater)
MyScene & s;
PathUpdater(MyScene & scene) : s(scene) {
s.m_pathItem.setPath({}); // avoid a copy-on-write
}
~PathUpdater() {
s.m_pathItem.setPath(s.m_path);
s.m_status = {0, s.m_path.elementCount()};
for (auto i = 0; i < s.m_status.elements; ++i) {
auto element = s.m_path.elementAt(i);
if (element.type == QPainterPath::MoveToElement)
s.m_status.paths++;
}
emit s.statusChanged(s.m_status);
}
};
void mousePressEvent(QGraphicsSceneMouseEvent *event) override {
PathUpdater updater(*this);
auto pos = event->scenePos();
m_lineItem.setLine(0, 0, pos.x(), pos.y());
m_lineItem.setVisible(true);
if (m_path.elementCount() == 0 || m_newItem)
m_path.moveTo(pos);
m_path.lineTo(pos.x()+1,pos.y()+1); // otherwise lineTo is a NOP
m_newItem = {};
}
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override {
PathUpdater updater(*this);
auto pos = event->scenePos();
m_lineItem.setLine(0, 0, pos.x(), pos.y());
m_path.setElementPositionAt(m_path.elementCount()-1, pos.x(), pos.y());
}
void mouseReleaseEvent(QGraphicsSceneMouseEvent *) override {
m_lineItem.setVisible(false);
}
public:
MyScene() {
addItem(&m_pathItem);
addItem(&m_lineItem);
m_pathItem.setPen({Qt::red});
m_pathItem.setBrush(Qt::NoBrush);
m_lineItem.setPen({Qt::white});
m_lineItem.setVisible(false);
}
Q_SLOT void clear() {
PathUpdater updater(*this);
m_path = {};
}
Q_SLOT void newItem() {
m_newItem = true;
}
Q_SIGNAL void statusChanged(const MyScene::Status &);
Status status() const { return m_status; }
};
int main(int argc, char *argv[])
{
using Q = QObject;
QApplication app{argc, argv};
MainWindow w;
MyScene scene;
w.setMinimumSize(600, 600);
w.setScene(&scene);
Q::connect(&w, &MainWindow::clearScene, &scene, &MyScene::clear);
Q::connect(&w, &MainWindow::newItem, &scene, &MyScene::newItem);
auto onStatus = [&](const MyScene::Status & s){
w.setText(QStringLiteral("Paths: %1 Elements: %2").arg(s.paths).arg(s.elements));
};
Q::connect(&scene, &MyScene::statusChanged, onStatus);
onStatus(scene.status());
w.show();
return app.exec();
}
#include "main.moc"

Fill function in basic graphics app in Qt

I have been creating a basic graphics program(like MS Paint) with simple GUI. I have two classes where one is a MainWindow which holds all buttons, sliders etc and a second class is a custom widget called drawingArea on which a user is able to draw.
Basicly I've implemented most of functions, but unfortunetly I stucked at filling function, which should work just like one in MS Paint. I decided to use so called floodFill algorithm and after few hours of fighting(I am a beginner in Qt) I have managed to make it work. But not at all. The problem is I am only able to fill black-colored regions(shapes, lines, points etc) with the color I choose. But when it comes to filling different colors, it just puts one pixel in chosen color. In funcion fill(...) below I am passing two arguments - point(mouseClicked) and a color of that point. Here:
void drawingArea::fill(const QPoint &point, QColor act)
{
QPainter painter(&image);
QPen myPen(actualColor);
myPen.setWidth(1);
painter.setPen(myPen);
QQueue<QPoint> pixels;
pixels.enqueue(point);
while(pixels.isEmpty() == 0)
{
QPoint newPoint = pixels.dequeue();
QColor actual;
actual.fromRgb(image.pixel(newPoint));
if(actual == act)
{
painter.drawPoint(newPoint);
update();
QPoint left((newPoint.x()-1), newPoint.y());
if(left.x() >0 && left.x() < image.width() && image.pixel(left) == act.rgb())
{
pixels.enqueue(left);
painter.drawPoint(left);
update();
}
QPoint right((newPoint.x()+1), newPoint.y());
if(right.x() > 0 && right.x() < image.width() && image.pixel(right) == act.rgb())
{
pixels.enqueue(right);
painter.drawPoint(right);
update();
}
QPoint up((newPoint.x()), (newPoint.y()-1));
if(up.y() > 0 && up.y() < image.height() && image.pixel(up) == act.rgb())
{
pixels.enqueue(up);
painter.drawPoint(up);
update();
}
QPoint down((newPoint.x()), (newPoint.y()+1));
if(down.y() > 0 && down.y() < image.height() && image.pixel(down) == act.rgb())
{
pixels.enqueue(down);
painter.drawPoint(down);
update();
}
}
}
}
I call this fill(...) function inside mousePressEvent here:
void drawingArea::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton)
{
lastPoint = event->pos();
QColor clr;
clr.fromRgb(image.pixel(lastPoint));
firstPoint = event->pos();
isDrawing = true;
if(isColorcheck) colorcheck(lastPoint);
if(isFill) fill(lastPoint,clr);
}
}
The really don't understand how it is working only on black color, when passing argument QColor of different colors.
I would really appreciate any help or ideas.
There is a fundamental problem with your code. Don't do the drawing from just any method called in your class. The drawing needs to be done in paint event callback that you define. QWidget::paintEvent worth exploring.
void MyClass::paintEvent(QPaintEvent *event)
{
// do the drawing depending on the context and you may
// define that context in other event handlers, etc.
}
Other than that it is hard to say what else is wrong. BTW, you can trigger drawing event by repaint() or update(). You need to consider how the exact area of redraw is defined in your code.

Painting in graphicsview

I am using the graphics view to paint the graphicsitem in it. Earlier when I clicked the button the respective item was painted only once, to again paint the same entity I had topush the button again. To overcome this I constructed the signal to allow to add the entities multiple times without having the need to push the button again. But when I using vector to store the points.It does not append, limiting its capacity to 2 only. Following is my output and the code
circle.cpp
void circle::mousePressEvent(QGraphicsSceneMouseEvent *e)
{
if(e->button()==Qt::LeftButton) {
if(mFirstClick){
x1 = e->pos().x();
y1 = e->pos().y();
mFirstClick = false;
mSecondClick = true;
}
else if(!mFirstClick && mSecondClick){
x2 = e->pos().x();
y2 = e->pos().y();
mPaintFlag = true;
mSecondClick = false;
update();
emit DrawFinished();
_store.set_point(e->pos());
store_point.push_back(_store);
qDebug() << _store.getValue();
qDebug() << "Size of vector =" << store_point.size() << "and" << store_point.capacity();
update();
}
}
mainwindow.cpp
void MainWindow::drawCircle(){
item2 = new circle;
scene->addItem(item2);
qDebug() << "Circle Created";
connect(item2, SIGNAL(DrawFinished()), this, SLOT(drawCircle()));
}
output
Circle Created
QPointF(60, 87)
Size of vector = 1 and 1
Circle Created
QPointF(77, 221)
Size of vector = 2 and 2
QPointF(333, 57)
Size of vector = 1 and 1
When I remove the signal DrawFinished(), the points store perfectly but the item gets painted only once. I need to pushthe button again:(. Following is the output after removing the signal.
QPointF(74, 80)
Size of vector = 1 and 1
QPointF(118, 165)
Size of vector = 2 and 2
QPointF(335, 97)
Size of vector = 3 and 4
What needs to be done to perfectly store the points as well as allow repainting. Please do help me to sort out all this.
Well, not sure if this would answer your request but a comment is too small to write what i want to tell you.
I don't really get what is the purpose of your signal DrawFinished(). Even if it's obvious thanks to the name, I don't think you need it.
If I sum up what you really want, you have a QGraphicView where you want to draw some shapes. Next to it, you have at least one (let's say 3) buttons to select which shapes you want to draw (Circle, Triangle, Rectangle).
Lets say you want to draw some circles, you click on the CircleButton, and then, click on the QGraphicView.
To my mind, I would create something like this:
Two classes, MainWindow and View, view which inherits from QGraphicView. Your three buttons are defined with Qt designer in your MainWindow class. So When you click on a button, you can emit a signal to notify the View.
In the View class you could have one vector for each shapes.
MainWindow.h
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
signals:
void drawCircle();
void drawRectangle();
void drawTriangle();
private:
Ui::MainWindow *ui;
View view;
private slots:
void slotOnCircleButton();
void slotOnRectangleButton();
void slotOnTriangleButton();
};
MainWindow.cpp
[...]
void MainWindow::slotOnCircleButton()
{
emit(drawCircle());
}
[...]
View.h
class View : public QGraphicsView
{
Q_OBJECT
public:
explicit View(QWidget *parent = 0);
enum DrawingMode
{
UNDEFINED,
CIRCLE,
TRIANGLE,
RECTANGLE
}
signals:
public slots:
void slotOnDrawCircle();
void slotOnDrawRectangle();
void slotOnDrawTriangle();
private:
DrawingMode mode;
QVector<QPointF> vectorCircle;
QVector<QPointF> vectorTriangle;
QVector<QPointF> vectorRectangle;
};
View.cpp
[...]
void View::slotOnDrawCircle()
{
this->mode = CIRCLE;
}
[...]
void View::mousePressEvent(QGraphicsSceneMouseEvent *e)
{
if(e->button()==Qt::LeftButton && this->mode != UNDEFINED)
{
qreal x1 = e->pos().x();
qreal y1 = e->pos().y();
if(this->mode == CIRCLE)
{
this->vectorCircle.append(e->pos());
}
else if(...)
[...]
// updatePainting();
}
}
When updating the view, you just have to travel throw your 3 vectors and drawing circle, rectangle or triangle.
This way you don't have such a spaghetti code, it's quite clear.
I didn't run the code so there is probable some minor mistakes, don't forget to make your connections and your initializations.

How to access widgets coordinates in QGridLayout

I am in the process of putting Qlabels in a QGridLayout to arrange them nicely in a class derived from QWidget. However, when I try to access the coordinates, it always returns (0,0)
Here is my code :
class brick : public QWidget
{
Q_OBJECT
public:
explicit brick(QWidget *parent);
...
private:
QLabel* label;
QPixmap* brickPic;
}
brick::brick(QWidget *parent) :
QWidget(parent)
{
rect=new QRect();
label=new QLabel;
brickPic=new QPixmap(100,15);
brickPic->fill(QColor(255,0,0));
label->setPixmap(*brickPic);
}
class Grid : public QWidget
{
QOBJECT
public:
void fill_grid(QWidget* parent);
...
private:
QGridLayout* grid;
}
void Grid::fill_grid(QWidget* parent)
{
for (int i=0;i<10;i++)
{
for (int j=0;j<12;j++)
{
brick* fillingBrick=new brick(parent);
grid->addWidget(fillingBrick->getLabel(),j,i);
qDebug()<<fillingBrick->parentWidget();
brickVector.push_back(fillingBrick);
}
}
}
These are to be shown as I said before in the following class derived from QWidget :
class render_area : public QWidget
{
Q_OBJECT
public:
render_area(QWidget *parent = 0);
Grid* getGrid() const;
...
private:
Grid* grid;
QRect* bar;
...
}
render_area::render_area(QWidget *parent)
:QWidget(parent)
{
setFocusPolicy(Qt::StrongFocus);
setBackgroundRole(QPalette::Base);
setAutoFillBackground(true);
bar=new QRect(width()/2,height()+50,150,10);
grid=new Grid(this);
grid->fill_grid(this);
std::cout<<"Grid filled !"<<std::endl;
qDebug()<<grid->getBrick(5)->pos();
...
}
The qDebug()<<fillingBrick->parentWidget() returns render_area which is good but qDebug()<<grid->getBrick(5)->pos() returns QPoint(0,0) which is not.
I wonder how I can get pos() or x() and y() to return the coordinates in render_area inside of which every brick is placed.
I tried with cellRect from QGridLayout, MapToParent() inherited from QWidget, forcing render_area to show(); but no luck so far.
Edit : this is the loop I'm talking about :
void render_area::update_timer()
{
int x1,x2,y1,y2;
bar->getCoords(&x1,&y1,&x2,&y2);
if(is_fixed==false)
{
//gestion du sol
if(yc+diametre>height())
{
timer.stop();
QDialog* gameOver=new QDialog;
gameOver->setLayout(new QGridLayout);
gameOver->layout()->addWidget(new QPushButton("Ok"));
gameOver->setGeometry(500,500,300,150);
gameOver->show();
}
if(xc>x1 && xc<x1+150)
{
if(yc+diametre>y1)
{
vyc=-vyc;
}
}
//plafond
for (int i=0;i<12;i++)
{
for(int j=0;j<10;j++)
{
grid->getBrick(i,j)->setRect(grid->getGrid()->cellRect(i,j));
}
}
for(int i=0;i<widgets.size();i++)
{
if(widgets.at(i)->getRect()->intersects(ballRectangle))
{
std::cout<<i<<std::endl;
widgets.at(i)->getPixmap()->fill(QColor(255,255,255));
widgets.at(i)->getLabel()->setPixmap(*(widgets.at(i)->getPixmap()));
delete widgets.at(i);
vyc=-vyc;
}
//qDebug()<<grid->getBrick(i,j)->getRect();
}
//bord droit
if(xc+diametre>width())
{
vxc=-vxc;
}
//bord gauche
if(xc<0)
{
vxc=-vxc;
}
//integration (Euler explicite)
xc=xc+dt*vxc;
yc=yc+dt*vyc;
}
repaint();
}
The widget positions and sizes are determined by the layout, and are calculated in calls from event loop sometime after you call show() on the window widget, and sometime before that window becomes visible.
Since you want to implement a brick breaker game, there are two approaches:
Use the graphics view/scene system.
Use widgets, like you want to do.
Your widget-based approach is excellent to demonstrate that the solution to your problems can be solved naturally by not checking brick positions at all.
You will be updating the ball position in a timer event, or using the animation system. In either case, the ball widget will receive a moveEvent with the new position. There, you can easily calculate the intersections of the ball rectangle with each of the brick widget rectangles. Since they are children of the same parent, they'll be in the same coordinate system. At that time, all the positions will be current. Upon detecting a collision, you reflect the ball's direction, and literally delete the brick widget. You can also start playing a "hit" sound effect.
It's reasonably easy to use the QObject property system to mark different brick widgets depending on their behavioral traits. All this can be done without having to subclass the base widget, which could be a QFrame for example.

Fixed QGraphicsItem position, without changing behaviour of other QGraphicsItems in scene

This question is related to: Forcing QGraphicsItem To Stay Put
I'd like to have a QGraphicsItem on a fixed location when moving around in the scene.
The suggested solution is to override the void paintEvent(QPaintEvent*) of the sub-classed QGraphicsView.
void MyGraphicsView::paintEvent(QPaintEvent*) {
QPointF scenePos = mapToScene(0,0); // map viewport's top-left corner to scene
myItem->setPos(scenePos);
}
However, the problem is that I want everything else in the scene to stay intact, i.e. if I zoom or move I want all other QGraphicsItems to behave as default.
One poor way of solving this is to call void QGraphicsView::paintEvent(QPaintEvent*) from within void MyGraphicsView::paintEvent(QPaintEvent*).
void MyGraphicsView::paintEvent(QPaintEvent* event) {
QGraphicsView::paintEvent(event);
QPointF scenePos = mapToScene(0,0); // map viewport's top-left corner to scene
myItem->setPos(scenePos);
}
However, this adds a flickering behaviour to my_item since it's positioned first using QGraphicsView::paintEvent(event); and then using the added code
QPointF scenePos = mapToScene(0,0); // map viewport's top-left corner to scene
myItem->setPos(scenePos);
The question is, do I have to re-implement void MyGraphicsView::paintEvent(QPaintEvent*) from scratch and write code for both the desired behaviour of myItem and the default behaviour of all other QGraphicsItems, or is there an easier way to do this?
Thank you.
I think this is what you are looking for:
http://qt-project.org/doc/qt-4.8/qgraphicsitem.html#setFlag
QGraphicsItem::ItemIgnoresTransformations
Description from the docs:
The item ignores inherited transformations (i.e., its position is still anchored to its parent, but the parent or view rotation, zoom or shear transformations are ignored). This flag is useful for keeping text label items horizontal and unscaled, so they will still be readable if the view is transformed. When set, the item's view geometry and scene geometry will be maintained separately. You must call deviceTransform() to map coordinates and detect collisions in the view. By default, this flag is disabled. This flag was introduced in Qt 4.3. Note: With this flag set you can still scale the item itself, and that scale transformation will influence the item's children.
You may also want to parent everything that does pan around to something else. Then, you move or scale or rotate a single graphics group to affect everything except your "un-transformable" objects.
https://qt-project.org/doc/qt-4.8/graphicsview.html#the-graphics-view-coordinate-system
https://qt-project.org/doc/qt-4.8/painting-transformations.html (a cool example, though it doesn't show this feature really)
http://qt-project.org/doc/qt-4.8/demos-chip.html (great example of using QGraphicsView)
Hope that helps.
EDIT:
Example showing how you can achieve a static layer using parenting:
main.cpp
#include <QApplication>
#include "mygraphicsview.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MyGraphicsView w;
w.show();
return a.exec();
}
mygraphicsview.h
#ifndef MYGRAPHICSVIEW_H
#define MYGRAPHICSVIEW_H
#include <QGraphicsView>
#include <QGraphicsItemGroup>
#include <QMouseEvent>
class MyGraphicsView : public QGraphicsView
{
Q_OBJECT
public:
MyGraphicsView(QWidget *parent = 0);
~MyGraphicsView();
public slots:
void mousePressEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
private:
bool down;
QPointF m_last_pos;
QGraphicsItemGroup * m_group;
};
#endif // MYGRAPHICSVIEW_H
mygraphicsview.cpp
#include "mygraphicsview.h"
#include <QGraphicsItem>
#include <QGraphicsEllipseItem>
#include <QGraphicsTextItem>
MyGraphicsView::MyGraphicsView(QWidget *parent)
: QGraphicsView(parent)
{
down = false;
this->setScene(new QGraphicsScene);
// Anything not added to the "group" will stay put
this->scene()->addEllipse(20, 20, 50, 50);
this->scene()->addEllipse(180, 180, 50, 50);
this->scene()->addText("Click and drag with the mouse to move only the tiny dots.");
// This group will receive all transformations
m_group = new QGraphicsItemGroup;
for(int r = 0; r < 20; r ++)
{
for(int c = 0; c < 20; c++)
{
if(c % 5 == 0 && r % 5 == 0)
{
QGraphicsTextItem * txt = new QGraphicsTextItem(QString::number(r) + "," + QString::number(c));
m_group->addToGroup(txt);
txt->setPos(r*100, c*100);
}
m_group->addToGroup(new QGraphicsEllipseItem(r *100, c*100, 5, 5));
}
}
this->scene()->addItem(m_group);
}
MyGraphicsView::~MyGraphicsView()
{
}
void MyGraphicsView::mousePressEvent(QMouseEvent *event)
{
m_last_pos = mapToScene(event->pos());
down = true;
}
void MyGraphicsView::mouseReleaseEvent(QMouseEvent *)
{
down = false;
}
void MyGraphicsView::mouseMoveEvent(QMouseEvent *event)
{
if(down)
{
QPointF temp = mapToScene(event->pos());
QPointF delta = temp - m_last_pos;
m_last_pos = temp;
// Apply transformation to the group, not the scene!
m_group->translate(delta.x(), delta.y());
}
}