QML : What is the equivalent of QPainterPath? - c++

In order to convert one GUI interface written in C++ with the Qt libraries to QML, I've to find an alternative to QPainterPath in QML. Indeed, for now, a bunch of shapes are drawn in the GUI interface and the C++ code modifies the color of those objects when certain events happen. QPainterPath objects are used to store those shapes.
I would appreciate if you can show me how to draw two rectangle objects in a QML canvas and then how to modify their filled color within the C++ code.

As I said in my comment, one option could be Canvas, it has methods similar to QPainterPath. In the next part I will show an example where the color can be changed from C ++ through a method that generates random colors and are called by a QTimer:
main.cpp
#include <QColor>
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QTime>
#include <QTimer>
class ColorProvider: public QObject{
Q_OBJECT
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
public:
QColor color() const{
return mColor;
}
void setColor(const QColor &color){
if(color == mColor)
return;
mColor = color;
emit colorChanged(mColor);
}
Q_INVOKABLE void randomColor(){
qsrand((uint)QTime::currentTime().msec());
setColor(QColor(qrand() % 256, qrand() % 256, qrand() % 256));
}
signals:
void colorChanged(const QColor &color);
private:
QColor mColor;
};
#include "main.moc"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
ColorProvider obj;
QTimer timer;
QObject::connect(&timer, &QTimer::timeout, &obj, &ColorProvider::randomColor);
timer.start(100);
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("colorProvider", &obj);
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
main.qml
import QtQuick 2.7
import QtQuick.Controls 2.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Canvas {
id:canvas
anchors.fill: parent
onPaint:{
var ctx = canvas.getContext('2d');
ctx.lineWidth = 4
ctx.fillStyle = "orange"
ctx.strokeStyle = "red"
ctx.fillRect(50, 40, 100, 100)
ctx.stroke()
ctx.fill()
ctx.lineWidth = 10
ctx.fillStyle = colorProvider.color
ctx.fillRect(150, 150, 300, 300)
ctx.stroke()
ctx.fill()
ctx.roundedRect(20, 20, 40, 40, 10, 10)
}
}
Connections {
target: colorProvider
onColorChanged: canvas.requestPaint()
}
}
The complete example can be found in the following link.

Related

QQuickView as item in a QGraphicsScene

I would like to integrate a QQuickView in a QGraphicsView, but I only get a gray rectangle.
I know this is a horrible choice to integrate qml into qgraphicsview, but my team is working on this application for 3 years now and it is not possible to change the main QGraphicsView. I just want to know if there is a way to include some QML now.
Maybe there is a solution for this ? Maybe I just have to forget about it. Maybe you know. Thanks !
Code sample
main.cpp
#include <QApplication>
#include <QMainWindow>
#include <QtQuick/QQuickView>
#include <QGraphicsView>
#include <QGraphicsScene>
// Main
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene * scene = new QGraphicsScene;
QGraphicsView * view = new QGraphicsView(scene);
QQuickView * quickView = new QQuickView;
quickView->setSource(QUrl::fromLocalFile("test.qml")); // Displays a text
QWidget * container = QWidget::createWindowContainer(quickView);
container->setFixedSize(500, 100);
scene->addWidget(container);
scene->addEllipse(50, 50, 100, 100); // Just to know my scene is correctly drawn
view->show();
return a.exec();
}
test.qml
import QtQuick 2.15
Rectangle {
width: 500
height: 100
Text {
text: "This is a text in a QML view"
anchors.left: parent.left
anchors.top: parent.top
}
}

Create qml object dynamically from c++ object (by using setContextProperty)

