I am trying to add a QWidget to a QQuick 2 application.
The approach is the following:
Create a QQuickItem derived object, which creates the QWidget in an initialize function. The QWindow is obtained from the QWidget and reparented to the "main" QQuickWindow.
During runtime the result is very good (it looks like a normal view, not an external window).
However when I close the main window I receive:
External WM_DESTROY received for QWidgetWindow(0x202b3a4ffe0, name="QWidgetClassWindow") , parent: QWindow(0x0) , transient parent: QWindow(0x0)
Any idea how to avoid this warning?
QML:
import QtQuick 2.12
import QtQuick.Controls 2.5
import QtQuick.Window 2.2
import QWidgetWrapper 1.0
import QtQml 2.12
Item {
id: root
QWidgetWrapper {
id: ewas
anchors.fill: parent
Component.onCompleted: {
initialize(Window.window)
}
}
}
Cpp:
#pragma once
#include <QQuickItem>
#include <QWindow>
class QWidgetWrapper : public QQuickItem
{
Q_OBJECT
public:
QWidgetWrapper(QQuickItem *parent = nullptr) : QQuickItem(parent) {}
~QWidgetWrapper() {
}
Q_INVOKABLE void initialize(QWindow *window) {
m_widget = new QWidget();
m_widget->setWindowFlags(Qt::FramelessWindowHint);
m_widgetWindow = QWindow::fromWinId(m_widget->winId());
m_widgetWindow->setParent(window);
m_widget->show();
}
private:
QWidget *m_widget = nullptr;
QWindow *m_widgetWindow = nullptr;
};
Related
I'm trying to figure out how to get QList> from C++ signal in QML, i'm only getting either QVariant(RecordList, ) or QVariant(QList, ). Tried with different supported sequence type and they work perfectly (QList. I'll appreciate if somebody can help me to understand my error. Kind regards.
jsonreaderasync.h
typedef QPair<qreal,qreal>Record;
typedef QList<Record>RecordList;
class JsonReaderAsync : public QObject
{
Q_OBJECT
public:
explicit JsonReaderAsync(QObject *parent = nullptr);
Q_INVOKABLE void start(const QString& fileName);
signals:
void started();
//void finished(QList<qreal> record);
void finished(RecordList record);
//void finished(QList<Record> record);
};
jsonreaderasync.cpp
#include <QDebug>
#include "jsonreaderasync.h"
Q_DECLARE_METATYPE(Record)
Q_DECLARE_METATYPE(RecordList)
//Q_DECLARE_METATYPE(QList<Record>)
JsonReaderAsync::JsonReaderAsync(QObject *parent) : QObject(parent)
{
qRegisterMetaType<Record>("Record");
qRegisterMetaType<RecordList>("RecordList"); // qml prints QVariant(RecordList, )
//qRegisterMetaType<QList<Record>>("QList<Record>"); //qml prints QVariant(QList<Record>, )
}
void JsonReaderAsync::start(const QString &fileName)
{
QList<Record> record;
record.append(qMakePair(1,1));
record.append(qMakePair(1,2));
record.append(qMakePair(1,3));
// QList<qreal> foo;
// foo.append(1);
// foo.append(1);
// foo.append(1);
emit finished(record);
}
main.cpp
#include "jsonreaderasync.h"
typedef QPair<qreal,qreal>Record;
typedef QList<QPair<qreal,qreal>>RecordList;
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QQmlApplicationEngine *engine = new QQmlApplicationEngine;
JsonReaderAsync* dataReaderAsync = new JsonReaderAsync();
engine->rootContext()->setContextProperty("JsonReaderAsync", dataReaderAsync);
engine->load(QUrl(QStringLiteral("main.qml")));
return app.exec();
}
main.qml
import QtQuick 2.12
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.13
import QtQuick.Window 2.13
import QtQuick.Controls 2.12
import QtCharts 2.3
Window {
id: appWindow
visible: true
minimumWidth : 400
minimumHeight: 400
Connections
{
target: JsonReaderAsync
onStarted:{
console.log("onStarted")
}
onFinished:{
console.log("onFinished")
console.log(record)
console.log(record[0])
}
}
Button {
width : 40
height: 40
anchors.centerIn: parent
onClicked: {
JsonReaderAsync.start("")
}
}
}
In QML, the meta-type system is the only way that QML engine can access C++ structures from a QML environment.
That is, only Predefined C++ Types and custom objects that have Q_PROPERTY declarations could access from QML environment.
Here's my recommend (simplest) modification:
jsonreaderasync.h
#include <QObject>
#include <QVariant>
class Record : public QPair<qreal, qreal> {
Q_GADGET
Q_PROPERTY(qreal first MEMBER first CONSTANT FINAL)
Q_PROPERTY(qreal second MEMBER second CONSTANT FINAL)
public:
Record() = default;
Record(qreal a, qreal b) : QPair<qreal, qreal>(a, b) {}
};
class JsonReaderAsync : public QObject
{
Q_OBJECT
public:
explicit JsonReaderAsync(QObject *parent = nullptr);
Q_INVOKABLE void start(const QString& fileName);
signals:
void started();
void finished(QVariantList record); // RecordList
};
jsonreaderasync.cpp
#include <QDebug>
#include "jsonreaderasync.h"
JsonReaderAsync::JsonReaderAsync(QObject *parent) : QObject(parent)
{
qRegisterMetaType<Record>("Record");
}
void JsonReaderAsync::start(const QString &fileName)
{
QVariantList record;
record.append(QVariant::fromValue(Record(1,1)));
record.append(QVariant::fromValue(Record(1,2)));
record.append(QVariant::fromValue(Record(1,3)));
emit finished(record);
}
Now you can access records from QML:
onFinished: {
for (var i = 0; i < record.length; ++i)
console.log(record[i].first + "->" + record[i].second);
}
Note that converting between C++ objects and QML objects does incur a bit overhead, if these codes are performance sensitive, please consider using C++ Data Models.
While #GPBeta solution works, I wanted more flexibility for Qml supported pairs. I tried to work with templates, but Q_GADGET doesn't support it. There might be a smart wrapper (Template + QVariant) solution, I guess... Nonetheless, here is my approach to the problem:
class PairQml {
Q_GADGET
Q_PROPERTY(QVariant first MEMBER first CONSTANT FINAL)
Q_PROPERTY(QVariant second MEMBER second CONSTANT FINAL)
public:
PairQml() = default;
PairQml(QVariant f, QVariant s): first(f), second(s) {}
QVariant first;
QVariant second;
};
Don't forget to register: qRegisterMetaType<PairQml>("PairQml");
I have a C++ plugin system, where a QQmlComponent is created and a qml file is loaded when the user requests a new plugin instance.
Currently I am using setContextProperty() to tell QML about a QObject that is needed for proper initialization.
mEngine->rootContext()->setContextProperty("controller", QVariant::fromValue(mController));
mComponent = new QQmlComponent(mEngine);
mComponent->loadUrl(QUrl{ "qrc:///MyPlugin.qml" });
The problem is, when instantiating a second plugin, both will use the controller of the second one because "controller" is global in QML.
Repeater {
model: controller.numEntries
Is there a way to set a context property locally (only for the current instance)?
I found solutions using setProperty() or QQmlIncubator and setInitialState(), but they all seem to require an object that was already created from my component. But in my plugin I only define the component, which is loaded in the main application through a Loader item. So, when trying these approaches, I always ended up in setting the value in a copy of the item, but not the one being created in my backend.
How can I get access to a property of the component that is created in QML?
mComponent->findChild<QQuickItem*>("controller");
does not give me any results, even if I defined the property in MyPlugin.qml.
Maybe you can create a QObject based class and have a slot and instead of using property you can call slot to slot create a new property in c++ and return it to QML
ControllerCreator.h
#ifndef CONTROLLERCREATOR_H
#define CONTROLLERCREATOR_H
#include <QObject>
class ControllerCreator : public QObject {
Q_OBJECT
public:
explicit ControllerCreator(QObject *parent = nullptr);
signals:
public slots:
int propertyCreator();
private:
int m_example;
};
#endif // CONTROLLERCREATOR_H
ControllerCreator.cpp
#include "ControllerCreator.h"
ControllerCreator::ControllerCreator(QObject *parent)
: QObject(parent), m_example(0)
{
}
int ControllerCreator::propertyCreator()
{
m_example++;
return m_example;
}
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "ControllerCreator.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
ControllerCreator controllerCreator;
engine.rootContext()->setContextProperty("creator", &controllerCreator);
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
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Column{
anchors.fill: parent
Text{
text: creator.propertyCreator()
color: "blue"
}
Text{
text: creator.propertyCreator()
color: "red"
}
Text{
text: creator.propertyCreator()
color: "green"
}
}
}
i would like to code a program by Qt tcp socket and QML together.
i am going to change a textbox variable that created by QML to the new data that get by remote client by tcp socket?
my snippet code is :
void Server::startRead()
{
client->waitForReadyRead(3000);
QString buf= client->readAll();
qDebug() << buf;
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
QObject *rootObject = engine.rootObjects().first();
QObject *qmlObject = rootObject->findChild<QObject*>("ttt");
qDebug() << "before change"
qDebug() << qmlObject->property("text");
qmlObject->setProperty("text", "new data");
qDebug() << "after change"
qDebug() << qmlObject->property("text");
}
main.qml
import QtQuick 2.0
import QtQuick 2.2
import QtQuick.Window 2.1
import QtQuick.Window 2.2
import QtQuick.Controls 1.2
Window{
width:400
height:400
visible:true
Item {
id: rootItem
property string text
Text {
id: message
text: rootItem.text
}
}
}
I really appreciate it.
There are many ways to do that. Next examples demonstrate how to change qml element properties from cpp. If you need to change visibility just use bool properties instead of string that i used in examples.
1. This is good solution i think.
You could create qml adapter
class ServerQMLAdapter : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(QString data READ data WRITE setData NOTIFY dataChanged)
public:
explicit ServerQMLAdapter(QQuickItem *parent = nullptr) : QQuickItem(parent){}
QString data() const {
return mData;
}
void setData(const QString &data) {
if(mData == data)
return;
mData = data;
emit dataChanged();
}
private slots:
/**
this slots called on data read
*/
void onDataRead(const QString &data) {
setData(data);
}
private:
QString mData = QString();
};
in application class you sould register your adapter
qmlRegisterType<ServerQMLAdapter>("ServerQMLAdapter", 1, 0, "ServerQMLAdapter");
After that you can use it in your qml file
import ServerQMLAdapter 1.0
Item {
ServerQMLAdapter {
id: myAdapter
}
Text {
id: message
text: myAdapter.data
}
}
2. You can change properties from cpp.
Just like you do in your snippet
auto rootObject = engine.rootObjects().first();
rootObject->setProperty("text", "new data");
and add this propetry to your qml file
Item {
id: rootItem
property string text
Text {
id: message
text: rootItem.text
}
}
3. Use invoked methods
From you cpp file
auto rootObject = engine.rootObjects().first();
QMetaObject::invokeMethod(rootObject, "setText",
Q_ARG(QVariant, QVariant::fromValue(data)));
and add function to your qml file
Item {
id: rootItem
function setText(data) {
message.text = data;
}
Text {
id: message
}
}
I was following an example on web to populate combobox, but it did not wok for me and I do not know why!. I have two classes stock and DbCon, stock has three private fields along with public accessor and mutators. DbCon has a Q_Property and two public function, one returns a database connection and the other creates and returns stock list as a QList<QObject*>. In main.cpp I have created a contextual property named "data" to access DbCon from QML.
in main.qml I have
....
ComboBox{
model: data.stockModel
textRole: "code"
}
....
in main.cpp
DbCon db;
engine.rootContext()->setContextProperty("data", &db);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
in dbcon.h
class DbCon:public QObject
{
Q_OBJECT
Q_PROPERTY(QList<QObject*> stockModel READ stockModel)
public:
explicit DbCon(QObject *parent = 0);
QSqlDatabase db();
QList<QObject*> stockModel();
};
in implementation of QList<QObject*> stockModel() of dbcon.h
QList<QObject*> data;
....
while (query.next()) {
stock *s = new stock();
....
data.append(s);
}
return data;
and in stock.h
class stock : public QObject
{
Q_OBJECT
private:
QString m_name;
QString m_code;
int m_id;
public:
explicit stock(QObject *parent = 0);
QString name();
void setname(QString &name);
QString code();
void setcode(QString &code);
int id();
void setid(int &id);
};
When I run the application I get the following message in application output
QQmlExpression: Expression qrc:/main.qml:16:20 depends on non-NOTIFYable properties:
QQuickComboBox::data
and I do not get anything in combobox!
If I create another contextual property in main.cpp in this way
engine.rootContext()->setContextProperty("myModel", QVariant::fromValue(data));
and set myModel as model for combobox, it works fine. But I want to do it in this way because onCurrentIndexChanged I will call another function that returns another QList<QObject*> for a TableView of another qml file.
EDIT: Entrie qml
import QtQuick 2.5
import QtQuick.Window 2.2
import QtQuick.Controls 2.0
Window {
visible: true
width:600
height:600
property string contentSource
Column{
spacing:10
ComboBox{
model: data.stockModel
textRole: "code"
}
Loader {
id: content
width: parent.width
height:400
}
Row{
spacing:10
Button{
text: "Page1"
onClicked: content.source = "Page1.qml"
}
Button{
text: "Page2"
onClicked: content.source = "Page2.qml"
}
}
}
}
By changing data to dataStore in main.cpp and data.stockModel to dataStore.stockModel in main.qml I get following error
file:///C:/Qt/Qt5.7.0/5.7/mingw53_32/qml/QtQuick/Controls.2/ComboBox.qml:62:15: Unable to assign [undefined] to QString
You have two issues:
Your stockModel property should be NOTIFYable, which means that you should define the property with e.g. Q_PROPERTY(QList<QObject*> stockModel READ stockModel NOTIFY stockModelChanged) and provide a void stockModelChanged(const QList<QObject *> &) signal in the DbCon class.
stock::name() must be a property too, so you need to declare that with Q_PROPERTY(QString name READ name NOTIFY nameChanged) and provide a void nameChanged(const QString &) signal in the stock class as well.
I have defined a QML object under MyQMLObject.qml. This QML file looks like this:
import QtQuick 2.4
Item {
id: rootItem
implicitWidth: LayoutUtils.maxImplicitWidth(children)
implicitHeight: LayoutUtils.maxImplicitHeight(children)
Text {
id: text1
}
Text {
id: text2
}
// ...
Text {
id: textN
}
}
The text is added dynamically when the application starts. For each language different text is added, there for the width of the rootItem varies by the chosen language. I would like to somehow create MyQMLObject only once at application startup without even visualizing it and save its actual width in a singleton for example so I can reuse that value throughout my code without creating MyQMLObject more then once. How could I achieve this?
Right now I have a singleton QML file, which holds a QtObject which contains some constant values. Can I somehow create an instance of MyQMLObject within this singleton QtObject?
My singleton Style.qml looks like this:
pragma Singleton
import QtQuick 2.4
QtObject {
readonly property int maxWidth: 400
// ...
}
Firstly, if possible, you could use a Column instead of manually calculating the maximum width:
MyQMLObject.qml
import QtQuick 2.4
Column {
Text {
text: "blah"
}
Text {
text: "blahblah"
}
}
You can use dynamic object creation to create the temporary Column item:
Style.qml
pragma Singleton
import QtQuick 2.4
QtObject {
readonly property int maxWidth: {
var component = Qt.createComponent("qrc:/MyQMLObject.qml");
if (component.status === Component.Error) {
console.error(component.errorString());
return 0;
}
return component.createObject().width;
}
}
main.qml
import QtQuick 2.5
import QtQuick.Window 2.2
import App 1.0
Window {
visible: true
Component.onCompleted: print(Style.maxWidth)
}
Then, register the singleton:
main.cpp
#include <QtGui>
#include <QtQml>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
qmlRegisterSingletonType(QUrl("qrc:///Style.qml"), "App", 1, 0, "Style");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
However, note that this approach could be improved by calculating the maximum width from C++, eliminating the need to construct an item only to throw it away. Working off this example:
#include <QtGui>
#include <QtQml>
class Style : public QObject
{
Q_OBJECT
Q_PROPERTY(int maxWidth READ maxWidth CONSTANT)
public:
Style(QObject* parent = 0) :
QObject(parent),
mMaxWidth(0)
{
QFontMetrics fontMetrics(qApp->font());
// Here is where you'd fetch the text...
QStringList dummyText;
dummyText << "blah" << "blahblah";
foreach (const QString &string, dummyText) {
const int width = fontMetrics.boundingRect(string).width();
if (width > mMaxWidth)
mMaxWidth = width;
}
}
int maxWidth() const
{
return mMaxWidth;
}
private:
int mMaxWidth;
};
static QObject *singletonTypeProvider(QQmlEngine *, QJSEngine *)
{
Style *style = new Style();
return style;
}
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
qmlRegisterSingletonType<Style>("App", 1, 0, "Style", singletonTypeProvider);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
#include "main.moc"
It uses QFontMetrics to calculate the width.
main.qml remains unchanged.