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"
Related
I am trying to bounce a QWidget around the screen. This is the code i tried.
class Window : public QMainWindow {
public:
void moveEvent(QMoveEvent* aEvent) override;
};
void Window::moveEvent(QMoveEvent* aEvent) {
QSizeF screenSize = QGuiApplication::primaryScreen()->screenSize();
QRect oldRect = this->geometry();
QRect newRect = oldRect;
QPoint offset;
if (newRect.left() == 0) {
offset.setX(1);
}
else if (newRect.right() == screenSize.width()) {
offset.setX(-1);
}
if (newRect.top() == 0) {
offset.setX(1);
}
else if (newRect.bottom() == screenSize.height()) {
offset.setX(-1);
}
newRect.setTopLeft(newRect.topLeft() + offset);
newRect.setBottomRight(newRect.bottomRight() + offset);
QTimer::singleShot(1, [this, newRect]() {
setGeometry(newRect);
});
}
int main(int argc, char** argv) {
QApplication app{argc, argv};
Window* w = new Window();
w->show();
w->setGeometry(w->geometry());
return app.exec();
}
However, the window does not move around the screen, but somewhat jitters in place. When i move the window with the mouse and let go. It moves sporadically around the desktop, which is also not what i want.
Does anyone know if this is possible? If so, does anyone know the right way to do this?
There are several problems with the posted code, including:
The Window class doesn't have any member-variable to keep track of its current direction of motion. Without keeping that state, it's impossible to correctly calculate the next position along that direction of motion.
Driving the animation from within moveEvent() is a bit tricky, since moveEvent() gets called in response to setGeometry() as well as in response to the user actually moving the window with the mouse; that makes unexpected feedback loops possible, resulting in unexpected behavior.
The code assumes that the screen's usable surface area starts at (0,0) and ends at (screenSize.width(),screenSize.height()), which isn't necessarily a valid assumption. The actual usable area of the screen is a rectangle given by availableGeometry().
When calling setGeometry(), you are setting the new location of the area of the window that the Qt program can actually draw into. However that's only a 99% subset of the actual on-screen area taken up by the window, because the window also includes the non-Qt-controlled regions like the title bar and the window-borders. Those parts need to fit into the availableGeometry() also, otherwise the window won't be positioned quite where you wanted it to be, which can lead to anomalies (like the window getting "stuck" on the top-edge of the screen)
In any case, here's my attempt at rewriting the code to implement a closer-to-correct "bouncing window". Note that it's still a bit glitchy if you try to mouse-drag the window around while the window is also trying to move itself around; ideally the Qt program could detect the mouse-down-event on the title bar and use that to disable its self-animation until after the corresponding mouse-up-event occurs, but AFAICT that isn't possible without resorting to OS-specific hackery, because the window-title-bar-dragging is handled by the OS, not by Qt. Therefore, I'm leaving that logic unimplemented here.
#include <QApplication>
#include <QMainWindow>
#include <QMoveEvent>
#include <QShowEvent>
#include <QScreen>
#include <QTimer>
class Window : public QMainWindow {
public:
Window() : pixelsPerStep(5), moveDelta(pixelsPerStep, pixelsPerStep)
{
updatePosition(); // this will get the QTimer-loop started
}
private:
void updatePosition()
{
const QRect windowFrameRect = frameGeometry(); // our on-screen area including window manager's decorations
const QRect windowRect = geometry(); // our on-screen area including ONLY the Qt-drawable sub-area
// Since setGeometry() sets the area not including the window manager's window-decorations, it
// can end up trying to set the window (including the window-decorations) slightly "out of bounds",
// causing the window to "stick to the top of the screen". To avoid that, we'll adjust (screenRect)
// to be slightly smaller than it really is.
QRect screenRect = QGuiApplication::primaryScreen()->availableGeometry();
screenRect.setTop( screenRect.top() + windowRect.top() - windowFrameRect.top());
screenRect.setBottom( screenRect.bottom() + windowRect.bottom() - windowFrameRect.bottom());
screenRect.setLeft( screenRect.left() + windowRect.left() - windowFrameRect.left());
screenRect.setRight( screenRect.right() + windowRect.right() - windowFrameRect.right());
// Calculate where our window should be positioned next, assuming it continues in a straight line
QRect nextRect = geometry().translated(moveDelta);
// If the window is going to be "off the edge", set it to be exactly on the edge, and reverse our direction
if (nextRect.left() <= screenRect.left()) {nextRect.moveLeft( screenRect.left()); moveDelta.setX( pixelsPerStep);}
if (nextRect.right() >= screenRect.right()) {nextRect.moveRight( screenRect.right()); moveDelta.setX(-pixelsPerStep);}
if (nextRect.top() <= screenRect.top()) {nextRect.moveTop( screenRect.top()); moveDelta.setY( pixelsPerStep);}
if (nextRect.bottom() >= screenRect.bottom()) {nextRect.moveBottom(screenRect.bottom()); moveDelta.setY(-pixelsPerStep);}
setGeometry(nextRect);
QTimer::singleShot(20, [this]() {updatePosition();});
}
const int pixelsPerStep;
QPoint moveDelta; // our current positional-offset-per-step in both X and Y direction
};
int main(int argc, char** argv) {
QApplication app{argc, argv};
Window* w = new Window();
w->show();
return app.exec();
}
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.
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());
}
}
Basically what I wanna do is to draw rectangles for each number in my list. The bigger the number is, the larger the rectangle is.
My problem is when I actually wanna do it, step-by-step, and waiting a few seconds between every drawing. I've looked out for a few solutions but I can't get them to work for this particular case. I saw I could use fflush to release whatever it's in the buffer but I don't know how I can use it for this.
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setBrush(QBrush(Qt::green, Qt::SolidPattern));
int weight=300/lista.size;
int posx=weight;
for (int i=1; i<=lista.size; i++){
List_node * node = list.get_element_at(i);
int num=node->getValue(); //this returns the value of the node
if (i==3){
painter.setBrush(QBrush(Qt::red, Qt::SolidPattern)); // this line is to draw a rectangle with a different color. Testing purposes.
}
painter.drawRect(posx,400-(num*10),weight,num*10);
sleep(1); //this sleep isn't working correctly.
painter.setBrush(QBrush(Qt::green, Qt::SolidPattern));
posx+=weight;
}
Any help would be really appreciated.
sleep() won't work for this -- it blocks the Qt event loop and keeps Qt from doing its job while it is sleeping.
What you need to do is keep one or more member variables to remember the current state of the image you want to draw, and implement paintEvent() to draw that current single image only. paintEvent() (like every function running in Qt's GUI thread) should always return immediately, and never sleep or block.
Then, to implement the animation part of things, set up a QTimer object to call a slot for you at regular intervals (e.g. once every 1000mS, or however often you like). Implement that slot to adjust your member variables to their next state in the animation-sequence (e.g. rectangle_size++ or whatever) and then call update() on your widget. update() will tell Qt to call paintEvent() again on your widget as soon as possible, so your display will be updated to the next frame very shortly after your slot method returns.
Below is a trivial example of the technique; when run it shows a red rectangle getting larger and smaller:
// begin demo.h
#include <QWidget>
#include <QTimer>
class DemoObj : public QWidget
{
Q_OBJECT
public:
DemoObj();
virtual void paintEvent(QPaintEvent * e);
public slots:
void AdvanceState();
private:
QTimer _timer;
int _rectSize;
int _growthDirection;
};
// begin demo.cpp
#include <QApplication>
#include <QPainter>
#include "demo.h"
DemoObj :: DemoObj() : _rectSize(10), _growthDirection(1)
{
connect(&_timer, SIGNAL(timeout()), this, SLOT(AdvanceState()));
_timer.start(100); // 100 milliseconds delay per frame. You might want to put 2000 here instead
}
void DemoObj :: paintEvent(QPaintEvent * e)
{
QPainter p(this);
p.fillRect(rect(), Qt::white);
QRect r((width()/2)-_rectSize, (height()/2)-_rectSize, (_rectSize*2), (_rectSize*2));
p.fillRect(r, Qt::red);
}
void DemoObj :: AdvanceState()
{
_rectSize += _growthDirection;
if (_rectSize > 50) _growthDirection = -1;
if (_rectSize < 10) _growthDirection = 1;
update();
}
int main(int argc, char ** argv)
{
QApplication app(argc, argv);
DemoObj obj;
obj.resize(150, 150);
obj.show();
return app.exec();
}
Mission: Draw two lines with different color on one graph with automatic cliping, by adding points bit by bit.
So, what am I doing. Create class GraphWidget, inherited from QGraphicsView. Create member of QGraphicsScene. Create 2 QPainterPath instances, and add them to graphicsScene.
Then, I eventually call graphWidget.Redraw(), where call for QPainterPath.lineTo() for both instances. And I expect appearance of that lines of graphics view, but it doesn't.
I tired from reading Qt's doc and forums. What am I doing wrong?
We need to know more, what does not happen? Does the window appear at all? Are the lines not drawn? In the meantime try out this sample code if you want :) Edit: updated to show updating.
#include ...
class QUpdatingPathItem : public QGraphicsPathItem {
void advance(int phase) {
if (phase == 0)
return;
int x = abs(rand()) % 100;
int y = abs(rand()) % 100;
QPainterPath p = path();
p.lineTo(x, y);
setPath(p);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene s;
QGraphicsView v(&s);
QUpdatingPathItem item;
item.setPen(QPen(QColor("red")));
s.addItem(&item);
v.show();
QTimer *timer = new QTimer(&s);
timer->connect(timer, SIGNAL(timeout()), &s, SLOT(advance()));
timer->start(1000);
return a.exec();
}
You should get something like this:
The path in any QGraphicsPathItem can of course be updated later. You might want to keep the original painter path somewhere to avoid performance hit caused by all the path copying (I'm not sure if QPainterPath is implicitly shared...)
QPainterPath p = gPath.path();
p.lineTo(0, 42);
gPath.setPath(p);
Animation
It seems that you're trying to do some sort of animation/on-the-fly updating. There is entire framework for this in Qt. In the simplest form you can subclass QGraphicsPathItem, reimplement its advance() slot to automatically fetch next point from motion. The only thing left to do then would be calling s.advance() with the required frequency.
http://doc.trolltech.com/4.5/qgraphicsscene.html#advance
Evan Teran, sorry for that comment.
// Constructor:
GraphWidget::GraphWidget( QWidget *parent ) :
QGraphicsView(parent),
bounds(0, 0, 0, 0)
{
setScene(&scene);
QPen board_pen(QColor(255, 0, 0));
QPen nature_pen(QColor(0, 0, 255));
nature_path_item = scene.addPath( board_path, board_pen );
board_path_item = scene.addPath( nature_path, nature_pen );
}
// Eventually called func:
void GraphWidget::Redraw() {
if(motion) {
double nature[6];
double board[6];
// Get coords:
motion->getNature(nature);
motion->getBoard(board);
if(nature_path.elementCount() == 0) {
nature_path.moveTo( nature[0], nature[1] );
} else {
nature_path.lineTo( nature[0], nature[1] );
}
if(board_path.elementCount() == 0) {
board_path.moveTo( board[0], board[1] );
} else {
board_path.lineTo( board[0], board[1] );
}
}
}