Load property from QML-file in C++ - c++

I'm building a plugin-system for my QML+C++ application. The plugins are QML-files.
A qml-file could look like this:
Item {
title: "Sexy Plugin"
version: "1.0"
}
How can I read title and version within C++?

Every QML item inherits QObject directly or indirectly, so you can use the meta system to read and write properties "dynamically".
QVariant QObject::property(const char * name) const
Returns the value of the object's name property. If no such
property exists, the returned variant is invalid.
If the item happens to be the root item, you can use QQuickItem * QQuickView::rootObject() const to get it, if not, you will have to set the objectName : QString property, avaiable for every QObject derived object and call findChild<QObject*>("name") from the root object.

The easiest wold be, to write a QuickItem in C++. Even when its just a small component, that just holds title and version. Then it is easily accessible within C++ and its the cleanest way, to decouple view and logic.Then you can just define properties with the well-known Q_PROPERTY Macro.
If you actually want to use only Qml-written components and don't want to write anything in C++, there is a solution mentioned at this page: http://qt-project.org/doc/qt-5/qtqml-cppintegration-interactqmlfromcpp.html
Quoting the relevant part from "Accessing Members of a QML Object Type from C++":
QQmlEngine engine;
QQmlComponent component(&engine, "MyItem.qml");
QObject *object = component.create();
qDebug() << "Property value:" << QQmlProperty::read(object, "someNumber").toInt();
QQmlProperty::write(object, "someNumber", 5000);
qDebug() << "Property value:" << object->property("someNumber").toInt();
object->setProperty("someNumber", 100);
So QQmlProperty might help you.
As I don't know, what your goal is, I won't recommend anything. Anyway in my opinion, the first way of just writing the Item in C++ is in many cases much cleaner then trying to get the properties from Qml.

Related

QML: C++ classes with "bring your own component"

I'm trying to develop a Qt C++ application, with a QML frontend, but I hit a roadblock.
This is what I have so far:
A Factory class that outputs a choice of objects. These objects, that I'm going to call "controllers", control different pieces of hardware.
The Factory would be exposed to the QML layer with setContextProperty.
The controller would be chosen basically with a combo box controlling the factory.
Now, for the tricky bit. I want that the "controllers" behave in a "bring your own component" way. This means that they would have a method returning the respective QML file for their controller. That shouldn't be to hard to do, it's basically biding a Loader to a method of the Factory/Manager saying the file with the component to load into a placeholder.
But the problem is: how can this newly created component and this newly created controller know and talk to each other? This is something I did before with QWidgets, just having pointers between the classes. Quite trivial.
I tried an architecture like this before for QWidgets, but seems to not be ideal for QML.
I made this drawing of what I would ultimately like to happen:
This architecture allows for a very trivial plugin system (at least in the QWidgets world) and I would very much like to keep that. Not a massive singleton and account for every possible action...
I'd appreciate ideas!
I think this is actually very easy, if you return a QQuickItem from the C++ side. If you do so you can create it with a specific context, in which you can set your "specific hardware controller" as a property
QQmlComponent *qml_controller = new QQmlComponent(qengine, "some_file.qml");
QQmlContext *context = new QQmlContext(); //should probably give a pointer to owning object
context->setContextProperty("controller", pointer_to_hw_cont);
return qml_controller->create(context);
The Loader setSource method have additional parameter you could pass to provide initial value for some property. Something like this:
ComboBox {
model: controlerFactory.specificHWListModel
onCurrentTextChanged: {
var specificHWControler = controlerFactory.getObjectFor( currentText );
loader1.setSource(
specificHWControler.qml_file,
{ "controler": specificHWControler }
);
}
}
Loader {
id: loader1
}
The specificHWListModel cold be QStringList or some custom QAbstractListModel.
And getObjectForcould be just a invokable function.
Q_INVOKABLE QObject* getObjectFor(QString hwName);
The object returned from Q_INVOKABLE function will be managed by QQmlEngine by default if you don't set by the QQmlEngine::setObjectOwnership. Remember to register your SpecificHWControler class to QQmlEngine.
The qml_file SpecificView.ui.qml, should have property controler, and could be edited with Designer:
import SpecificHWControlerModule 1.0
Item {
property SpecificHWControler controler
}
https://doc.qt.io/qtcreator/quick-connections-backend.html

Passing QObject pointer from a QML object to C++

