QQuickWidget and C++ interaction - c++

I am experiencing with the new QQuickWidget. How can I interact between the QQuickWidget and C++?
C++
QQuickWidget *view = new QQuickWidget();
view->setSource(QUrl::fromLocalFile("myqml.qml"));
view->setProperty("test", 0);
myLayout->addWidget(view);
QML
import QtQuick 2.1
Rectangle {
id: mainWindow
width: parent.width
height: parent.height
Text {
id: text
width: mainWindow.width
font.pixelSize: 20
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: test
}
}
text: test does not work: ReferenceError: test is not defined
How can I give my QML file some properties via C++?
Is it also possible to get the Text object in C++ and update its text?

Give it a try:
view->rootContext()->setContextProperty("test", "some random text");
instead of
view->setProperty("test", 0);
setProperty(name, val) works if object has the property name defined as Q_PROPERTY.
It is possible to pass QObject-derived object as view's context property:
class Controller : public QObject
{
Q_OBJECT
QString m_test;
public:
explicit Controller(QObject *parent = 0);
Q_PROPERTY(QString test READ test WRITE setTest NOTIFY testChanged)
QDate test() const
{
return m_test;
}
signals:
void testChanged(QString arg);
public slots:
void setTest(QDate arg)
{
if (m_test != arg) {
m_test = arg;
emit testChanged(arg);
}
}
};
Controller c;
view->rootContext()->setContextProperty("controller", &c);
Text {
id: text
width: mainWindow.width
font.pixelSize: 20
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: controller.test
}
Is it also possible to get the Text object in C++ and update its text?
In general, it doesn't seem to be the best approach -- c++ code shouldn't be aware of presentation if it follows model-view pattern.
However it is possible as described here.

Related

Qt: Connect list in c++ to listview in QML

