Why doesn't child QGraphicsObject repaint its background correctly - qt

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));
}

Related

QGraphicsView paint not called

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();

Properties and QGraphicsSimpleTextItem

I was wondering if we could use properties to animate in a class which inherits QGraphicsSimpleTextItem?
I'm drawing this button :
It is made up of :
A circle, which inherits QGraphicsObject and override the geometry property
An ellipse, which basicaly is the same but takes the circle as a parent
A text, which inherits QObject and QGraphicsSimpleTextItem
For the first two, the animations works. But concerning the last one, I have the followings errors :
QPropertyAnimation: you're trying to animate a non-existing property localisation of your QObject
QPropertyAnimation: you're trying to animate a non-existing property localisation of your QObject
QPropertyAnimation: you're trying to animate a non-existing property sizePolicy of your QObject
QPropertyAnimation: you're trying to animate a non-existing property sizePolicy of your QObject
Here is my class 'MyText' :
class MyTextOk : public QObject, public QGraphicsSimpleTextItem
{
Q_PROPERTY(QPointF localisation READ localisation WRITE setLocalisation)
Q_PROPERTY(QFont sizePolicy READ sizePolicy WRITE setSizePolicy)
public:
explicit MyTextOk(QGraphicsObject *parent = 0);
~MyTextOk();
QPointF localisation() const;
void setLocalisation(const QPointF &value);
QFont sizePolicy() const;
void setSizePolicy(const QFont &value);
private:
QRectF boundingRect() const;
protected :
QPointF point;
QFont font;
};
And my .ccp
QVariant myFontInterpolator(const QFont &start, const QFont &end, qreal progress)
{
if (progress<0.5)
{
int a = (1-progress)*50 + progress*45;
QFont rt(start);
rt.setPointSize(a);
return rt;
}
else
{
int a = (1-progress)*45 + progress*50;
QFont rt(start);
rt.setPointSize(a);
return rt;
}
Q_UNUSED(end)
}
MyTextOk::MyTextOk(QGraphicsObject *parent)
: QObject(parent), QGraphicsSimpleTextItem(parent)
{
point = QPointF(-40,-45);
this->setText("Ok");
this->setPos(point);
this->setBrush(QBrush(Qt::white));
font = QFont("Colibri",50);
this->setFont(font);
qRegisterAnimationInterpolator<QFont>(myFontInterpolator);
}
MyTextOk::~MyTextOk()
{
}
QPointF MyTextOk::localisation() const
{
return point;
}
void MyTextOk::setLocalisation(const QPointF &value)
{
if(point!=value)
{
point = value;
update();
}
}
QFont MyTextOk::sizePolicy() const
{
return font;
}
void MyTextOk::setSizePolicy(const QFont &value)
{
if(font!=value)
{
font=value;
update();
}
}
QRectF MyTextOk::boundingRect() const
{
return QRectF(0,0,0,0);
}
And in my MainWindow I animate :
void MainWindow::lancerAnimBoutonRond()
{
animationBoutonRondTaille = new QPropertyAnimation(roundButton, "geometry");
animationBoutonRondTaille->setDuration(300);
animationBoutonRondTaille->setKeyValueAt(0, QRectF(-90, -90, 180, 180));
animationBoutonRondTaille->setKeyValueAt(0.5, QRectF(-85,-85,170,170));
animationBoutonRondTaille->setKeyValueAt(1, QRectF(-90, -90, 180, 180));
animationBoutonRondTaille -> start();
animationBoutonRondEllipse = new QPropertyAnimation(whiteShadow, "geometry");
animationBoutonRondEllipse->setDuration(300);
animationBoutonRondEllipse->setKeyValueAt(0,QRectF(-70,-80,140,80));
animationBoutonRondEllipse->setKeyValueAt(0.5,QRectF(-65,-75,130,90));
animationBoutonRondEllipse->setKeyValueAt(1,QRectF(-70,-80,140,80));
animationBoutonRondEllipse->start(); // These two work
animationBoutonRondOk = new QPropertyAnimation(textOk,"localisation");
animationBoutonRondOk->setDuration(300);
animationBoutonRondOk->setKeyValueAt(0,QPointF(-40,-45));
animationBoutonRondOk->setKeyValueAt(0.5,QPointF(-35, -40));
animationBoutonRondOk->setKeyValueAt(1,QPointF(-40, -45));
animationBoutonRondOk->start(); //error : QPropertyAnimation: you're trying to animate a non-existing property localisation of your QObject
animationBoutonRondOkTaille = new QPropertyAnimation(textOk,"sizePolicy");
animationBoutonRondOkTaille->setDuration(300);
animationBoutonRondOkTaille->setStartValue(QFont("Colibri",50));
animationBoutonRondOkTaille->setEndValue(QFont("Colibri",50));
animationBoutonRondOkTaille->start(); //error : 'QPropertyAnimation: you're trying to animate a non-existing property sizePolicy of your QObject'
}
I don't know if I can name my "own" properties, but I can't (?) override the font and pos properties since I inherits QGraphicsSimpleTextItem and use setFont() and setPos()
You can find all the code here if you want to try.
Thank you for your time.
Problem solved.
Q_OBJECT macro in the MyTextOk class definition was missing. After placing it, the code runs fine.
You can find a working example of my button here.

