How to draw something with QPainter when the button is pushed - qt

I am working on my project from programming and I need to draw, for example, a circle every time the pushButton is pressed using QPainter. This is the first problem, and the second one here is that I need some information to be sent to the drawing function too, for example, int vector, and being able to draw so many circles, as there are elements in the vector with radii of the elements itself. I have found some code based on signals and slots.
The sender:
public:
Listener(QObject *p = 0) : QObject(p) {
QTimer * t = new QTimer(this);
t->setInterval(200);
connect(t, SIGNAL(timeout()), this, SLOT(sendData()));
t->start();
}
signals:
void dataAvaiable(int, int);
public slots:
void sendData() {
emit dataAvaiable(qrand() % 200, qrand() % 200);
}
The reciever:
void receiveData(int x, int y) {
QPainter painter(this);
QPen pen(Qt::white, 5);
painter.setRenderHint(QPainter::HighQualityAntialiasing);
painter.setPen(pen);
QPoint point(x, y);
painter.drawEllipse(x, y, 100, 100);
data.append(point);
}
The connection itself in main.cpp
QObject::connect(&l, SIGNAL(dataAvaiable(int,int)), &w, SLOT(receiveData(int,int)));
But the code doesn't suit for my exact task with buttons and doesn't even want to draw anythig, just any circle at all. Howewer, in debugger the code executes properly, and I am relatively new to Qt and C++ so I can't figure out by myself, where the problem is and how to solve my task.Can someone please do a minimal of code or simply explain to me, where exactly the problem is? Need to solve the problem as soon as possible. Thank you.
Upd: any possible solution with or without QPainter would be good now.

Qt Forum users gave me an answer.
Quote:
From the QPainter class description:
Warning: When the paintdevice is a widget, QPainter can only be used inside a
paintEvent() function or in a function called by paintEvent().
You can force calling paintEvent() by invoking update(), so you must connect the onclicked() signal of your button to the update() slot of the widget you're drawing on.
For your second problem, the data can be a member variable.
Here's an example:
// mywidget.h
#include <QVector>
#include <QPoint>
// other includes and the constructor...
protected:
virtual void paintEvent(QPaintEvent *event);
private slots:
void onButtonClicked();
private:
QPushButton* mButton;
QVector<QPoint> mCirclesData;
// mywidget.cpp
MyWidget::MyWidget(QWidget *parent) : QWidget(parent)
{
mButton = new QPushButton(this);
// customise your button...
connect(mButton, &QPushButton::clicked, this, &MyWidget::onButtonClicked);
}
//...
void MyWidget::onButtonClicked(){
int x = qrand() % 200, y = x;
mCirclesData << QPoint(x,y);
update(); // force calling paintEvent
}
void MyWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
QPen pen(Qt::white, 5);
painter.setRenderHint(QPainter::HighQualityAntialiasing);
painter.setPen(pen);
painter.drawEllipse(mCirclesData.last().x(), mCirclesData.last().y(), 100, 100);
}
``

Related

What is the basic working of qtimer?

I created a rectangle and want it to move with QTimer , I want to know how the methods of QTimer exactly work. This code is running but the figure i drew is not moving.
.h
Header file
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
void paintEvent(QPaintEvent *event);
~Widget();
private slots:
void update();
private:
Ui::Widget *ui;
};
this is the .cpp file
.cpp
#include "widget.h"
#include "ui_widget.h"
#include<QPainter>
#include<QTimer>
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
QTimer *timer = new QTimer(this);
timer->setInterval(1000);
connect(timer, SIGNAL(timeout()), this, SLOT(update()));
timer->start();
}
Widget::~Widget()
{
delete ui;
}
void Widget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.drawRect(50,80,70,80);
}
void Widget::update()
{
update();
}
You say "the figure i drew is not moving". In your code, I don't see code for a moving figure, I only see a static rectangle being drawn.
In addition, the update() function calls itself, leading to infinite recursion. Remove the update() from your code, the base class implementation QWidget::update() does the correct thing (scheduling a call to paintEvent()), no need to reimplement update().
First of all, update() slot method already has a specific meaning and purpose, you should not override it for other purposes. Further, it is not virtual, which tells you it is not even meant to be overriden (and doing it can lead to very confusing situations). So rename your own method to... updateAnimation() or something.
Then you need to add private member variables for your rectangle position, say rectX, rectY, rectWidth, rectHeight (or just single QRect if you prefer). Some code snippets help you get the idea:
void Widget::paintEvent(QPaintEvent *event)
{
// default setting is that Qt clears the widget before painting,
// so we don't need to worry about erasing previous rectangle,
// just paint the new one
QPainter painter(this);
painter.drawRect(rectX, rectY, rectWidth, rectHeight);
}
void Widget::updateAnimation()
{
// modify rectX, rectY, rectWidth and rectHeight here
update(); // make Qt do redrawing
}

