Understanding QGraphicsScene in Qt - qt

I am trying to understand the basics of Qt. After going through some posts, I came to know that ui_mainwindow.h gets created by UIC tool and that ui_mainwindow.h contains the information about my form/ui that I created.
In my GUI, I have taken a pushbutton and a graphics view. I want a simple image (which I am creating inside the program itself) gets displayed in the graphicsView. I am trying to do it with two ways (for learning purpose):
I can write the code inside on_pushButton_clicked()(i.e. the slot of my push_button).
I am trying to put the image from the main()
Problem: I am done with the first method. I used the following lines of code inside on_pushButton_clicked() and it worked.
void MainWindow::on_pushButton_clicked()
{
//Display image in the graphics viewer
Mat img(200,200, CV_8UC3, Scalar(255,0,0));
QImage image( img.data, img.cols, img.rows, img.step, QImage::Format_RGB888 );
QGraphicsScene* scene = new QGraphicsScene();
QGraphicsPixmapItem* item = new QGraphicsPixmapItem(QPixmap::fromImage(image));
scene->addItem(item);
ui->graphicsView->setScene(scene);
}
Now, I want to do the similar thing from the main(). To do that, now my main() looks like following:
#include "mainwindow.h"
#include <QApplication>
//For image
#include <QImage>
#include <QPixmap>
#include <QGraphicsPixmapItem>
//#include "ui_mainwindow.h"
//OPENCV Headers
#include <opencv2/opencv.hpp>
#include <opencv2/core.hpp>
#include <opencv2/imgcodecs/imgcodecs.hpp>
using namespace cv;
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
//Display image in the graphics viewer
Mat img(200,200, CV_8UC3, Scalar(255,0,0));
QImage image( img.data, img.cols, img.rows, img.step, QImage::Format_RGB888 );
QGraphicsScene* scene = new QGraphicsScene();
QGraphicsPixmapItem* item = new QGraphicsPixmapItem(QPixmap::fromImage(image));
scene->addItem(item);
w.ui->graphicsView->setScene(scene);
w.show();
return a.exec();
}
The above code written inside the main() works if I put #include "ui_mainwindow.h" in main.cpp. But if I comment #include "ui_mainwindow.h" and w.ui->graphicsView->setScene(scene); then, it throws error for the QGraphicsScene* scene = new QGraphicsScene();.
Error is main.cpp:32: error: allocation of incomplete type 'QGraphicsScene' QGraphicsScene* scene = new QGraphicsScene();
QUESTIONS: Why is the connection between QGraphicsScene and "ui_mainwindow.h". I understand that I need "ui_mainwindow.h" for the line w.ui->graphicsView->setScene(scene); becasue there I am using my ui but I don't understand the need for QGraphicsScene.

If You want to draw only static image better use just QLabel and set in some image
QImage *image = new QImage (":/prefix/image/vl.jpg" );
QLabel *magelab = new QLabel();
magelab->setPixmap(QPixmap::fromImage(*image));
Generaly QGraphicsScene better using with connection QGraphicsView. That is mean QGraphicsView set some object (scene) from QGraphicsScene just like:
class SomeObject : public QGraphicsView
{
Q_OBJECT
public:
explicit Schemat(QWidget *parent = 0);
private:
QGraphicsScene *scene;
};
and source
Schemat::Schemat( QWidget *parent) : QGraphicsView(parent)
{
scene = new QGraphicsScene(this);
this->setScene(scene);
// to Your scene You can add some image for example
scene->addPixmap(SOME_PIXMAP)
}
Then You create main window and add with Your QGraphicsView, for example as some part QGroupBox
void MainWindow::SetupSchemat()
{
schema = new Schemat();
QGroupBox *schbox;
QHBoxLayout *hschbox;
schbox = new QGroupBox(this);
hschbox = new QHBoxLayout(this);
schbox->setTitle("SomeScene");
hschbox->addWidget(schema); //add scene to layout
schbox->setLayout(hschbox);
}
QGraphicsScene is like some part of Your MainWindow on which you can make some animation, You can something draw. QGraphicsScene is more better to using if You want use animation not only static image it supplies more option to manipulate image (ex scaling, catch mouse click, manage via cordinates others object), and other object each should be animate or just display. To QGraphicsScene You can add some QGraphicsItem in turn each QGraphicsItem can moving onto QGraphicsScene with particular conditions defined erly or in flow. Together QGraphicsView, QGraphicsScene and QGraphicsItem can created animation or just image in some part Your main window.
Also nice explained You will find here
https://www.youtube.com/watch?v=fmSs2mNGh9I

