Qt: How to adjust QGraphicsItem to dynamicaly change size - c++

I've this problem for several days. I have created QGraphicsItem and I want to stretch/adjust it's size to size of my QGraphicsView. I was using paint() method, but got with it problems with updating. Now I've used boundingRect() but it uses fixed size. When I set too big size it expand my scene and scrollbars are appearing. Is there way to adjust size of item to size of View?
EDIT: I want only adjust height of my object.
Here's some code:
Header of my Item:
#ifndef POINTER_H
#define POINTER_H
#include <QObject>
#include <QColor>
#include <QRect>
#include <QGraphicsLineItem>
#include <QPainter>
#include <QGraphicsSceneMouseEvent>
class Pointer : public QGraphicsLineItem
{
public:
Pointer();
void paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget);
QRectF boundingRect() const;
int position;
void changePosition(int x);
};
#endif // TRACKPOINTER_H
Implementation of my Item:
#include "pointer.h"
Pointer::Pointer()
{
//this->setFlag(QGraphicsLineItem::ItemIsMovable);
//setFlag(QGraphicsLineItem::ItemIsFocusable);
//setFocus();
}
void Pointer::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
QPen pen(Qt::red);
painter->setPen(pen);
painter->setBrush(QColor(77,77,77));
painter->drawLine(0,0,0,2000);
}
QRectF Pointer::boundingRect() const
{
return QRectF(0,0,2,600);
}
void Pointer::changePosition(int x)
{
//position = x;
setPos(x,0);
update();
}
And my Window:
Window::Window(Timers *timer, TrackPointer *tp)
{
Timeline = new QGraphicsScene(this);
TimelineView = new QGraphicsView(Timeline);
TimelineView->setAlignment(Qt::AlignTop|Qt::AlignLeft);
QVBoxLayout *timeLineLayout = new QVBoxLayout;
timeLineLayout->addWidget(TimelineView);
Pointer *pointer = new Pointer;
Timeline->addItem(pointer);
}
I have also problems with my scene: When my object moves somewhere away - it expand scene. Later when I bring my object back to it's starting position scene still is expanded and I have scrollbars to scroll my view around the scene. Is there way to decrease scene size using my object?

The promised example looks something like this:
MainWindow.h
#include <QMainWindow>
class QGraphicsView;
class QGraphicsRectItem;
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
bool eventFilter(QObject *watched, QEvent *event) override;
private:
QGraphicsView *m_view;
QGraphicsRectItem *m_item;
};
MainWindow.cpp
#include "MainWindow.h"
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QEvent>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
m_view(new QGraphicsView(this)),
m_item(new QGraphicsRectItem(0, 0, 1, 1))
{
m_view->setScene(new QGraphicsScene());
m_view->setFrameStyle(QFrame::NoFrame);
m_view->setAlignment(Qt::AlignLeft | Qt::AlignTop);
m_view->setSceneRect(0, 0, 1, 1);
m_view->installEventFilter(this);
m_view->scene()->addItem(m_item);
setCentralWidget(m_view);
resize(600, 400);
}
bool MainWindow::eventFilter(QObject *watched, QEvent *event)
{
if ((watched == m_view) && (event->type() == QEvent::Resize))
m_item->setRect(m_view->viewport()->rect().adjusted(5, 5, -5, -5));
return QMainWindow::eventFilter(watched, event);
}

look into :QGraphicsView::fitInView
http://doc.qt.io/qt-4.8/qgraphicsview.html#fitInView
QPixmap *pixMap= new QPixmap();;
QGraphicsScene *scene=new QGraphicsScene();;
pixMap->loadFromData(jpegData);
scene->clear();
pixMapItem = scene->addPixmap(*pixMap);
ui->graphicsView->fitInView(scene->sceneRect(), Qt::KeepAspectRatio);
ui->graphicsView->show();
ui->graphicsView->viewport()->update();

Related

Custom widget is flickering and clipping while moving

