In Qt/c++, I have an overloaded/custom QGraphicsView class.
In this classes' drawBackground event function (same issue happens with paintEvent), I am drawing some lines using the provided Qpainter:
void myGraphicsView::drawBackground(QPainter *painter, const QRectF &rect)
{
QGraphicsView::drawBackground(painter, rect);
QPen pen;
pen.setWidth(2);
painter->setPen(pen);
painter->drawLine(rect.x(),rect.y(),rect.x()+rect.width(),rect.y()+rect.height());
}
Initialization of the GraphicsView custom class:
myGraphicsView::myGraphicsView(GLWidget *myOpenGLWindow,QWidget * parent) : QGraphicsView(parent)
{
setDragMode(QGraphicsView::ScrollHandDrag);
myScene=new actionEditorButtons(this);
myScene->setMainView(this);
setScene(myScene);
setStyleSheet("background: transparent");
setAlignment(Qt::AlignBottom);
}
Initialization of the custom QGraphicsScene, with a button that gets "corrupted" with the line drawn in the custom graphicsview:
actionEditorButtons::actionEditorButtons(BrainClass *theBrain,int receivedActionNb,QObject *parent) : QGraphicsScene(parent)
{
beginButton = new QPushButton();
beginButton->setIcon(QIcon(":/icons/actionBegin.png"));
beginButton->setIconSize(QSize(40,40));
beginButton->setFixedSize(QSize(40,40));
beginButton->setStyleSheet("border: none");
beginProxy=addWidget(beginButton);
beginProxy->setPos(0,80);
connect(beginButton, SIGNAL (released()),this, SLOT (handleBeginButton()));
}
My issue is that this line is not only drawn on this custom QGraphicsView, but it is drawn too on all widgets added this scene. How can I only draw in the main graphicsView and not in the added widgets ?
Related
We are having an issue with artifacts in an application using multiple QGraphicsScene/QGraphicsViews. Essentially it seems the problem is when when in mousePosChanged event handler for a scene, and call setPos() on an item in a different scene, as well as update a region on the view for the other scene, it leaves artifacts.
I tried to set up a minimal example that hopefully will be easy to spot what is wrong
Essentially I have two scenes (scene 1 and scene 2), each with one ellipse item. When the mouse moves in the first scene, its ellipse item tracks the mouse. It also sends out a signal with mouse position. The second scene is connected to this signal, and updates the position of its ellipse item to the same location. The second graphics view is also connected to the signal, and it updates its viewport in an arbitrary location.
The second graphics view ends up with artifacts as you move the mouse around (see picture below)
This is the code from my minimal example:
class MyView : public QGraphicsView
{
Q_OBJECT
public:
MyView(QGraphicsScene *scene, QWidget *parent = 0);
public slots:
void onMouseMoveEvent();
};
class MyScene : public QGraphicsScene
{
Q_OBJECT
public:
MyScene(QObject *parent = 0);
public slots:
void setTrackerPos(const QPointF &pos);
signals:
void mousePosChanged(const QPointF &pos);
protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent);
private:
QGraphicsEllipseItem *mCursorTracker;
};
MyScene::MyScene(QObject *parent) :
QGraphicsScene(QRectF(0.,0.,1000.,1000.), parent),
mCursorTracker(new QGraphicsEllipseItem(0., 0., 50., 50.))
{
mCursorTracker->setFlag(QGraphicsItem::ItemSendsGeometryChanges);
mCursorTracker->setBrush(QBrush(Qt::red, Qt::SolidPattern));
addItem(mCursorTracker);
}
void MyScene::setTrackerPos(const QPointF &pos)
{
mCursorTracker->setPos(pos);
}
void MyScene::mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
mCursorTracker->setPos(mouseEvent->scenePos());
emit mousePosChanged(mouseEvent->scenePos());
}
MyView::MyView(QGraphicsScene *scene, QWidget *parent) :
QGraphicsView(scene, parent)
{
setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate);
setMouseTracking(true);
}
void MyView::onMouseMoveEvent(const QPointF &pos)
{
viewport()->update(QRect(0,0,250,250));
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget *w = new QWidget;
QHBoxLayout *layout = new QHBoxLayout(w);
MyScene *scene1 = new MyScene(w);
MyScene *scene2 = new MyScene(w);
MyView *view1 = new MyView(scene1, w);
MyView *view2 = new MyView(scene2, w);
layout->addWidget(view1);
layout->addWidget(view2);
QObject::connect(scene1, SIGNAL(mousePosChanged(QPointF)), scene2, SLOT(setTrackerPos(QPointF)));
QObject::connect(scene1, SIGNAL(mousePosChanged(QPointF)), view2, SLOT(onMouseMoveEvent()));
w->show();
return a.exec();
}
I understand that changing to a full update on the view will fix this. However in our real application it is too expensive to repaint the whole scene. The update on the viewport is for a small foreground layer, and only one graphics item position changes (like in this example)
This is my code:
#include "mainwindow.h"
#include <QDebug>
#include <QCameraInfo>
#include <QHBoxLayout>
#include <fstream>
#include <assert.h>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
m_QPushButton_calibration = new QPushButton("Calibrate", this);
connect(m_QPushButton_calibration, SIGNAL (released()),this, SLOT (handleButton()));
QList<QCameraInfo> l_QListQCameraInfo_available_cameras = QCameraInfo::availableCameras();
m_QWidget_viewfinder_holder = new QWidget;
m_QWidget_viewfinder_holder->setStyleSheet ("background-color: black");
m_QCameraViewfinder_viewfinder = new QCameraViewfinder(m_QWidget_viewfinder_holder);
if (l_QListQCameraInfo_available_cameras.length() >= 2)
{
m_QCamera_required_camera = new QCamera (l_QListQCameraInfo_available_cameras[1]);
m_QCamera_required_camera->setViewfinder(m_QCameraViewfinder_viewfinder);
m_QCamera_required_camera->start ();
}
m_QWidget_central = new QWidget;
m_QGridLayout_central = new QGridLayout;
m_QWidget_central->setLayout (m_QGridLayout_central);
m_QGridLayout_central->addWidget (m_QPushButton_calibration, 0, 0, 1, 1);
m_QGridLayout_central->addWidget (m_QWidget_viewfinder_holder, 1, 0, 1, 1);
this->setCentralWidget (m_QWidget_central);
m_QCameraViewfinder_viewfinder->show();
}
void MainWindow::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setPen(Qt::white);
painter.setFont(QFont("Arial", 30));
painter.drawText(rect(), Qt::AlignCenter, "Qt");
}
MainWindow::~MainWindow()
{
delete m_QPushButton_calibration;
delete m_QCameraViewfinder_viewfinder;
delete m_QCamera_required_camera;
delete m_QGridLayout_central;
delete m_QWidget_central;
}
void MainWindow::handleButton()
{
qDebug() << "handleButton";
}
I actually wish to draw a line on m_QWidget_viewfinder_holder widget.
How will that QPaintEvent function know where do I want it to draw line?
Can I use QPaintEvent as a member function in a class inherited from QMainWindow?
How to draw with QPainter on a specific widget from a group of widgets in QMainWindow?
You cannot draw on a widget from another widget. Each widget is responsible for drawing its own surface in the paintEvent() function.
How will that QPaintEvent function know where do I want it to draw line?
First, note that QPaintEvent is a class, not a function.
Now you probably want to talk about the paintEvent() function.
The function "knows" where to draw because it is part of a widget and the widget has a geometry.
For instance if I want to create a Rectangle widget that draws a rectangle with a 5px margin, I would write something like:
void Rectangle::paintEvent(QPaintEvent * e)
{
QRect rectangle(5, 5, width() - 5, height() - 5);
QPainter painter(this);
painter.drawRect(rectangle);
}
Can I use QPaintEvent as a member function in a class inherited from QMainWindow?
You can reimplement the paintEvent() member function in any class that inherits QWidget. If you inherits from a class that already draws something you need to call your parent class function.
void MainWindow::paintEvent(QPaintEvent *event)
{
QMainWindow::paintEvent(event); // Let QMainWindow draw itself
QPainter painter(this);
painter.setPen(Qt::white);
painter.setFont(QFont("Arial", 30));
painter.drawText(rect(), Qt::AlignCenter, "Qt");
}
However, please note that you are not likely willing to reimplement the painteEvent() of a MainWindow. What you generally want to do is to add a child widget to the MainWindow.
I actually wish to draw a line on m_QWidget_viewfinder_holder widget.
Create a ViewFinderHolder class like so:
class ViewFinderHolder: public QWidget {
Q_OBJECT
public:
explicit ViewFinder(QWidget *parent = 0)
...
}
Reimplement the paintEvent() function:
class ViewFinderHolder: public QWidget {
Q_OBJECT
public:
explicit ViewFinderHolder(QWidget *parent = 0)
...
protected:
void paintEvent(QPaintEvent *e);
}
void ViewFinderHolder::paintEvent(QPaintEvent *event)
{
QLineF line(10.0, 80.0, 90.0, 20.0);
QPainter(this);
painter.drawLine(line);
}
Finally in the MainWindow constructor replace:
m_QWidget_viewfinder_holder = new QWidget;
by:
m_QWidget_viewfinder_holder = new ViewFinder();
However, as m_QCameraViewfinder_viewfinder is a child of m_QWidget_viewfinder_holder, it will be drawn over it and may hide the drawing you did in ViewFinderHolder::paintEvent().
On a side note, you can remove the delete statements in the destructor of MainWindow. Deleting an instance of MainWidow will delete its child widgets.
In Windows when I create a QMainWindow I can move it around the screen by clicking the title bar and dragging it.
In my application I've hidden the title bar by using setWindowFlags(Qt::CustomizeWindowHint) and I'm trying to build a custom title bar using a widget and setting it in the menu space with setMenuWidget(myWidget).
Now I want to reproduce the original behaviour: I want to click on my MyWidget widget inside the QMainWindow and, while mouse is pressed, dragging the mouse moves the window.
Is there a way to do it?
This is an example on how to implement a fake title bar, that has standard buttons (minimize, maximize, close), and can be dragged to move the whole window (this is based on the approach in #Kevin's answer).
#include <QtWidgets>
class FakeTitleBar : public QWidget{
Q_OBJECT
public:
explicit FakeTitleBar(QWidget* parent= nullptr):QWidget(parent){
label.setSizePolicy(QSizePolicy::Expanding,
QSizePolicy::Expanding);
layout.addWidget(&label);
layout.addWidget(&buttonMinimize);
layout.addWidget(&buttonMaximize);
layout.addWidget(&buttonClose);
//connecting buttons' signals to slots
connect(&buttonMinimize, &QPushButton::clicked,
this, &FakeTitleBar::MinimizeWindow);
connect(&buttonMaximize, &QPushButton::clicked,
this, &FakeTitleBar::MaximizeWindow);
connect(&buttonClose, &QPushButton::clicked,
this, &FakeTitleBar::CloseWindow);
//setting vertical fixed size policy
//so that the title bar does not take up any additional space
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
//a bit of styling
setStyleSheet("QPushButton {margin:0px; padding:5px;}"
"QWidget {background-color:blue; color:white;}");
}
public slots:
//slots for corresponding buttons
void MinimizeWindow(){
window()->showMinimized();
}
void MaximizeWindow(){
if(!window()->isMaximized())
window()->showMaximized();
else
window()->showNormal();
}
void CloseWindow(){
window()->close();
}
protected:
void mousePressEvent(QMouseEvent* event){
//save the press position (this is relative to the current widget)
pressPos= event->pos();
isMoving= true;
}
void mouseMoveEvent(QMouseEvent* event){
//isMoving flag makes sure that the drag and drop event originated
//from within the titlebar, because otherwise the window shouldn't be moved
if(isMoving){
//calculate difference between the press position and the new Mouse position
//(this is relative to the current widget)
QPoint diff= event->pos() - pressPos;
//move the window by diff
window()->move(window()->pos()+diff);
}
}
void mouseReleaseEvent(QMouseEvent* /*event*/){
//drag and drop operation end
isMoving= false;
}
//double-clicking on the title bar should maximize the window
void mouseDoubleClickEvent(QMouseEvent* /*event*/){
MaximizeWindow();
}
//in order for the style sheet to apply on this custom widget
//see https://doc.qt.io/qt-5/stylesheet-reference.html#qwidget-widget
void paintEvent(QPaintEvent *)
{
QStyleOption opt;
opt.init(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
}
private:
QHBoxLayout layout{this};
QLabel label{"Fake Title Bar"};
QPushButton buttonMinimize{"-"};
QPushButton buttonMaximize{"M"};
QPushButton buttonClose{"X"};
QPoint pressPos;
bool isMoving{false};
};
//sample usage
class Widget : public QWidget{
public:
explicit Widget(QWidget* parent= nullptr):QWidget(parent){
setWindowFlags(Qt::CustomizeWindowHint);
layout.addWidget(&titleBar);
layout.addWidget(&label);
layout.setContentsMargins(0, 0, 0, 0);
label.setAlignment(Qt::AlignCenter);
//default size for the window
resize(320,240);
}
~Widget(){}
private:
QVBoxLayout layout{this};
FakeTitleBar titleBar;
QLabel label{"this is a sample window"};
};
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
Widget w;
w.show();
return app.exec();
}
#include "main.moc"
You just need to implement the necessary mouse event handling by overwriting MyWidget's mousePressEvent(), mouseMoveEvent() and mouseReleaseEvent() handlers.
Detect the mouse down, get current mouse position
While moving, get current mouse position, calculate difference, save new position, move window by diff
You can get the window (top level widget) from inside MyWidget through the window() method.
I'm trying to create an interface for myself on Qt and I need a few rectangles over a DICOM image (a magnetic ressonance image), so they need to be some color other than black but I can't find a way to set a brush for the QGraphicsItemGroup I'm using to keep the rectangles organized.
QGraphicsScene lets me add a QRect associated to a QBrush individually with
QgraphicsScene *scene = new QGraphicsScene();
QRectF rect = QRectF(QPoint(1,2),QPoint(3,4));
scene->addRect(rect, QBrush(Qt::red)); // using red as example
but adding each rectangle individually would make it all too messy and probably way slower. I need a way to set a QBrush for the rectangles but using QGraphicsItemGroup to be added to the QGraphicsScene.
Why can you not reimplement QGraphicsItemGroup and inside have a function called:
void ReimplementedQGraphicsItemGroup::SetRectangleBrush(const QBrush& brush)
and inside that function iterate over every rectangle you have added to the group setting the brush
QgraphicsScene *scene = new QGraphicsScene();
ReimplementedQGraphicsItemGroup ReimplGraphicsGroup = new ReimplementedQGraphicsItemGroup()
// First rect
QGraphicsRectItem rect(1,2,3,4);
rect.setBrush(QColor(Qt::red);
ReimplGraphicsGroup->AddRectangle(rect);
// Second rect
QGraphicsRectItem rect2(5,6,7,8);
rect2.setBrush(QColor(Qt::blue);
ReimplGraphicsGroup->AddRectangle(rect2);
// add reimplemented graphics item group to scene
scene->addItem(ReimplGraphicsGroup);
void ReimplementedQGraphicsItemGroup::SetRectangleBrush(const QBrush& brush)
{
foreach (QGraphicsRectItem rect, m_ListRects)
{
rect.setBrush(brush);
}
}
class ReimplementedQGraphicsItemGroup : public QGraphicsItemGroup {
// a member of ReimplementedQGraphicsItemGroup
QList<QGraphicsRectItem> m_ListRects;
}
void ReimplementedQGraphicsItemGroup::AddRectangle(QGraphicsRectItem rect)
{
addToGroup(rect);
m_ListRects.append(rect);
}
The example code is from my project. I've tried to make it as short as possible and to the point.
The overlay is used to draw over all the other widgets in the app. This works for most widgets, but today I've started to notice that QAbstractScrollArea subclasses are giving me a hard time. The problem is that the overlay appears not on top, and whatever drawing that happens is blocked.
#include <QtGui/QApplication>
#include <QtGui/QVBoxLayout>
#include <QtGui/QGraphicsView>
#include <QtGui/QPushButton>
class View : public QGraphicsView{
public:
View(){
//delete viewport(); setViewport(new QWidget);
}
};
class Widget : public QWidget{
QWidget* overlay_;
public:
Widget(){
resize(512, 512);
QVBoxLayout* layout = new QVBoxLayout;
QPushButton* button = new QPushButton(" Click Me! ");
layout->addWidget(button);
layout->addWidget(new View);
overlay_ = new QWidget(this);
overlay_->installEventFilter(this);
connect(button, SIGNAL(clicked()),
overlay_, SLOT(show()));
overlay_->hide();
setLayout(layout);
}
bool eventFilter(QObject* target, QEvent* event){
if(target == overlay_){
if(event->type() == QEvent::Paint && overlay_->isVisible()){
overlay_->resize(size());
QPainter painter(overlay_);
painter.setPen(QPen(QColor(1, 102, 192, 255), 1, Qt::SolidLine,
Qt::FlatCap, Qt::MiterJoin));
painter.drawRect(rect().adjusted(60, 0, -60, 0));
return true;
}
}
}
};
int main(int argc, char *argv[]){
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
To fix this in this example and have overlay go on top of View, you'll need to uncomment the commented line at the top. So my question is this: why do I need to delete and assign a new viewport widget in the constructor in order for overlay not get overdrawn?
This isn't a bug with QGraphicsView, it will happen if you use a standard QScrollArea as well.
The issue, I think, is the order in which Qt draws child widgets. Sibling widgets are drawn in the order they are added to the parent (although you can't rely on this).
The reason that resetting the viewport "solved" the problem is because when you do that you create a new QWidget that has no background to be the viewport. The QGraphicsView is still being drawn over the overlay_, it just has a transparent viewport. Notice how it's still drawn behind the pushbutton, however.
If you want to draw an overlay only over the QGraphicsView, you can override QGraphicsView::paintEvent() and do it there. If you want to draw the overlay over your entire widget, I would embed your layout inside a second QWidget and then try using QWidget::raise() to force the overlay visually to the top.