I'm new here and i can't solve this thing.
I have a QGraphicScene (which at first initializes a Cartesian plane) in which it draw points \ lines with the methods AddLine \ ecc.Everything works.But I want to have a method that delete all "objects" drawn on the scene and reloads the cartesian plane.Any suggestion?
Here you can find my classes:
class draw: public QWidget
{
Q_OBJECT
private:
QGraphicsScene scene;
int x1;
int y1;
int x2;
int y2;
public:
disegna (QWidget *parent = 0);
void setdot(QString,QString);
void setsegment(QString,QString,QString,QString);
~disegna(){}
};
draw::draw(QWidget *parent) : QWidget(parent) {
scene.setBackgroundBrush(Qt::white);
scene.addRect(QRectF(0,0, 600, 200));
scene.addLine(0, 100 ,600, 100);
scene.addLine(300, 0 ,300, 200);
scene.addEllipse(297.5,97.5,5,5,QPen(), QBrush(Qt::red));
int i=0;
for (int a=0;a<120;a++) {
scene.addLine(i+5, 98 ,i+5, 102);
i=i+5;
}
int j=0;
for (int a=0;a<40;a++) {
scene.addLine(298,j+5,302,j+5);
j=j+5;
}
QGraphicsView * view = new QGraphicsView(&scene,this);
view->show();
}
void draw::setdot(QString x1,QString y1){
scene.addEllipse(x1.toInt()+298, 98-y1.toInt(),4,4,QPen(), QBrush(Qt::blue));
}
void draw::setsegment(QString x1,QString y1,QString x2,QString y2) {
scene.addLine(x1.toInt()+300, 100-y1.toInt(),x2.toInt()+300, 100-y2.toInt(),QPen());
scene.addEllipse(x1.toInt()+298, 100-y1.toInt(),4,4,QPen(), QBrush(Qt::blue));
scene.addEllipse(x2.toInt()+298, 100-y2.toInt(),4,4,QPen(), QBrush(Qt::blue));
}
For cleaning the scene, did you tried the clear method ?
If you want to clear and redraw, I suggest you to move the drawing code from your constructor to a method.
Then you can call clear then your drawing method.
Hope it helps
Related
I have some QGraphicsItems in the QGraphicsScene which should keep the same size and position when scaling. I've tried QGraphicsItem::ItemIgnoresTransformations but it turns out that the items get wrong positions. Below is a sample code:
I have subclassed QGraphicsView like this:
class Graphics : public QGraphicsView
{
public:
Graphics();
QGraphicsScene *scene;
QGraphicsRectItem *rect;
QGraphicsRectItem *rect2;
protected:
void wheelEvent(QWheelEvent *event);
};
And in its constructor:
Graphics::Graphics()
{
scene = new QGraphicsScene;
rect = new QGraphicsRectItem(100,100,50,50);
rect2 = new QGraphicsRectItem(-100,-100,50,50);
scene->addLine(0,200,200,0);
rect->setFlag(QGraphicsItem::ItemIgnoresTransformations, true);
scene->addItem(rect);
scene->addItem(rect2);
setScene(scene);
scene->addRect(scene->itemsBoundingRect());
}
The wheelEvent virtual function:
void Graphics::wheelEvent(QWheelEvent *event)
{
if(event->delta() < 0)
scale(1.0/2.0, 1.0/2.0);
else
scale(2, 2);
scene->addRect(scene->itemsBoundingRect());
qDebug() << rect->transform();
qDebug() << rect->boundingRect();
qDebug() << rect2->transform();
qDebug() << rect2->boundingRect();
}
orginal view looks like this:
1
take the line as road and rect aside as a symbol. When zoomed out, the rect maintain its size but jumps out of the scene:
2
which should be that topleft of rect to middle of line. I'm also confused with debug info showing that the boundingRect and transform stays the same, which seems that nothing has changed! What causes the problem and is there any way to solve it? Could someone help? Thank you!
Sorry for delay, now I've solved the problem myself.
I found QGraphicsItem::ItemIgnoresTransformations only works when the point you want stick to is at (0,0) in item's coordinate. You need also update boundingRect manually in this way. Nevertheless, the best solution I've found is subclass QGraphicsItem and set matrix in paint() according to world matrix. Below is my code .
QMatrix stableMatrix(const QMatrix &matrix, const QPointF &p)
{
QMatrix newMatrix = matrix;
qreal scaleX, scaleY;
scaleX = newMatrix.m11();
scaleY = newMatrix.m22();
newMatrix.scale(1.0/scaleX, 1.0/scaleY);
qreal offsetX, offsetY;
offsetX = p.x()*(scaleX-1.0);
offsetY = p.y()*(scaleY-1.0);
newMatrix.translate(offsetX, offsetY);
return newMatrix;
}
And the paint function:
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget)
{
QPointF p(left, top);
painter->setMatrix(stableMatrix(painter->worldMatrix(), p));
painter->drawRect(left, top, width, height);
}
The second argument of stableMatrix is sticked point, in my sample code it's top-left of the item. You can change it to your preference. It works really fine!
Hope this post help :)
The solution to this is even simpler.
QGraphicsItem::ItemIgnoresTransformations
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). [...]
And that's the key! Item ignores all transformations, but is still bound to its parent. So you need two items: a parent item that will keep the relative position (without any flags set) and a child item that will do the drawing (with QGraphicsItem::ItemIgnoresTransformations flag set) at parent's (0,0) point.
Here is some working code of a crosshair that have constant size and rotation, while keeping the relative position to its parent:
#include <QGraphicsItem>
#include <QPainter>
class CrossHair : public QGraphicsItem
{
private:
class CrossHairImpl : public QGraphicsItem
{
public:
CrossHairImpl (qreal len, QGraphicsItem *parent = nullptr)
: QGraphicsItem(parent), m_len(len)
{
setFlag(QGraphicsItem::ItemIgnoresTransformations);
}
QRectF boundingRect (void) const override
{
return QRectF(-m_len, -m_len, m_len*2, m_len*2);
}
void paint (QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override
{
painter->setPen(QPen(Qt::red, 1));
painter->drawLine(0, -m_len, 0, m_len);
painter->drawLine(-m_len, 0, m_len, 0);
}
private:
qreal m_len;
};
public:
CrossHair (qreal x, qreal y, qreal len, QGraphicsItem *parent = nullptr)
: QGraphicsItem(parent), m_impl(len, this) // <-- IMPORTANT!!!
{
setPos(x, y);
}
QRectF boundingRect (void) const override
{
return QRectF();
}
void paint (QPainter *, const QStyleOptionGraphicsItem *, QWidget *) override
{
// empty
}
private:
CrossHairImpl m_impl;
};
I am beginner to QT. Trying to fill color on top of image. For every 10 sec I want to fill different colors on the same area.My issue is, Since previously filled color still exists on the image, colors are overlapping every time.How can I clear the color before fill again.
GraphicsView_Layout .h
class GraphicsView_Layout : public QWidget
{
Q_OBJECT
int mTimer;
QImage* mfloorImg;
protected:
void timerEvent(QTimerEvent *event);
void Fill_Area_With_Color(float* nPmv)
};
GraphicsView_Layout .cpp
GraphicsView_Layout ::GraphicsView_Layout (QWidget *parent)
: QWidget(parent)
{
ui.setupUi(this);
mfloorImg=new QImage("./img.jpg");
mTimer=startTimer(10000);
}
//Timer to update color
void GraphicsView_Layout::timerEvent(QTimerEvent* event)
{
float nPmv[5]={0.1,-0.5,1,3,0};
Fill_Area_With_Color(nPmv);
}
void GraphicsView_Layout::Fill_Area_With_Color(float* nPmv)
{
QPainter painter(mfloorImg);
QColor areaColor;
int nPos_X=20;
int nPos_Y=30;
int g_area_count=8;
int nblock_count=20;
//I want to clear previous color before filling again
//But Erase not happening
for(int i=0;i<g_area_count;i++)
{
for(int j=0;j<nblock_count;j++)
{
painter.eraseRect(QRect(nPos_X+(i*j),nPos_Y+(i*j),28,24));
}
}
//Fill Color
for(int i=0;i<g_area_count;i++)
{
areaColor=Get_Area_Color(nPmv[i]); //Get area color
for(int j=0;j<nblock_count;j++)
{
painter.fillRect(QRect(nPos_X+(i*j),nPos_Y+(i*j),28,24),QBrush(areaColor));
}
}
}
As my work are going well on my long project of QT, I've tried to resolve my actual steps, but I think it is not working, so I have one question:
Draw points from data file on QGraphicsView
My code is writting actually at the end of the process a data file where the first line is the number of elements (size of the vector) and the other lines the values of the point with x and y separated by a blank.
res.dat
250
12 23
30 40
25 67
...
I would like to read this file (with the fstream) and to display each points of my vector on a QGraphicsView in order to have a graphic result, and ideally but not the most important result, to lie this point to have the desired form.
I have already tried this part of code on a main source file to test, but it is just doing a circle and I think it is not reading my file.
main.cpp
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QPointF>
#include <QVector>
#include <QApplication>
#include <fstream>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
int n;
std::ifstream ifs1("res.dat");
ifs1 >> n;
QVector <QPointF> points(n);
// Create a view, put a scene in it and add tiny circles
// in the scene
QGraphicsView * view = new QGraphicsView();
QGraphicsScene * scene = new QGraphicsScene();
view->setScene(scene);
for(int i = 1; i< n; i++)
scene->addEllipse(points[i].x(), points[i].y(), 512, 512);
// Show the view
view->show();
return a.exec();
}
Thank you in advance for reading, and for your response.
From your code it's completely missing the part where you read the actual coordinates of the points.
I have changed slightly your code to read the points into the vector but please remember:
a) This code is all but "solid" for example if your res.dat file is just a bit out of format it crashes.
b) With this loop you really do not need to know how many points you have in the file, right now it keeps reading until the end of file.
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
int n;
std::ifstream ifs1("res.dat");
ifs1 >> n;
QVector <QPointF> points;
qreal px,py;
while (ifs1 >> px) {
ifs1 >> py;
points.append(QPointF(px,py));
}
// Create a view, put a scene in it and add tiny circles
// in the scene
QGraphicsView * view = new QGraphicsView();
QGraphicsScene * scene = new QGraphicsScene();
view->setScene(scene);
foreach (QPointF point, points)
scene->addEllipse(point.x(), point.y(), 51, 51);
// Show the view
view->show();
return a.exec();
}
After you helped me, I've gone further in my code, and I have a problem I don't understand.
in the header file
//All includes
class ProjectWindow;
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
// GraphicsView 1
QGraphicsView view1;
QGraphicsScene scene1;
QGraphicsPixmapItem item1;
QGraphicsPixmapItem item2;
QString fileName;
// GraphicsView 2
QGraphicsScene scene2;
QGraphicsView view2;
QVector <QPointF> points;
qreal px,py;
QPointF point;
QPainter painter;
QGraphicsEllipseItem *ellipse;
public slots:
void openBrowser();
void drawcirc();
void drawCircle();
void runSnakes();
void displayResult();
};
#endif // MAINWINDOW_H
in the .cpp file
void MainWindow::displayResult()
{
scene2.removeItem(&item2); //removing a picture
scene2.addItem(&item2); // adding a picture
scene2.setSceneRect(scene2.itemsBoundingRect());
// Reading res.dat file and plot the contour
int m;
std::ifstream ifs2("final_contour.dat");
ifs2 >> m;
std::cout << m;
while (ifs2 >> px)
{
ifs2 >> py;
points.append(QPointF(py,px));
}
foreach(point, points)
ellipse = scene2.addEllipse(QRectF(point.y(), width-point.x(),1,1),QPen(Qt::red));
// Saving the final contour into a picture
QImage pixmap(width, width, QImage::Format_ARGB32_Premultiplied);
painter.begin(&pixmap);
painter.setRenderHint(QPainter::Antialiasing, false);
scene2.render(&painter);
painter.end();
pixmap.save("finalcontour.bmp", "BMP");
}
I want to remove the ellipse I add to the scene with removeItem() (in order to see the differents results with changing parameters), so I need to create a QGraphicsEllipseItem and to tell to the scene, addItem() that I can remove later the item with removeItem().
the header file
//All includes
class ProjectWindow;
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
// GraphicsView 1
QGraphicsView view1;
QGraphicsScene scene1;
QGraphicsPixmapItem item1;
QGraphicsPixmapItem item2;
QString fileName;
// GraphicsView 2
QGraphicsScene scene2;
QGraphicsView view2;
QVector <QPointF> points;
qreal px,py;
QPointF point;
QPainter painter;
QGraphicsEllipseItem ellipse; // The only line changed
public slots:
void openBrowser();
void drawcirc();
void drawCircle();
void runSnakes();
void displayResult();
};
#endif // MAINWINDOW_H
the cpp file
void MainWindow::displayResult()
{
scene2.removeItem(&item2); //removing a picture
scene2.addItem(&item2); // adding a picture
scene2.setSceneRect(scene2.itemsBoundingRect());
// Reading res.dat file and plot the contour
int m;
std::ifstream ifs2("final_contour.dat");
ifs2 >> m;
std::cout << m;
while (ifs2 >> px)
{
ifs2 >> py;
points.append(QPointF(py,px));
}
foreach(point, points)
ellipse.setRect(QRectF(point.y(), width-point.x(),1,1));
ellipse.setPen(QPen(Qt::red));
scene2.addItem(&ellipse);
// Saving the final contour into a picture
QImage pixmap(width, width, QImage::Format_ARGB32_Premultiplied);
painter.begin(&pixmap);
painter.setRenderHint(QPainter::Antialiasing, false);
scene2.render(&painter);
painter.end();
pixmap.save("finalcontour.bmp", "BMP");
}
But changing the way I want to add the ellipse, it is not displaying the whole points like this with the first way I define the ellipse:
But just one point and not the vector of points, like this:
I create my scene in my dialog.cpp and draw some QGraphicsItem's in my scene.cpp. When I add my QTimer to my dialog.cpp it makes whenever I move the cursor over the scene to crash.
dialog.cpp
#include "dialog.h"
#include "scene.h"
#include "ui_dialog.h"
#include "instructions.h"
#include "settings.h"
#include "highscore.h"
Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
// Create and configure scene
scene = new Scene;
scene->setBackgroundBrush(Qt::black);
scene->setItemIndexMethod(QGraphicsScene::NoIndex);
ui->graphicsView->setScene(scene);
scene->setSceneRect(-200, -150, 400, 300);
ui->graphicsView->setMouseTracking(true);
QPixmap tankbase1(":/images/tankbase.jpg");
ui->tankbaseplay1->setPixmap(tankbase1);
//\/\/\/This is my problem. And not sure why\/\/\/\/\/\/
// timer = new QTimer(this);
// QObject::connect(timer, SIGNAL(timeout()), scene, SLOT(advance()));
// timer->start(10);
}
Dialog::~Dialog()
{
delete ui;
}
//void Dialog::shoot()
//{
//}
void Dialog::on_startButton_clicked()
{
ui->settingsButton->hide();
ui->titlescreen->hide();
ui->highscoreButton->hide();
ui->instructionButton->hide();
ui->startButton->hide();
QGraphicsTextItem *FirstP;
QString P1 = "Player1";
FirstP = scene->addText(P1);
FirstP->setFont(QFont("Nimbus Mono L", 12,QFont::Bold));
FirstP->setDefaultTextColor(Qt::white);
FirstP->setPos(-300, -220);
QGraphicsTextItem *SecondP;
QString P2 = "Player2";
SecondP = scene->addText(P2);
SecondP->setFont(QFont("Nimbus Mono L", 12,QFont::Bold));
SecondP->setDefaultTextColor(Qt::white);
SecondP->setPos(230, -220);
}
void Dialog::on_instructionButton_clicked()
{
Instructions intDialog;
intDialog.setModal(true);
intDialog.exec();
}
void Dialog::on_settingsButton_clicked()
{
settings intDialog;
intDialog.setModal(true);
intDialog.exec();
}
void Dialog::on_highscoreButton_clicked()
{
highscore intDialog;
intDialog.setModal(true);
intDialog.exec();
}
scene.cpp
#include "scene.h"
#include <QGraphicsEllipseItem>
#include <QGraphicsLineItem>
#include <QGraphicsSceneMouseEvent>
#include <QTimer>
#include "qmath.h"
#include <math.h>
class GraphicsCircle : public QGraphicsEllipseItem
// class for the fire bullets
{
public:
GraphicsCircle(qreal dirx, qreal diry)
: m_Speed(3)
, m_DirX(dirx)
, m_DirY(diry)
{
setRect(-3.0,-3.0,8.0,8.0);
setPos(-195, 130);
QRadialGradient rGrad( 0.0, 0.0, 20.0, 0.0, 0.0);
rGrad.setColorAt(0.0, QColor(255,255,255));
rGrad.setColorAt(0.7, QColor(255,255,225));
rGrad.setColorAt(1.0, QColor(255,0,0,0));
setBrush(QBrush(rGrad) );
setPen(QPen(Qt::NoPen));
}
virtual ~GraphicsCircle() {}
void advance(int phase)
{
if(!phase) return;
setPos(x()+m_Speed*m_DirX, y()+m_Speed*m_DirY);
}
private:
qreal m_Speed;
qreal m_DirX;
qreal m_DirY;
};
Scene::Scene() : QGraphicsScene()
{
// added the lines below to setup an item, pointing in the positive x direction
int x1 = 0;
int y1 = 0;
cannon = new QGraphicsLineItem(x1, y1, x1 + 50, y1);
cannon->setPen(QPen(Qt::white, 6));
this->addItem(cannon);
cannon->setPos(-195, 130);
//Create bullets
m_FireTimer= new QTimer();
QObject::connect(m_FireTimer, SIGNAL(timeout()), this, SLOT(fire()));
}
void Scene::mousePressEvent(QGraphicsSceneMouseEvent *e)
{
m_FireTarget = e->scenePos();
m_FireTimer->start();
QGraphicsScene::mousePressEvent(e);
}
void Scene::mouseMoveEvent(QGraphicsSceneMouseEvent *e)
{
// emit mouseMoving(e->scenePos());
// FirstPlayer->setPos(e->scenePos());
// qAtan2(cannon->pos(), e->scenePos());
m_FireTarget = e->scenePos();
QGraphicsScene::mouseMoveEvent(e);
QLineF arm(cannon->pos(), e->scenePos());
cannon->setRotation(360 - arm.angle());
}
void Scene::mouseReleaseEvent ( QGraphicsSceneMouseEvent * e )
{
m_FireTimer->stop();
QGraphicsScene::mouseReleaseEvent(e);
}
void Scene::fire()
// creates a fire bullet
// the bullet will move in the direction of the mouse cursor
// the trajectory is sligthly perturbated by a random small angle
{
qreal dirx = m_FireTarget.x()-195;
qreal diry = m_FireTarget.y()-195;
qreal length = sqrt(dirx*dirx+diry*diry);
if (length!=0)
{
// normalized direction vector
qreal invLength= 1.0/length;
dirx *= invLength;
diry *= invLength;
// creating an angle perturbation of +/- 3°
qreal alphaPerturbation = static_cast<qreal>(qrand()%6-3) * M_PI / 180.0;
qreal xPerturbation = cos(alphaPerturbation);
qreal yPerturbation = sin(alphaPerturbation);
dirx = dirx*xPerturbation - diry*yPerturbation;
diry = diry*xPerturbation + dirx*yPerturbation;
GraphicsCircle * circle = new GraphicsCircle(dirx, diry);
addItem(circle);
}
}
void Scene::advance()
{
// first remove the pellet out of the sceneRect
for (int i=0; i<items().count(); ++i)
{
QGraphicsItem * item = items().at(i);
qreal x= item->x();
qreal y= item->y();
qreal sx=sceneRect().width();
qreal sy= sceneRect().height();
if ( (x < 0.0) || (y < 0.0) || (x > sx) || (y > sy))
{
removeItem(item);
delete item;
}
}
QGraphicsScene::advance();
}
When I run the code without the QTimer code in dialog.cpp it runs and my QGraphicsItems are displayed and move accordingly. When I add the QTimer the QGraphicsItem disappears. Completely lost to what the issue is.
Also I have taken the scene code and ran it separately and it works. The only difference is the scene and QTimer is created in main.cpp.
Help greatly needed!!!!!
You are iterating through a list of items while you are deleting items in the list. That sounds like trouble.
http://qt-project.org/doc/qt-5/qrect.html#intersects
http://qt-project.org/doc/qt-5/qgraphicsitem.html#boundingRect
http://qt-project.org/doc/qt-5/qrectf.html#contains
I think this might be a little cleaner in your advance() function.
QList <QGraphicsItem *> itemsToRemove;
foreach( QGraphicsItem * item, this->items() )
{
if( !this->sceneRect().intersects(item->boundingRect()) )
{
// The item is no longer in the scene rect, get ready to delete it
itemsToRemove.append(item);
}
}
foreach( QGraphicsItem * item, itemsToRemove )
{
this->removeItem(item);
delete(item);
}
Also reading the description of QGraphicsScene,
http://qt-project.org/doc/qt-5/qgraphicsscene.html#details
there a number of helper methods that can make finding items in an area or one item colliding with another, much easier.
Hope that helps.
I'm messing around with QGraphicsView and QGraphicsScene to create a Tic Tac Toe clone. I add some QGraphicsLineItems to my scene and override the resizeEvent method of the Widget that contains the view, so that when the window is resized, the view and its contents are scaled appropriately. This works fine, except for the first time that I run the program:
Once I resize the window by any amount, the scene is scaled correctly:
Here's the code:
main.cpp:
#include <QtGui>
#include "TestApp.h"
int main(int argv, char **args)
{
QApplication app(argv, args);
TestApp window;
window.show();
return app.exec();
}
TestApp.h:
#ifndef TEST_APP_H
#define TEST_APP_H
#include <QtGui>
class TestApp : public QMainWindow
{
Q_OBJECT
public:
TestApp();
protected:
void resizeEvent(QResizeEvent* event);
QGraphicsView* view;
QGraphicsScene* scene;
};
#endif
TestApp.cpp:
#include "TestApp.h"
TestApp::TestApp()
: view(new QGraphicsView(this))
, scene(new QGraphicsScene(this))
{
resize(220, 220);
scene->setSceneRect(0, 0, 200, 200);
const int BOARD_WIDTH = 3;
const int BOARD_HEIGHT = 3;
const QPoint SQUARE_SIZE = QPoint(66, 66);
const int LINE_WIDTH = 10;
const int HALF_LINE_WIDTH = LINE_WIDTH / 2;
QBrush lineBrush = QBrush(Qt::black);
QPen linePen = QPen(lineBrush, LINE_WIDTH);
for(int x = 1; x < BOARD_WIDTH; ++x)
{
int x1 = x * SQUARE_SIZE.x();
scene->addLine(x1, HALF_LINE_WIDTH, x1, scene->height() - HALF_LINE_WIDTH, linePen);
}
for(int y = 1; y < BOARD_HEIGHT; ++y)
{
int y1 = y * SQUARE_SIZE.y();
scene->addLine(HALF_LINE_WIDTH, y1, scene->width() - HALF_LINE_WIDTH, y1, linePen);
}
view->setScene(scene);
view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
view->show();
view->installEventFilter(this);
setCentralWidget(view);
}
void TestApp::resizeEvent(QResizeEvent* event)
{
view->fitInView(0, 0, scene->width(), scene->height());
QWidget::resizeEvent(event);
}
I've tried adding a call to fitInView at the end of TestApp's constructor, but it doesn't seem to do anything - resizeEvent seems to be called once at the start of the program's execution anyway.
Cheers.
Handle the view fit also inside the showEvent:
void TestApp::showEvent ( QShowEvent * event )
{
view->fitInView(0, 0, scene->width(), scene->height());
QWidget::showEvent(event);
}