I have a C++ object exposed to QML as myObject. This C++ object has a Q_PROPERTY:
Q_PROPERTY (bool myProperty READ getMyPropert NOTIFY myPropertyChanged)
In QML I use this as a guard in a SignalTransition as follows:
property bool foo: myObject.myProperty
SignalTransition {
targetState: someState
signal: someSignal
onTriggered: console.info("foo: " + foo)
onTriggered: console.info("myObject.myProperty: " + myObject.myProperty)
}
The output is the following:
foo: false
myObject.myProperty: true
An event frame later, foo becomes true as well.
Is this behaviour normal?
EDIT:
Here is a working example:
MyClass.h:
#pragma once
#include <QObject>
class MyClass : public QObject {
Q_OBJECT
Q_PROPERTY(bool myProperty_1 READ getMyProperty_1 NOTIFY myClassChanged)
Q_PROPERTY(bool myProperty_2 READ getMyProperty_2 NOTIFY myClassChanged)
public:
static void initQML();
MyClass();
bool getMyProperty_1();
bool getMyProperty_2();
Q_INVOKABLE void setMyProperty(bool value);
signals:
void myClassChanged();
private:
bool m_MyProperty_1;
bool m_MyProperty_2;
};
MyClass.cpp:
#include "MyClass.h"
#include <QtQml>
#include <QDebug>
#include <QMetaType>
/*static*/ void MyClass::initQML() {
qmlRegisterType<MyClass>("Test", 1, 0, "MyClass");
}
MyClass::MyClass() : m_MyProperty_1(false), m_MyProperty_2(false) {
qDebug() << "Constructor called.";
}
bool MyClass::getMyProperty_1() {
return m_MyProperty_1;
}
bool MyClass::getMyProperty_2() {
return m_MyProperty_2;
}
void MyClass::setMyProperty(bool value) {
m_MyProperty_1 = value;
m_MyProperty_2 = value;
myClassChanged();
}
main.cpp:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "MyClass.h"
int main(int argc, char *argv[])
{
QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
// Init
MyClass::initQML();
// Main loop
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
main.qml:
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQml.StateMachine 1.0 as QtQml_StateMachine
import Test 1.0
ApplicationWindow {
visible: true
width: 480
height: 272
Component.onCompleted: _stateMachine.start()
// UI
Button {
text: "Switch"
onClicked: {
console.info("Switch pressed with setter")
myClass.setMyProperty(true)
}
}
// StateMachine
property MyClass myClass: MyClass {}
QtQml_StateMachine.StateMachine {
id: _stateMachine
initialState: _defaultState
onStarted: console.info("_stateMachine started")
onExited: console.info("_stateMachine exited")
QtQml_StateMachine.State {
id: _defaultState
property bool myProperty_1: myClass.myProperty_1
property bool myProperty_2: myClass.myProperty_2
onMyProperty_1Changed: console.info("onMyProperty_1Changed: " + myProperty_1)
onMyProperty_2Changed: console.info("onMyProperty_2Changed: " + myProperty_2)
onEntered: console.info("_defaultState entered")
onExited: console.info("_defaultState exited")
// Doesn't work:
QtQml_StateMachine.SignalTransition {
targetState: _nextState
signal: _defaultState.myProperty_1Changed
guard: _defaultState.myProperty_2
onTriggered: console.info("myProperty_2")
}
// Works:
// QtQml_StateMachine.SignalTransition {
// targetState: _nextState
// signal: _defaultState.myProperty_1Changed
// guard: myClass.myProperty_2
// onTriggered: console.info("myClass.myProperty_2")
// }
}
QtQml_StateMachine.State {
id: _nextState
onEntered: console.info("_nextState entered")
onExited: console.info("_nextState exited")
}
}
}
If you use the commented SignalTransition it works. If you use the other one it won't work.
The issue is that there is a race condition, due to proxying the C++ object's properties to have individual Changed signals. This is due to the direct connections in QML.
The update works in the following order:
myClassChanged -> set: m_MyProperty_1 (C++) -> signal myProperty_1Changed (QML) -> transition and hit the guard, then -> set: m_MyProperty_2 (C++) -> signal m_MyProperty_2Changed (QML)
so myProperty_1 racing against myProperty_2.
Related
In the following code, if the signal errorHappened is emitted from the main thread it works without problem. However if it is emitted from the QThread thread it fails with the following error:
QObject::connect: Cannot queue arguments of type 'ErrorCode'
(Make sure 'ErrorCode' is registered using qRegisterMetaType().)
Is there a way that the signal can be successfully emitted from the QThread thread? If so, how?
Full code in this Gist
MyClass.h
#import <QThread>
#import <atomic>
class MyClass : public QThread
{
Q_OBJECT
public:
explicit MyClass(QObject *parent = Q_NULLPTR);
virtual ~MyClass() override;
enum ErrorCode {
ErrorA,
ErrorB,
ErrorC
};
Q_ENUM(ErrorCode)
signals:
void errorHappened(ErrorCode errorCode);
public slots:
void mainThreadError();
void otherThreadError();
private:
std::atomic<bool> m_running;
std::atomic<bool> m_signalStop;
std::atomic<bool> m_signalError;
void run() override;
void stop();
};
MyClass.cpp
#include "MyClass.h"
MyClass::MyClass(QObject *parent)
: QThread(parent)
{
start();
}
MyClass::~MyClass()
{
stop();
}
void MyClass::mainThreadError()
{
emit errorHappened(ErrorCode::ErrorA);
}
void MyClass::otherThreadError()
{
m_signalError = true;
}
void MyClass::run()
{
m_running = true;
while (!m_signalStop) {
if (m_signalError) {
emit errorHappened(ErrorCode::ErrorA);
m_signalError = false;
}
msleep(1);
}
m_running = false;
m_signalStop = false;
}
void MyClass::stop()
{
if (m_running) {
m_signalStop = true;
wait();
}
}
main.cpp
#include <QGuiApplication>
#include <QQuickView>
#include "MyClass.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQuickView *view = new QQuickView();
qmlRegisterType<MyClass>("MyClass", 1, 0, "MyClass");
view->setSource((QUrl(QStringLiteral("qrc:/main.qml"))));
view->create();
view->show();
return app.exec();
}
main.qml
import QtQuick 2.12
import QtQuick.Controls 2.5
import MyClass 1.0
Rectangle {
id: root
width: 800
height: 600
focus: true
MyClass {
id: tester
onErrorHappened: {
var s
switch (errorCode) {
case MyClass.ErrorA:
s = "Error A happened"
break
}
console.log(s)
}
}
Row {
spacing: 30
Button {
id: mainThreadButton
enabled: !tester.testRunning
text: "Test on main thread"
onClicked: tester.mainThreadError()
}
Button {
id: otherThreadButton
enabled: !tester.testRunning
text: "Test on other thread"
onClicked: tester.otherThreadError()
}
}
}
qmlRegisterType makes the QObject (MyClass) class accessible in QML (Q_PROPERTY, Q_ENUM, Q_SIGNAL, Q_SLOT, Q_INVOKABLE, etc.) but does not allow the transmission of data between threads, for this case it must be registered using qRegisterMetaType<MyClass::ErrorCode>("ErrorCode"):
main.cpp
#include <QGuiApplication>
#include <QQuickView>
#include "MyClass.h"
static void registerTypes(){
qRegisterMetaType<MyClass::ErrorCode>("ErrorCode");
qmlRegisterType<MyClass>("MyClass", 1, 0, "MyClass");
}
Q_COREAPP_STARTUP_FUNCTION(registerTypes)
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQuickView view;
view.setSource((QUrl(QStringLiteral("qrc:/main.qml"))));
view.show();
return app.exec();
}
your type "ErrorCode" defined only in thread (not in main)
For user defined types you can use (read this https://doc.qt.io/qt-5/qmetatype.html)
Q_DECLARE_METATYPE(ErrorCode);
Use standard types for arguments (int,QMap,QString....)
I'm struggling to get my serial input "analogread2" converted to a double, to display in QML.
Ive added the Q_property, the root context in the main.cpp, and I can add the property in the qml, but I cant get it to display in text format.
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
SerialPort serialport;
//engine.rootContext()->setContextProperty("serialport", &serialport);
qmlRegisterType<SerialPort>("SerialPortlib", 1, 0, "SerialPort");
engine.load(QUrl(QStringLiteral("qrc:/main.qml")))
header.
Q_PROPERTY(double newValue MEMBER m_oil_pressure_volt WRITE set_oil_pressure_volt NOTIFY oil_pressure_volt_Changed )
qml
Window {
visible: true
width: 640
height: 480
id: gauge
SerialPort{
// what would I put in here to display the text value (double)
}
}
any help is much appreciated.
To do the test I am assuming that you are sending the data from the arduino in one of the following ways:
Serial.println(data);
Serial.print(data);
Assuming the above has been implemented the serialport class, this has a function called openDefault that scans the devices that are connected through serial and searches for the word "Arduino" within description or manufacturer and if one finds it connects to it.
The complete code can be found at: https://github.com/eyllanesc/stackoverflow/tree/master/Arduino-QML
serialport.h
#ifndef SERIALPORT_H
#define SERIALPORT_H
#include <QObject>
#include <QSerialPort>
#include <QSerialPortInfo>
class SerialPort : public QObject
{
Q_OBJECT
Q_PROPERTY(double oil_pressure_volt READ get_oil_pressure_volt WRITE set_oil_pressure_volt NOTIFY oil_pressure_volt_Changed )
public:
SerialPort(QObject *parent = 0);
~SerialPort();
double get_oil_pressure_volt() const;
void set_oil_pressure_volt(double newValue);
public slots:
void onReadData();
signals:
void oil_pressure_volt_Changed(double newValue);
private:
QSerialPort *arduino;
double mOil_pressure_volt;
QSerialPortInfo portInfo;
void openDefault();
};
serialport.cpp
#include "serialport.h"
#include <QDebug>
SerialPort::SerialPort(QObject *parent):QObject(parent)
{
arduino = new QSerialPort(this);
connect(arduino, &QSerialPort::readyRead, this, &SerialPort::onReadData);
openDefault();
}
SerialPort::~SerialPort()
{
delete arduino;
}
void SerialPort::set_oil_pressure_volt(double newValue)
{
if (mOil_pressure_volt == newValue)
return;
mOil_pressure_volt = newValue;
emit oil_pressure_volt_Changed(mOil_pressure_volt);
}
void SerialPort::onReadData()
{
if(arduino->bytesAvailable()>0){
QByteArray data = arduino->readAll();
QString value = QString(data).trimmed();
qDebug()<< value;
bool ok;
double val = value.toDouble(&ok);
if(ok)
set_oil_pressure_volt(val);
}
}
void SerialPort::openDefault()
{
for(auto info: QSerialPortInfo::availablePorts()){
qDebug()<<info.portName()<<info.description()<<info.manufacturer();
if(!info.isBusy() && (info.description().contains("Arduino") || info.manufacturer().contains("Arduino"))){
portInfo = info;
break;
}
}
if(portInfo.isNull()){
return;
}
arduino->setPortName(portInfo.portName());
arduino->setBaudRate(QSerialPort::Baud115200);
arduino->setDataBits(QSerialPort::Data8);
arduino->setParity(QSerialPort::NoParity);
arduino->setStopBits(QSerialPort::OneStop);
arduino->setFlowControl(QSerialPort::NoFlowControl);
if(arduino->open(QSerialPort::ReadWrite))
qDebug()<<"Connected to "<< portInfo.manufacturer()<< " on " << portInfo.portName();
else
qCritical()<<"Serial Port error: " << arduino->errorString();
}
double SerialPort::get_oil_pressure_volt() const
{
return mOil_pressure_volt;
}
Example:
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "serialport.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
qmlRegisterType<SerialPort>("SerialPortLib", 1, 0, "SerialPort");
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
main.qml
import QtQuick 2.6
import QtQuick.Window 2.2
import SerialPortLib 1.0
Window {
visible: true
width: 640
height: 480
title: qsTr("Test")
SerialPort{
onOil_pressure_voltChanged: {
tx.text = "%1".arg(oil_pressure_volt);
}
}
Text {
id: tx
anchors.fill: parent
font.family: "Helvetica"
font.pointSize: 20
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
}
I have a Qt application and I'd like to show some log. I use a TextArea. However, if the log is large or the events come too fast the GUI can't draw Textarea fast enough.
I have analyzed this problem with Qt Creator (QML Profiler) and if the log is large it takes 300 ms to draw the GUI. I use this software on a Raspberry Pi2.
Any ideas how to solve this? Should I use other QML controls? Thanks.
QML code:
TextArea {
text: appHandler.rawCommunication
readOnly: true
}
C++ code:
Q_PROPERTY(QString rawCommunication READ rawCommunication WRITE setrawCommunication NOTIFY rawCommunicationChanged)
void setrawCommunication(QString val)
{
val.append("\n");
val.append(m_rawCommunication);
m_rawCommunication = val;
emit rawCommunicationChanged(m_rawCommunication);
}
Use a view, like ListView. They instantiate their delegates as needed, based on which data the view says it needs to show depending on the position the user is at in the list. This means that they perform much better for visualising large amounts of data than items like TextArea, which in your case is going to keep a massive, ever-growing string in memory.
Your delegate could then be a TextArea, so you'd have one editable block of text per log line. However, if you don't need styling, I'd recommend going with something a bit lighter, like TextEdit. Taking it one step further: if you don't need editable text, use plain old Text. Switching to these might not make much of a difference, but if you're still seeing slowness (and have lots of delegates visible at a time), it's worth a try.
I tried the ListView suggestion but it has several drawbacks:
No easy way to keep the view positioned at the bottom when new output is added
No selection across lines/delegates
So I ended up using a cached TextArea, updating once every second:
TextArea {
id: outputArea_text
wrapMode: TextArea.Wrap
readOnly: true
font.family: "Ubuntu Mono, times"
function appendText(text){
logCache += text + "\n";
update_timer.start();
}
property string logCache: ""
Timer {
id: update_timer
// Update every second
interval: 1000
running: false
repeat: false
onTriggered: {
outputArea_text.append(outputArea_text.logCache);
outputArea_text.logCache = "";
}
}
Component.onCompleted: {
my_signal.connect(outputArea_text.appendText)
}
}
try this approach:
create a c++ logger class
append all logs to this class
and print them using some action, example a button click
this will solve your performance issue
Example of code:
Logger.h
#ifndef LOGGER_H
#define LOGGER_H
#include <QQmlContext>
#include <QObject>
#include <QStringList>
#include <QQmlEngine>
#include <QString>
#include <QtCore>
#include <QDebug>
class Logger : public QObject
{
Q_OBJECT
public:
explicit Logger(QObject *parent = 0);
~Logger();
Q_INVOKABLE QStringList *getLogStream();
Q_INVOKABLE void printLogStream();
Q_INVOKABLE void appendLog(QString log);
Q_INVOKABLE void log(QString log="");
Q_INVOKABLE void log(QString fileName, QString log);
signals:
public slots:
private:
QStringList* stringStream_;
};
#endif // LOGGER_H
Logger.cpp
#include "logger.h"
Logger::Logger(QObject *parent) :
QObject(parent),
stringStream_(new QStringList)
{
}
~Logger(){
if(stringStream_ != NULL)
{
delete stringStream_;
stringStream_ = NULL;
}
}
QStringList* Logger::getLogStream(){
return stringStream_;
}
void Logger::printLogStream()
{
QStringListIterator itr(*stringStream_);
while (itr.hasNext())
qDebug()<< itr.next()<<"\n";
}
void Logger::appendLog(QString log){
stringStream_->push_back(log) ;
}
void Logger::log(QString fileName,QString log)
{
#ifdef ENABLElogs
fileName.push_front(" [");
if(!fileName.contains(".qml"))
{
fileName.append(".qml]:");
}
qDebug()<<fileName<<log;
#else
Q_UNUSED(log);
Q_UNUSED(fileName);
#endif
}
void Logger::log(QString log)
{
#ifdef ENABLElogs
qDebug()<<log;
#else
Q_UNUSED(log);
#endif
}
main.cpp
#include <QtGui/QGuiApplication>
#include "qtquick2applicationviewer.h"
#include "logger.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QtQuick2ApplicationViewer *viewer = new QtQuick2ApplicationViewer;
Logger* stream = new Logger;
viewer->rootContext()->setContextProperty("Stream",stream);
viewer->setMainQmlFile(QStringLiteral("qml/project/main.qml"));
viewer->showExpanded();
return app.exec();
}
main.qml
import QtQuick 2.0
import QtQuick.Controls 1.1
Rectangle {
width: 800
height: 480
Text {
text: qsTr("Hello World")
anchors.centerIn: parent
Component.onCompleted: Stream.appendLog("Text object is completed")
}
Column{
x:300
Button{
text:"append"
onClicked: {
Stream.appendLog("MouseArea object clicked")
}
Component.onCompleted: Stream.appendLog("Button object is completed")
}
Button{
text:"logger"
onClicked: {
Stream.printLogStream()
}
Component.onCompleted: Stream.appendLog("Button logger object is completed")
}
}
TextArea{
text:"blablabla"
Component.onCompleted: Stream.appendLog("TextArea object is completed")
}
Component.onCompleted: Stream.appendLog("the main object is completed")
}
project.pro
#add this line
# comment it, run qmake and recompile to disable logs
DEFINES += ENABLElogs
using this approch you can stop all logs with a single line change when you want to release your soft
However, I have included complete code, using "QAbstractListModel" for a Logging heavy data to QML
listmodel.h
#ifndef LISTMODEL_H
#define LISTMODEL_H
#include <QAbstractListModel>
class ListModel: public QAbstractListModel
{
Q_OBJECT
public:
ListModel();
// Q_PROPERTY(QStringList logs READ name WRITE nameChanged)
int rowCount(const QModelIndex & parent = QModelIndex()) const;
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
Q_INVOKABLE QVariant activate(int i);
private:
QStringList m_list;
};
#endif // LISTMODEL_H
listmodel.cpp
#include "listmodel.h"
#include <QFile>
#include <QHash>
ListModel::ListModel()
{
QFile file("/home/ashif/LogFile");
if(!file.open(QIODevice::ReadOnly))
{
qDebug( "Log file open failed" );
}
bool isContinue = true;
do
{
if(file.atEnd())
{
isContinue = false;
}
m_list.append(file.readLine());
}
while( isContinue);
}
int ListModel::rowCount(const QModelIndex & parent ) const
{
return m_list.count();
}
QVariant ListModel::data(const QModelIndex & index, int role ) const
{
if(!index.isValid()) {
return QVariant("temp");
}
return m_list.value(index.row());
}
QVariant ListModel::activate(int i)
{
return m_list[i];
}
main.qml
import QtQuick 2.3
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
Window {
visible: true
ListView
{
width: 200; height: 250
anchors.centerIn: parent
model:mylistModel
delegate: Text
{
text:mylistModel.activate(index)
}
}
}
main.cpp
#include <QGuiApplication>
#include <QQmlContext>
#include <QQmlApplicationEngine>
#include "logger.h"
#include "listmodel.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
Logger myLogger;
ListModel listModel;
engine.rootContext()->setContextProperty("mylistModel", &listModel);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
I am developing an app for mobile to run in iOS and Android and I am facing some difficulties to access the image gallery of the devices with Qml.
I need to list the images from image gallery in a GridView.
I have tried to return the pictures folder using QStandardPaths but it just works for desktop computers. For smartphones running iOS and Android it returns a folder that is not the folder of the gallery.
Could someone help me to figure out how I can do that? My code is below:
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include "caminhoimagens.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
qmlRegisterType<caminhoImagens>("PathImagens", 1, 0, "CaminhoImagens");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
main.qml
import QtQuick 2.4
import QtQuick.Window 2.2
import QtQuick.Controls 1.3
import QtQuick.Dialogs 1.2
import Qt.labs.folderlistmodel 2.1
import PathImagens 1.0
Window {
visible: true
width: 360
height: 640
maximumHeight: 640
minimumHeight: 640
maximumWidth: 360
minimumWidth: 360
title: "Acessar Galeria Test"
Rectangle {
id: principal
anchors.fill: parent
ListModel {
id: listModel
}
FolderListModel {
id: folderListModel
folder: "file://" + caminhoImagens.retornaCaminhoImagens()
nameFilters: "*.jpeg"
}
CaminhoImagens {
id: caminhoImagens
}
Item {
id: listaFotosDelegate
property Component delegateComponent: listaFotosDelegateComponent
Component {
id: listaFotosDelegateComponent
Image {
id: imagem
source: folderListModel.folder + "/" + fileName
width: principal.width / 4.2
height: principal.width / 4.2
fillMode: Image.PreserveAspectCrop
}
}
}
GridView {
id: listaFotosGridView
anchors.fill: parent
clip: true
model: folderListModel
delegate: listaFotosDelegate.delegateComponent
cellWidth: parent.width / 4
cellHeight: parent.width / 4
}
}
}
caminhoimagens.h
#ifndef CAMINHOIMAGENS_H
#define CAMINHOIMAGENS_H
#include <QObject>
#include <QStandardPaths>
class caminhoImagens : public QObject
{
Q_OBJECT
public slots:
QString retornaCaminhoImagens();
public:
caminhoImagens();
};
#endif // CAMINHOIMAGENS_H
caminhoimagens.cpp
#include "caminhoimagens.h"
caminhoImagens::caminhoImagens()
{
}
QString caminhoImagens::retornaCaminhoImagens()
{
return QStandardPaths::locate(QStandardPaths::PicturesLocation, QString(), QStandardPaths::LocateDirectory);
}
Answering my own question.
in iOS just create a FileDialog inside the QML file and set folder: shortcuts.pictures. It will call the iOS gallery.
In Android it is a harder job once it's needed to integrate java code.
I have done my code in Qt using QAndroidJniObject to write a equivalent java code.
caminhoimagens.h
#ifndef CAMINHOIMAGENS_H
#define CAMINHOIMAGENS_H
#include <QObject>
#include "imagepickerandroid.h"
#include <QDebug>
class caminhoImagens : public QObject
{
Q_OBJECT
Q_PROPERTY(QString imagemCaminho READ imagemCaminho NOTIFY imagemCaminhoChanged)
public slots:
void buscaImagem();
void retornaImagem(QString path);
public:
caminhoImagens();
QString imagemCaminho();
private:
QString m_imagemCaminho = "";
signals:
void imagemCaminhoChanged();
};
#endif //CAMINHOIMAGENS_H
caminhoimagens.cpp
#include "caminhoimagens.h"
caminhoImagens::caminhoImagens()
{
}
void caminhoImagens::buscaImagem()
{
imagePickerAndroid *imagePicker = new imagePickerAndroid();
connect(imagePicker, SIGNAL(imagemCaminhoSignal(QString)), this, SLOT(retornaImagem(QString)));
imagePicker->buscaImagem();
}
void caminhoImagens::retornaImagem(QString path)
{
qDebug() << path;
m_imagemCaminho = path;
emit imagemCaminhoChanged();
}
QString caminhoImagens::imagemCaminho()
{
return m_imagemCaminho;
}
imagepickerandroid.h
#ifndef IMAGEPICKERANDROID_H
#define IMAGEPICKERANDROID_H
#include <QObject>
#include <QtAndroidExtras>
#include <QDebug>
class imagePickerAndroid : public QObject, public QAndroidActivityResultReceiver
{
Q_OBJECT
public:
imagePickerAndroid();
void buscaImagem();
virtual void handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject & data);
signals:
void imagemCaminhoSignal(QString);
};
#endif // IMAGEPICKERANDROID_H
imagepickerandroid.cpp
#include "imagepickerandroid.h"
imagePickerAndroid::imagePickerAndroid()
{
}
void imagePickerAndroid::buscaImagem()
{
QAndroidJniObject ACTION_PICK = QAndroidJniObject::fromString("android.intent.action.GET_CONTENT");
QAndroidJniObject intent("android/content/Intent");
if (ACTION_PICK.isValid() && intent.isValid())
{
intent.callObjectMethod("setAction", "(Ljava/lang/String;)Landroid/content/Intent;", ACTION_PICK.object<jstring>());
intent.callObjectMethod("setType", "(Ljava/lang/String;)Landroid/content/Intent;", QAndroidJniObject::fromString("image/*").object<jstring>());
QtAndroid::startActivity(intent.object<jobject>(), 101, this);
qDebug() << "OK";
}
else
{
qDebug() << "ERRO";
}
}
void imagePickerAndroid::handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject &data)
{
qDebug() << "Trabalha com os dados";
jint RESULT_OK = QAndroidJniObject::getStaticField<jint>("android/app/Activity", "RESULT_OK");
if (receiverRequestCode == 101 && resultCode == RESULT_OK) {
QString imagemCaminho = data.callObjectMethod("getData", "()Landroid/net/Uri;").callObjectMethod("getPath", "()Ljava/lang/String;").toString();
emit imagemCaminhoSignal(imagemCaminho);
qDebug() << imagemCaminho;
}
else
{
qDebug() << "Caminho errado";
}
}
My question is pretty simple I think. Nevertheless I was not able to figure it out. I have a TextArea defined in my .qml file, which needs to be updated dynamically from the C++ code.
Unfortunately, I don't know how to access/update the TextArea from within the imserver.cpp class.
Can anyone help me out please?
Here is the .qml file:
import QtQuick 2.2
import QtQuick.Controls 1.1
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("IMServer")
menuBar: MenuBar {
Menu {
title: qsTr("File")
MenuItem {
text: qsTr("Exit")
onTriggered: Qt.quit();
}
}
}
TextArea {
id: serverInformation
x: 0
y: 0
width: 247
height: 279
}
}
My main.cpp:
#include <QApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QQmlEngine>
#include <QtQml>
#include "imserver.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
IMServer server(2000);
qmlRegisterUncreatableType<IMServer>("App", 1, 0, "IMServer", "");
engine.rootContext()->setContextProperty("imserver", &server);
server.startServer();
return app.exec();
}
imserver.h
#ifndef IMSERVER_H
#define IMSERVER_H
#include <QTcpServer>
#include <QTcpSocket>
#include <QAbstractSocket>
#include <QThreadPool>
class IMServer : public QTcpServer {
Q_OBJECT
Q_PROPERTY(QString text WRITE setText NOTIFY textChanged)
public:
explicit IMServer(int port, QObject *parent = 0);
void startServer();
void setText(const QString &txt);
signals:
void textChanged();
public slots:
protected:
void incomingConnection(qintptr fd);
private:
int port;
QThreadPool *pool;
QString m_text;
};
#endif // IMSERVER_H
imserver.cpp:
#include "imserver.h"
#include "clienthandler.h"
IMServer::IMServer(int port, QObject *parent) : QTcpServer(parent) {
this->pool = new QThreadPool(this);
this->pool->setMaxThreadCount(100);
this->port = port;
}
void IMServer::startServer() {
setText("TEST");
if (!this->listen(QHostAddress::Any, this->port)) {
qDebug() << "Server could not be started";
} else {
qDebug() << "Server started, listening...";
}
}
void IMServer::setText(const QString &txt) {
m_text = txt;
emit textChanged();
}
void IMServer::incomingConnection(qintptr fd) {
ClientHandler *client = new ClientHandler();
client->setAutoDelete(true);
client->fd = fd;
this->pool->start(client);
}
There are several ways. Here is how I'd do it.
First you should register your IMServer class:
qmlRegisterUncreatableType<IMServer>("App", 1, 0, "IMServer", "");
Then you add your IMServer instance to QML:
enigne.rootContext()->setContextProperty("imserver", &server);
Your IMServer class then needs a signal, that your TextArea can be connected to, or even better add a property (you need to add the getText() function and textChanged() signal here as well, for a read-only property):
Updated:
Q_PROPERTY(QString text READ getText NOTIFY textChanged)
In the TextArea you can then create a binding:
TextArea {
text: imserver.text
}
Then whenever you emit textChanged in your IMServer class, the TextArea's text will be updated.
For more information: http://doc.qt.io/qt-5/qtqml-cppintegration-topic.html