QGraphicsItem setShear ? (applicable for single or multiple items)

To apply rotation on a QGraphicsItem, I can either call rotate() or setRotation(). What I see happening:
item.rotate(angle);
results in rotation, as I expect; yet if I copy the item (using a cop constructor in a subclass), no rotation will be copied (even if I copy all transformations).
item.setRotation(angle);
results in the item having a rotation() property which I can copy - but requires an update.
So the second version is what I need.
I would like to be able to apply shear to my items as well.
item.shear(shx, shy);
looks good on initial item - but I cannot find a way to copy this shear. Nor can I find a similar property as for rotation: there is no setShear() that I can find.
Even more, if I try to play with transformations, to achieve a group shear (like this question's rotation), I get very weird results...
How can I create a similar stored property for shear ?
Edit:
Trying
QTransform t;
t.shear(shx, shy);
item->setTransform(t, true);
also gives me a giant scaling and some rotation... I have tried to divide shx and shy by 100 and it seems reasonable (not sure if correct ?).
Note - will move the code snippet in answer, since it seems it will work.
It seems that I have to divide shear factors by 100 (this doesn't seem true always).
void shearItem(qreal shx, qreal shy)
{
QRectF rect;
foreach(QGraphicsItem* item, scene()->selectedItems())
rect |= item->mapToScene(item->boundingRect()).boundingRect();
QPointF center = rect.center();
QTransform t;
t.translate(center.x(), center.y());
// seems shear increases item to gigantic proportions so I tried
t.shear(shx/100, shy/100);
t.translate(-center.x(), -center.y());
foreach(QGraphicsItem* item, scene()->selectedItems())
{
item->setPos(t.map(item->pos()));
item->setTransform(t, true);
}
}
I think the transformations are combined on item, so I don't need to do any action to combine any rotation or scaling or shear... I can just use
item->setTransform(t, true);
for all transforms so there is no need to separately set rotation or scale.
There does not seem to exist any conflict though between translate(), rotate(), scale() and shear() methods and setting the transform matrix. So combining these functions, and then copying the transform seems fine. (http://doc.qt.io/qt-4.8/qtransform.html#setMatrix).
There does not seem to be any documented reason for having to scale down the shear factors... (http://doc.qt.io/qt-4.8/qtransform.html#shear).
If I set shear in item constructor, it works without having to divide by 100.
Yet if I call a shear as in code above, without scaling the factors down I get a resize as well. I find this very confusing... But as long as it works....
Full sample code: included rotation change and shear change
myview.h
#ifndef MYVIEW_H
#define MYVIEW_H
#include <QGraphicsView>
#include <QAction>
#include <QContextMenuEvent>
#include <QGraphicsItem>
class MyView : public QGraphicsView
{
Q_OBJECT
public:
MyView(QWidget *parent = 0);
protected:
virtual void contextMenuEvent(QContextMenuEvent *event);
private slots:
void rotateItem(qreal deg = 10);
void shearItem(qreal shx = 10, qreal shy = 10);
private:
void createActions();
QAction *rotateAct;
QAction *shearAct;
};
#endif // MYVIEW_H
myview.cpp
#include "myview.h"
#include <QMenu>
MyView::MyView(QWidget *parent) : QGraphicsView(parent)
{
createActions();
setScene(new QGraphicsScene(-500, -150, 1000, 600, this));
setDragMode(RubberBandDrag);
}
void MyView::contextMenuEvent(QContextMenuEvent *event)
{
QGraphicsItem* item = itemAt(event->pos());
if(item != NULL)
item->setSelected(true);
if(scene()->selectedItems().isEmpty()) return;
QMenu menu(this);
menu.addAction(rotateAct);
menu.addAction(shearAct);
menu.exec(event->globalPos());
}
void MyView::rotateItem(qreal deg)
{
QRectF rect;
foreach(QGraphicsItem* item, scene()->selectedItems())
rect |= item->mapToScene(item->boundingRect()).boundingRect();
QPointF center = rect.center();
QTransform t;
t.translate(center.x(), center.y());
t.rotate(deg);
t.translate(-center.x(), -center.y());
foreach(QGraphicsItem* item, scene()->selectedItems())
{
item->setPos(t.map(item->pos()));
item->setRotation(item->rotation() + deg);
}
}
void MyView::shearItem(qreal shx, qreal shy)
{
QRectF rect;
foreach(QGraphicsItem* item, scene()->selectedItems())
rect |= item->mapToScene(item->boundingRect()).boundingRect();
QPointF center = rect.center();
QTransform t;
t.translate(center.x(), center.y());
t.shear(shx, shy);
// seems shear increases item to gigantic proportions so I tried
//t.shear(shx/100, shy/100);
t.translate(-center.x(), -center.y());
foreach(QGraphicsItem* item, scene()->selectedItems())
{
item->setPos(t.map(item->pos()));
item->setTransform(t, true);
}
}
void MyView::createActions()
{
rotateAct = new QAction(tr("Rotate 10 degrees"), this);
connect(rotateAct, SIGNAL(triggered()), this, SLOT(rotateItem()));
shearAct = new QAction(tr("Shear 10, 10"), this);
connect(shearAct, SIGNAL(triggered()), this, SLOT(shearItem()));
}
main.cpp:
#include <QtGui>
#include "myview.h"
int main( int argc, char **argv )
{
QApplication app(argc, argv);
MyView* view = new MyView();
QGraphicsRectItem* rItem = view->scene()->addRect(30, 40, 100, 100, Qt::NoPen, Qt::blue);
QGraphicsRectItem* rItem1 = view->scene()->addRect(-30, -40, 100, 100, Qt::NoPen, Qt::red);
QGraphicsEllipseItem* eItem = view->scene()->addEllipse(70, -50, 100, 100, Qt::NoPen, Qt::blue);
foreach(QGraphicsItem* item, view->scene()->items())
{
item->setFlag(QGraphicsItem::ItemIsMovable);
item->setFlag(QGraphicsItem::ItemIsFocusable);
item->setFlag(QGraphicsItem::ItemIsSelectable);
}
view->show();
return app.exec();
}

How to look for equivalent functions of OpenGL in QGLWidget?

I'm new to OpenGL and Glut. There is a project implemented by Glut. I googled and found that there is an OpenGL implementation in Qt, called QGLWidget. However, it's hard for me converting the old Glut code to new Qt code since I don't know how to find equivalent function for Glut functions in Qt. Part of the code look like this:
glutInit(&argc,argv);
glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGB);
glutInitWindowSize(gray1->width,gray1->height);
glutInitWindowPosition(100,100);
glutCreateWindow("hello");
init();
glutDisplayFunc(&display);
glutReshapeFunc(reshape);
glutMouseFunc(mouse);
glutMotionFunc(mouse_move);
glutMainLoop();
The glut* functions above don't exist in Qt's document. So my problem is how can I find equivalent glut functions in functions of QGLWidget?
You need to implement your own class inherited from QGLWidget, for example:
class GLWidget : public QGLWidget
{
Q_OBJECT
public:
GLWidget(QWidget *parent = 0);
protected:
void initializeGL();
void resizeGL(int w, int h);
void paintGL();
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
};
You also need to override three important functions, initializeGL() where you're preparing your OpenGL. resizeGL() where you update the viewport and projection matrix if your panel is resized, and paintGL() the actual rendering.
The window initialization, of course, is handled by Qt.
For mouse events, there are three functions you can override: mousePressEvent(), mouseMoveEvent(), and mouseReleaseEvent()
void GLWidget::initializeGL()
{
glClearColor(0.5, 0.5, 0.5, 1.0);
}
void GLWidget::resizeGL(int width, int height)
{
glViewport(0, 0, width(), height());
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0, width(), 0, height());
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void GLWidget::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT);
// draw a red triangle
glColor3f(1,0,0);
glBegin(GL_POLYGON);
glVertex2f(10,10);
glVertex2f(10,600);
glVertex2f(300,10);
glEnd();
}
OK. Have you looked at the HelloGL sample?
So there you'll learn how to display a QGLWidget and process mouse input. I think this is what you are looking for.
Since Qt provides SIGNAL and SLOTS input processing is kind of different but also very intuitive. So you have to connect mouse SIGNALS to your SLOTS. Those SLOTS will then process the mouse event.
But look at the sample, it's quite intuitive.

Qt Beginner QPainter and QRect

How would I go about drawing a rectangle?
I have tried two different ways;
void MyWidget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(Qt::black);
QRect rect = QRect(290, 20, 70, 40);
painter.drawText(rect, Qt::AlignCenter,
"Data");
painter.drawRect(rect);
}
Which works fine (even though the parameter is not named nor used), but I don't want to use the QPaintEvent * I have no use for it.
So I tried just renaming my function;
void MyWidget::draw()
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(Qt::black);
QRect rect = QRect(290, 20, 70, 40);
painter.drawText(rect, Qt::AlignCenter,
"Data");
painter.drawRect(rect);
}
This doesn't display anything (yet has no errors).
Why would it not work if I don't use QPaintEvent * ??
The paint event is the method that is called by the paint system when a widget needs to be redrawn. That is why simply naming your own method does not work. It is never called by the paint system.
You really should be using the QPaintEvent. It gives you the rect that needs to be drawn. This rect will be based upon the size of the widget, so instead of using an explicit rect in your paint event, set your widget to the right size. A paint event will be generated should your widget ever move, resize, etc.
void MyWidget::paintEvent(QPaintEvent *event)
{
QRect rect = event->rect();
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(Qt::black);
painter.drawText(rect, Qt::AlignCenter,
"Data");
painter.drawRect(rect);
}
Now if you want to separate your paint logic into another method, that is fine. But you would need to have it called from the paint event:
void MyWidget::paintEvent(QPaintEvent *event)
{
QRect rect = event->rect();
draw(rect);
}
void MyWidget::draw(QRect &rect)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(Qt::black);
painter.drawText(rect, Qt::AlignCenter,
"Data");
painter.drawRect(rect);
}
If you want to completely bypass the paint events as you said, and only want to create a static rectangle to display, one way is to just draw it once to a pixmap and display it in a QLabel:
QPixMap pix(200,100);
QPainter painter(&pix);
// do paint operations
painter.end()
someLabel.setPixmap(pix)
Any data that your paintEvent() needs should be accessible as fields of the containing class, in your case, private fields of MyWidget. These private fields can be exposed to clients of MyWidget via "setters" which would set the data values before calling update() on MyWidget which will trigger a call to paintEvent().
This playlist contains the best Qt tutorials , starting tutorial 74 would be useful for you (Qpainter and QPen), tutorial 75 is how to draw rectangles using QRect.
As well #Mat told you: the "event" is the correct way to launch a painter.
QPainter can only be evoked after a QPaintEvent event, which carries the safe region where the object may be drawn.
So you must find another strategy to transport your data, to help
I will propose a method simple, which can be adjusted to many cases.
widget.cpp
#include <QtGui>
#include "widget.h"
#define MIN_DCX (0.1)
#define MAX_DCX (5.0)
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
dcx=MIN_DCX;
setFixedSize(170, 100);
}
void Widget::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
QPainter painter;
painter.begin(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(Qt::black);
pcx=dcx*2;
QRect rect = QRect(50-dcx,25-dcx,60+pcx,40+pcx);
painter.drawText(rect, Qt::AlignCenter,printData);
painter.drawRect(rect);
painter.end();
}
void Widget::setPrintData(QString value){
printData = value;
dcx=(dcx>MAX_DCX)?MIN_DCX:dcx+MIN_DCX;
}
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent);
void setPrintData(QString value);
protected:
void paintEvent(QPaintEvent *event);
private:
QString printData;
float dcx;
float pcx;
};
#endif
window.cpp
#include <QtGui>
#include "widget.h"
#include "window.h"
#define MAX_SDCX 20
Window::Window()
: QWidget()
{
gobject = new Widget(this);
textMode=1;
rectMode=1;
gobject->setPrintData(msgs[textMode]);
QGridLayout *layout = new QGridLayout;
layout->addWidget(gobject, 0, 0);
setLayout(layout);
QTimer *timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(dataOnAir()));
timer->start(10);
setWindowTitle(tr("Rect Shaking"));
}
void Window::dataOnAir(){
if((++rectMode)>MAX_SDCX){
rectMode=0;
textMode^=1;
}
gobject->setPrintData(msgs[textMode]);
gobject->repaint();
}
window.h
#ifndef WINDOW_H
#define WINDOW_H
#include <QWidget>
#include "widget.h"
class Window : public QWidget
{
Q_OBJECT
public:
Window();
private slots:
void dataOnAir();
private:
Widget *gobject;
const QString msgs[2] = {"Hello","World"};
int textMode;
int rectMode;
};
#endif
main.cpp
#include <QApplication>
#include "window.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Window window;
window.show();
return app.exec();
}
As you can see in the code is executed a timer, outside the object "widget"
every 10ms sends a repaint the widget to redraw a "rect" with a different size and every 20 cycles (200ms) changes the text "hello" for "world"
In this example you can see that in any way need overwrite the QPainterDevice architecture.
You may also notice that the "event" within the "paintEvent" is silenced and not used directly, but it is essential to execute a sequence QPainter.
Overriding the paintEvent() function of a widget enables you to customize the widget and this function is called periodically to redraw the widget. Therefore any drawing should be made in this function. However overriding paintEvent() may cause some performance issues. I would prefer using a QGraphicsScene and QGraphicsView then I would add a rectangle to the scene which is the common way of doing this kind of drawing stuff. Please check the GraphicsView Framework
http://qt-project.org/doc/qt-4.8/graphicsview.html