I'll give you a minimal reproduceable example that was a part of the more complex widget.
Here we just have a custom widget(called MovableItem) with a simple paintEvent. Widget is created, placed onto the central widget and moved to mouse position on MainWindow::mousePressEvent-s.
When moving, it seems like the widget is getting clipped at the side it's moving towards.
mainwindow.h
#include <QMainWindow>
#include <QMouseEvent>
#include <QPropertyAnimation>
#include "movableitem.h"
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
void mousePressEvent(QMouseEvent *event) override;
QWidget* mItem;
};
mainwindow.cpp
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
resize(640, 480);
QWidget* central_widget = new QWidget(this);
mItem = new MovableItem(central_widget);
mItem->move(20, 20);
setCentralWidget(central_widget);
}
void MainWindow::mousePressEvent(QMouseEvent *event) {
QPoint pos = event->pos();
QPropertyAnimation* anim = new QPropertyAnimation(mItem, "geometry");
anim->setDuration(750);
anim->setStartValue(QRect(mItem->x(), mItem->y(), mItem->width(), mItem->height()));
anim->setEndValue(QRect(pos.x(), pos.y(), mItem->width(), mItem->height()));
anim->start();
}
MainWindow::~MainWindow() {}
movableitem.h
#include <QWidget>
#include <QPainter>
#include <QPainterPath>
class MovableItem : public QWidget
{
Q_OBJECT
public:
MovableItem(QWidget *parent = nullptr);
QSize sizeHint() const override;
void paintEvent(QPaintEvent *event) override;
};
movableitem.cpp
#include "movableitem.h"
MovableItem::MovableItem(QWidget *parent) : QWidget(parent)
{
setParent(parent);
}
QSize MovableItem::sizeHint() const {
return QSize(150, 40);
}
void MovableItem::paintEvent(QPaintEvent *event) {
QRect r = rect();
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
QPainterPath path;
path.addRoundedRect(r, 5, 5);
QBrush brush(QColor(217, 217, 217));
painter.fillPath(path, brush);
painter.drawPath(path);
}
Example
As you can see, movement is not fluid, but choppy. I have no idea what is happening. Am I doing something completely wrong ? Do I need to implement some additional functions, is double buffering needed, is this because of Qt's automatic clipping ? Should I rewrite it in QGraphicsView ?
Add mItem->repaint(); and mItem->update(); in mousePressEvent function
void MainWindow::mousePressEvent(QMouseEvent *event)
{
QPoint pos = event->pos();
QPropertyAnimation* anim = new QPropertyAnimation(mItem, "geometry");
anim->setDuration(750);
anim->setStartValue(QRect(mItem->x(), mItem->y(), mItem->width(), mItem->height()));
anim->setEndValue(QRect(pos.x(), pos.y(), mItem->width(), mItem->height()));
anim->start();
mItem->repaint();
mItem->update();
}
my out put is here :

QGraphicsScene item is drawn at twice(x2) position

