I created "fruit_swap" project in Qt Creator bt choosing-- Application -> Qt Quick Application -> Qt 5.5.
In fruit_swap, 'main()' function calls forkSwapper() which forks a process that swaps fruit image (apple <--> pear) once every second.
The swapping is done inside setImageURL() method which also emits "imageURLChanged" signal for the qml. From the output(shown below at the bottom), the signal is proven to be delivered to the qml side.
I was expecting the qml Image swapping. However, it didn't. The first image shown('pear') stayed motionlessly. What could have I done wrong? Any suggestion is welcome. I uploading every single source code below.
fruit_swap.pro
/* generated by Qt Creator */
TEMPLATE = app
QT += qml quick
CONFIG += c++11
SOURCES += main.cpp \
imageitem.cpp
RESOURCES += qml.qrc
# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH =
# Default rules for deployment.
include(deployment.pri)
HEADERS += \
imageitem.h
DISTFILES += \
FruitFrame.qml
imageitem.h
/* https://quickgit.kde.org/?p=scratch%2Fsune%2Fimageitem.git */
#include <QQuickPaintedItem>
/**
* #brief QQuickItem to show images/pixmaps/colors in a QML item
*
* As opposed to the Image from plain qml, this works on
* QImages, QPixmaps and QColors
*/
class ImageItem : public QQuickPaintedItem
{
// Q_PROPERTY(QVariant imageData READ imageData() WRITE setImageData
// NOTIFY imageDataChanged)
Q_OBJECT
Q_PROPERTY(QVariant imageURL READ imageURL() WRITE setImageURL
NOTIFY imageURLChanged)
public:
explicit ImageItem(QQuickItem *parent = 0);
/**
* \reimpl
*/
Q_INVOKABLE void paint(QPainter* painter) Q_DECL_OVERRIDE;
/**
* #brief image data u-ed by this item
* #return a QVariant wrapping the data
*/
QVariant imageData() const;
/**
* #brief Sets the image data
* #param newData
*/
void setImageData(const QVariant& newData);
QVariant imageURL() const;
/**
* #brief Sets the image data
* #param newData
*/
void setImageURL(const QVariant& fileName);
Q_SIGNALS:
/**
* #brief imageChanged
*/
void imageDataChanged();
void imageURLChanged();
private:
enum Type {
Unknown,
Pixmap,
Image,
Color
};
Type m_type;
QVariant m_imageData;
QVariant m_imageURL;
QRectF scaledRect(const QRect& sourceRect) const;
};
#endif // IMAGEITEM_H
imageitem.cpp
/* https://quickgit.kde.org/?p=scratch%2Fsune%2Fimageitem.git */
#include "imageitem.h"
#include <QPainter>
#include <qobjectdefs.h>
ImageItem::ImageItem(QQuickItem *parent) :
QQuickPaintedItem(parent)
{
m_imageURL = QUrl::fromLocalFile(QString("apple.jpg"));
}
void ImageItem::paint(QPainter* painter)
{
switch(m_type) {
case Unknown: {
return;
}
case Image: {
QImage image = m_imageData.value<QImage>();
painter->drawImage(scaledRect(image.rect()), image);
return;
}
case Pixmap: {
QPixmap pixmap = m_imageData.value<QPixmap>();
painter->drawPixmap(scaledRect(pixmap.rect()).toRect(), pixmap);
return;
}
case Color: {
QColor color = m_imageData.value<QColor>();
painter->fillRect(contentsBoundingRect(),color);
return;
}
}
}
QVariant ImageItem::imageData() const
{
return m_imageData;
}
QVariant ImageItem::imageURL() const
{
return m_imageURL;
}
void ImageItem::setImageURL(const QVariant &fileName)
{
m_imageURL = QUrl::fromLocalFile(fileName.value<QString>());
if (m_imageURL.canConvert<QUrl>()) {
QUrl url = m_imageURL.value<QUrl>();
if (!url.isEmpty() && url.isValid()
&& url.isLocalFile())
{
qDebug() << "URL is valid";
} else {
qDebug() << "URL is INvalid";
}
} else {
qDebug() << "URL is INvalid";
}
emit imageURLChanged();
}
void ImageItem::setImageData(const QVariant& newData)
{
switch(newData.userType()) {
case QMetaType::QPixmap: {
m_type = Pixmap;
break;
}
case QMetaType::QImage: {
m_type = Image;
break;
}
case QMetaType::QColor: {
m_type = Color;
break;
}
default: {
m_type = Unknown;
break;
}
}
m_imageData = newData;
emit imageDataChanged();
}
QRectF ImageItem::scaledRect(const QRect& sourceRect) const
{
QRectF targetRect = contentsBoundingRect();
QSizeF scaledSize;
double widthScale = targetRect.width() / sourceRect.width();
double heightScale = targetRect.height() / sourceRect.height();
if(widthScale < heightScale) {
scaledSize.setHeight(sourceRect.height() * widthScale);
scaledSize.setWidth(sourceRect.width() *widthScale);
} else {
scaledSize.setHeight(sourceRect.height() * heightScale);
scaledSize.setWidth(sourceRect.width() *heightScale);
}
QRectF result = QRectF(targetRect.left() + targetRect.width() /2 - scaledSize.width() /2,
targetRect.top() + targetRect.height()/2 - scaledSize.height()/2,
scaledSize.width(),scaledSize.height());
// QRectF result(QPointF(0,0),scaledSize);
qDebug() << result << targetRect << sourceRect << widthScale << heightScale ;
return result;
}
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QImage>
#include <unistd.h>
#include "imageitem.h"
void forkSwapper(ImageItem * fImage);
int main(int argc, char *argv[])
{
qmlRegisterType<ImageItem>("ImageItem",1,0,"ImageItem");
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
ImageItem *fImage = new ImageItem();
fImage->setImageURL("fruit.jpg");
engine.rootContext()->setContextProperty("cpp_imageURL", fImage);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
forkSwapper(fImage);
return app.exec();
}
void forkSwapper(ImageItem * fImage) {
int pid = fork();
if (pid == 0) {
int i = 0;
while (true) {
if (i++ % 2 == 0)
fImage->setImageURL("apple.jpg");
//std::system("cp apple.jpg fruit.jpg");
else
fImage->setImageURL("pear.jpg");
//std::system("cp pear.jpg fruit.jpg");
qDebug() << "fruit image changed";
sleep(1);
}
_exit (EXIT_FAILURE);
} else {
qDebug() << "swapper forked, PID:" << pid;
}
}
FruitFrame.qml
import QtQuick 2.5
import ImageItem 1.0
Rectangle {
property alias mouseArea: mouseArea
width: 360
height: 360
MouseArea {
id: mouseArea
anchors.fill: parent
Connections {
target: cpp_imageURL
onImageURLChanged: {
fruit_image.update();
// fruit_image.source = cpp_imageURL.imageURL;
console.log("image UURL-" + cpp_imageURL.imageURL);
}
}
Image {
id: fruit_image
x: 39
y: 94
width: 274
height: 204
source: cpp_imageURL.imageURL
cache: false
}
}
Text {
anchors.centerIn: parent
text: "Hello World"
anchors.verticalCenterOffset: -137
anchors.horizontalCenterOffset: -104
}
}
main.qml
import QtQuick 2.5
import QtQuick.Window 2.2
import ImageItem 1.0
Window {
visible: true
width: 360
height: 460
FruitFrame {
anchors.fill: parent
mouseArea.onClicked: {
Qt.quit();
}
}
}
Application Output
Starting /home/jbpark03/wem/fruit_swap/build/fruit_swap...
QML debugging is enabled. Only use this in a safe environment.
URL is valid
URL is valid
qml: image UURL-file:apple.jpg
fruit image changed
swapper forked, PID: 3078
URL is valid
qml: image UURL-file:pear.jpg
fruit image changed
URL is valid
qml: image UURL-file:apple.jpg
fruit image changed
URL is valid
qml: image UURL-file:pear.jpg
fruit image changed
URL is valid
qml: image UURL-file:apple.jpg
END.
The code works perfectly well. I think the problem is you're blocking the Qt application because you're using sleep.
In fact, you see the image pear.jpg because is the first image shown after fruit.jpg and before any sleep.
If, for example, you use a QTimer or a QThread, you will check the image is properly updated.
Example:
mytimer.h
#ifndef MYTIMER_H
#define MYTIMER_H
#include <QObject>
#include <QTimer>
#include "imageitem.h"
class MyTimer : public QObject
{
Q_OBJECT
public:
explicit MyTimer(ImageItem * fImage, QObject *parent = 0);
private:
QTimer *timer;
ImageItem *myImage;
signals:
public slots:
void update();
};
#endif // MYTIMER_H
mytimer.cpp
#include <QDebug>
#include <QTest>
#include "mytimer.h"
MyTimer::MyTimer(ImageItem * fImage, QObject *parent) : QObject(parent)
{
myImage = fImage;
timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(update()));
timer->start(1000);
}
void MyTimer::update()
{
myImage->setImageURL(":pear.jpg");
QTest::qWait(250);
myImage->setImageURL(":apple.jpg");
}
With this timer, we're going to change the image every 1 second. Now, in our main.cpp:
#include "mytimer.h"
int main(int argc, char *argv[])
{
qmlRegisterType<ImageItem>("ImageItem",1,0,"ImageItem");
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
ImageItem *fImage = new ImageItem();
MyTimer *timer = new MyTimer(fImage);
fImage->setImageURL(":fruit.jpg");
engine.rootContext()->setContextProperty("cpp_imageURL", fImage);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
Related
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
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();
}
Let's say I have a slider control and my user is sliding it back and forth really fast.
Is it possible to limit the rate at which QML calls the "new value available" C++ callback?
If you want to completely avoid the value being updated while the slider is dragged, you can use the updateValueWhileDragging property in Qt Quick Controls 1, and the live property in Qt Quick Controls 2.
In Qt Quick Controls 2, the slider controls have a valueAt() function which can be called to check the value at any time.
If you're writing your own slider in QML, you could limit the change signal emission using a Timer, for example:
property int value
readonly property int actualValue: // some calculation...
Timer {
running: slider.pressed
interval: 200
repeat: true
onTriggered: slider.value = slider.actualValue
}
Here's a generic C++-side solution that works with any QObject:
// https://github.com/KubaO/stackoverflown/tree/master/questions/qml-rate-limter-42284163
#include <QtCore>
class PropertyRateLimiter : public QObject {
Q_OBJECT
qint64 msecsPeriod{500};
const QByteArray property;
bool dirty{};
QVariant value;
QElapsedTimer time;
QBasicTimer timer;
QMetaMethod slot = metaObject()->method(metaObject()->indexOfSlot("onChange()"));
void signal() {
if (time.isValid()) time.restart(); else time.start();
if (dirty)
emit valueChanged(value, parent(), property);
else
timer.stop();
dirty = false;
}
Q_SLOT void onChange() {
dirty = true;
value = parent()->property(property);
auto elapsed = time.isValid() ? time.elapsed() : 0;
if (!time.isValid() || elapsed >= msecsPeriod)
signal();
else
if (!timer.isActive())
timer.start(msecsPeriod - elapsed, this);
}
void timerEvent(QTimerEvent *event) override {
if (timer.timerId() == event->timerId())
signal();
}
public:
PropertyRateLimiter(const char * propertyName, QObject * parent) :
QObject{parent}, property{propertyName}
{
auto mo = parent->metaObject();
auto property = mo->property(mo->indexOfProperty(this->property));
if (!property.hasNotifySignal())
return;
connect(parent, property.notifySignal(), this, slot);
}
void setPeriod(int period) { msecsPeriod = period; }
Q_SIGNAL void valueChanged(const QVariant &, QObject *, const QByteArray & name);
};
#include "main.moc"
And a test harness for it:
#include <QtQuick>
const char qmlData[] =
R"__end(
import QtQuick 2.6
import QtQuick.Controls 2.0
ApplicationWindow {
minimumWidth: 300
minimumHeight: 250
visible: true
Column {
anchors.fill: parent
Slider { objectName: "slider" }
Label { objectName: "label" }
}
}
)__end";
int main(int argc, char ** argv) {
QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app{argc, argv};
QQmlApplicationEngine engine;
engine.loadData(QByteArray::fromRawData(qmlData, sizeof(qmlData)-1));
auto window = engine.rootObjects().first();
auto slider = window->findChild<QObject*>("slider");
auto label = window->findChild<QObject*>("label");
PropertyRateLimiter limiter("position", slider);
QObject::connect(&limiter, &PropertyRateLimiter::valueChanged, [&](const QVariant & val){
label->setProperty("text", val);
});
return app.exec();
}
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;
}
}
I inherited QQuickWindow and created a frame-less window that can be moved by drag. Inside my window I put a Slider element. The problem is that the Slider forwards the events to the parent window and when I try to change the value on the slider, the window moves along. Here's how it behaves:
Is there a possibility to make the slider accept the mouse events and not forward them to the window?
Here's my code:
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QUrl>
#include "mywindow.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
qmlRegisterType<MyWindow>("mycustomlib", 1, 0, "MyWindow");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
main.qml
import QtQuick 2.7
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
import mycustomlib 1.0
MyWindow {
width: 300
height: 180
visible: true
x: 250
y: 250
color: "beige"
Slider {
anchors.fill: parent
value: 0.5
}
}
mywindow.h
#ifndef MYWINDOW_H
#define MYWINDOW_H
#include <QQuickWindow>
class MyWindow : public QQuickWindow
{
Q_OBJECT
public:
MyWindow(QWindow *pParent = Q_NULLPTR);
protected:
virtual void mouseMoveEvent(QMouseEvent *e) Q_DECL_OVERRIDE;
virtual void mousePressEvent(QMouseEvent *e) Q_DECL_OVERRIDE;
virtual void mouseReleaseEvent(QMouseEvent* e) Q_DECL_OVERRIDE;
private:
bool m_move;
QPoint m_initialMouseClickPos;
};
#endif // MYWINDOW_H
mywindow.cpp
#include "mywindow.h"
#include <QDebug>
#include <QCursor>
MyWindow::MyWindow(QWindow *pParent) :
QQuickWindow(pParent),
m_move(false)
{
setFlags(Qt::FramelessWindowHint);
}
void MyWindow::mouseMoveEvent(QMouseEvent *e)
{
if (m_move) {
const QPoint newMousePosition = e->pos() - m_initialMouseClickPos + position();
setPosition(newMousePosition);
}
QQuickWindow::mouseMoveEvent(e);
}
void MyWindow::mousePressEvent(QMouseEvent *e)
{
if (e->button() == Qt::LeftButton)
{
m_initialMouseClickPos = e->pos();
m_move = true;
}
QQuickWindow::mousePressEvent(e);
}
void MyWindow::mouseReleaseEvent(QMouseEvent *e)
{
if (e->button() == Qt::LeftButton)
{
m_move = false;
}
QQuickWindow::mouseReleaseEvent(e);
}
The problem is that QQuickWindow::mouseXxxEvent() delivers the event to the item it belongs to. You have overridden the event handlers, do your handling first, and then pass on the event to QQuickWindow. Therefore the Slider receives the events right after you have done your custom event handling. Either don't call the base class implementation when you don`t want it to deliver the event to items, or call the base class implementation first and do your custom handling afterwards only if the event was not accepted or so.
void MyWindow::mousePressEvent(QMouseEvent *e)
{
QQuickWindow::mousePressEvent(e);
if (!e->isAccepted() && e->button() == Qt::LeftButton)
{
m_initialMouseClickPos = e->pos();
m_move = true;
}
}
The slider isn't a widget, and it doesn't process events like widgets do :(
To implement drag on a QQuickWindow, you could have a mouse area in Qt Quick, behind the controls, and have it forward drags to a helper object that then drags the window around.