I'm a beginner at Qt
and c++ and I wanted to see how to use a QPainter and events in Qt but I got stuck because of an error message during the execution, my original code:
the main.cpp
#include "customwidget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QScopedPointer<QWidget> widget(new customWidget());
widget->resize(240, 120);
widget->show();
return a.exec();
}
and the header:
#ifndef CUSTOMWIDGET_H
#define CUSTOMWIDGET_H
#include <QWidget>
#include <QMouseEvent>
#include <QPoint>
#include <QPainter>
class customWidget : public QWidget
{
Q_OBJECT
public:
explicit customWidget(QWidget *parent = 0);
void paintEvent(QPaintEvent *);
void mouseMoveEvent(QMouseEvent *event);
void mousePressEvent(QMouseEvent *event);
private:
QPoint m_mousePos;
QRect m_r2;
signals:
void needToRepaint();
public slots:
};
#endif // CUSTOMWIDGET_H
and the .cpp:
#include "customwidget.h"
customWidget::customWidget(QWidget *parent) : QWidget(parent)
{
QRect m_r2;
QPoint m_mousePos;
QObject::connect(this, SIGNAL(needToRepaint()), this, SLOT(repaint()));
}
void customWidget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
// ############ First Rectangle ****************************************
QRect r1 = rect().adjusted(10, 10, -10, -10);
painter.setPen(QColor("#FFFFFF"));
painter.drawRect(r1);
// ############ Seconde Rectangle ****************************************
QRect r2(QPoint(0, 0), QSize(100, 100));
m_r2.moveCenter(m_mousePos);
QPainter painter2;
QPen pen;
painter2.setPen(QColor("#000000"));
pen.setWidth(3);
painter2.setPen(pen);
painter2.drawRect(m_r2);
update();
}
void customWidget::mouseMoveEvent(QMouseEvent *event)
{
m_mousePos = event->pos();
emit needToRepaint();
}
I tried to search it on the web and saw that it's because the QPainter isn't located in the paintEvent but it's not the case in my code, thanks for your help.
You only need one painter. The second one wasn't activated, and you don't need it anyway.
Don't ever call repaint() unless you somehow absolutely need the painting to be done before repaint() returns (that's what happens!). If you keep the event loop running properly, you won't ever need that.
Don't call update() from paintEvent(): it's nonsense (literally).
When you wish to repaint the widget, call update(): it schedules an update from the event loop. Multiple outstanding updates are coalesced to keep the event loop functional and prevent event storms.
Let the compiler generate even more memory management code for you. You've done the first step by using smart pointers - that's good. Now do the second one: hold the instance of CustomWidget by value. It doesn't have to be explicitly dynamically allocated. C++ is not C, you can leverage values.
In a simple test case, you don't want three files. Your code should fit in as few lines as possible, in a single main.cpp. If you need to moc the file due to Q_OBJECT macros, add #include "main.moc" at the end, and re-run qmake on the project to take notice of it.
This is how such a test case should look, after fixing the problems. Remember: it's a test case, not a 100kLOC project. You don't need nor want the meager 35 lines of code spread across three files. Moreover, by spreading out the code you're making it harder for yourself to comprehend.
Even in big projects, unless you can show significant build time improvements if doing the contrary, you can have plenty of small classes implemented Java-style completely in the header files. That's about the only Java-style-anything that belongs in C++.
// https://github.com/KubaO/stackoverflown/tree/master/questions/simple-paint-38796140
#include <QtWidgets>
class CustomWidget : public QWidget
{
QPoint m_mousePos;
public:
explicit CustomWidget(QWidget *parent = nullptr) : QWidget{parent} {}
void paintEvent(QPaintEvent *) override;
void mouseMoveEvent(QMouseEvent *event) override {
m_mousePos = event->pos();
update();
}
};
void CustomWidget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
auto r1 = rect().adjusted(10, 10, -10, -10);
painter.setPen(Qt::white);
painter.drawRect(r1);
auto r2 = QRect{QPoint(0, 0), QSize(100, 100)};
r2.moveCenter(m_mousePos);
painter.setPen(QPen{Qt::black, 3, Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin});
painter.drawRect(r2);
}
int main(int argc, char ** argv) {
QApplication app{argc, argv};
CustomWidget w;
w.show();
return app.exec();
}
This error can happen too when the QPixmap used to create the QPainter(QPixmap) is invalid (if there is no file at such path).
Be sure your QPixmap is correct before painting on it.
Related
I have a ComboBox and set it to be edited.
QComboBox *myCombo = new QComboBox(this);
myCombo->setEditable(true);
myCombo->setStyleSheet("QComboBox::down-arrow{image: url(:/bulb.png);}");
myCombo->setCursor( QCursor( Qt::PointingHandCursor ) );
So now when i click onto the editing field, nothing happen. But what I need is, when I click onto the bulb (which is the down-arrow), something (like a table or a dialog....) should be appeared. How can I recognize this click event in this case? I looked at the list of signals for combo box but could not find any signal for that.
By overwriting the mousePressEvent() method you must use hitTestComplexControl() method to know that QStyle::SubControl has been pressed by issuing a signal if it is QStyle::SC_ComboBoxArrow.
#include <QtWidgets>
class ComboBox: public QComboBox
{
Q_OBJECT
public:
using QComboBox::QComboBox;
signals:
void clicked();
protected:
void mousePressEvent(QMouseEvent *event) override{
QComboBox::mousePressEvent(event);
QStyleOptionComboBox opt;
initStyleOption(&opt);
QStyle::SubControl sc = style()->hitTestComplexControl(QStyle::CC_ComboBox, &opt, event->pos(), this);
if(sc == QStyle::SC_ComboBoxArrow)
emit clicked();
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
ComboBox w;
w.setEditable(true);
w.setStyleSheet("QComboBox::down-arrow{image: url(:/bulb.png);}");
QObject::connect(&w, &ComboBox::clicked, [](){
qDebug()<<"clicked";
});
w.show();
return a.exec();
}
#include "main.moc"
Although showPopup() is a possible option this can be called directly without the down-arrow being pressed, for example by calling it directly: myCombo->showPopup() so it is not the correct option.
A possible solution is to subclass QComboBox and reimplement showPopup() virtual method:
.h:
#ifndef COMBOBOXDROPDOWN_H
#define COMBOBOXDROPDOWN_H
#include <QComboBox>
#include <QDebug>
class ComboBoxDropDown : public QComboBox
{
public:
ComboBoxDropDown(QWidget *parent = nullptr);
void showPopup() override;
};
#endif // COMBOBOXDROPDOWN_H
.cpp:
#include "comboboxdropdown.h"
ComboBoxDropDown::ComboBoxDropDown(QWidget *parent)
: QComboBox (parent)
{
}
void ComboBoxDropDown::showPopup()
{
//QComboBox::showPopup();
qDebug() << "Do something";
}
Problem
When QPainter is created after glClear the latter has no effect.
Description
I use Qt 5.7.1. I get same results with gcc on Linux and vc++ on Windows.
I have the following in a widget derived from QGLWidget:
void CanvasWidget::initializeGL()
{
qglClearColor(m_backgroundColor);
}
V1:
void CanvasWidget::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT);
QPainter painter(this);
painter.drawLine(0, 0, 1000, 1000);
}
Produces:
V2:
void CanvasWidget::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT);
}
Produces:
What I want is:
Which can be done with a hack:
void CanvasWidget::paintGL()
{
QPainter painter(this);
qglClearColor(m_backgroundColor);
glClear(GL_COLOR_BUFFER_BIT);
painter.drawLine(0, 0, 1000, 1000);
}
Question
What is going on? Why can't glClean and QPainter work together? Why can't I get it with V1?
Minimal Reproducible Example
main.cpp
#include "MainWindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MainWindow mainwindow;
mainwindow.show();
return app.exec();
}
MainWindow.h
#pragma once
#include "CanvasWidget.h"
#include <QMainWindow>
#include <memory>
class MainWindow : public QMainWindow
{
public:
explicit MainWindow(QWidget *parent = 0);
MainWindow(const MainWindow &) = delete;
MainWindow & operator= (const MainWindow &) = delete;
virtual ~MainWindow() = default;
private:
std::unique_ptr<CanvasWidget> m_canvasWidget;
};
MainWindow.cpp
#include "MainWindow.h"
#include <QHBoxLayout>
MainWindow::MainWindow(QWidget* parent)
: QMainWindow(parent)
, m_canvasWidget(new CanvasWidget(parent))
{
setCentralWidget(m_canvasWidget.get());
}
CanvasWidget.h
#pragma once
#include <QGLWidget>
class CanvasWidget : public QGLWidget
{
Q_OBJECT
public:
CanvasWidget(QWidget* parent = 0, const QGLWidget* shareWidget = 0, Qt::WindowFlags f = 0);
private:
virtual void initializeGL() override;
virtual void paintGL() override;
private:
QColor m_backgroundColor;
};
CanvasWidget.cpp
#include "CanvasWidget.h"
#include <QMessageBox>
#include <QWheelEvent>
CanvasWidget::CanvasWidget(
QWidget* parent /*= 0*/,
const QGLWidget* shareWidget /*= 0*/,
Qt::WindowFlags f /*= 0 */)
: QGLWidget(parent, shareWidget, f)
, m_backgroundColor(0, 93, 196)
{}
void CanvasWidget::initializeGL()
{
qglClearColor(m_backgroundColor);
}
void CanvasWidget::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT);
QPainter painter(this);
painter.drawLine(0, 0, 1000, 1000);
}
Adding setAutoFillBackground(false) in CanvasWidget constructor solves the problem.
All the credit goes to #G.M.
In Qt tutorial about overpainting OpenGL with QPainter it is stated:
When overpainting 2D content onto 3D content, we need to use a
QPainter and make OpenGL calls to achieve the desired effect. Since
QPainter itself uses OpenGL calls when used on a QGLWidget subclass,
we need to preserve the state of various OpenGL stacks when we perform
our own calls
So, looking at your code in V1 I suppose that QPainter sets its own clear color state, and if you don't set your glClearColor explicitly, you will get what QPainter object has set.
And I would also suggest you to use a tool like RenderDoc or gDebugger to trace GL commands of your app to see exactly what happens under the hood.
I am doing a project in Qt and there is an object QPainter which is declared as :
QPainter painter(this);
Where this points to the present class. My problem is that I need to declare this object such that it is accessible to the entire class functions.
If I declare it inside the constructor then its scope is not valid for other functions, and I cannot declare outside all function in my .cpp file as this variable doesn't make any sense.
So how can I declare my object such that it is accessible to all the functions?
Edit : Painter Code :
void MainWindow :: paintEvent(QPaintEvent * e)
{
QMainWindow::paintEvent(e);
if(1)
{
QPainter painter(this);
QPen paintpen(Qt::red);
paintpen.setWidth(5);
QPoint p1;
p1.setX(mFirstX);
p1.setY(mFirstY);
painter.setPen(paintpen);
painter.drawPoint(p1);
}
}
Mouse Event Code :
void MainWindow :: mousePressEvent(QMouseEvent *e)
{
mFirstX=0;
mFirstY=0;
mFirstClick=true;
mpaintflag=false;
if(e->button() == Qt::LeftButton)
{
//store 1st point
if(1)
{
mFirstX = e->x();
mFirstY = e->y();
mFirstClick = false;
mpaintflag = true;
qDebug() << "First image's coordinates" << mFirstX << "," << mFirstY ;
update();
}
}
}
PROBLEM : I want to create the point but I don't want it to disappear from the widget(mainWindow). Here since the object is created during the paintEvent method each time the point that I am drawing is disappearing when the next point is drawn.
And if I declare it outside the paintEvent then I get the following error:
QWidget::paintEngine: Should no longer be called
QPainter::begin: Paint device returned engine == 0, type: 1
QPainter::setPen: Painter not active
In general, declaring the QPainter object globally is not a good idea. It is better to construct it in the QWidget::paintEvent (where it will be active) and, as #Mat said, pass it (by reference) to the functions that need it.
In your particular case the point disappears because the widget is redrawn, not because the QPainter is destroyed. So the strategy should be to create and manage a buffer of points. Add a point to the buffer in a mouse event, e.g. QWidget::mousePressEvent (there do some management too, e.g. limiting the number of points) and in the paint event - paint all the points from the buffer.
Here is an oversimplified example which could easily be adapted for your specific purpose:
MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QMouseEvent>
#include <QPaintEvent>
#include <QPainter>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
protected:
void mousePressEvent(QMouseEvent *event);
void paintEvent(QPaintEvent *event);
private:
QList<QPoint> m_points;
};
#endif // MAINWINDOW_H
MainWindow.cpp
#include "MainWindow.h"
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
QPalette p = QPalette();
p.setColor(QPalette::Window, Qt::white);
setPalette(p);
setAutoFillBackground(true);
resize(400, 400);
}
void MainWindow::mousePressEvent(QMouseEvent *event)
{
m_points.append(event->pos());
update();
}
void MainWindow::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.setClipping(true);
painter.setClipRect(event->rect());
painter.setPen(QPen(Qt::red, 5));
foreach (QPoint point, m_points) { painter.drawPoint(point); }
}
That's what class attributes are for :
class MyObject : public QObject {
Q_OBJECT
public:
MyObject(QObject *parent = 0) : QObject(parent), painter_(this) {
// Other things if you need them
}
void aFunction() {
// You can use painter_ here !!
}
private :
QPainter painter_;
}
In the private access specifier of your main window class in mainWindow.h, you could write this line
QPainter *painterPointer
creating a pointer accessible in all MainWindow slots. You could use that painterPointer as though it were any painter pointer initialised in any slot. However if you wanted to use this pointer in a developer defined function or method outside of the MainWindow class, you would have to pass the pointer as a reference (or pointer to the pointer).
might have to include a line similar to this in the MainWindow Constructor
painterPointer = new QPainter(this);
I create class Widget, it creates window, this class paints something on the window (i.e. it works as I want).
I create yet one class, Circle, I want to paint on the window of class Widget.
I pass adress of Widget and try to paint on Widget using QPainter paint (address of Widget); (in the instance of Circle) but i don't see anything.
I've tried to make code as shorter as possible during the execution of program I type out address of object Widget. It doesn't change. It means that the address of Widget was passed right.
Everywhere, where I type out address of Widget I receive the same address. Here is the code:
header Widget
class Widget : public QWidget
{
public:
int mi,mcount;
Widget(QWidget *parent = 0);
QPaintEvent *ev;
virtual void paintEvent(QPaintEvent *);
void drawcircle();
};
Widget.cpp
Widget::Widget(QWidget *parent) : QWidget(parent)
{
QWidget::paintEvent(ev);
qDebug()<<this<<"\n"; //
}
void Widget::drawcircle()
{
QPainter paint(this);
paint.drawEllipse(0,0,100,100);
}
void Widget::paintEvent(QPaintEvent *ev)
{ this->drawcircle(); }
header Circle.h
class Circle :public QWidget
{
public:
Circle(Widget *widget); // i do trick here!!!
Widget *mwidg;
QPaintEvent *ev;
virtual void paintEvent(QPaintEvent *);
void drawcircle(Widget *mwidg);
};
Circle.cpp
Circle::Circle(Widget *widget)
{
qDebug()<<"circle widget"<<widget;
QWidget::paintEvent(ev);
mwidg=widget;
qDebug()<<"\n"<<mwidg;
}
void Circle::paintEvent(QPaintEvent *ev)
{ qDebug()<<"circle paintEvent mwidget"<<mwidg<<"\n";
this->drawcircle(mwidg);
}
void Circle::drawcircle(Widget *mwidg)
{
QPainter paint(mwidg);
paint.drawEllipse(20,10,40,40);
paint.drawLine(0,0,500,500);
}
main
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget *w=new Widget;
qDebug()<<"main address of widget"<<w<<"\n";
Circle *f=new Circle(w);
w->show();
return a.exec();
}
program is compiled and linked successful
What exactly are you trying to achieve? You can only paint on any widget in it's own paintEvent() handler, and you should not call paintEvent() yourself, it won't work. Also, get rid of the QPaintEvent member variables.
I would suggest that you make Circle a child of Widget, and then paint the circle from Circle::paintEvent(). Alternatively, use QGraphicsView.
well , thank you for your attempts to help
but all that i was needing:
this -> setParent(widget);
in costructor Circle::Circle, if somebody'll want to see my solve,one can see that figures are moved
source code is here source code
I'm trying for half an eternity now overriding QWidgets keyPressEvent function in QT but it just won't work. I've to say i am new to CPP, but I know ObjC and standard C.
My problem looks like this:
class QSGameBoard : public QWidget {
Q_OBJECT
public:
QSGameBoard(QWidget *p, int w, int h, QGraphicsScene *s);
signals:
void keyCaught(QKeyEvent *e);
protected:
virtual void keyPressEvent(QKeyEvent *event);
};
QSGameBoard is my QWidget subclass and i need to override the keyPressEvent and fire a SIGNAL on each event to notify some registered objects.
My overridden keyPressEvent in QSGameBoard.cpp looks like this:
void QSGameBoard::keyPressEvent(QKeyEvent *event) {
printf("\nkey event in board: %i", event->key());
//emit keyCaught(event);
}
When i change QSGameBoard:: to QWidget:: it receives the events, but i cant emit the signal because the compiler complains about the scope. And if i write it like this the function doesn't get called at all.
What's the problem here?
EDIT:
As pointed out by other users, the method I outlined originally is not the proper way to resolve this.
Answer by Vasco Rinaldo
Use Set the FocusPolicy to Qt::ClickFocus to get the keybordfocus by
mouse klick. setFocusPolicy(Qt::ClickFocus);
The previous (albeit imperfect) solution I gave is given below:
Looks like your widget is not getting "focus". Override your mouse press event:
void QSGameBoard::mousePressEvent ( QMouseEvent * event ){
printf("\nMouse in board");
setFocus();
}
Here's the source code for a working example:
QSGameBoard.h
#ifndef _QSGAMEBOARD_H
#define _QSGAMEBOARD_H
#include <QWidget>
#include <QGraphicsScene>
class QSGameBoard : public QWidget {
Q_OBJECT
public:
QSGameBoard(QWidget *p, int w, int h, QGraphicsScene *s);
signals:
void keyCaught(QKeyEvent *e);
protected:
virtual void keyPressEvent(QKeyEvent *event);
void mousePressEvent ( QMouseEvent * event );
};
#endif /* _QSGAMEBOARD_H */
QSGameBoard.cpp
#include <QKeyEvent>
#include <QLabel>
#include <QtGui/qgridlayout.h>
#include <QGridLayout>
#include "QSGameBoard.h"
QSGameBoard::QSGameBoard(QWidget* p, int w, int h, QGraphicsScene* s) :
QWidget(p){
QLabel* o = new QLabel(tr("Test Test Test"));
QGridLayout* g = new QGridLayout(this);
g->addWidget(o);
}
void QSGameBoard::keyPressEvent(QKeyEvent* event){
printf("\nkey event in board: %i", event->key());
}
void QSGameBoard::mousePressEvent ( QMouseEvent * event ){
printf("\nMouse in board");
setFocus();
}
main.cpp
#include <QtGui/QApplication>
#include <QtGui/qmainwindow.h>
#include "QSGameBoard.h"
int main(int argc, char *argv[]) {
// initialize resources, if needed
// Q_INIT_RESOURCE(resfile);
QApplication app(argc, argv);
QMainWindow oM;
QGraphicsScene o;
QSGameBoard a(&oM, 1, 2, &o);
oM.setCentralWidget(&a);
a.show();
oM.show();
// create and show your widgets here
return app.exec();
}
You don't have to reimplement mousePressEvent yourself just to call setFocus. Qt planed it already.
Set the FocusPolicy to Qt::ClickFocus to get the keybordfocus by mouse klick.
setFocusPolicy(Qt::ClickFocus);
As said in the manual:
This property holds the way the widget accepts keyboard focus.
The policy is Qt::TabFocus if the widget accepts keyboard focus by tabbing, Qt::ClickFocus if the widget accepts focus by clicking, Qt::StrongFocus if it accepts both, and Qt::NoFocus (the default) if it does not accept focus at all.
You must enable keyboard focus for a widget if it processes keyboard events. This is normally done from the widget's constructor. For instance, the QLineEdit constructor calls setFocusPolicy(Qt::StrongFocus).
If the widget has a focus proxy, then the focus policy will be propagated to it.
Set the FocusPolicy to Qt::ClickFocus to get the keybordfocus by mouse klick.
setFocusPolicy(Qt::ClickFocus);