(warning crossposted on: https://forum.qt.io/topic/105158/qgraphicsscene-item-is-drawn-at-twice-x2-position)
In the following code I am creating a custom widget which has a subclass of QGraphicsScene embeded in it. My aim is to click and add a point to the scene. A point is my subclass of QGraphicsItem (GIPoint). I want to move that point around and later connect it to other points and make a spline path.
The problem I am facing is that the point is not drawn at where I click but at a place which is formed by doubling the mouse-event's scenePos() coordinates. So if I click at (100,100) the point is drawn at (200,200). I suspect that I have misunderstood the coordinate system despite reading the documentation.
The question How to add item in a QGraphicsScene? seems relevant but the proposed solution to transform the mouse-event's coordinates via mapToScene(event->pos()); actually doubles up the position (before it will print that it draws on same position but it would then be x2. Now it also prints it as x2).
So I am asking additionally to point me to some simple-to-digest advice on how the widgets placement works. btw. is QRectF GIPoint::boundingRect() const {
return QRectF(pos().x(), pos().y(), 5, 5);
correct regarding the (x,y) coordinates of the rectangle?
My example code follows:
/* use the following pro:
QT += widgets core gui
CONFIG += debug console
SOURCES = example.cpp
TARGET = example
*/
#include <QGraphicsItem>
#include <QPainter>
#include <QWidget>
#include <QRectF>
#include <QPointF>
#include <QGraphicsScene>
#include <QStyleOptionGraphicsItem>
#include <QGraphicsSceneMouseEvent>
#include <QKeyEvent>
#include <QGraphicsView>
#include <QApplication>
#include <QOpenGLWidget>
#include <QMainWindow>
#include <QGridLayout>
#include <QDebug>
class GIPoint : public QGraphicsItem{
public:
GIPoint(QGraphicsItem * parent, const QPointF &position);
protected:
QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override;
QRectF boundingRect() const override;
void paint(
QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget *widget
);
};
class GraphicsSceneWidget : public QGraphicsScene {
public:
explicit GraphicsSceneWidget(QObject *parent);
~GraphicsSceneWidget();
virtual void mousePressEvent(QGraphicsSceneMouseEvent *event);
};
class VectorGraphicsWidget : public QWidget {
public:
VectorGraphicsWidget(QWidget *parent);
~VectorGraphicsWidget();
private:
GraphicsSceneWidget *myGraphicsSceneWidget;
};
// implementation
GIPoint::GIPoint(
QGraphicsItem *parent,
const QPointF &position
) : QGraphicsItem(parent) {
setFlag(QGraphicsItem::ItemIsMovable, true);
setFlag(QGraphicsItem::ItemIsSelectable, true);
setPos(position);
qWarning() << "GIPoint::GIPoint() : init at " << position;
}
QVariant GIPoint::itemChange(
GraphicsItemChange change,
const QVariant &value
){
if (change == QGraphicsItem::ItemPositionChange) {
qWarning("position changed");
}
return value;
}
QRectF GIPoint::boundingRect() const {
return QRectF(pos().x(), pos().y(), 5, 5);
// return QRectF(0,0, 5, 5);
}
void GIPoint::paint(
QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget *widget
){
(void )option;
(void )widget;
QPointF xx = scenePos();
QRectF rect = QRectF(xx.x(), xx.y(), 10, 10);
qWarning() << "painting: scenePos " << scenePos() << ", rect " << rect;
QBrush brush = QBrush(Qt::black, Qt::SolidPattern);
//painter->fillRect(rect, brush);
painter->drawRect(rect);
}
GraphicsSceneWidget::GraphicsSceneWidget(QObject *parent)
: QGraphicsScene(parent)
{}
GraphicsSceneWidget::~GraphicsSceneWidget(){}
void GraphicsSceneWidget::mousePressEvent(
QGraphicsSceneMouseEvent *event
){
GIPoint *gip = new GIPoint(Q_NULLPTR, event->scenePos());
addItem(gip);
QGraphicsScene::mousePressEvent(event);
}
VectorGraphicsWidget::VectorGraphicsWidget(QWidget *parent) :
QWidget(parent)
{
myGraphicsSceneWidget = new GraphicsSceneWidget(this);
QGraphicsView *view = new QGraphicsView(myGraphicsSceneWidget);
myGraphicsSceneWidget->setSceneRect(QRectF(0, 0, 500, 500));
QGridLayout *centralLayout = new QGridLayout;
centralLayout->addWidget(view);
setLayout(centralLayout);
myGraphicsSceneWidget->addRect(
QRectF(0, 0, 100, 100),
QPen(Qt::black),
QBrush(Qt::green)
);
view->show();
}
VectorGraphicsWidget::~VectorGraphicsWidget() {
delete myGraphicsSceneWidget;
}
int main(int argc, char **argv){
QApplication app(argc, argv);
app.setApplicationName("test");
app.setOrganizationName("myorg");
app.setOrganizationDomain("myorg.com");
QMainWindow *w = new QMainWindow();
w->resize(500, 500);
w->setCentralWidget(new VectorGraphicsWidget(Q_NULLPTR));
w->show();
return app.exec();
}
The problem is that the boundingRect() and the paint() methods is respect the coordinate system of the item, not the scene. So the solution is not to use scenePos() in both methods but 0, 0:
QRectF GIPoint::boundingRect() const {
return QRectF(0, 0, 10, 10);
}
void GIPoint::paint(
QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget *widget
){
(void )option;
(void )widget;
QRectF rect = QRectF(0, 0, 10, 10);
qWarning() << "painting: scenePos " << scenePos() << ", rect " << rect;
QBrush brush = QBrush(Qt::black, Qt::SolidPattern);
painter->fillRect(rect, brush);
painter->drawRect(rect);
}
Although I would recommend using QGraphicsRectItem as it implements what you have done.

How do I scale/resize a QGraphicsWidget based on the QGraphicScene?

I would like my QGraphicsWidget to scale its size based on the size of the scene. The QGraphicsWidget I have currently is a fixed size depending on the return value of sizeHint (QGraphicsWidget is always 200 x 200). Attached below is minimal example:
MainWindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QGraphicsScene>
#include <QGraphicsView>
#include "RectangleWidget.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui;
QGraphicsScene * m_scene;
QGraphicsView * m_view;
RectangleWidget * m_rectangleWidget;
};
#endif // MAINWINDOW_H
MainWindow.cpp:
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
m_scene = new QGraphicsScene(this);
m_view = new QGraphicsView(m_scene, this);
m_view->setAlignment(Qt::AlignLeft | Qt::AlignTop);
m_rectangleWidget = new RectangleWidget();
m_scene->addItem(m_rectangleWidget);
setCentralWidget(m_view);
}
MainWindow::~MainWindow()
{
delete ui;
}
RectangleWidget.h:
#ifndef RECTANGLEWIDGET_H
#define RECTANGLEWIDGET_H
#include <QGraphicsLinearLayout>
#include <QGraphicsWidget>
class RectangleWidget: public QGraphicsWidget
{
public:
RectangleWidget(QGraphicsWidget* parent = nullptr);
QRectF boundingRect() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;
void setGeometry(const QRectF &geom) override;
QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF()) const override;
};
#endif // RECTANGLEWIDGET_H
RectangleWidget.cpp:
#include "rectanglewidget.h"
#include <QPainter>
RectangleWidget::RectangleWidget(QGraphicsWidget* parent)
{
}
void RectangleWidget::paint(QPainter *painter,
const QStyleOptionGraphicsItem *option, QWidget *widget /*= 0*/)
{
Q_UNUSED(widget);
Q_UNUSED(option);
//Draw border
painter->drawRoundedRect(boundingRect(), 0.0, 0.0);
}
QRectF RectangleWidget::boundingRect() const
{
return QRectF(QPointF(0,0), geometry().size());
}
void RectangleWidget::setGeometry(const QRectF &geom)
{
prepareGeometryChange();
QGraphicsLayoutItem::setGeometry(geom);
setPos(geom.topLeft());
}
QSizeF RectangleWidget::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const
{
switch (which) {
case Qt::MinimumSize:
return QSizeF(200, 200);
default:
break;
}
return constraint;
}
Any help on this would be appreciated.
Background
Your QGraphicsWidget have to be aware of two things:
When it is added to a scene
In order to do that you have to reimplement QGraphicsWidget::itemChange and look for a change of type QGraphicsItem::ItemSceneHasChanged.
When the size of this scene changes
This could be done by connecting a slot or a lambda function to the QGraphicsScene::sceneRectChanged signal.
Solution
Based on the given explanation, my solution would be the following:
In RectangleWidget.h after QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF()) const override; add:
protected:
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;
private:
QSize m_rectSize;
In RectangleWidget.cpp change return QSizeF(200, 200); to return m_rectSize; and add at the end:
QVariant RectangleWidget::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
{
if (change == ItemSceneHasChanged) {
connect(value.value<QGraphicsScene *>(), &QGraphicsScene::sceneRectChanged, [this](const QRectF &rect){
m_rectSize.setWidth(rect.size().width());
m_rectSize.setHeight(rect.size().height());
});
}
return QGraphicsWidget::itemChange(change, value);
}
Finally, in MainWindow.cpp after m_scene->addItem(m_rectangleWidget); set the sceneRect as follows:
m_scene->setSceneRect(0, 0, 100, 400);
Note: The rectangle will respond to the changes of the scene, not the view. So if you resize the window, the rectangle will not be resized.
Adjustment
This will make the rectangle exactly the same size as the scene. If you want a different ratio, say 0.5, instead of m_rectSize.setWidth(rect.size().width()); write m_rectSize.setWidth(rect.size().width() / 2);, respectively m_rectSize.setHeight(rect.size().height() / 2);.

