Using C++ model in QML, white page - c++

I'am trying to send a object Cloud from C++ to QML. I want to show the name of my cloud in the QML using the model property.
I have no compilation error but when I execute the code severals infos are wrote :
QQUickView only supports loading of root objects that derive from
QuickItem
for this one i've tried to change all my QObject in QuickItem, without success.
If your example is using QML2, and the .qml file you loaded has 'import QTquick1,0' or 'import QT 4,7', this error will occur
I look for the import signaled, but I can't find any QtQuick1,0 or QT4
7 in my code.
Here my Cloud.h:
#ifndef CLOUD_H
#define CLOUD_H
#include <QObject>
class Cloud: public QObject
{
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
Q_PROPERTY(QString description READ description WRITE setDescription NOTIFY descriptionChanged)
public:
Cloud(QObject *parent=0);
Cloud(const QString &name, const QString &description, QObject *parent=0);
QString name() const;
void setName(const QString &name);
QString description() const;
void setDescription(const QString &name);
signals:
void nameChanged();
void descriptionChanged();
private:
QString m_name;
QString m_description;
};
#endif // CLOUD_H
My Cloud.cpp
#include "cloud.h"
#include <QDebug>
Cloud::Cloud(QObject *parent)
:QObject(parent)
{
}
Cloud::Cloud(const QString &name, const QString &description, QObject *parent)
:QObject(parent), m_name(name), m_description(description)
{
}
QString Cloud::name() const{
return m_name;
}
void Cloud::setName(const QString &name){
if(name != m_name){
m_name = name;
emit nameChanged();
}
}
QString Cloud::description() const{
return m_description;
}
void Cloud::setDescription(const QString &description){
if(description != m_description){
m_description = description;
emit descriptionChanged();
}
}
My main.cpp
#include <QGuiApplication>
#include <qqml.h>
#include <QtQuick/qquickitem.h>
#include <QtQuick/qquickview.h>
#include <QQmlContext>
#include "cloud.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
qmlRegisterType<Cloud>("Sky", 1,0,"Cloud");
QList<Cloud*> cloudList;
cloudList.append(new Cloud("Cumulus Mediocris", "super nuage brocoli"));
cloudList.append(new Cloud("Cumulus Towering", "super nuage tour"));
cloudList.append(new Cloud("Cumulonimbus", "Gros nuage pas content"));
QQuickView view;
view.setResizeMode(QQuickView::SizeRootObjectToView);
QQmlContext *ctxt = view.rootContext();
ctxt->setContextProperty("myModel", QVariant::fromValue(cloudList));
view.setSource(QUrl("qrc:main.qml"));
view.show();
return app.exec();
}
And finally my main.qml:
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Window 2.2
import QtQuick.Dialogs 1.2
ApplicationWindow {
title: qsTr("Hello World")
width: 640
height: 480
visible: true
Rectangle{
width:10;height:10
color:"red"
anchors.left : parent.left
anchors.top:parent.top
}
ListView{
width:100;height:100
model: myModel
delegate:Rectangle{
height:25
width:100
color:"pink"
Text{text:model.modelData.name}
}
}
}
To write this code I took a look to Models and views in QtQuick and Using C++ Models with QtQUick View
Thank you in advance for your help :)

The problem is in your main function. QQuickView is a quick way to display a QML scene in a window (QQuickView is itself a subclass of QQuickWindow). In your QML, the root object is ApplicationWindow. You cannot display a window inside a window.
The solution is to switch to QQmlApplicationEngine:
QQmlApplicationEngine view;
QQmlContext *ctxt = view.rootContext();
ctxt->setContextProperty("myModel", QVariant::fromValue(cloudList));
view.load(QUrl("qrc:main.qml"));
The second problem is with your model. ListView would understand a QList<QObject*>, but not a QList<Cloud*>. Just change the declaration to:
QList<QObject*> cloudList;

Related

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");

How can I fix the error saying Created graphical object was not placed in the graphics scene