I just started learning Qt. To be honest, some of confuses my a lot. I'm trying to load a predefined .csv table into a listview.
I created a qml file with the simple layout of a textfield, a listview and a button to load a file. The file looks like this:
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.2
ApplicationWindow {
id: applicationWindow
objectName: "App"
visible: true
width: 640
height: 480
title: qsTr("Rosinenpicker")
ColumnLayout {
id: columnLayout
anchors.rightMargin: 40
anchors.leftMargin: 40
anchors.bottomMargin: 40
anchors.topMargin: 40
anchors.fill: parent
TextField {
id: name
text: qsTr("Text Field")
Layout.preferredHeight: 50
Layout.fillWidth: true
}
ListView {
id: list
x: 0
y: 0
width: 110
height: 160
spacing: 0
boundsBehavior: Flickable.DragAndOvershootBounds
Layout.fillHeight: true
Layout.fillWidth: true
objectName: "listView"
model: guestModel
delegate: Item {
x: 5
width: 80
height: 40
Text {
text: model.modeldata.name
font.bold: true
anchors.verticalCenter: parent.verticalCenter
}
}
}
Button {
id: button
width: 50
text: qsTr("Open File")
Layout.preferredWidth: 100
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: fileDialog.visible = true;
}
FileDialog {
id: fileDialog
title: "Please choose a valid guestlist file"
objectName: "fileDialog"
nameFilters: ["Valid guestlist files (*.csv)"]
selectMultiple: false
signal fileSelected(url path)
onAccepted: fileSelected(fileDialog.fileUrl)
}
}
}
In my main file I create a CsvParser class a connect it to the fileSelected signal of the file dialog.
I can parse the csv-File. But when I try to connect it to the listview, I'm lost.
The main file:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtDebug>
#include <QUrl>
#include <QQuickView>
#include "csvparser.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
auto root = engine.rootObjects().first();
auto fileDialog = root->findChild<QObject*>("fileDialog");
CsvParser parser(engine.rootContext());
QObject::connect(fileDialog, SIGNAL(fileSelected(QUrl)), &parser, SLOT(loadData(QUrl)));
return app.exec();
}
My parser looks like this:
#include "csvparser.h"
#include <QtDebug>
#include <QFile>
#include "guest.h"
CsvParser::CsvParser(QQmlContext *context)
{
this->context = context;
}
void CsvParser::loadData(QUrl path)
{
friendList.clear();
guestList.clear();
QFile file(path.toLocalFile());
file.open(QIODevice::ReadOnly);
// ignore first lines
for(int i=0; i<3; i++)
file.readLine();
while(!file.atEnd()) {
auto rowCells = file.readLine().split(',');
if(rowCells.size() != 6)
continue;
checkedAdd(friendList, rowCells[0], rowCells[1]);
checkedAdd(guestList, rowCells[3], rowCells[4]);
}
qDebug() << guestList.size();
auto qv = QVariant::fromValue(guestList);
context->setContextProperty("guestModel", qv);
}
void CsvParser::checkedAdd(QList<QObject*> &list, QString name, QString familyName)
{
if(name == "" && familyName == "")
return;
list.append(new Guest(name, familyName));
}
And the guest, which has only a name and a familyname looks like this:
#ifndef GUEST_H
#define GUEST_H
#include <QObject>
class Guest : public QObject
{
public:
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
Q_PROPERTY(QString fName READ fName WRITE setfName NOTIFY fNameChanged)
public:
Guest(QString name, QString fName);
QString name();
QString fName();
void setName(QString name);
void setfName(QString fName);
signals:
void nameChanged();
void fNameChanged();
private:
QString m_name, m_fName;
};
#endif // GUEST_H
I get the following output:
qrc:/main.qml:41: ReferenceError: guestModel is not defined
QFileInfo::absolutePath: Constructed with empty filename
2
qrc:/main.qml:49: TypeError: Cannot read property 'name' of undefined
qrc:/main.qml:49: TypeError: Cannot read property 'name' of undefined
What am I doing wrong? Any help is appreciated. Thanks!
There are a few things I think are wrong. QML will not find guestModel, because it tries to render the ListView but it has not been made available to QML since you are setting the context property in loadData(), which is however only invoked when the user interacts with the FileDialog. Also, the Q_OBJECT macro needs to appear in the private section of the class (see http://doc.qt.io/qt-5/qobject.html#Q_OBJECT). Also, I do not think it is nice to make a data container/parser class aware of the UI, i.e. you export the rootContext to the parser. It would be better if you export something from the parser to the rootContext in main. The parser should have no notion of the UI IMHO. Finally, if you want to implement a custom list of data items to be displayed, you really should think about using an AbstractListModel as Yuki has suggested (see, http://doc.qt.io/qt-5/qabstractlistmodel.html) or another suitable List-based model (see, http://doc.qt.io/qt-5/qtquick-modelviewsdata-cppmodels.html). This way, changes in C++ will automatically result in updates in the UI.

Accessing/modifying all instances of a type in Qt/QML

In QML, I have a custom object type (a separate QML file) and I would like a way of accessing and/or modifying each instance of this type. For a very simple example:
MyText.qml:
Text {
height: 100
width: 100
color: "red"
function logStuff() {
console.log("This is MyText")
}
}
SomePage.qml:
MyText {
id: text1
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
text: "foo"
}
MyText {
id: text2
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
text: "bar"
}
When an event occurs (e.g. a button on SomePage.qml being clicked or a signal being emitted), I would like to be able to change all instances of MyText to have the same value for one property, or call each MyText's logStuff() function.
Note: in my actual use case, there are actually several dozen of these instances, and there will be other Text elements which are not instances of MyText.
I have a little experience with jQuery and was hoping there would be something similar to jQuery selectors, but I have been unable to find anything remotely similar. Either a QML or a C++ (or mixed) solution would be fine.
I would like to suggest a method using C++.
Since there is no way to get base class of QML object you can base the searching on objectName property witch is accessible in C++.
Suppose we have base QML object:
import QtQuick 2.3
Item {
id: base
objectName: "BaseItem"
property int someValue: 0
onSomeValueChanged: {
console.log("'someValue' was changed to " + someValue + " for " + base);
}
}
And another QML file, where these objects are used:
import QtQuick 2.4
import QtQuick.Window 2.2
Window {
visible: true
width: 300
height: 300
id: mainWindow
Base {
id: derived1
}
Base {
id: derived2
}
Base {
id: derived3
}
}
The idea is that all derived object inherit the same objectName from the base class.
So now you can simply find all of the objects in C++:
QObject *root = engine.rootObjects().first();
QList<QObject *> list = root->findChildren<QObject *>("BaseItem");
qsrand(QTime::currentTime().msec());
foreach(QObject *item,list) {
item->setProperty("someValue",qrand());
}
Here I just change the property but you also can call some method with QMetaObject::invokeMethod() etc.
I'm not sure to understand your need but I would just use a Connections element like so :
SomePage.qml:
MyText {
id: text1
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
text: "foo"
Connections {
target: senderItemId
onFooChanged: logStuff()
}
}
MyText {
id: text2
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
text: "bar"
Connections {
target: senderItemId
onFooChanged: logStuff()
}
}
There's no API call in Qt to do this (that I could find), but you can find all the QML QObject's by a type using QObject::children() and QMetaObject in C++. Below is an implementation example.
QList<QObject *> MySingleton::findObjectsOfComponent(QObject *parent, QQmlComponent *component) const
{
auto object = component->create(QQmlEngine::contextForObject(parent));
auto foundObjects = findObjectsOfObject(parent, object);
delete object;
return foundObjects;
}
QList<QObject *> MySingleton::findObjectsOfObject(QObject *parent, const QObject *object) const
{
auto className = object->metaObject()->className();
QList<QObject*> items;
for(auto child : parent->children()) {
if(child->metaObject()->className() == className) {
items.append(child);
}
items.append(findObjectsOfObject(child, object));
}
return items;
}
Then you should register MySingleton as a QML singleton and use a Component to find the type.
Component {
id: errorLabelComponentId
ErrorLabel {}
}
Button {
onClicked: {
//Un-ignore all error labels
var errorLabels = MySingleton.findObjectsOfComponent(pageId, errorLabelComponentId)
for(var i in errorLabels) {
//ignoreError is a property of ErrorLabel {}
errorLabels[i].ignoreError = false
}
}
}

Binding C++ to QML code when using Components

---edited url and changed dynamic part to something compilable----
(Using Qt 5.3)
I tried to create a compact sample, but its still too big to post all the files here separately, so i added a link to "uploaded.to" as i cannot seem to attach a zip file here :-((
(warning, spam links and / or waiting time, any better fileshare site you recommend ?
Here is a link to "bindtest.zip" via uploaded.com, beware of spam/ugly pix:
http://ul.to/lqemy5jx
Okay, i will try to post the essence of the files here anyways:
I tried to create a simple Class in C++ containing a StringList and an index.
I Instantiated two Objects of this Class and exposed them via "setContextProperty"
This should be used in QML to initialize a ListView and to be in sync with it.
So whenever a User changes the index in QML, C++ should be notified AND vice versa.
So when i create two Component qml files using the hardwired names set in "setContextProperty" it seems to work fine.
But for the life of me i cannot create a single component file and pass the DataObject to it as a parameter, i simply do not know how to do it, although i tried.
My "final" target ist to create a QML Object dynamically and pass the DataObject to it, this does not work either :-(
So here it comes, code snippets of my sample Project:
Declaring my oh-so-simple Class (DataObject.h)
#ifndef DATAOBJECT_H
#define DATAOBJECT_H
#include <QObject>
#include <QDebug>
class DataObject : public QObject
{
Q_OBJECT
Q_PROPERTY( int index MEMBER m_index NOTIFY indexChanged )
public slots:
int count() const { return m_Elements.count(); }
QString at(int idx) const { return m_Elements.at(idx); }
public:
void setIndex(int theInt) { m_index = theInt; }
signals:
void indexChanged(int);
public: // too lazy to write accessors for this sample, so make it public
QStringList m_Elements;
private:
int m_index;
};
#endif // DATAOBJECT_H
Registering it in main.cpp:
qmlRegisterType<DataObject>("bindtestTypes", 1, 0, "DataObject");
Here is the part of "dialog.cpp" that initializes and exposes two DataObects:
//preparing first list
m_firstDO.m_Elements = QStringList() << "A" << "B" << "C" << "D";
m_firstDO.setIndex(0);
//preparing second list
m_secondDO.m_Elements = QStringList() << "a" << "b" << "c" << "d";
m_secondDO.setIndex(3);
//publish the 2 Dataobjects
m_engine.rootContext()->setContextProperty( "cppDataList_1", &m_firstDO);
m_engine.rootContext()->setContextProperty( "cppDataList_2", &m_secondDO);
Here is the QML file "ShowLists.qml" that should simply show the 2 ListVies on Top of each other, i commented the 2 NOT working approaches that i would love to work, especially the dynamic one:
import QtQuick 2.2
import QtQuick.Window 2.1
import bindtestTypes 1.0
Window {
visible: true
width: 200
height: 400
Rectangle{
anchors.fill: parent
//dynamic: does not work :-(
// need to click on it to create it
// Rectangle{
// id:upperList
// anchors.top: parent.top;
// anchors.left: parent.left
// width:200
// height:200
// MouseArea{
// anchors.fill: parent
// onClicked: {
// var component = Qt.createComponent("SimpleList.qml");
// var dyncbb = component.createObject(parent, {"theDO": cppDataList_1});
// }
// }
// }
// Rectangle{
// id:lowerList
// anchors.bottom: parent.bottom;
// anchors.left: parent.left
// width:200
// height:200
// MouseArea{
// anchors.fill: parent
// onClicked: {
// var component = Qt.createComponent("SimpleList.qml");
// var dyncbb = component.createObject(parent, {"theDO": cppDataList_2});
// }
// }
// }
//static: would not be my first choice but isnt working anyways...
// SimpleList {
// id:upperList
// property DataObject theDO: cppDataList_1
// anchors.top: parent.top;
// anchors.left: parent.left
// }
// SimpleList {
// id:lowerList
// property DataObject theDO: cppDataList_2
// anchors.bottom: parent.bottom;
// anchors.left: parent.left
// }
//hardwired works, but its not workable for my rather complex project...
SimpleList1 {
id:upperList
anchors.top: parent.top;
anchors.left: parent.left
}
SimpleList2 {
id:lowerList
anchors.bottom: parent.bottom;
anchors.left: parent.left
}
}
}
Here is the first hardwired SimpleList1.qml that works fine, as well as the second:
import QtQuick 2.2
ListView {
id: list_view
width: 200
height: 200
currentIndex: cppDataList_1.index
model: cppDataList_1.count()
delegate: Rectangle {
height: 20
width: 200
Text { text: cppDataList_1.at(index); color: (list_view.currentIndex === index)?"red":"black" }
MouseArea{ anchors.fill: parent; onClicked: list_view.currentIndex = index }
}
onCurrentIndexChanged: cppDataList_1.index = currentIndex;
}
This is the "SimpleList.qml" that i cannot seem to get to work:
import QtQuick 2.2
import bindtestTypes 1.0
Rectangle {
ListView {
id: list_view
property DataObject theDO
width: 200
height: 200
currentIndex: theDO.index
model: theDO.count()
delegate: Rectangle {
height: 20
width: 200
Text { text: theDO.at(index); color: (list_view.currentIndex === index)?"red":"black" }
MouseArea{ anchors.fill: parent; onClicked: list_view.currentIndex = index }
}
onCurrentIndexChanged: theDO.index = currentIndex
}
}
So, can anyone of you help me to get this solved ??
IF you dare to follow the uploaded link and run my sample you can see one more glitch.
It displays 2 Windows, one QQQuickWIndow and a Widget.
In the Widget i can change the indexes as well as in the QML Window.
At first they are in sync but then the QML Window does not get updated anymore by changing the index in the widget, i hope its a glitch and not another general error i made.
Greetings & thanks for any help !
Nils
Argh, i found the problem, i did a very simple mistake:
The property i want to set in the SimpleList Component has to be in the root Object, so instead of this:
Rectangle {
ListView {
id: list_view
property DataObject theDO
...
It has to be done this way:
Rectangle {
property DataObject theDO
ListView {
id: list_view
...
Wow, thats an easy solution for a (seemingly) complex Problem.
Greetings,
Nils

Use QQmlListProperty to show and modify QList in Qml

again, well i have a question (and maybe a problem), i make a program with qt and qml in qt5 and qml with qtquick 2.0, and i have a c++ model qlist, and i need modify the list in runtime, i use q QQmlListProperty and show the items in qml, but they are not hide and show in the moment when i add or remove my code is next:
class ConceptsList: public QObject{
Q_OBJECT
Q_PROPERTY(QQmlListProperty<Concept> concepts READ concepts NOTIFY conceptsChanged)
Q_CLASSINFO("DefaultProperty", "concepts")
public:
ConceptsList(QObject *parent=0);
QQmlListProperty<Concept> concepts();
Q_INVOKABLE static void append_concept(QQmlListProperty<Concept> *list, Concept *cpt);
Q_INVOKABLE void removeConcept(int index);
Q_INVOKABLE void addConcept(QString m_id,QString description, QString quantity, QString price, QString unit, QString total);
Q_INVOKABLE int countConcepts();
static void clearConcepts(QQmlListProperty<Concept> *property);
static int conceptsSize(QQmlListProperty<Concept> *property);
static Concept *conceptAt(QQmlListProperty<Concept> *property, int index);
signals:
void conceptsChanged();
private:
QList<Concept *> m_concepts;
}
I use a listview and delegate and i not have problems to view, but my question is if i can use a QQmlListProperty and modify the Qlist, or i will change a form to expose qlist to qml, if it's posible how call the method from qml, or how implement in C++, i ask because exists really little number or examples with work this form.
in qml my code is next:
ConceptsList{
id:cpts
concepts:[
Concept{
m_id:"7"
m_quantity: "3"
m_price: "1"
m_unit:"1"
m_description:"algo"
m_total:"2"
}
]
}
ListView {
id: listConceptsView
objectName: "list"
anchors.fill: parent
anchors.margins: 5
clip: true
focus: true
highlight: highlightBar
highlightFollowsCurrentItem: false
Component{
id: tableConceptDelegate
Item{
anchors.margins: 4
width: 515
height: 27
clip: true
Row {
spacing: 4
Text {
height: 26; width: 76
text: model.m_id
color: "black"
font.bold: true
horizontalAlignment: Text.AlignHCenter
}
...
...
Text {
height: 26; width: 120
text: model.m_total//amountTotal
color: "black"
font.bold: true
horizontalAlignment: Text.AlignHCenter
}
}
MouseArea {
id: mouse_area1
anchors.fill: parent
onClicked:
{
listConceptsView.currentIndex = index
}
}
}
}
delegate: tableConceptDelegate
model:cptCpt // i define this alias how cptCpt: cpt.concepts
}
I got the answer for my self, first, I stopped using the attribute Q_INVOCABLE in the method append_concept, second, I added a line of code in the implementation of addConcept. Here is the code:
Before:
Q_INVOKABLE static void append_concept(QQmlListProperty<Concept> *list, Concept *cpt);
Now:
static void append_concept(QQmlListProperty<Concept> *list, Concept *cpt);
Maybe this not affect, but i prefer not take risks.
And in the implementations of addConcept and removeConcept:
void ConceptsList::addConcept(QString m_id, QString quantity, QString price, QString unit, QString description)
{
Concept *cpt=new Concept(m_id, quantity, unit, price, description);
m_concepts.append(cpt);
this->conceptsChanged();
}
void ConceptsList::removeConcept(int index)
{
m_concepts.removeAt(index);
this->conceptsChanged();
}

Attached properties in QML

Can I create my own attached for everything property like a Component?
Item{
Component.onCompleted : {} // Component is attached to everyone Items
}
Yes, for example:
#include <QQmlEngine>
#include <QTimer>
class TestAttached : public QObject
{
Q_OBJECT
// Declare property with setter and getter
Q_PROPERTY(int val READ getVal WRITE setVal)
public:
TestAttached() Q_DECL_EQ_DEFAULT;
explicit TestAttached(QObject *parent):
QObject{parent},
m_val{100500}
{
Q_ASSERT(parent);
qDebug() << "* We just have created the object of attached properties for" << parent->metaObject()->className();
}
~TestAttached() Q_DECL_EQ_DEFAULT;
// define static function qmlAttachedProperties(QObject *object)
// which returns pointer to instance of TestAttached class
static TestAttached *qmlAttachedProperties(QObject *object)
{
TestAttached* testAttached { new TestAttached{object} };
QTimer* timer { new QTimer{testAttached} };
connect(timer, &QTimer::timeout,
[testAttached] {
testAttached->setVal(testAttached->getVal()+1);
emit testAttached->testDone(testAttached->getVal());
});
timer->start(3000);
return testAttached;
}
inline int getVal() const
{
return m_val;
}
inline void setVal(int val)
{
m_val = val;
}
signals:
void testDone(int val);
private:
int m_val;
};
// Makes the type TestAttached known to QMetaType (for using with QVariant)
QML_DECLARE_TYPE(TestAttached)
// declares that the TestAttached supports attached properties.
QML_DECLARE_TYPEINFO(TestAttached, QML_HAS_ATTACHED_PROPERTIES)
Register attached type in QML system (e.g. at main.cpp):
qmlRegisterUncreatableType<TestAttached>("my.test", 1, 0, "Test", QObject::tr("Test is an abstract type that is only available as an attached property."));
And try it in main.qml:
import QtQuick 2.15
import QtQuick.Window 2.15
//import our module
import my.test 1.0
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World of Attached properties")
Rectangle {
color: focus ? Qt.lighter("red") : "red"
width: parent.width/2; height: parent.height
anchors.left: parent.left
Component.onCompleted: {
console.log("Rectangle is completed", Test.val)
}
Keys.onReturnPressed: {
console.log("Rect 1: Attachet value is", Test.val)
}
Test.val: 20000
Test.onTestDone: function(num) {
console.log("We received", num, "from attached object")
}
MouseArea {
anchors.fill: parent
onClicked: parent.focus = true
}
}
Rectangle {
color: focus ? Qt.lighter("blue") : "blue"
width: parent.width/2; height: parent.height
anchors.right: parent.right
focus: true
Keys.onReturnPressed: {
// The attached object will created after return pressed
console.log("Rect 2: Attachet value is", Test.val)
}
MouseArea {
anchors.fill: parent
onClicked: parent.focus = true
}
}
}
You may not be able to attach properties to Items or Components you did not create. But why would you want to do that anyway?
Instead you could consider using signals and global properties.
For global properties that you can access from anywhere you can set the context property of the root context of the declarative view.
i.e,
QmlApplicationViewer viewer;
MyPropertyClass myProp;
viewer->rootContext()->setContextProperty("MyPropClass", &myProp);
Now, in your QML file you can access the properties of this class as
Rectangle {
Text {
text: MyPropClass.getMyPropText()
}
MouseArea {
onClicked: { MyPropClass.text = "Clicked" }
}
}
This will invoke the Q_INVOKABLE method getMyPropText() from the MyPropertyClass. and Q_PROPERTY 'text' can be set when some signal is emitted.
Would this suit you needs?