I am trying to create a qml object dynamically in c++ using the object of c++ class. Below is the minimal code for my approach. Upon execution of this code and after clicking, the application is crashing(see the comment in main.qml).
I have pasted the code below and It can be downloaded here.
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "scene.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
scene sc(engine);
QQmlContext* context = engine.rootContext();
context->setContextProperty("sc", &sc);
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
scene.h
#ifndef SCENE_H
#define SCENE_H
#include <QObject>
#include <QQmlApplicationEngine>
#include <QQmlComponent>
class scene : public QObject
{
Q_OBJECT
public:
explicit scene(QQmlApplicationEngine& engine, QObject *parent = nullptr);
QQmlApplicationEngine& engine;
public slots:
void create_rect_object();
};
#endif // SCENE_H
scene.cpp
#include "scene.h"
scene::scene(QQmlApplicationEngine &engine, QObject *parent) : engine(this->engine),QObject(parent)
{
}
void scene::create_rect_object()
{
QQmlComponent component(&engine, QUrl::fromLocalFile("myrect.qml"));
QObject *object = component.create();
object->setProperty("width", 200);
object->setProperty("height", 150);
object->setProperty("color", "blue");
}
main.qml
import QtQuick 2.11
import QtQuick.Window 2.11
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Rectangle{
anchors.fill: parent
color: "red"
MouseArea{
anchors.fill: parent
onClicked: {
console.log("Before click");
sc.create_rect_object(); // application is crashing here
console.log("after click");
}
}
}
}
myrect.qml
import QtQuick 2.0
Rectangle {
id:id_rec
width: 100
height: 100
color: "green"
x:0
y:0
}
Update
The object to be created is not the child of the root of main window but child of one of the item inside the chain of children items of root of mainwindow. The pseudo structure looks like below.
main.qml
Window {
customitem1{
id:id_ci1
}
customitem2{
id:id_ci1
}
}
customitem1.qml
Item {
customitem3{
id:id_ci3
}
customitem3{
id:id_ci4
}
}
[UPDATED]
You have two errors for crashing and one for not showing rectangles
1.Your scene's Constructor member initializer list is falsy which causes the app crash
(TIP : use different naming for members of the class by prefixing them with m_ e.g: m_engine for READABILITY and not get confused)
//Correct WAY
class Something
{
private:
int m_value1;
double m_value2;
char m_value3;
public:
//################# YOUR CASE ###############################
Something(int number) : m_value1(number), m_value2(2.2), m_value3('c') // directly initialize our member variables
{
// No need for assignment here
}
//#############################################################
Something() : m_value1(1), m_value2(2.2), m_value3('c') // directly initialize our member variables
{
// No need for assignment here
}
void print()
{
std::cout << "Something(" << m_value1 << ", " << m_value2 << ", " << m_value3 << ")\n";
}
}
and it should be like this :
scene::scene(QQmlApplicationEngine &engine, QObject *parent) : engine(engine),QObject(parent)
instead of
scene::scene(QQmlApplicationEngine &engine, QObject *parent) : engine(this->engine),QObject(parent)
2.The url of myrect.qml which you get from local file that isn't found at runtime caused the app crash aslo and the one of remedies is to load it from your qrc file
QQmlComponent component(&engine, QUrl("qrc:/myrect.qml"));
3.And you'll notice after clicking you got no rectangles that's because the rectangles getting created doesn't have a parent and by changing your create_rect_object()(In this example the parent is the invisible root of our window contentItem) you'll get some rectangles :)
//A QQuickWindow always has a single invisible root item containing all of its content.
//To add items to this window, reparent the items to the contentItem or to an existing item in the scene.
//http://doc.qt.io/qt-5/qquickwindow.html#contentItem-prop
void scene::create_rect_object()
{
QQmlComponent component(&engine, QUrl("qrc:/myrect.qml"));
QObject *object = component.create();
QQuickItem *item = qobject_cast<QQuickItem*>(object);
// Set the parent of our created qml rect
item->setParentItem((QQuickItem*)((QQuickWindow *) engine.rootObjects()[0])->contentItem());
//Set some random position and color
item->setProperty("color", QColor::fromRgb(QRandomGenerator::global()->generate()));
item->setX(20+qFloor(QRandomGenerator::global()->generateDouble()*20));
item->setY(20+qFloor(QRandomGenerator::global()->generateDouble()*20));
}
Finding QML Objects from C++
For finding objects and using them as parentItem, you have to set the objectName of your qml object
Rectangle {
...
objectName : "rect_1"
...
}
and in C++
QObject* obj = dynamic_cast<QObject*>(engine.rootObjects()[0]).findChild("rect_1");