Related

Qt QQuickWidget conflicting with QGraphicsVideoItem

I have been using QGraphicsScene and QGraphicsVideoItem as my canvas. And to control them, I've chosen to use qml and QQuickWidget to develop custom objects easily for a different module. However, I quickly ran into an issue where the QGraphicsVideoItem would not render in the QGraphicsScene but rather inside the QQuickWidget (both when the widget is empty or with a qml source attached). And the issue seems to be reproducible using a fresh project as well just by placing an empty QQuickWidget(through qt designer) anywhere inside the main ui.
Here is the reproducible code:
#include "QtGuiApplication1.h"
QtGuiApplication1::QtGuiApplication1(QWidget *parent): QMainWindow(parent)
{
ui.setupUi(this);
QGraphicsView* view = new QGraphicsView(ui.widget);
QGraphicsScene* scene = new QGraphicsScene();
QGraphicsVideoItem* video = new QGraphicsVideoItem();
QMediaPlayer* player = new QMediaPlayer();
QUrl path = QUrl::fromLocalFile("D:/My Documents/Videos/XIII.mp4");
QVBoxLayout* layout = new QVBoxLayout();
layout->addWidget(view);
ui.widget->setLayout(layout);
video->setFlags(QGraphicsVideoItem::ItemIsMovable | QGraphicsVideoItem::ItemIsFocusable | QGraphicsVideoItem::ItemIsSelectable);
video->setPos(100, 100);
//view->setSceneRect(QRectF(QPointF(100, 100), QPointF(800, 600)));
view->setScene(scene);
player->setMedia(path);
player->setVideoOutput(video);
scene->addItem(video);
player->play();
view->show();
}
#pragma once
#include "ui_QtGuiApplication1.h"
#include <QtCore>
#include <QDebug>
#include <QGraphicsVideoItem>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QMediaPlayer>
#include <QUrl>
#include <QString>
class QtGuiApplication1 : public QMainWindow
{
Q_OBJECT
public:
QtGuiApplication1(QWidget *parent = Q_NULLPTR);
private:
Ui::QtGuiApplication1Class ui;
};
The issue immediately went away when I removed the widget from the ui file as well. So am I missing something here?
When you call ui.widget->setLayout(layout); you break the layout that was set in Qt Designer.
Instead of programmatically creating the QGraphicsView and QVBoxLayout in your *.cpp file, add them all in Qt Designer.
(If the issue still persists, please edit your original post and include your *.ui file)

QT QGraphicsScene with QLabel and QPixmap

I have a QGraphicsView, in that I have a QGraphicsScene, in that I have a QLabel and I set a .png picture as QPixmap into the QLabel. The .png is set in background.qrc resource file.
My QLabel's size is 600x400. Without the pixmap it's okay, the QGraphicsScene's size is 600x400 too. But when I set the pixmap to the QLabel and scale it, it fails.
The QLabel's size is the same, the pixmap is scaled well within the QLabel and only visible within it, but the QGraphicsScene adopts the real size of the QPixmap, which is 720x720. So the QLabel is visible with the QPixmap in it's correct size, but there is a gray place around it, since the scene is bigger.
How can I fix this and make it work? I want the QGraphicScene to stay on the size of the QLabel.
Here's the code:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QPixmap>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QLabel>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
QGraphicsView *myView = new QGraphicsView(this);
QGraphicsScene *myScene= new QGraphicsScene();
QLabel *myLabel= new QLabel();
myLabel->setBaseSize(QSize(600, 400));
myLabel->resize(myLabel->baseSize());
myLabel->setScaledContents(true);
QPixmap pixmapBackground(":/new/cross.png");
myLabel->setPixmap(pixmapBackground);
myScene->addWidget(myLabel);
myView->setScene(myScene);
setCentralWidget(myView);
}
MainWindow::~MainWindow()
{
delete ui;
}
From your example code, you don't set the scene's size. You can do this with a call to setSceneRect. As the documentation states, when the rect is not set: -
If unset, or if set to a null QRectF, sceneRect() will return the largest bounding rect of all items on the scene since the scene was created (i.e., a rectangle that grows when items are added to or moved in the scene, but never shrinks).
Therefore without setting the scene rect, when the label is added to the scene, its size is changing, as in this example
#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QLabel>
#include <QPixmap>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsView *myView = new QGraphicsView;
QGraphicsScene *myScene= new QGraphicsScene();
// constrain the QGraphicsScene size
// without this, the defect as stated in the question is visible
myScene->setSceneRect(0,0,600,400);
QLabel *myLabel= new QLabel;
myLabel->setBaseSize(QSize(600, 400));
myLabel->resize(myLabel->baseSize());
myLabel->setScaledContents(true);
QPixmap pixmapBackground(720, 720);
pixmapBackground.fill(QColor(0, 255, 0)); // green pixmap
myLabel->setPixmap(pixmapBackground);
myScene->addWidget(myLabel);
myView->setScene(myScene);
myView->show();
return a.exec();
}
This should result in the correct scene size, as demonstrated here: -
As discussed in the comments, the example code above works as expected on OS X, but there still appears to be an issue when executing on Windows 10.

