I'm working on a tree view for a user-defined class called Family_tree with nodes having a pointer to their father, mother, spouse and a vector of children. I created a TreeViewNode class derived from QGraphicsItem with a node* m_node member and overrides for the boundingRect and paint methods like so:
QRectF TreeViewNode::boundingRect() const {
return QRectF(-50, -50, 100, 100);
}
void TreeViewNode::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) {
qDebug() << "Paint function called";
painter->drawRect(boundingRect());
painter->drawText(boundingRect(), Qt::AlignCenter, QString::fromStdString(m_node->getPatient().get_Name()));
for (auto child : m_node->getChildren()) {
TreeViewNode* childNode = new TreeViewNode(child, this);
childNode->setPos(0, 100);
QLineF line(0, 50, 0, 150);
painter->drawLine(line);
}
}
I've set up a Family_tree with just one node for simplicity (the root) and I'm trying to get it to show in the view in MainWindow but I don't see paint getting called at all despite me adding the root to the scene (scene.item().count() returns 1..) and the view is simply blank. Am I doing something blatantly wrong here? Thank you for your time!
//MainWindow.cpp
TreeViewNode* root = new TreeViewNode(family->get_root());
view = new QGraphicsView(); QGraphicsScene scene; view->setScene(&scene);
scene.addItem(root); root->setPos(100, 100); view->update();
Related
The QGraphicsObject in green rectangle, it is the parent of the QGraphicsObject in red rectangle.
childInRed->setParentItem(this);
When I drag the parent object in green rect and move it fast, the background of the child object in red rect is not repainted correctly.
I know I can use update in the parent's mouseMoveEvent force the child to repaint. But this is not good, because I don't need to repaint the parent at all.
#include "asdf.h"
#include <QtWidgets/QGraphicsScene>
#include <QtWidgets/QGraphicsView>
#include <QtWidgets>
class CTestGraphicsObject : public QGraphicsObject
{
public:
QColor m_c;
CTestGraphicsObject(QColor c)
: QGraphicsObject(NULL)
, m_c(c)
{
setFlag(QGraphicsItem::ItemIsMovable, true);
setFlag(QGraphicsItem::ItemIsFocusable, true);
setFlag(QGraphicsItem::ItemIsSelectable, true);
auto effect = new QGraphicsDropShadowEffect;
effect->setOffset(4, 4);
effect->setBlurRadius(20);
setGraphicsEffect(effect);
}
virtual QRectF boundingRect() const override
{
auto rc = QRectF(0, 0, 100, 100);
return rc;
}
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override
{
painter->setPen(QPen(m_c));
painter->drawRect(this->boundingRect());
}
};
asdf::asdf(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
auto s = new QGraphicsScene(this);
auto v = new QGraphicsView;
v->setScene(s);
CTestGraphicsObject* pParent = new CTestGraphicsObject(Qt::green);
CTestGraphicsObject* pChild = new CTestGraphicsObject(Qt::red);
pChild->setParentItem(pParent);
pChild->setPos(0, 100);
s->addItem(pParent);
s->addItem(pChild);
QVBoxLayout* l = new QVBoxLayout(this->centralWidget());
l->addWidget(v);
}
asdf::~asdf()
{
}
The QGraphicsDropShadowEffect causes this problem, It seems I'm not using it in right way.
According to the Qt documentation, the scene uses the bounding rect and region to define the area to repainted when an item is updated (moved in your case).
If you child is outside its parent, the scene will miss some part when repainting...
Extend the bouding rect/region to cover its children.
If you do something like that, it will work:
virtual QRectF boundingRect() const override
{
if (this->childrenBoundingRect().isEmpty()) // No children
return QRectF(0, 0, 100, 100);
return QRectF(0, 0, 100, 100).united(this->childrenBoundingRect());
}
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override
{
painter->setPen(QPen(m_c));
painter->drawRect(QRectF(0, 0, 100, 100));
}
I want to somehow paint a selection rectangle on scene, to show selected items (not he item bounding rectangle, but the bounding rectangle mapped to scene - and if multiple selection, the selection bounding rectangle).
I would like to try something like, on mouse press, to show the rectangle (and update based on current selection), and on mouse release, to hide it.
I am having trouble keeping the rectangle on the scene, and on mouse release it may be removing it, or maybe it was never there - and I get an error:
QGraphicsScene::removeItem: item 0x37828's scene (0x0) is different from this scene (0x1f57b68)
(The above error, and the fact that the item doesn't stay after mouse press, makes me think that it is not added properly but I don't understand why).
Here is a little sample code:
#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
class MyScene : public QGraphicsScene
{
public:
MyScene(qreal x, qreal y, qreal w, qreal h) {
setSceneRect(x, y, w, h);
m_selectionRectangle = new QGraphicsRectItem(0,0,1,1);
m_selectionRectangle->setBrush(Qt::magenta);
m_selectionRectangle->setOpacity(0.2);
}
~MyScene() {
if(m_selectionRectangle)
delete m_selectionRectangle;
}
protected:
virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) {
QGraphicsScene::mousePressEvent(event);
if(!selectedItems().isEmpty()) {
QRectF selectionRect = QRectF();
foreach(QGraphicsItem* item, selectedItems())
selectionRect |= item->mapToScene(item->boundingRect()).boundingRect();
m_selectionRectangle->setRect(selectionRect);
addItem(m_selectionRectangle);
}
}
virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {
QGraphicsScene::mouseReleaseEvent(event);
removeItem(m_selectionRectangle);
}
private:
QGraphicsRectItem* m_selectionRectangle;
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MyScene* s = new MyScene(0, 0, 800, 600);
QGraphicsView view(s);
view.setDragMode(QGraphicsView::RubberBandDrag);
view.show();
QGraphicsRectItem* xxx = new QGraphicsRectItem(200, 200, 100, 100);
QGraphicsEllipseItem* yyy = new QGraphicsEllipseItem(300, 300, 200, 100);
s->addItem(xxx);
s->addItem(yyy);
xxx->setFlags(QGraphicsItem::ItemIsMovable|QGraphicsItem::ItemIsFocusable|QGraphicsItem::ItemIsSelectable);
yyy->setFlags(QGraphicsItem::ItemIsMovable|QGraphicsItem::ItemIsFocusable|QGraphicsItem::ItemIsSelectable);
return app.exec();
}
What is the meaning of that error, what am I doing wrong in adding the selection rectangle, and why doesn't it stay there - and how can I fix it ?
The meaning of the error is literal: you're passing an item to removeItem that is not a child item of the scene you're trying to remove it from. It is nonsense to remove an item that is not in the scene to start with.
There is nothing that guarantees that the selection rectangle is on the scene when the mouse button is released, since there are paths through mousePressEvent that don't add the rectangle to the scene. I'm not even sure if you are guaranteed to get a press event preceding each release event at all.
You have to only remove the rectangle if it's on the scene (and virtual is not needed, but Q_DECL_OVERRIDE is!):
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) Q_DECL_OVERRIDE {
QGraphicsScene::mouseReleaseEvent(event);
if (m_selectionRectangle.scene()) removeItem(m_selectionRectangle);
}
Also, the destructor of your custom scene is unnecessary. Simply add the item by value:
class MyScene : public QGraphicsScene
{
QGraphicsRectItem m_selectionRectangle;
public:
MyScene(qreal x, qreal y, qreal w, qreal h) :
m_selectionRectangle(0, 0, 1 1)
{
setSceneRect(x, y, w, h);
m_selectionRectangle.setBrush(Qt::magenta);
m_selectionRectangle.setOpacity(0.2);
}
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event) Q_DECL_OVERRIDE {
...
}
...
};
I am trying to get the positions of the graphicsitems in the scene.
But their QPointF value always remains (0,0).
I am painting when mouse-click event occurs. On debugging scene->items(), I get
(QGraphicsItem(this =0x22edff0, parent =0x0, pos =QPointF(0, 0) , z = 0 , flags = ( ) ) )
for each graphics item in scene but with different memory address.
This is my mainwindow.cpp code:
#include "mainwindow.h"
#include <QDebug>
MainWindow::MainWindow()
{
scene = new QGraphicsScene;
view = new QGraphicsView;
view->setScene(scene);
button = new QPushButton("Item");
QGridLayout *layout = new QGridLayout;
layout->addWidget(button);
layout->addWidget(view);
setLayout(layout);
connect(button, SIGNAL(clicked()), this, SLOT(createItem()));
}
void MainWindow::createItem()
{
myEntity = new Item;
scene->addItem(myEntity);
count_items();
}
void MainWindow::count_items()
{
qDebug() << scene->items().count();
qDebug() << scene->items();
}
MainWindow::~MainWindow()
{}
This is my item.cpp code:
#include "item.h"
Item::Item()
{
ClickFlag = true;
PaintFlag = false;
}
Item::~Item(){}
QRectF Item::boundingRect() const
{
// outer most edges
return QRectF(0,0,1450,1400);
}
void Item::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if(event->button()==Qt::LeftButton){
if(ClickFlag){
x = event->pos().x();
y = event->pos().y();
PaintFlag = true;
ClickFlag = false;
}
}
}
void Item::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget)
{
if(PaintFlag){
QPen paintPen;
paintPen.setWidth(4);
pt.setX(x);
pt.setY(y);
painter->setPen(paintPen);
painter->drawPoint(x,y);
update();
}
}
I can't seem to find the position of these items correctly.
This task is supposed to be implemented in another way. For example:
Use QGraphicsScene::addEllipse to add small ellipse (which will look like a point) to the scene. Save the pointer to it in a class variable. The ellipse itself should be at the center, e.g. (-1, -1, 2, 2).
Reimplement QGraphicsScene::mousePressEvent, detect mouse clicks and call setPos for the ellipse item (or add new ellipse each time and immediately call setPos if you need multiple points).
Use QGraphicsItem::pos to get previously set positions.
Reimplementing QGraphicsItem::paint is usually an over-complication. Qt have plenty of item classes for all common needs. Just build your scene from geometric primitives, pixmaps, etc.
I have a class MenuItem which inherits from QGraphicsItem and reimplemented boundingRect(), shape(), paint(), outlineRect():
MenuItem::MenuItem(const QString& qsText, qreal qrYPos)
{
m_qsText = qsText;
m_BackgroundColor = Qt::white;
m_OutlineColor = Qt::darkBlue;
m_TextColor = Qt::darkGreen;
qDebug() << pos();
setPos(mapToParent(200,200)); //<-- when calling this method, mousePressEvent()
// behaves not as expected
qDebug() << pos();
}
QRectF MenuItem::boundingRect() const
{
const int iMargin = 1;
return outlineRect().adjusted(-iMargin, -iMargin, +iMargin, +iMargin);
}
QPainterPath MenuItem::shape() const
{
QRectF rect = outlineRect();
QPainterPath path;
path.addRect(rect);
return path;
}
void MenuItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
QPen pen(m_OutlineColor);
painter->setPen(pen);
painter->setBrush(m_BackgroundColor);
QRectF rect = outlineRect();
painter->drawRect(rect);
painter->setPen(m_TextColor);
painter->drawText(rect, Qt::AlignCenter, m_qsText);
}
void MenuItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
qDebug() << "Item Mouse Pressed";
}
QString MenuItem::getText() const
{
return m_qsText;
}
QRectF MenuItem::outlineRect() const
{
const int iPadding = 8;
QFontMetricsF metrics = QFontMetricsF(QApplication::font());
QRectF rect = metrics.boundingRect(m_qsText);
rect.adjust(-iPadding, -iPadding, +iPadding, +iPadding);
rect.translate(-rect.center());
return rect;
}
In another class, called Menu which inherits from QGraphicsScene, I added one instance of MenuItem:
Menu::Menu()
: QGraphicsScene()
{
setSceneRect(0, 0, 800, 600);
m_miNewGame = new MenuItem("New Game", 300);
this->addItem(m_miNewGame);
//m_miNewGame->setPos(200,200);
}
The Menu class reimplements mousePressEvent
void Menu::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
//qDebug() << "Menu Mouse Pressed";
MenuItem *gi = dynamic_cast<MenuItem*>(itemAt(event->pos(), QTransform()));
if (gi)
qDebug() << gi->getText();
QGraphicsScene::mousePressEvent(event); // this forwards the event to the item
if (itemAt(event->pos(), QTransform()))
{
qDebug() << "You Pressed an Item";
}
}
If I use setPos() method inside the MenuItem constructor the MenuItem gets positioned right but inside the Menu::mousePressEvent() method, MenuItem* returned from itemAt() is always NULL.
Omitting the setPos() method, the MenuItem stays in the top left corner (0,0) of the scene and mousePressEvents are handled as expected: returning the MenuItems Text with the getText() method.
Why is the MenuItem* NULL when calling setPos()?
Do I have to reimplement setPos() or what am I doing wrong?
Any help is welcome.
In MenuItem() constructor you use mapToParent. But your item doesn't have any parent item. So using mapToParent is pointless, it's equivalent to mapToScene in this case. And since your item's initial position is (0, 0) and no transformation has been applied, mapToScene will return its argument's value without changes. So it's equivalent to setPos(200, 200). It seems strange to use the result of mapToParent or mapToScene in setPos. I don't understand what you were trying to do.
QGraphicsSceneMouseEvent::pos returns coordinates of the event in target item's coordinates. Since you're using it in QGraphicsScene::keyPressEvent, the event has not been propagated to any item, and pos() always returns (0, 0). The documentation isn't clear about it, but I've checked it.
If you didn't use setPos, your item's position will be (0, 0) and itemAt(0, 0) will find your item (regardless of the point the user have actually clicked). But if you did use setPos, itemAt(0, 0) returns 0 because there is no item at this point. If you replace event->pos() with event->scenePos(), it will work correct.
However, it's unusual to reimplement QGraphicsScene::keyPressEvent to catch clicking on item. You should reimplement QGraphicsItem::mousePressEvent instead. It will be called only if the item has been clicked, and you don't have to check event's coordinates to determine that.
So here's what I'm trying to do - Using a custom QGraphicsItem, I have my QPainter setup to paint into a QImage, which I then save to a file (or just keep the QImage in memory until I need it).
The issue I've found is that QGraphicsItem::paint() is only called if the QGraphcsItem belongs to a scene, the scene belongs to a view, AND the view and scene are not hidden.
Here's the code outside my project for testing purposes:
MyQGfx Class
void MyQGfx::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
qDebug() << "begin of paint function";
QRectF rec = boundingRect();
QImage image(boundingRect().size().toSize(),
QImage::Format_ARGB32_Premultiplied);
image.fill(0);
// construct a dedicated offline painter for this image
QPainter imagePainter(&image);
imagePainter.translate(-boundingRect().topLeft());
// paint the item using imagePainter
imagePainter.setPen(Qt::blue);
imagePainter.setBrush(Qt::green);
imagePainter.drawEllipse(-50, -50, 100, 100);
imagePainter.end();
if(image.save("C://plot.jpg"))
{
qDebug() << "written";
}
else {
qDebug() << "not written";
}
}
MainWindow Class
....
QGraphicsView* view = new QGraphicsView(this);
QGraphicsScene* scene = new QGraphicsScene(this);
view->setScene(scene);
MyQGfx* gfx = new MyQGfx();
scene->addItem(gfx);
gfx->update();
....
This all works fine, but I don't want a view/scene necessary, as it would be displayed on the mainwindow - is there any way around this?
Can't you just create a custom method accepting a QPainter, one painting on a QImage and one on your item?