Qt Drawing Without Erasing Components

I am using Qt 5.11.1 and Qt Creator to create a project. My code draws several ellipses in the paintEvent function that I have overriden. But because of the paintEvent function's working style the buttons that I have under the ellipses are being erased. I want to have a window that has ellipses at the top and buttons at the bottom of the window. It will roughly seem like this:
Is there any way to do this. Right now, the buttons are being erased and I only have the ellipses. I would be really glad if someone could guide me.
Thanks in advance.
Note: My ellipses are green and my background is black but I have tried by changing the background to white or changing the stylesheet of the buttons, it didn't work.
This is my .h file:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
Ui::MainWindow *ui;
protected:
void paintEvent(QPaintEvent *e);
void setBackGroundColorToBlack();
};
#endif // MAINWINDOW_H
This is my .cpp file:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QtGui>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
setBackGroundColorToBlack();
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::paintEvent(QPaintEvent *event) {
setUpdatesEnabled(false);
QPainter painterObj;
painterObj.begin(this);
painterObj.setPen(QPen(Qt::green, 2, Qt::SolidLine, Qt::RoundCap));
painterObj.drawEllipse(0, 0, 318, 390);//456
painterObj.drawEllipse(53, 65, 212, 260);//304
painterObj.drawEllipse(106, 130, 106, 130);//152
painterObj.end();
}
void MainWindow::setBackGroundColorToBlack() {
QPalette pal = palette();
// set black background
pal.setColor(QPalette::Background, Qt::black);
this->setAutoFillBackground(true); // This enables the qt to fill the background before the paint event.
this->setPalette(pal);
//update();
}
This is what I get:
My ui file is like this:
In general - don't use a QMainWindows class. It's for "big" desktop applications that have a menu, a status bar, etc. All you need is to derive from QDialog or even just QWidget. And you can paint the background yourself, so no need to mess with the pallete. The minimal example that should work is below. If the Bar button doesn't show up, then your Qt for the target is broken: the default platform style doesn't work.
// https://github.com/KubaO/stackoverflown/tree/master/questions/painted-with-children-51498155
// This project is compatible with Qt 4 and Qt 5
#include <QtGui>
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
#include <QtWidgets>
#endif
QRectF scaled(const QRectF &rect, qreal scale) {
auto const center = rect.center();
auto const w = rect.width()*scale;
auto const h = rect.height()*scale;
return {center.x() - w/2.0, center.y() - h/2.0, w, h};
}
QRectF marginAdded(const QRectF &rect, qreal margin) {
return rect.adjusted(margin, margin, -margin, -margin);
}
const char buttonQSS[] =
"* { background-color: white; border-width: 2px; border-style:solid; border-color: red;"
" border-radius: 3px; padding: 3px; }"
"*:pressed { padding-left: 5px; padding-top: 5px; background-color: lightGray; }";
class MyWindow : public QWidget {
Q_OBJECT
QPushButton m_restoreButton{"Restore Size"};
QPushButton m_otherButton{"Bar"};
QGridLayout m_layout{this};
Q_SLOT void onRestore() { resize(sizeHint()); }
public:
explicit MyWindow(QWidget *parent = {}) : QWidget(parent) {
setAttribute(Qt::WA_OpaquePaintEvent);
m_layout.addItem(new QSpacerItem(0, 100, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 0, 0);
m_layout.addWidget(&m_restoreButton, 1, 0);
m_layout.addWidget(&m_otherButton, 1, 1);
m_restoreButton.setStyleSheet(buttonQSS);
connect(&m_restoreButton, SIGNAL(clicked(bool)), SLOT(onRestore()));
}
protected:
void paintEvent(QPaintEvent *) override {
qreal const penWidth = 2.0;
// Cover the area above all the children
QRectF const area = marginAdded(QRect(0, 0, width(), childrenRect().top() - 10), penWidth);
QPainter p(this);
p.fillRect(rect(), Qt::black);
p.setPen({Qt::green, penWidth});
p.drawEllipse(scaled(area, 3./3.));
p.drawEllipse(scaled(area, 2./3.));
p.drawEllipse(scaled(area, 1./3.));
}
QSize sizeHint() const override { return {320, 568}; /* iPhone 5 */ }
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MyWindow w;
w.show();
return a.exec();
}
#include "main.moc"

'QML Connections: Cannot assign to non-existent Property' despite the opposite being true

So, I checked the following questions, which seemed most similar to my existing problem:
QML: Using cpp signal in QML always results in "Cannot assign to non-existent property"
Unfortunately that did not help. (Nor did any other solution that I could find on stackoverflow/google/qt forums, etc)
I kept getting the following two errors:
qrc:/view.qml:30:9: QML Connections: Cannot assign to non-existent
property "onNewFrameReceived" qrc:/view.qml:31: ReferenceError:
imageProvide is not defined
Here is my code (edited down to make it, into a 'Minimum Working Example').
The only files important should be:
main.cpp
view.qml
imageprovidervm.cpp
imageprovidervm.h
I included the imagesource class, just to be complete, in case someone wants to compile this on his own as well.
Q1. So, I don't understand why even after setting the context property in main.cpp, the following error appears.
qrc:/view.qml:31: ReferenceError: imageProvide is not defined
What is funny is that, intellisense/autocomplete seems to detect imageProvide completely correctly.
Q2. Even though in my imageprovider.h, I added properties (newimage) and signals (newFrameReceived) that should be seen in the qml file, still I get the following error. Also, the Qt intellisense/autocomplete fails to show my defined signal (onNewFrameReceived) here.
qrc:/view.qml:30:9: QML Connections: Cannot assign to non-existent
property "onNewFrameReceived"
Additional info: Debugging and stopping on a break point in the qml file at line 31, shows in the "locals and expressions" of the qtcreator that I have only 2 signals available here, namely "objectNameChanged" and "targetChanged".
Why ???
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "imageprovidervm.h"
#include "imagesource.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
QQmlContext *context = new QQmlContext(engine.rootContext());
auto model = std::make_shared<ImageSource>();
auto vm = new ImageProviderVM(model);
engine.addImageProvider(QLatin1String("imageProvider"), vm);
context->setContextProperty("imageProvide", vm );
model->generateImages();
engine.load(QUrl(QStringLiteral("qrc:/view.qml")));
return app.exec();
}
view.qml
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQml.Models 2.2
import QtQuick.Dialogs 1.2
import QtQuick.Window 2.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
menuBar: MenuBar {
Menu {
title: qsTr("File")
MenuItem {
text: qsTr("&Open")
onTriggered: console.log("Open action triggered");
}
MenuItem {
text: qsTr("Exit")
onTriggered: Qt.quit();
}
}
}
Rectangle {
Connections {
target: imageProvide
onNewFrameReceived: image.reload();
}
anchors.fill: parent
Column {
Image {
id: image
source: "image://imageProvider/images.jpeg?id=" + Math.random()
cache: false
asynchronous: true
function reload() {
var oldSource = source;
source = "";
source = oldSource;
}
}
}
}
Label {
text: qsTr("Hello World")
anchors.centerIn: parent
}
}
imageprovidervm.h
#ifndef IMAGEPROVIDERVM_H
#define IMAGEPROVIDERVM_H
#include <QQuickImageProvider>
#include <QObject>
#include "imagesource.h"
class ImageProviderVM : public QObject, public QQuickImageProvider
{
Q_OBJECT
public:
ImageProviderVM(std::shared_ptr<ImageSource> model);
~ImageProviderVM();
virtual QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override;
virtual QImage requestImage(const QString & id, QSize * size, const QSize & requestedSize) override;
// Properties
Q_PROPERTY(QImage newimage READ getNewImage NOTIFY newFrameReceived)
// Signals
signals:
void newFrameReceived();
private:
QImage getNewImage() const;
QPixmap m_pixmap;
QImage m_image;
std::shared_ptr<ImageSource> m_model;
};
#endif // IMAGEPROVIDERVM_H
imageprovidervm.cpp
#include "imageprovidervm.h"
#include <functional>
#include <QPixmap>
#include <QDebug>
ImageProviderVM::ImageProviderVM()
: QQuickImageProvider(QQuickImageProvider::Image)
{
}
ImageProviderVM::ImageProviderVM(std::shared_ptr<ImageSource> model)
: QQuickImageProvider (QQuickImageProvider::Image)
, m_pixmap()
, m_model(model)
{
m_model->subscribeNewPixMap([this](QPixmap pixmap) {
qDebug() << "setting m_pixmap";
if (pixmap.size().isValid()) {
m_pixmap = pixmap;
}
else
qDebug() << "is it NULL ??? " << pixmap.isNull();
});
m_model->subscribeNewImage([this](QImage image) {
qDebug() << "setting m_image";
if (image.size().isValid()) {
m_image = image;
emit newFrameReceived();
}
else
qDebug() << "is it NULL ??? " << image.isNull();
});
qDebug() << "imageproviderVM constructed";
}
ImageProviderVM::~ImageProviderVM()
{
}
QPixmap ImageProviderVM::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize)
{
// look into the parameters id, size and requestedSize once the rest of the structure is there
return m_pixmap;
}
QImage ImageProviderVM::requestImage(const QString & id, QSize * size, const QSize & requestedSize)
{
return m_image;
}
QQuickTextureFactory * ImageProviderVM::requestTexture(const QString & id, QSize * size, const QSize & requestedSize)
{
// return QQuickTextureFactory::createTexture();
}
QImage ImageProviderVM::getNewImage() const
{
return m_image;
}
imagesource.h
#ifndef IMAGESOURCE_H
#define IMAGESOURCE_H
#include <QImage>
#include <boost/signals2.hpp>
class ImageSource
{
public:
ImageSource();
void generateImages();
void generatePixmaps(const QString &id, QSize *size, const QSize &requestedSize);
typedef boost::signals2::signal<void (QPixmap)> NewPixMapDelegate;
boost::signals2::connection subscribeNewPixMap(NewPixMapDelegate::slot_function_type f);
typedef boost::signals2::signal<void (QImage)> NewImageDelegate;
boost::signals2::connection subscribeNewImage(NewImageDelegate::slot_function_type f);
private:
NewPixMapDelegate m_newPixMap;
NewImageDelegate m_newImage;
};
#endif // IMAGESOURCE_H
imagesource.cpp
#include "imagesource.h"
#include <QPixmap>
#include <QPainter>
#include <thread>
ImageSource::ImageSource()
{
}
boost::signals2::connection ImageSource::subscribeNewImage(NewImageDelegate::slot_function_type f)
{
return m_newImage.connect(f);
}
void ImageSource::generateImages()
{
std::thread t([this]() {
auto image = QImage("/home/junaid/testing_ground/fourthtime/images.jpeg");
m_newImage(image);
/// useless wait. just simulating that another image comes after sometime and so on onwards.
int random_wait = 2; //sec
sleep(random_wait);
image = QImage("/home/junaid/Downloads/pnggrad16rgb.png");
m_newImage(image);
});
t.detach();
}
boost::signals2::connection ImageSource::subscribeNewPixMap(NewPixMapDelegate::slot_function_type f)
{
return m_newPixMap.connect(f);
}
void ImageSource::generatePixmaps(const QString &id, QSize *size, const QSize &requestedSize)
{
int width = 100;
int height = 50;
if (size) {
*size = QSize(width, height);
}
QPixmap pixmap(requestedSize.width() > 0 ? requestedSize.width() : width,
requestedSize.height() > 0 ? requestedSize.height() : height);
pixmap.fill(QColor(id).rgba());
// write the color name
QPainter painter(&pixmap);
QFont f = painter.font();
f.setPixelSize(20);
painter.setFont(f);
painter.setPen(Qt::black);
if (requestedSize.isValid())
painter.scale(requestedSize.width() / width, requestedSize.height() / height);
painter.drawText(QRectF(0, 0, width, height), Qt::AlignCenter, id);
m_newPixMap(pixmap);
}
and here is the CMake file:
cmake_minimum_required(VERSION 2.8.12)
project(non_existent_property LANGUAGES CXX)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
find_package(Qt5 COMPONENTS Core Quick REQUIRED)
file( GLOB SRCS *.cpp *.h )
add_executable(${PROJECT_NAME} "qml.qrc" ${SRCS})
target_link_libraries(${PROJECT_NAME} Qt5::Core Qt5::Quick "pthread")
So, here is how the working main.cpp looks. I was mislead by the QmlContext that I was creating, and with help from #GrecKo's and jpnurmi's comments, I understood that I had to set the property for the root context.
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "imageprovidervm.h"
#include "imagesource.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
// QQmlContext *context = new QQmlContext(engine.rootContext());
auto model = std::make_shared<ImageSource>();
auto vm = new ImageProviderVM(model);
engine.addImageProvider(QLatin1String("imageProvider"), vm);
engine.rootContext()->setContextProperty("imageProvide", vm );
model->generateImages();
engine.load(QUrl(QStringLiteral("qrc:/view.qml")));
return app.exec();
}

