Expose QAbstractListModel element properties to QML in Qt 5.0 - c++

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.

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

Mock a C++ class instantiated in QML

I am trying to write a plugin that contains some QML files and some C++ classes that provide lower-level functionalities and communicate with another application. They are used by the QML components.
I want to be able to manage the life time of these C++ objects from QML (i.e. they should be created when the QML file is loaded and destroyed when QML is destroyed), while still being able to mock the C++ objects.
I tried a few different approaches so far. Ideally, the result will be that I can use qmlscene on the QML file I want to edit and have a dummydata folder next to that file which contains the mock for the instantiated C++ class.
If I try that by using qmlRegisterType in a plugin class that inherits from QQmlExtensionPlugin (similar to the example in https://qmlbook.github.io/ch17-extensions/extensions.html), and I pass the resulting library to qmlscene, the QML file will not use the mock, but instantiate a C++ object instead. This means that sometimes, I need to start up a fair bit of logic to get some mocked data into my QML file.
It seems like the example in the "QML Book" suggests to completely design the QML component with a mock before introducing any C++ to QML. Is there a way to do that more sustainable? I guess, I could avoid using qmlRegisterType for a C++ class that I want to mock for a while, by commenting out the according line, but I would like to not have to do that.
The other approach I tried was using QQMLContext::setContextProperty from a central C++ controller class. That enables me to pass the C++ object to QML from C++ and also use the dummydata, however the object's lifetime will not be managed by the QML component, but from C++. Also, each class should potentially be instantiated multiple times and connecting signals properly is pretty error-prone. This is what I found so far:
auto proxy = std::make_shared<Proxy>();
//make `proxy` object known in QML realm
_qmlEngine.rootContext()->setContextProperty("proxy", proxy.get());
connect(&_qmlEngine, &QQmlApplicationEngine::objectCreated,
[&proxy](QObject *object, const QUrl &url) {
if (url == QUrl("qrc:/imports/Common/TestWindow.qml")) {
// make sure the proxy is not destroyed when leaving scope of this function
connect(qobject_cast<QQuickWindow *>(object),
&QWindow::visibilityChanged, // as a dirty workaround for missing QWindow::closing signal
[proxy]() mutable { proxy.reset(); }); // delete proxy when closing TestWindow
}
});
_qmlEngine.load(QUrl("qrc:/imports/Common/TestWindow.qml"));
Is there a "comfortable" way to mock data instantiated in QML and originally coming from C++, or is there at least a good way to attach the life time of such a C++ object to the life time of the QML object?
The way I solved this issue is as follows:
The actual production application will use a C++ plugin, containing only C++ files and no QML.
For mocking, there is a QML module with the same name as the C++ plugin, containing QML files which provide the same interface as the equivalent C++ classes. This module is passed to qmlscene in addition to the general QML includes.
If the C++ class header looks like this:
class Proxy : public QObject
{
Q_OBJECT
public:
Q_PROPERTY(int foo)
Q_INVOKABLE void start();
signals:
void started();
}
And this class is made available to QML like this:
qmlRegisterType<Proxy>("Logic", 1, 0, "Proxy");
The QML mock (in file Proxy.qml) can look like this:
import QtQml 2.12
QtObject {
signal started()
property var foo: 42
function start() { console.log("start") }
}
And be importable in QML with a qmldir file that looks like this:
module Logic
Proxy 1.0 Proxy.qml
The final call to qmlscene would be
qmlscene [path/to/prototype/qml] -I [path/to/folder/containing/proxy/mock/]

Load property from QML-file in 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.

QML property of property of C++ object

I am new to QML and have a problem in accessing a property.property of a C++ object:
C++, frequency and station both are Qt metatype registered objects:
CStation *station = new CStation(...); // QObject
CFrequency *frequency = new CFrequency(..); // QObject
QQmlContext *qmlContext = viewer.rootContext();
qmlContext->setContextProperty("myatcstation", station);
qmlContext->setContextProperty("myfrequency", frequency);
QML:
RowLayout { ....
TextField {
text: myatcstation.toQString(true)
}
}
.... text: myfrequency.toQString(true)
This works, but when I write: text: myatcstation.frequency.toQString(true) I do get TypeError: Object [object Object] has no method 'toQString'
frequency is a property of class CStation set as Q_PROPERTY(CFrequency frequency READ getFrequency)
Crosscheck in C++ works:
CFrequency test = station->property("frequency").value<CFrequency>();
-- Edit: Frank's answer --
Both classes are derived from QObject, and it is not as per textbook as they are made copyable. I am aware of the Identity vs value situation.
Basically frequency is a value object, but I have made it QObject based so I am able to use properties with it (see Any chance to use non QObject classes with QML ). In the example, toString is Q_INVOKABLE, frequency in the non-working case returns a copy of a QObject derived CFrequency object.
-- Edit: Further findings --
When I change the frequency property to return CFrequency* instead of CFrequency it does not work either. As I get TypeError: Cannot call method 'toQString' of undefined the situation seems to be the same. CFrequency alone works, but QML does not understand that myatcstation.frequency is a frequency object which has toString.
CFrequency isn't a QObject I assume, otherwise you wouldn't return it by value but by pointer. To make `toQString() callable from QML, it must be either Q_INVOKABLE or a slot, which means that CFrequency must be a QObject as well.
If a station has only one frequency, consider moving the relevant information into the station object, i.e. add the frequency information you need as properties to CStation.
To get updates when the frequency changes, consider to use a property such as Q_PROPERTY(QString frequencyAsString READ frequencyAsString NOTIFY frequencyAsStringChanged) instead of toQString(). Properties have the update mechanism "built-in" via property bindings, while there's no good way to tell QML that it should call toQString again because the frequency changed.
I solved a similar problem:
class TopObject : public QObject
{
Q_OBJECT
Q_PROPERTY(ValueObject* theValue ...
...
}
class ValueObject : public QObject
{
Q_OBJECT
Q_PROPERTY(QString value ...
...
}
In the main application:
qRegisterMetaType<ValueObject>("ValueObject");
qmlRegisterType<ValueObject>("com.name.comp", 1, 0, "ValueObject");
...->setContextProperty("topObject", new TopObject());
And in the qml code:
import com.name.comp 1.0
... {
text: topObject.theValue.value
...
It needed both, returning the property (ValueObject) as pointer and registering it with qmlRegisterType.
See also Exchange Data and Objects between C++ and QML and vice versa

Do I need to implement my own QAbstractTableModel?

I found this question: How to change the background color for a QTreeView Header (aka QHeaderView)?
I want to be able to set the color for each header section. So the question seen above seems to be my solution!
The solution says "the easiest way to do that is probably to derive a new model from QAbstractItemModel or another model class and reimplement the headerData()". I went and looked at the Qt source tree for QTableWidget, QTableModel, QTableWidgetItem... these classes are supposedly "default models" so I thought they would be a good example and then I would go implement my own QAbstractTableModel.
The 3 files I saw are a whopping 3300 lines of code. That is definitely NOT "the easiest way" IMO!!!
I would like the functionality of QTableWidget but then I want to add the following ability:
horizontalHeader.setSectionColor(index,color)
verticalHeader.setSectionColor(index,color)
Do I really need to inherit/implement QAbstractTableModel if all I want is to change the color of a section header?
Update:
I am not using my own custom view and model classes. I am using the convenience class QTableWidget right now (it is called a convenience class b/c it implements the view and model). The function headerData() is part of the model. The model class, QTableModel, is not accessible via Qt lib/headers so I can't inherit from it.
Update:
I tried creating a new item with background brush QBrush(QColor(Qt::red)) and then setting the table's header with the new item (using QTableWidget::setHorizontalHeaderItem(int column, QTableWidgetItem *item). I also tried inheriting QTableWidgetItem and overriding the virtual data() method with:
QVariant HeaderItem::data(int role) const
{
if(role==Qt::BackgroundRole) {
return QVariant(QBrush(QColor(Qt::red)));
} else if(role==Qt::ForegroundRole) {
return QVariant(QBrush(QColor(Qt::green)));
} else {
return QTableWidgetItem::data(role);
}
}
I can change the header sections foreground. But when I try to change the header's background brush... nothing happens... it's like the QTableWidgetItem's background brush that I set for the header section is ignored.
Instead of creating model with custom headerData() from scratch create subclass of QTableWidgetItem with desired implementation of QTableWidgetItem::data() and use the instances of this class for QTableWidget::setHorizontalHeaderItem.