Basic Qt GUI: QPushButton for Drawing a line

I want to draw a grid (series of lines) when I click the draw button, and I want them to clear when I click the clear button.
I got the grid to appear as a standalone program, but I cannot figure out how to combine it with QPushButton.
I get the following message when clicking on the Draw button while the program is running.
"QPainter::begin: Paint device returned engine == 0, type: 1
QPainter::setPen: Painter not active"
Thank you
#include <QtGui>
#include <QPainter>
#include "myqtapp.h"
// including <QtGui> saves us to include every class user, <QString>, <QFileDialog>,...
myQtApp::myQtApp(QWidget *parent)
{
setupUi(this); // this sets up GUI
// signals/slots mechanism in action
connect( pushButton_draw, SIGNAL( clicked() ), this, SLOT( draw() ) );
connect( pushButton_clear, SIGNAL( clicked() ), this, SLOT( clear() ) );
connect( pushButton_about, SIGNAL( clicked() ), this, SLOT( about() ) );
}
void myQtApp::draw()
{
//draw the grid
int lineSpacing(30),// line spacing in pixels
numberOfLines;
QPen pen(Qt::black, 2, Qt::SolidLine);
QPainter painter(this);
painter.setPen(pen);
//Grid takes up at most a 400x400 area starting at (right 150, down 50) from upper left
numberOfLines = 400/lineSpacing; //Round down grid size to fit in 400x400
for(int i = 0; i<numberOfLines; i++){
painter.drawLine(150, 50+i*lineSpacing, 150+(numberOfLines-1)*lineSpacing, 50+i*lineSpacing);
painter.drawLine(150+i*lineSpacing, 50, 150+i*lineSpacing, 50+(numberOfLines-1)*lineSpacing );
}
}
The problem you are having is because you are trying to draw on the UI using QPainter outside of the paintEvent() call of a widget - from the Qt docs :
The common use of QPainter is inside a widget's paint event: Construct
and customize (e.g. set the pen or the brush) the painter. Then draw.
Remember to destroy the QPainter object after drawing.
If you try and draw on the widget outside of the paintEvent() call, results are unpredictable.
The correct way to do this would be something like this:
// myQtApp.h
class myQtApp : public QWidget
{
Q_OBJECT
public:
myQtApp(QWidget *parent = 0); // Constructor as you have
protected:
void paintEvent(QPaintEvent *event); // This is re-implemented from QWidget
protected slots:
void draw();
private:
bool drawTheLines;
}
and
// myQtApp.cpp
void myQtApp::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
if(drawTheLines)
{
// Do the drawing here - as in your current draw() function
}
QWidget::paintEvent(event); // call the base class so everything else is drawn OK
}
void draw();
{
drawTheLines = true;
update(); // This forces a repaint of the widget with paintEvent()
}

Resources