QGraphicsSceneMouseEvent is not called

When I click on my scene - nothing happens. I tried many various methods with QMouseEvent and QGraphicsSceneMouseEvent, tried to set focus on my object and then try to click on it - with no result. I was also looking for solution on the Internet on posts from other people, but didn't found anything that would work for me. Where I make mistakes?
Here is some code below:
This is one of my windows, where I create scene, view and object - that I want make respond on my mouse events:
TrackWindow::TrackWindow(Timers *timer)
{
timeLineBar = new QGraphicsScene(this);
timeLineBarView = new QGraphicsView(timeLineBar);
timeLineBarView->setFixedHeight(20);
timeLineBarView->setAlignment(Qt::AlignTop|Qt::AlignLeft);
timeLineBarView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
timeLineBarView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
timeLineBarView->setStyleSheet("background-color: #333333; border:none;");
QVBoxLayout *timeLineLayout = new QVBoxLayout;
timeLineLayout->addWidget(timeLineBarView);
timeLineLayout->addWidget(tracksTimelineView);
timeline = new TimeLine(timer);
timeLineBar->addItem(timeline);
//timeline->setFlag(QGraphicsItem::ItemIsFocusable);
//timeline->setFocus();
}
Here is my Timeline class implementation:
TimeLine::TimeLine(Timers *t)
{
timer = new Timers;
timer = t;
timeLineWidth = 200000;
timeLineScale = 0.1;
}
QRectF TimeLine::boundingRect() const
{
return QRectF(0,0,timeLineWidth,18);
}
void TimeLine::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
QRectF rect = boundingRect();
QPen pen(Qt::NoPen);
painter->setPen(pen);
painter->setBrush(QColor(200,200,200));
painter->drawRect(rect);
for(int i=0;i<200;i++)
{
painter->drawText(i*30,9,QString::number(i+1));
}
}
void TimeLine::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if(event->button() == Qt::LeftButton)
{
timer->start();
}
}
And header of my class:
#ifndef TIMELINE_H
#define TIMELINE_H
#include <QObject>
#include <QColor>
#include <QRect>
#include <QGraphicsRectItem>
#include <QPainter>
#include <QGraphicsSceneMouseEvent>
#include "timers.h"
class TimeLine: public QGraphicsRectItem
{
public:
TimeLine(Timers *t);
QRectF boundingRect() const;
void paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget);
float timeLineScale;
int timeLineWidth;
void mousePressEvent(QGraphicsSceneMouseEvent * event);
private:
Timers *timer;
protected:
void mouseReleaseEvent(QGraphicsSceneMouseEvent * event);
};
#endif // TRACK_H
The problem is caused by overwriting the boundingRect() method, how are you inheriting from QGraphicsRectItem it is correct to use setRect()
timeline.h
#ifndef TIMELINE_H
#define TIMELINE_H
#include "timers.h"
#include <QGraphicsRectItem>
class TimeLine : public QGraphicsRectItem
{
public:
TimeLine(Timers *t, QGraphicsItem *parent = 0);
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event);
private:
int timeLineWidth;
float timeLineScale;
Timers *timer;
};
#endif // TIMELINE_H
timeline.cpp
#include "timeline.h"
#include <QGraphicsSceneMouseEvent>
#include <QPainter>
#include <QTimer>
TimeLine::TimeLine(Timers *t, QGraphicsItem *parent):QGraphicsRectItem(parent)
{
timer = t;
timeLineWidth=200000;
setRect(QRectF(0,0,timeLineWidth,18));
timeLineScale = 0.1;
}
void TimeLine::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
Q_UNUSED(option)
Q_UNUSED(widget)
QRectF rect = boundingRect().toRect();
QPen pen(Qt::NoPen);
painter->setPen(pen);
painter->setBrush(QColor(200,200,200));
painter->drawRect(rect);
painter->setPen(Qt::red);
for(int i=0;i<200;i++)
{
painter->drawText(i*30,9,QString::number(i+1));
}
}
void TimeLine::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if(event->button() == Qt::LeftButton)
timer->start();
}
Note: it is not necessary to create a Timers inside TimeLine: timer = new Timers; since afterwards you are changing the direction of the pointer and therefore that space is unusable besides that it is unnecessary.
In the following link you will find the project that you use to test the functionality.