Reparenting a Qt widget

I have a WidgetA widget, which is an owner-drawn widget. It's currently placed in QMainWindow's QVBoxLayout. After clicking a button, I'd like to "detach" WidgetA from this QVBoxLayout, insert QSplitter into this QVBoxLayout and "readd" WidgetA to this QSplitter. All this without destroying WidgetA, so it will preserve its drawing context, etc.
So, currently I have this (only one widget in a window):
I'd like to put a QSplitter between WidgetA and QMainWindow, and create a new widget, WidgetB, so I'd end up with:
Later I'd like it to split even further, so both WidgetA and WidgetB would still allow themselves to be detached and placed in a new QSplitter, so it would be possible to create f.e. this hierarchy:
And, to be complete, one more step:
I'm not very experienced in Qt, so what I'm trying to do may seem pretty obvious, but I couldn't find how to "reparent" widgets. Is this possible in Qt?
Please, see reparent example, may be it helps you:
//MyMainWindow.h
#include <QWidget>
#include <QPainter>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>
#include <QSplitter>
class MyWidget: public QWidget
{
public:
MyWidget(QWidget* parent, int number)
: QWidget(parent),
m_number(number)
{
}
private:
virtual void paintEvent(QPaintEvent* e)
{
QWidget::paintEvent(e);
QPainter p(this);
p.fillRect( rect(), Qt::red);
p.drawText( rect(), Qt::AlignCenter, QString::number(m_number) );
}
private:
int m_number;
};
class MyMainWindow: public QWidget
{
Q_OBJECT
public:
MyMainWindow()
{
setFixedSize(300, 200);
m_mainLayout = new QVBoxLayout(this);
QHBoxLayout* buttonLayout = new QHBoxLayout;
m_mainLayout->addLayout(buttonLayout);
m_button = new QPushButton("Button", this);
buttonLayout->addWidget(m_button);
connect(m_button, SIGNAL(clicked()), this, SLOT(onButtonClickedOnce()));
m_initWidget = new MyWidget(this, 1);
m_mainLayout->addWidget(m_initWidget);
}
private slots:
void onButtonClickedOnce()
{
m_button->disconnect(this);
m_mainLayout->removeWidget(m_initWidget);
QSplitter* splitter = new QSplitter(Qt::Horizontal, this);
m_mainLayout->addWidget(splitter);
splitter->addWidget(m_initWidget);
MyWidget* newWidget = new MyWidget(splitter, 2);
splitter->addWidget(newWidget);
}
private:
QVBoxLayout* m_mainLayout;
QWidget* m_initWidget;
QPushButton* m_button;
};
//main.cpp
#include <QtWidgets/QApplication>
#include "MyMainWindow.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MyMainWindow mainWindow;
mainWindow.show();
return a.exec();
}
When you operate with widget which is part of layout, then you need to use appropriate methods of QLayout (parent of QVBoxLayout) to detach the item from layout:
QLayout::removeWidget (removeItem if it is not widget, but spacer item or another layout)
QLayout::addWidget (addItem --/--)
Btw: even when widget moves between layouts, its parent may even stay same. I guess you have no need to call QWidget::setParent() as the calls of addWidget/removeWidget will do all work for you.

QGraphicsProxyWidget has clipped context menu in QGraphicsScene

