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?
-
Could you explain me better please – eyllanesc Apr 17 '17 at 23:00
-
Hi, I need to split this over multiple comments, due to message-length limitations. Suppose I draw one thick purple line (the first one) already. Then I start to draw a second line, we click on the start point first. Than drag the mouse. We see a thin dashed white line whose endpoint follows the mouse. – Jack Berkhout Apr 18 '17 at 10:16
-
So I draw it in white. Then to update its position, I first un-draw it (just using the background color), and draw it again on the new position using white color. When I drag the line over the first one, we can see we draw a lot of black lines over that one. Finally, when I click on the endpoint, the thick purple line will be drawn. The black line marks are still visible on the first line, which we crossed during moving the mouse after the first click. – Jack Berkhout Apr 18 '17 at 10:17
-
If we could use a second layer for the line construction phase, we would not end up with (black) marks in the first layer. Or if we used an XOR pen mode, it would reconstruct the underlying color again, by drawing the second time on the same position. I hope this explains it better. In VS on Windows, this is simple, but I use Linux, and in QT it is not simple, as it seems to lack XOR functionality in graphicsscene. – Jack Berkhout Apr 18 '17 at 10:17
-
I've made a modification of your code, check if it's what you want: https://github.com/eyllanesc/stackoverflow/tree/master/GraphicsScene – eyllanesc Apr 18 '17 at 14:46
-
Hi eyllanesc, thank you very much for your work! That works perfect! Incredible! Now figure how it works... – Jack Berkhout Apr 18 '17 at 22:07
-
What purple lines? And how should they appear? – eyllanesc Apr 18 '17 at 22:10
-
I figured it, and that works now. I changed the above comment, just within 5 minutes. After I added addLine(...), those lines appear, and there are no more black marks! Now, I'm studying the code. How does it work? – Jack Berkhout Apr 18 '17 at 22:15
-
I'm reading http://doc.qt.io/qt-4.8/qgraphicslineitem.html, I'm trying to study what you created. It's exactly what I'm looking for. Also Antialiasing works now. :-) – Jack Berkhout Apr 18 '17 at 22:26
-
https://github.com/JackBerkhout/Draw002 Is with the purple line, antialiasing enabled and your working modification. The purple line is to check if that works, and it does. – Jack Berkhout Apr 18 '17 at 22:39
1 Answers
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 byQGraphicsPathItem
to avoid an unnecessary implicit copy. That's the precondition necessary before modifyingm_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
ordelete
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
andMyScene
. TheMainWindow
knows nothing about the specifics ofMyScene
, and vice-versa. The code withinmain
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
// 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"

- 1
- 1

- 95,931
- 16
- 151
- 313
-
@JackBerkhout See the edit. The Qt example I provide above is much simpler than anything you could ever do using raw Borland C++ and their graphics library. – Kuba hasn't forgotten Monica Apr 19 '17 at 14:29
-
Hi Kuba,thank you very much! Now I understand that part. I have registered for some C++ en QT courses on Udemy.com first... Lots to learn. – Jack Berkhout Apr 22 '17 at 17:13