I have a publisher/subscriber key-value DB class in Qt/C++. The subscribers can connect by passing the key ( string ) , their QObject pointer and the property.
Whenever a value of the subscribed key changes, the properties of the subscribed QObject changes to the new value. Works in Qt/C++ fine.
Now I want to make a view in QML. Is it possible to pass from QML to C++ an object with 3 parameters:
QObject pointer of the QML object
property as string
DB-key as string
?
The preferable solution were, as if the property connects to another property:
Item{ myQmlProp: MyCppInst("myDBKey") }
EDIT
What currently works is this solution:
Item{
id:myqmlitem
myQmlProp: MyCppInst("myDBKey","myQmlProp",myqmlitem)
}
or like this:
Item{
id:myqmlitem
Component.onCompleted:{
MyCppPublisher.subscribe("myDBKey1","myQmlProp1",myqmlitem)
MyCppPublisher.subscribe("myDBKey2","myQmlProp2",myqmlitem)
}
}
Compared to the preferable solution, I have to pass the connected property name and the QML item instance explicitly. But it is ok, many thanks for the answers!
I've hoped to use QML's this-Keyword but have learned, that it is currently undefined in QML :-(
Just give the object an id and pass that id to the function, it will become a QObject * on the C++ side. Then you can use the meta system to access properties by name:
// qml
Item {
id: someitem
...
CppObj.cppFoo(someitem)
}
// c++
void cppFoo(QObject * obj) {
...obj->property("myDBKey")...
}
A reference would do as well, for example children[index].
What you could do is a function taking just your dbkey as a parameter, and return a QObject* exposing a Q_PROPERTY with a READ function and NOTIFY signal.
This way, you just have to tell with the notify signal the value has changed, and the QML will call the read function automatically.
It could be implemented like that Item{ myQmlProp: MyCppInst("myDBKey").value }.
If you know the db keys at compile time you could just add a property for each of them in your MyCppInst directly, or if you know them at the creation of your cpp class you could put them in a QQmlPropertyMap.
Usage would be like that : Item { myQmlProp: MyCppInst.myDbKey } (or MyCppInst["myDbKey"] if you need to be dynamic in the QML side).

Qt: when can I access dynamic properties from qtcreator?

I have a couple of widgets in QtCreator that are promoted from the same class. However, I'd like them to have subtle differences between the two, so I'd like to pass some differences in the UI file that the promoted class can use to distinguish itself. Dynamic properties seem like the way to go so in the UI editor I've assigned a dynamic property to each promoted widget. In the code I tried accessing the property, but noticed it seems to only be available post construction (probably because Qt is calling setProperty() after the object is created.
MyWidget::MyWidget(QWidget* parent) : QGLWidget(parent)
{
this->property("someProperty").toString(); // returns blank
}
void MyWidget::initializeGL()
{
this->property("someProperty").toString(); // returns string set in UI file
}
So my question how do people use these properties for constructor-type stuff? I could just do that in initializeGL, but that seems odd since these properties might not be related to initializing OpenGL. I imagine I could also connect to the property changed signal and do it there. Is that the common way to handle this?
If the generated code for setupUi() from your .ui file does something like this:
MyWidget *w = new MyWidget;
w->setProperty(...);
then your constructor is accessing a meta property that does not yet exist.
You can reimplement QObject::event() to capture QDynamicPropertyChangeEvents, letting you act once the property is initialized.
bool MyWidget::event(QEvent *ev)
{
if (ev->type() == QEvent::DynamicPropertyChange) {
if (QDynamicPropertyChangeEvent *propEv = static_cast<QDynamicPropertyChangeEvent *>(ev)) {
if (propEv->propertyName() == "someProperty")
...
}
}
}
Bear in mind that this code will be called every time there is a dynamic property change.
A better approach may be to create a function to perform the necessary initilization on the widget after setupUi() etc. are called and the dynamic property is created.
void setupMyWidget(MyWidget *w)
{
QString s = w->property("someProperty").toString();
...
}
Typically, dynamic properties are assigned a default value in the constructor so that they are always available and non-null later.
setProperty("someProperty", defaultValue);

Qt: How to pass variable value betweeen QWizardPages with registerField()

I'm working on Qt 4.8.5. I'm using a QWizard structure with its QWizardPages (lets name them wp1, wp2, wp3,...). I need to pass one value from wp2 to wp4 but every time I try it, I get an empty string :(
The value I need is on a variable (QString sVar;) so not a widget and I've tried some things:
Using RegisterField with the wizardpage itselfs (as its still a type of qwidget) like this: registerField("myField",this); but ofcourse when i go to wp4 and try to qDebug()<< "data: " << field("myField").toString();it is empty.
I've see in some forums ppl saying that you can create a Q_PROPERTY and then use the register field. I've set it as Q_PROPERTY sData READ getData() WRITE setDATA() and then with registerField("myfield, this, ...and here I have a problem because i expect sData to apear but it doesn't.
So... any idea about how can I achieve this using registerField (I know I can also create my own slot and signal, emit it from wp2 and catch it up on wp4 but I would like to avoid it if possible)
Added the solution:
Class A.h:
class ClassA: public QWizardPage
{
Q_OBJECT
Q_PROPERTY(QString sAP READ getAP WRITE setAP)
....
public:
QString getAP() const {return AP;}
void setAP(QString s){AP=s;};
private:
QString AP;
Class A constructor:
registerField("AP_field",this, "sAP", SIGNAL(APChanged()));
Class A ::initializePage() function:
switch(m_iVar)
{
case 0 :...
break;
case 1:
setAP("AP1");
emit APChanged();
break;
}
And then in Class B (Where you need to know that data):
qDebug() << " AP QPROPERTY = " <<field ("AP_Field").toString();
According to the docs:
When we create a field using QWizardPage::registerField(), we pass a
unique field name and a widget. We can also provide a Qt property name
and a "changed" signal (a signal that is emitted when the property
changes) as third and fourth arguments; however, this is not necessary
for the most common Qt widgets, such as QLineEdit, QCheckBox, and
QComboBox, because QWizard knows which properties to look for.
So you still need a signal, but Qt will handle necessary connections for you and will catch your new value as you change it. You have to register like this:
registerField("myField", this, "myProperty", SIGNAL(myPropertyChanged()));
Then you have to remember to emit the signal each time you change your variable, and of course register it as a property.
This works from some but not all widgets that emit a signal. For QDoubleSpinWidgets,
QWizard::setDefaultProperty("QDoubleSpinBox", "value", SIGNAL(valueChanged(double)));
This is because the valueChanged() is for QString and double....does not know which value to take without the parameter specifying:
"Every time the value changes QDoubleSpinBox emits two valueChanged() signals, one taking providing a double and the other a QString. The QString overload provides the value with both prefix() and suffix(). The current value can be fetched with value() and set with setValue()." doc.qt.io/qt-5/qdoublespinbox.html I hope this never troubles others and this complete solution, no warnings, errors nor unpredictable behaviour.

Expose QAbstractListModel element properties to QML in Qt 5.0

I've been loosely following the article on Christophe Dumez's blog to get a custom QAbstractListModel class to expose the data to a QML (QtQuick2) interface (QtQuick2ApplicationViewer). However, since I'm using Qt 5.0.0 (and MSVC2012), there are some parts of his article that don't apply. For example, the ListModel constructor no longer has to call setRoleNames(), because setRoleNames() has been depreciated in Qt 5.
ListModel::ListModel(ListItem* prototype, QObject *parent) :
QAbstractListModel(parent), m_prototype(prototype)
{
setRoleNames(m_prototype->roleNames());
}
It is my understanding that the class that inherits from QAbstractListModel must only define roleNames(), as it has been changed to be a purely virtual function in Qt 5. So in his example, I simply comment out setRoleNames(m_prototype->roleNames()); in the constructor and everything should work. Right?
But instead, all of the defined roles are undefined, when accessed through QML. I can check the names in C++ with this:
QHash<int, QByteArray> mynames = model->find("Elephant")->roleNames();
qDebug() << "Model: " << mynames;
In this case, the role names for the Elephant object print as expected.
Are my assumptions correct, or do I need to do something else to get a QAbstractListModel object to share list element properties with QML2? This seems like a stupid question, but the Qt5 docs are so broken right now, I can't figure it out.
Thanks!
You need to reimplement QAbstractListModel::roleNames() const method and your roles get registered in QML automatically.
There's a working example of an exposing QAbstractListModel-based model to QML at examples/quick/modelviews/abstractitemmodel.
You can also consider usage of QQmlListProperty.