The following code is based on the documentation of Graphics View Framework. I embed a QLineEdit in a QGraphicsScene and run the program. When I right click the line edit in the scene I get a clipped context menu. The context menu of a QGraphicsProxyWidget is drawn by the scene as a child QGraphicsProxyWidget so it get's clipped if the window is too small. I want all embedded widgets to show their context menus as top-level windows like they do when not being embedded in a QGraphicsScene. I have tried the BypassGraphicsProxyWidget flag in two ways but it doesn't work as I want. Tested on Qt 4.8 / 5.0 on Linux and Windows. Same issue on all platforms.
How can I make the embedded widgets display normal, top-level context menus with native look? Overloading QGraphicsView's contextMenuEvent gives a native top-level context menu - could I do some sort of delegation and make QGraphicsView display the context menu of embedded widgets in the scene?
#include <QApplication>
#include <QLineEdit>
#include <QGraphicsScene>
#include <QGraphicsProxyWidget>
#include <QGraphicsView>
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QGraphicsScene scene;
QGraphicsProxyWidget *proxy = scene.addWidget(new QLineEdit(), Qt::BypassGraphicsProxyWidget);
QGraphicsView view(&scene);
view.setWindowFlags(Qt::BypassGraphicsProxyWidget);
view.show();
return app.exec();
}
Unfortunately, this is a known bug QTBUG-10683. A workaround is suggested in the last comment to the bug report.
You get native context menus by adding a QWidget that has the Qt::BypassGraphicsProxyWidget set. Children will render it's context menus as pop-ups native style.
#ifndef QGLPARENT_H
#define QGLPARENT_H
#include <QGLWidget>
#include <QGraphicsScene>
#include <QGraphicsProxyWidget>
#include <QGraphicsView>
class QGLParent : public QGraphicsView
{
private:
QGraphicsProxyWidget *child;
public:
QGLParent(QWidget *parent, QWidget *child) : QGraphicsView(parent)
{
setFrameShape(QFrame::NoFrame);
QGLFormat format(QGL::SampleBuffers);
format.setSwapInterval(1);
setScene(new QGraphicsScene());
setViewport(new QGLWidget(format));
//setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
child->setWindowFlags(Qt::BypassGraphicsProxyWidget);
this->child = scene()->addWidget(child);
}
protected:
void resizeEvent(QResizeEvent *event)
{
scene()->setSceneRect(QRect(QPoint(0, 0), event->size()));
child->resize(event->size().width(), event->size().height());
QGraphicsView::resizeEvent(event);
}
};
#endif

Exit Application in Qt