Qt: QWidget::paintEngine: Should no longer be called

I'm trying to make an app where you can draw with your finger on a canvas.
To achieve this, I'm subclassing QWidget as MFCanvas, registered the class in QML with
qmlRegisterType<>(), implementing the virtual paintEvent(); function, and
drawing on it with a QPainter inside the paintEvent(); function.
The Problem:
Upon construction, the QPainter throws this warning:
QWidget::paintEngine: Should no longer be called
Then, serveral other related warnings are thrown:
QPainter::begin: Paint device returned engine == 0, type: 1
QPainter::setPen: Painter not active
No wonder: the QPainter didn't draw anything...
Also, am i supposed to call paintEvent(); by myself?
Or should it be called every frame by QWidget, and i somehow messed it up?
I searched the web, but all posts i found had either no answer to them, or they where
using something else than QWidget.
My Code:
mfcanvas.cpp:
#include "mfcanvas.h"
#include <QDebug>
#include <QPainter>
#include <QVector2D>
#include <QList>
MFCanvas::MFCanvas(QWidget *parent) : QWidget(parent)
{
paths = new QList<QList<QVector2D>*>();
current = NULL;
QWidget::resize(100, 100);
}
MFCanvas::~MFCanvas()
{
delete paths;
}
void MFCanvas::paintEvent(QPaintEvent *)
{
if(current!=NULL){
if(current->length() > 1){
QPainter painter(this);
painter.setPen(Qt::black);
for(int i = 1; i < current->length(); i++){
painter.drawLine(current->at(i-1).x(), current->at(i-1).y(), current->at(i).x(), current->at(i).y());
}
}
}
}
void MFCanvas::pressed(float x, float y)
{
if(current==NULL){
qDebug() << "null:"<<current;
current = new QList<QVector2D>();
current->append(QVector2D(x, y));
}else{
qDebug() << "current:"<<current;
}
paintEvent(NULL);
}
void MFCanvas::update(float x, float y)
{
current->append(QVector2D(x, y));
}
void MFCanvas::resize(int w, int h)
{
QWidget::resize(w, h);
}
main.cpp:
#include <QApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include <QSurfaceFormat>
#include "creator.h"
#include "mfcanvas.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
qmlRegisterType<MFCanvas>("com.cpp.mfcanvas", 1, 0, "MFCanvas");
QQmlApplicationEngine engine;
QQmlComponent *component = new QQmlComponent(&engine);
QObject::connect(&engine, SIGNAL(quit()), QCoreApplication::instance(), SLOT(quit()));
Creator creator(component);
QObject::connect(component, SIGNAL(statusChanged(QQmlComponent::Status)), &creator, SLOT(create(QQmlComponent::Status)));
component->loadUrl(QUrl("qrc:///main.qml"));
int rv;
rv = app.exec();
delete component;
return rv;
}
creator.cpp:
#include "creator.h"
#include <QQuickWindow>
#include <QDebug>
Creator::Creator(QQmlComponent *component)
{
this->component = component;
}
void Creator::create(QQmlComponent::Status status)
{
if(status == QQmlComponent::Ready){
QObject *topLevel = component->create();
QQuickWindow::setDefaultAlphaBuffer(true);
QQuickWindow *window = qobject_cast<QQuickWindow *>(topLevel);
QSurfaceFormat surfaceFormat = window->requestedFormat();
window->setFormat(surfaceFormat);
window->show();
}
}
main.qml: (the important part)
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Controls.Styles 1.2
import QtQuick.Layouts 1.1
import QtQuick.Window 2.0
import com.cpp.mfcanvas 1.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("MFCanvas")
onSceneGraphInitialized: {
drawMenu.visible = true;
lineWidth.visible = true;
colorMenu.visible = true;
drawMenu.visible = false;
lineWidth.visible = false;
colorMenu.visible = false;
}
Rectangle {
id: main
anchors.fill: parent
property real toolsH: 15
property real iconW: 25
property real menuH: 8
property real menuW: 16
property real dpi: (Screen.logicalPixelDensity == undefined ? 6 : Screen.logicalPixelDensity) * 1.5
property color choosenColor: Qt.hsla(hue.value, saturation.value, luminance.value, 1)
Text {
anchors.centerIn: parent
font.pointSize: 60
text: "MFCanvas"
}
MFCanvas {
id: canvas
Component.onCompleted: {
canvas.resize(main.width, main.height);
}
}
//...
}
}
Tell me if you need any additional information.
Thank you in advance! =)
This is nicely explained here:
https://forum.qt.io/topic/64693
In short: do not try to paint from the input event handler directly,
but overload the paintEvent method in your widget instead and create the
QPainter there. Use the input event exclusively to modify the internal
data model and use QPainter in paintEvent to display it, on the output path.
In your mfcanvas.cpp, void MFCanvas::pressed(float x, float y) function, the line
paintEvent(NULL);
seems to be disturbing. Tried it in a similar code - I get the same error.
Proposed solution: using this->repaint() or this->update() instead of paintEvent(NULL) to repaint a widget seems to be more appropriate.
Possible explanation: looks like paintEvent() shouldn't be called this straightforward (like paintEvent() is called when paint() function is called). As far as I understand from the QPainter doc, the QPainter works together with the QPaintDevice and the QPaintEngine, these three form the basis for painting. The error QWidget::paintEngine: Should no longer be called puts it quite straight. The lines
QPainter::begin: Paint device returned engine == 0, type: 1
QPainter::setPen: Painter not active
probably indicate that there's no QPaintEngine provided by this painter's QPaintDevice (like QPaintDevice::paintEngine). One can assume that this QPaintEngine is generated or otherwise called to existence by the paint device itself, for example, when the paint() function is called on a widget.
I have found a simple solution myself:
Instead of deriving from QWidget, derive from QQuickPaintedItem. QQuickPaintedItem is a class that was made exactly for what i need: Painting on a QML-Element using a QPainter. Here is the Code (Narrowed down to the essential part):
mfcanvas.h:
class MFCanvas : public QQuickPaintedItem
{
Q_OBJECT
public:
explicit MFCanvas(QQuickItem *parent = 0);
~MFCanvas();
protected:
void paint(QPainter *painter);
mfcanvas.cpp:
void MFCanvas::paint(QPainter *painter)
{
painter->translate(-translation.x(), -translation.y());
//...
}
As you can see, a simple paint() function is provided which hands over a pointer to a QPainter, ready to use. =)