How can we make a QRubberBand semi-transparent

I have already used
setOpacity();
setAttribute(Qt:WA_TranseculentBackground:)
even i have tied all the available solution nothing has effect.
this is my code
void Physician::mouseMoveEvent(QMouseEvent *e)
{
rubberBand->hide();
bottomRight = e->pos();
QRect rect = QRect(topLeft, bottomRight);
rubberBand->setGeometry(rect);//Area Bounding
QToolTip::showText(e->globalPos(), QString("%1,%2")
.arg(rubberBand->size().width())
.arg(rubberBand->size().height()), this);
}
void Physician::mousePressEvent(QMouseEvent *e)
{
rubberBand->hide();
if(e->x()<ui->videoShowLabel->x()||e->y()<ui->videoShowLabel->y())
{
selectWithInLabel.critical(0,"Error", "Select within the LABEL !");
selectWithInLabel.setFixedSize(500, 200);
}
else{
topLeft = e->pos();
myPoint = ui->videoShowLabel->mapFromGlobal(this->mapToGlobal(e->pos()));
}
}
void Physician::mouseReleaseEvent(QMouseEvent *e){
rubberBand->setWindowOpacity(0.5);
rubberBand->show();
}
void Physician::on_manualROIRadioButton_clicked()
{
rubberBand = new RubberBand(RubberBand::Rectangle, this);
}
What should i do to make rubberBand semiTransparent
I assume you sub classed QRubberBand (RubberBand).
After calling the setWindowopacity the paint event is generated (http://doc.qt.io/qt-5/qwidget.html#windowOpacity-prop)
So redefine the paint event in RubberBand class.
Inside the paint event call "initStyleOption" (given below)
http://doc.qt.io/qt-5/qrubberband.html#initStyleOption
By calling "initStyleOption" you can set the rubber band parameters for drawing.
The real issue with making the QRubberband semi-transparent is that mplayer is painting on a window without Qt having any knowledge of it. Hence Qt itself cannot act as a compositor to generate the required effect.
One possibility would be to make the QRubberBand a top level window. That way the compositing is the responsibility of the underlying graphics system rather than Qt.
With that in mind try the following. Firstly a utility base class to manage the geometry...
class abstract_rubber_band {
public:
virtual QRect get_geometry () const
{
return(QRect(m_parent->mapFromGlobal(widget().geometry().topLeft()), widget().size()));
}
virtual void set_geometry (const QRect &rect)
{
widget().setGeometry(map_rect(rect));
}
protected:
explicit abstract_rubber_band (QWidget *parent)
: m_parent(parent)
{}
/*
* #param point Coords relative to effective parent.
*/
QPoint map_point (QPoint point) const
{
if (point.x() < 0)
point.rx() = 0;
else if (point.x() >= m_parent->width())
point.rx() = m_parent->width() - 1;
if (point.y() < 0)
point.ry() = 0;
else if (point.y() >= m_parent->height())
point.ry() = m_parent->height() - 1;
point = m_parent->mapToGlobal(point);
return(point);
}
QRect map_rect (QRect rect) const
{
return(QRect(map_point(rect.topLeft()), map_point(rect.bottomRight())));
}
private:
QWidget &widget ()
{
return(dynamic_cast<QWidget &>(*this));
}
const QWidget &widget () const
{
return(dynamic_cast<const QWidget &>(*this));
}
QWidget *m_parent;
};
Now use the above as a base of the required rubber band class...
class rubber_band: public abstract_rubber_band,
public QRubberBand {
using super = QRubberBand;
public:
/*
* #param parent Note that this isn't actually used as the
* parent widget but rather the widget to which
* this rubber_band should be confined.
*/
explicit rubber_band (QWidget *parent)
: abstract_rubber_band(parent)
, super(QRubberBand::Rectangle)
{
setAttribute(Qt::WA_TranslucentBackground, true);
}
protected:
virtual void paintEvent (QPaintEvent *event) override
{
QPainter painter(this);
painter.fillRect(rect(), QColor::fromRgbF(0.5, 0.5, 1.0, 0.25));
QPen pen(Qt::green);
pen.setWidth(5);
painter.setPen(pen);
painter.setBrush(Qt::NoBrush);
painter.drawRect(rect().adjusted(0, 0, -1, -1));
/*
* Display the current geometry in the top left corner.
*/
QRect geom(get_geometry());
painter.drawText(rect().adjusted(5, 5, 0, 0),
Qt::AlignLeft | Qt::AlignTop,
QString("%1x%2+%3+%4").arg(geom.width()).arg(geom.height()).arg(geom.left()).arg(geom.top()));
}
};
The above rubber_band class should almost be a drop in replacement for QRubberBand. The main difference is that rather than reading/writing its geometry with geometry/setGeometry you must use get_geometry/set_geometry -- those will perform the mapping to/from global coordinates.
In your particular case create the rubber_band with...
rubberBand = new rubber_band(ui->videoShowLabel);

QGraphicsRectItem member of a custom scene class added on mouse action gives error

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 {
...
}
...
};