I have a bit of a strange error being caused by a seemingly simple problem.
In my source code I am attempting to use QQuickPaintedItem to render just the overall look of a QWidget derived class (QPushButton), and then painting it on to the QQuickPaintedItem
In doing so I ran into this error:
QQmlComponent: Created graphical object was not placed in the graphics
scene.
Followed by:
The program has unexpectedly finished.
Here's the context I am trying to create my special QQuickPaintedItem in along with the source code for it:
main.qml
import QtQuick 2.9
import com.particletool 1.0
import QtQuick.Particles 2.0
import QtQuick.Window 2.3
import QtQuick.Controls 2.2
import QtQuick.Dialogs 1.2
import QtQuick.Controls 1.4 as QQ1
Item {
id: root
width: Screen.width
height: Screen.height
WidgetInterface {
id: w
}
}
widgetInterface.h
#ifndef WIDGETINTERFACE_H
#define WIDGETINTERFACE_H
#include <QObject>
#include <QQuickItem>
#include <QQuickPaintedItem>
#include <QWidget>
#include <QPushButton>
#include <QtQuick>
class WidgetInterface : public QQuickPaintedItem
{
public:
WidgetInterface(QQuickItem *parent = nullptr, QWidget* renderWidget = nullptr);
QWidget* m_widget;
QPushButton* m_button;
protected:
void paint(QPainter* painter);
public slots:
void morphIntoButton(QString txt);
};
#endif // WIDGETINTERFACE_H
widgetinterface.cpp
#include "widgetinterface.h"
#include <QQuickPaintedItem>
#include <QObject>
#include <QPainter>
#include <QWidget>
#include <QPushButton>
WidgetInterface::WidgetInterface(QQuickItem *parent, QWidget* renderWidget) : QQuickPaintedItem(parent), m_widget(renderWidget)
{
morphIntoButton("test");
QQmlEngine::setObjectOwnership(m_button, QQmlEngine::JavaScriptOwnership);
}
void WidgetInterface::paint(QPainter *painter)
{
if (m_widget != nullptr) {
painter->end();
m_button->render(painter);
} else {
}
}
void WidgetInterface::morphIntoButton(QString txt)
{
m_widget->setGeometry(0,0, this->width(), this->height());
m_button = new QPushButton(m_widget);
m_button->setGeometry(m_widget->geometry());
m_button->setText(txt);
this->update(this->boundingRect().toRect());
}
main.cpp
#include <QtGui/QGuiApplication>
#include <QApplication>
#include <QtQml/QQmlApplicationEngine>
#include "src/interfaces/widgetinterface.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
qmlRegisterType<WidgetInterface>("com.particletool", 1, 0, "WidgetInterface");
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
The expected result is a button being drawn on to the QML scene graph
Does anyone know how I can achieve this using some method? ( I know that its not supported by Qt, but I am trying to implement a way to make it happen so dont both with the "You can't" answers because there is a way I'm sure.
The expected result is a button being drawn on to the QML scene graph
class WidgetInterface : public QQuickPaintedItem
{
public:
WidgetInterface(QQuickItem *parent = Q_NULLPTR) :
QQuickPaintedItem(parent)
{
mButton = new QPushButton("TEST", Q_NULLPTR);
auto resize = [=](){
mButton->setGeometry(QRect(QPoint(), boundingRect().size().toSize()));
QPixmap p(mButton->size());
mButton->render(&p);
if(!p.isNull())
mButtonPix = p;
update();
};
connect(this, &QQuickPaintedItem::widthChanged, this, resize, Qt::QueuedConnection);
connect(this, &QQuickPaintedItem::heightChanged, this, resize, Qt::QueuedConnection);
}
~WidgetInterface() {
mButton->deleteLater();
}
protected:
void paint(QPainter* painter){
painter->drawPixmap(0, 0, mButtonPix);
}
private:
QPushButton* mButton;
QPixmap mButtonPix;
};

Qt Qml connect to a signal of a QObject property of a Context Property

So this may seem like a strange setup. I have a C++ object that inherits from QObject called "MasterGuiLogic" for simplicity. It is created with a pointer to another object called "MainEventBroker" which as you might guess handles all of my applications events. The MasterGuiLogic object is registered with qml as a context property so that it's properties can be used anywhere in my qml. So main.cpp looks like this:
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
MasterEventBroker *MainEventBroker = new MasterEventBroker();
MasterGuiLogic *MainGuiLogic = new MasterGuiLogic(*MainEventBroker);
qmlRegisterUncreatableType<MasterGuiLogic>("GrblCom", 1, 0, "MasterGuiLogic", "");
qmlRegisterUncreatableType<GuiLogic_SerialCom>("GrblCom", 1, 0, "GuiLogic_SerialCom", "");
QQmlApplicationEngine engine;
QQmlContext* context = engine.rootContext();
context->setContextProperty("MasterGuiLogic", &(*MainGuiLogic));
engine.load(QUrl(QLatin1String("qrc:/QmlGui/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
MasterGuiLogic creates an instance of another class called SerialCom, which is set as a Q_PROPERTY so that it's properties and public slots can be reached in qml through the MasterGuiLogic property.
MasterGuiLogic.h:
class MasterGuiLogic : public QObject
{
Q_OBJECT
Q_PROPERTY(GuiLogic_SerialCom* serialCom READ serialCom CONSTANT)
public:
MasterEventBroker *eventBroker;
explicit MasterGuiLogic(MasterEventBroker &ev, QObject *parent = nullptr);
GuiLogic_SerialCom* serialCom() const {
return Gui_SerialCom;
}
private:
GuiLogic_SerialCom *Gui_SerialCom;
MasterGuiLogic.cpp:
MasterGuiLogic::MasterGuiLogic(MasterEventBroker &ev, QObject *parent) : QObject(parent)
{
this->eventBroker = &ev;
this->Gui_SerialCom = new GuiLogic_SerialCom(this);
}
SerialCom.h:
//Forward Declare our parent
class MasterGuiLogic;
class GuiLogic_SerialCom : public QObject
{
Q_OBJECT
Q_PROPERTY(QStringList portNames READ portNames NOTIFY portNamesChanged)
Q_PROPERTY(bool connectedToPort READ connectedToPort NOTIFY connectedToPortChanged)
public:
MasterGuiLogic *parent;
explicit GuiLogic_SerialCom(MasterGuiLogic *parent = nullptr);
std::map<QString, QSerialPortInfo> portsMap;
QStringList portNames() {
return _portNames;
}
bool connectedToPort() {
return _connectedToPort;
}
private:
QStringList _portNames;
bool _connectedToPort = false;
signals:
void portNamesChanged(const QStringList &);
void connectedToPortChanged(const bool &);
public slots:
void connectToPort(const QString portName);
void disconnectFromPort(const QString portName);
};
SerialCom.cpp:
GuiLogic_SerialCom::GuiLogic_SerialCom(MasterGuiLogic *parent) : QObject(qobject_cast<QObject *>(parent))
{
this->parent = parent;
QList<QSerialPortInfo> allPorts = QSerialPortInfo::availablePorts();
for (int i = 0; i < allPorts.size(); ++i) {
this->_portNames.append(allPorts.at(i).portName());
this->portsMap[allPorts.at(i).portName()] = allPorts.at(i);
}
emit portNamesChanged(_portNames);
}
void GuiLogic_SerialCom::connectToPort(const QString portName) {
//TODO: Connect To Port Logic Here;
//Set Connected
this->_connectedToPort = true;
emit connectedToPortChanged(this->_connectedToPort);
qDebug() << portName;
}
void GuiLogic_SerialCom::disconnectFromPort(const QString portName) {
//TODO: DisConnect To Port Logic Here;
//Set DisConnected
this->_connectedToPort = false;
emit connectedToPortChanged(this->_connectedToPort);
qDebug() << portName;
}
So from qml it's pretty easy to read any of these properties and even send signals from qml to c++
For example, this works just fine:
connectCom.onClicked: {
if (MasterGuiLogic.serialCom.connectedToPort === false) {
MasterGuiLogic.serialCom.connectToPort(comPort.currentText);
} else {
MasterGuiLogic.serialCom.disconnectFromPort(comPort.currentText);
}
}
The problem is, I can't seem to find a way to connect to signals that are emitted from SerialCom. I thought I would be able to do something like this:
Connections: {
target: MasterGuiLogic.serialCom;
onConnectedToPortChanged: {
if (MasterGuiLogic.serialCom.connectedToPort === false) {
connectCom.text = "Disconnect";
comPort.enabled = false;
} else {
connectCom.text = "Connect";
comPort.enabled = true;
}
}
}
This should listen to the boolean property on SerialCom to change, but I get the following error:
QQmlApplicationEngine failed to load component
qrc:/QmlGui/main.qml:21 Type Page1 unavailable
qrc:/QmlGui/Page1.qml:49 Invalid attached object assignment
This just means that I can't "connect" using the target line above. Is there any other way I can connect to signals from a Q_PROPERTY of type QObject inside a ContextProperty?
First of all what should &(*MainGuiLogic) mean?
You are dereferencing and referencing again the MainGuiLogic? Why?
context->setContextProperty("MasterGuiLogic", MainGuiLogic); will be enought.
But registering MasterGuiLogic as Type and adding the Object named MasterGuiLogic can overide themself in QML world.
Set it like context->setContextProperty("MyGuiLogic", MainGuiLogic); to eleminate this behavior.
Also don't pass references between C++ and QML worlds like:
void connectedToPortChanged(**const bool &**);.
Just use atomic type and values (const bool);
and give it a name, to be able to use it as named value in QML:
void connectedToPortChanged(bool connected)
Here is an example with the structure like yours, which works. Just click in window and look in output console.
test.h:
#ifndef TEST_H
#define TEST_H
#include <QObject>
class GuiLogic_SerialCom : public QObject
{
Q_OBJECT
public:
GuiLogic_SerialCom(){}
signals:
void connectedToPortChanged(bool connected);
public slots:
void connectToPort(const QString & portName);
};
class MasterGuiLogic : public QObject
{
Q_OBJECT
public:
MasterGuiLogic();
Q_PROPERTY(GuiLogic_SerialCom * serialCom READ serialCom CONSTANT)
GuiLogic_SerialCom* serialCom() const {return test;}
Q_INVOKABLE void generate_signal();
private:
GuiLogic_SerialCom * test;
};
#endif // TEST_H
test.cpp:
#include "test.h"
#include <QDebug>
MasterGuiLogic::MasterGuiLogic()
{
this->test = new GuiLogic_SerialCom();
}
void MasterGuiLogic::generate_signal()
{
qDebug() << __FUNCTION__ << "Calling serialcom to gen signal";
this->test->connectToPort("88");
}
void GuiLogic_SerialCom::connectToPort(const QString &portName)
{
qDebug() << __FUNCTION__ << "got signal" << portName;
emit this->connectedToPortChanged(true);
}
main.cpp:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "test.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
qmlRegisterUncreatableType<MasterGuiLogic>("GrblCom", 1, 0, "MasterGuiLogic", "");
qmlRegisterUncreatableType<GuiLogic_SerialCom>("GrblCom", 1, 0, "GuiLogic_SerialCom", "");
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty(QStringLiteral("Test"), new MasterGuiLogic());
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
import QtQuick.Layouts 1.3
import GrblCom 1.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
MouseArea
{
anchors.fill: parent
onClicked:
{
Test.generate_signal();
}
}
Connections
{
target: Test.serialCom
onConnectedToPortChanged:
{
console.log("Got signal from SerialCom in QML. passed bool value is: " + connected);
}
}
}
Ok, I found the problem... The provided answers, while indeed helpful for other reasons, were not the correct solution. After going over the code by #Xplatforms, I couldn't figure out what the difference was between what I was doing and what he did.... until I saw this in my own code:
Connections: {
target: MasterGuiLogic.serialCom;
onConnectedToPortChanged: {
...
}
}
There isn't supposed to be a colon(:) there...
Connections {
target: MasterGuiLogic.serialCom;
onConnectedToPortChanged: {
...
}
}
Never try programming while sleepy...lol

'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();
}

Passing QClipboard to QML

I am trying to make the clipboard available to some QML code.
Passing the clipboard object seems to work, but then I can't call its methods.
Here's an example of a QClipboard working fine in C++, being passed to QML, and in the debug output it still being a QClipboard object but losing its functions.
main.cpp
#include <QApplication>
#include <QClipboard>
#include <QQmlApplicationEngine>
#include <qqmlcontext.h>
#include <QtQml>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("clipboard", QApplication::clipboard());
qDebug() << QApplication::clipboard()->text(); // This correctly prints the copied text
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
main.qml
import QtQuick 2.5
import QtQuick.Controls 1.3
ApplicationWindow {
visible: true
MouseArea {
acceptedButtons: Qt.MiddleButton
anchors.fill: parent
onClicked: {
console.log(clipboard)
console.log(clipboard.getText())
}
}
}
qml.qrc
<RCC>
<qresource prefix="/">
<file>main.qml</file>
</qresource>
</RCC>
test.pro
TEMPLATE = app
VERSION = 1.0
QT += qml widgets gui
QMAKE_LFLAGS += -Wl,--as-needed
SOURCES += main.cpp
RESOURCES += qml.qrc
QObject's functions need to be slots if you want to call them from QML. QClipboard declares no slots, so you can't call its functions directly. You can create a proxy, though:
qclipboardproxy.hpp
#ifndef QCLIPBOARDPROXY_HPP
#define QCLIPBOARDPROXY_HPP
#include <QObject>
class QClipboard;
class QClipboardProxy : public QObject
{
Q_OBJECT
Q_PROPERTY(QString text READ text NOTIFY textChanged)
public:
explicit QClipboardProxy(QClipboard*);
QString text() const;
signals:
void textChanged();
private:
QClipboard* clipboard;
};
#endif // QCLIPBOARDPROXY_HPP
qclipboardproxy.cpp
#include "qclipboardproxy.hpp"
#include <QClipboard>
QClipboardProxy::QClipboardProxy(QClipboard* c) : clipboard(c)
{
connect(c, &QClipboard::dataChanged, this, QClipboardProxy::textChanged)
}
QString QClipboardProxy::text()
{
return clipboard->text();
}
and in
main.cpp
engine.rootContext()->setContextProperty("clipboard",
new QClipboardProxy(QGuiApplication::clipboard()));
Now you can call:
console.log(clipboard.text)
or even
someProperty: clipboard.text
Inspired by #krzaq's solution you could even make the proxy a QML instantiable type
class ClipboardProxy : public QObject
{
Q_OBJECT
Q_PROPERTY(QString text READ dataText WRITE setDataText NOTIFY dataChanged)
Q_PROPERTY(QString selectionText READ selectionText WRITE setSelectionText NOTIFY selectionChanged)
public:
explicit ClipboardProxy(QObject *parent = 0);
void setDataText(const QString &text);
QString dataText() const;
void setSelectionText(const QString &text);
QString selectionText() const;
signals:
void dataChanged();
void selectionChanged();
};
ClipboardProxy::ClipboardProxy(QObject *parent)
: QObject(parent)
{
QClipboard *clipboard = QGuiApplication::clipboard();
connect(clipboard, &QClipboard::dataChanged,
this, ClipboardProxy::dataChanged);
connect(clipboard, &QClipboard::selectionChanged,
this, ClipboardProxy::selectionChanged);
}
void ClipboardProxy::setDataText(const QString &text)
{
QGuiApplication::clipboard()->setText(text, QClipboard::Clipboard);
}
QString ClipboardProxy::dataText() const
{
return QGuiApplication::clipboard()->text(QClipboard::Clipboard);
}
void ClipboardProxy::setSelectionText(const QString &text)
{
QGuiApplication::clipboard()->setText(text, QClipboard::Selection);
}
QString ClipboardProxy::selectionText() const
{
return QGuiApplication::clipboard()->text(QClipboard::Selection);
}
Register as a type in main()
qmlRegisterType<ClipboardProxy>("Clipboard", 1, 0, "Clipboard");
Use as a type in QML
import QtQuick 2.4
import Clipboard 1.0
Column {
Clipboard {
id: clipboard
onDataChanged: console.log("Clipboard data changed");
onSelectionChanged: console.log("Clipboard selection changed");
}
Text {
text: clipboard.text
}
TextInput {
onEditingFinished: clipboard.text = text;
}
}