I have built an app in Qt that contains two buttons: an exit button and an import button. When the import button is pushed, a list of buttons is shown in a scrollarea on the screen (the file loggers.csv contains the data 1;2;3;4;5;).
It all works fine, but when I push the exit button (which of course should close everything), the app is not stopped properly (the stop button of Qt is still active, and the play button isn't). When I run the debugger and push the exit button it gives an error: Invalid address specified to RtlFreeHeap( 0ADF0000, 0028FE40 ). Can anybody help me?
main
#include <QtGui/QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.showFullScreen();
return a.exec();
}
Mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QtGui>
#include "logger.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
QPushButton exit_btn;
QPushButton import_btn;
private slots:
void createMenus();
void exit();
void import();
private:
int window_width;
int window_height;
int numLoggers;
int numSelected;
QVector<Logger*> loggers;
QScrollArea * scroll_area;
QVBoxLayout scrollLayout;
QWidget viewport;
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
Mainwindow.cpp:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "QtGui"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
window_width = QApplication::desktop()->width();
window_height = QApplication::desktop()->height();
createMenus();
connect(&exit_btn,SIGNAL(clicked()),this,SLOT(exit()));
connect(&import_btn,SIGNAL(clicked()),this,SLOT(import()));
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::createMenus()
{
import_btn.setParent(ui->centralWidget);
import_btn.setGeometry(400,300,100,100);
import_btn.setText("IMPORT");
exit_btn.setText("EXIT");
exit_btn.setParent(ui->centralWidget);
exit_btn.setGeometry(window_width-50,12,32,32);
viewport.setLayout(&scrollLayout);
viewport.resize(0,0);
scroll_area = new QScrollArea(ui->centralWidget);
scroll_area->setGeometry(0,66,317,window_height-116);
scroll_area->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
scroll_area->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
scroll_area->setWidget(&viewport);
scroll_area->setGeometry(0,97,317,window_height-228);
scrollLayout.setMargin(0);
scrollLayout.setSpacing(0);
}
void MainWindow::exit()
{
close();
qApp->quit();
}
void MainWindow::import()
{
numSelected=0;
QFile f("Loggers3.csv");
if (f.open(QIODevice::ReadOnly))
{
numLoggers=0;
QString data;
data = f.readAll();
QStringList vals = data.split(';');
while(vals.size()>=1)
{
Logger * logger = new Logger;
logger->setNumber(vals[0].toInt());
vals.removeAt(0);
loggers<<logger;
numLoggers++;
}
f.close();
for(int i=0; i<numLoggers;i++)
{
loggers[i]->createButtons();
scrollLayout.addWidget(loggers[i]->button);
}
viewport.resize(367,numLoggers*60);
}
}
logger.h
#ifndef LOGGER_H
#define LOGGER_H
#include <QtGui>
class Logger : public QWidget
{
Q_OBJECT
public:
explicit Logger(QWidget *parent = 0);
~Logger();
int number;
QLabel num;
QToolButton * button;
bool checked;
signals:
public slots:
void setNumber(int number);
void createButtons();
};
#endif // LOGGER_H
logger.cpp
#include "logger.h"
#include <QtGui>
Logger::Logger(QWidget *parent) :
QWidget(parent)
{
button = new QToolButton;
button->setCheckable(true);
button->setMinimumSize(317,60);
button->setStyleSheet("QToolButton{background-image: url(images/btn_bg); border:none}");
}
Logger::~Logger()
{
}
void Logger::setNumber(int logNumber)
{
number=logNumber;
}
void Logger::createButtons()
{
QLayout * layout = new QHBoxLayout;
QSpacerItem *spacer = new QSpacerItem(120, 31, QSizePolicy::Maximum, SizePolicy::Maximum);
num.setStyleSheet("color: white; font: bold 16px");
num.setText(QString::number(number));
layout->addWidget(&num);
layout->addItem(spacer);
button->setLayout(layout);
}
I'm not entirely certain about what you are trying to achieve... but your problem lies with these two lines:
viewport.setLayout(&scrollLayout);
viewport.resize(0,0);
In the documentation for the QWidget class it states that:
If there already is a layout manager installed on this widget, QWidget
won't let you install another. You must first delete the existing layout manager (returned by layout()) before you can call setLayout() with the new layout.
This is where your problem lies. Don't believe me, add this check before those two lines of code.
if(layout()){
qDebug() << "Another layout exists";
}
Source: QVBoxLayout Class Reference
The QVBoxLayout class lines up widgets vertically.
This class is used to construct vertical box layout objects. See QBoxLayout for details.
The simplest use of the class is like this:
QWidget *window = new QWidget;
QPushButton *button1 = new QPushButton("One");
QPushButton *button2 = new QPushButton("Two");
QPushButton *button3 = new QPushButton("Three");
QPushButton *button4 = new QPushButton("Four");
QPushButton *button5 = new QPushButton("Five");
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(button1);
layout->addWidget(button2);
layout->addWidget(button3);
layout->addWidget(button4);
layout->addWidget(button5);
window->setLayout(layout);
window->show();
First, we create the widgets we want in the layout. Then, we create the QVBoxLayout object and add the widgets into the layout. Finally, we call QWidget::setLayout() to install the QVBoxLayout object onto the widget. At that point, the widgets in the layout are reparented to have window as their parent.
Critical source of error in your project:
Widgets should be constructed on the heap because they will be deleted automatically when their parents are deleted. You have a custom widget class that you instantiate on the heap. The members should also go on the heap. Also, you should consider using the parent /child hierarchy in your GUI code to ensure proper memory management and proper deletion.
In my experience, if your program stops in RtlFreeHeap it is a good sign of memory corruption.
When calling
import_btn.setParent(ui->centralWidget);
centralWidget takes ownership of import_btn. That means, when centralWidget is deleted (which happens as part of delete ui;in your MainWindow's destructor), it will call delete on your member variable!
This leads to the reported memory corruption.
You need to allocate your QPushButton's dynamically, not as a plain member variable. So make them QPushButton*.
Here's how I did it from mainwindow.cpp, thanks to and this question: How to create a correct exit button in qt
QPushButton * quit_btn = new QPushButton(this);
quit_btn->setGeometry(540,440,93,27);
quit_btn->setText("Exit");
QObject::connect(quit_btn,SIGNAL(clicked()),qApp,SLOT(quit()));
Works flawlessly :D

Resources