How to make a QWidget alpha-transparent

I need to create an alpha transparent widget, it's basically a navigation bar with a shadow and the widgets below need to be partially visible through the shadow. The widget loads a PNG then draws it on the paint event. The problem is that the shadow is all black and is not alpha-transparent.
This is the code I'm currently using:
NavigationBar::NavigationBar(QWidget *parent) : XQWidget(parent) {
backgroundPixmap_ = new QPixmap();
backgroundPixmap_->load(FilePaths::skinFile("NavigationBarBackground.png"), "png");
setAttribute(Qt::WA_NoBackground, true); // This is supposed to remove the background but there's still a (black) background
}
void NavigationBar::paintEvent(QPaintEvent* event) {
QWidget::paintEvent(event);
QPainter painter(this);
int x = 0;
while (x < width()) {
painter.drawPixmap(x, 0, backgroundPixmap_->width(), backgroundPixmap_->height(), *backgroundPixmap_);
x += backgroundPixmap_->width();
}
}
Does anybody know what I need to change to make sure the widget is really transparent?
You're doing too much work :-)
The setAttribute call is not necessary. By default, a widget will not draw anything on its background (assuming Qt >= 4.1). Calling QWidget::paintEvent is also unnecessary - you don't want it to do anything.
Rather than doing the pattern fill yourself, let Qt do it with a QBrush:
NavigationBar::NavigationBar(QWidget *parent) : XQWidget(parent) {
backgroundPixmap_ = new QPixmap();
backgroundPixmap_->load(FilePaths::skinFile("NavigationBarBackground.png"), "png");
// debug check here:
if (!backgroundPixmap_->hasAlphaChannel()) {
// won't work
}
}
void NavigationBar::paintEvent(QPaintEvent* event) {
QPainter painter(this);
painter.fillRect(0, 0, width(), height(), QBrush(*backgroundPixmap));
}
Adjust the height parameter if you don't want the pattern to repeat vertically.
Are you sure your PNG file is actually transparent? The following (which is essentially what you are doing) is working for me. If this fails on your machine, perhaps include what version of Qt you are using, and what platform.
#include <QtGui>
class TransparentWidget : public QWidget {
public:
TransparentWidget()
: QWidget(),
background_pixmap_(":/semi_transparent.png") {
setFixedSize(400, 100);
}
protected:
void paintEvent(QPaintEvent *) {
QPainter painter(this);
int x = 0;
while (x < width()) {
painter.drawPixmap(x, 0, background_pixmap_);
x += background_pixmap_.width();
}
}
private:
QPixmap background_pixmap_;
};
class ParentWidget : public QWidget {
public:
ParentWidget() : QWidget() {
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(new TransparentWidget);
layout->addWidget(new QPushButton("Button"));
setLayout(layout);
setBackgroundRole(QPalette::Dark);
setAutoFillBackground(true);
}
};
int main(int argc, char **argv) {
QApplication app(argc, argv);
ParentWidget w;
w.show();
return app.exec();
}

Resources