Using QPainter to draw a line between QWidgets

I'm working on an application where I need to be able to draw a line between two QWidget objects. I have tried quite a few things, but my current attempt (which I think is in the right direction I just think I'm missing something) is to have the containing widget (which I called DrawWidget and which holds the QGridLayout that the QWidget objects are added to) override the paintEvent method and call the QPainter::drawLine() function.
The issues I'm having are that:
No matter how I try to get the position of the widgets, the endpoints of the line are always in the wrong place
Whenever I try to draw a second line, the first line that I drew gets erased.
Here is the paintEvent function of the containing widget:
void paintEvent(QPaintEvent *)
{
if (!drewSinceUpdate){
drewSinceUpdate = true;
QPainter painter(this);
painter.setPen(QPen(Qt::black));
painter.drawLine(start->geometry().center(), end->geometry().center());
}
}
I have tried many different ways to get the correct position of the widgets in the last line of paintEvent, which I will post some of the ways (I can't remember all of them):
painter.drawLine(start->pos(), end->pos());
painter.drawLine(start->mapToGlobal(start->geometry().center()), end->mapToGlobal(end->geometry().center()));
painter.drawLine(this->mapToGlobal(start->geometry().center()), this->mapToGlobal(end->geometry().center()));
painter.drawLine(start->mapTo(this, start->pos()), end->mapTo(this, end->pos()));
painter.drawLine(this->mapFrom(start, start->pos()), this->mapFrom(end, end->pos()));
And just to make my question clear, here is an example of what I am looking for, taken from QT Diagram Scene Example:
But this is what I end up getting:
Thank you for any help you can provide.
NOTE:
-start and end are both QWidget objects which I passed in using another method
-The hierarchy relevant to DrawWidget is:
QMainWindow
->QScrollArea
->DrawWidget
->QGridLayout
->Items <-- These are the things I want to connect
EDIT: To make a Complete and Verifiable example, here is the entirety of the relevant code.
MainWindow.cpp:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QTextEdit>
#include <QPushButton>
#include <QScrollBar>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
// Setting up the relevant hierarchy
ui->setupUi(this);
scrollArea = new QScrollArea();
setCentralWidget(scrollArea);
drawWidget = new DrawWidget();
gridLayout = new QGridLayout();
gridLayout->setSpacing(300);
drawWidget->setLayout(gridLayout);
scrollArea->setWidget(drawWidget);
scrollArea->setWidgetResizable(true);
AddItemSlot();
QApplication::connect(scrollArea->horizontalScrollBar(), SIGNAL(rangeChanged(int,int)), this, SLOT(scrollHorizontal()));
}
// This is just creating a single one of the example widgets which I want to connect
QWidget* MainWindow::CreateNewItem(){
QWidget* itemWidget = new QWidget();
itemWidget->setStyleSheet("background-color: lightgray");
QHBoxLayout* singleItemLayout = new QHBoxLayout();
itemWidget->setLayout(singleItemLayout);
QTextEdit* textEdit = new QTextEdit(std::to_string(counter++).c_str());
textEdit->setStyleSheet("background-color:white;");
singleItemLayout->addWidget(textEdit);
QVBoxLayout* rightSidePanel = new QVBoxLayout();
rightSidePanel->setAlignment(Qt::AlignTop);
QPushButton* button1 = new QPushButton("Top Button");
QApplication::connect(button1, SIGNAL(clicked(bool)), this, SLOT(AddItemSlot()));
rightSidePanel->addWidget(button1);
QWidget* rightPanelWidget = new QWidget();
rightSidePanel->setMargin(0);
rightPanelWidget->setLayout(rightSidePanel);
singleItemLayout->addWidget(rightPanelWidget);
itemWidget->setLayout(singleItemLayout);
itemWidget->setMinimumWidth(400);
itemWidget->setFixedSize(400,200);
return itemWidget;
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::scrollHorizontal()
{
scrollArea->ensureWidgetVisible(noteItems.back());
}
void MainWindow::AddItemSlot()
{
QWidget* w = CreateNewItem();
gridLayout->addWidget(w,currRow, currCol++);
if (!noteItems.empty()){
drawWidget->updateEndpoints(noteItems.back(), w);
}
noteItems.push_back(w);
}
MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QGridLayout>
#include <QWidget>
#include <QMainWindow>
#include <QScrollArea>
#include <drawwidget.h>
#include "drawscrollarea.h"
#include <vector>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
public slots:
void scrollHorizontal();
void AddItemSlot();
private:
Ui::MainWindow *ui;
QWidget* CreateNewItem();
int counter = 0, currCol = 0, currRow = 0;
std::vector<QWidget*> noteItems;
QScrollArea* scrollArea;
DrawWidget* drawWidget;
QGridLayout* gridLayout;
};
#endif // MAINWINDOW_H
DrawWidget.cpp:
#include "drawwidget.h"
#include <QDebug>
#include <QRect>
DrawWidget::DrawWidget(QWidget *parent) : QWidget(parent)
{
}
void DrawWidget::paintEvent(QPaintEvent *)
{
if (!drewSinceUpdate){
drewSinceUpdate = true;
QPainter painter(this);
painter.setPen(QPen(Qt::black));
for (ConnectedPair pair : items){
const QWidget* from = pair.from;
const QWidget* to =pair.to;
QPoint start = from->mapToGlobal(from->rect().topRight() + QPoint(0, from->height()/2));
QPoint end = to->mapToGlobal(to->rect().topLeft() + QPoint(0, to->height()/2));
painter.drawLine(mapFromGlobal(start), mapFromGlobal(end));
}
}
}
void DrawWidget::updateEndpoints(QWidget* startIn, QWidget* endIn){
drewSinceUpdate = false;
items.push_back(ConnectedPair{startIn, endIn});
}
DrawWidget.h
#ifndef DRAWWIDGET_H
#define DRAWWIDGET_H
#include <QWidget>
#include <QPainter>
#include <QtCore>
#include <vector>
class DrawWidget : public QWidget
{
Q_OBJECT
public:
explicit DrawWidget(QWidget *parent = nullptr);
void updateEndpoints(QWidget* startIn, QWidget* endIn);
virtual void paintEvent(QPaintEvent *);
signals:
private:
struct ConnectedPair {
const QWidget* from;
const QWidget* to;
};
std::vector<ConnectedPair> items;
bool drewSinceUpdate = true;
};
#endif // DRAWWIDGET_H
For this case we use the function mapToGlobal() and mapfromGlobal(), since pos() returns a position with respect to the parent and this can cause problems if the widget has different parents.
drawwidget.h
#ifndef DRAWWIDGET_H
#define DRAWWIDGET_H
#include <QWidget>
class DrawWidget : public QWidget
{
Q_OBJECT
public:
explicit DrawWidget(QWidget *parent = nullptr);
void addWidgets(const QWidget *from, const QWidget *to);
protected:
void paintEvent(QPaintEvent *);
private:
struct WidgetsConnected {
const QWidget* from;
const QWidget* to;
};
QList<WidgetsConnected> list;
};
#endif // DRAWWIDGET_H
drawwidget.cpp
#include "drawwidget.h"
#include <QPainter>
DrawWidget::DrawWidget(QWidget *parent) : QWidget(parent)
{
}
void DrawWidget::addWidgets(const QWidget * from, const QWidget * to)
{
list.append(WidgetsConnected{from , to});
update();
}
void DrawWidget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
for(const WidgetsConnected el: list){
const QWidget* from = el.from;
const QWidget* to = el.to;
QPoint start = from->mapToGlobal(from->rect().topRight() + QPoint(0, from->height()/2));
QPoint end = to->mapToGlobal(to->rect().topLeft() + QPoint(0, to->height()/2));
painter.drawLine(mapFromGlobal(start), mapFromGlobal(end));
}
}
The complete example can be found here.