Change C++ model with QML - c++

I want to extend the example called "Object ListModel Example" from Qt documentation
(you can get it on http://qt-project.org/doc/qt-4.8/declarative-modelviews-objectlistmodel.html).
I am trying to add a simple GUI functionality: a menu item that changes the content
(i.e. name) of the first data item in the model. Something like this:
MenuItem {
text: "Item 123"
onClicked: {
myModel.setProperty(0,"name","Item 123") //this gives me error
}
}
I am able to create a menu in QML but I cannot find the correct way to make changes in the model.
Btw, what is a difference between setContextProperty and qmlRegisterType (only the first one is used in this example but many other examples include the second one).

That kind of model is really not suitable for modification. There is no way for the view to be notified of changes. A better option is to use a QAbstractItemModel: http://qt-project.org/doc/qt-4.8/declarative-modelviews-abstractitemmodel.html
A simpler way to use a QAbstractItemModel is via QStandardItemModel: http://qt-project.org/doc/qt-4.8/qstandarditemmodel.html
setContextProperty() adds a single named property to the context. qmlRegisterType() registers a QObject-derived type with the QML engine, allowing it to instantiate that type. For example, the QDeclarativeItem type is registered with the engine as "Item", which is how the engine knows what to create when Item {} appears in QML code.

Related

How to add the UIA ExpandCollapse pattern in Qml

I'm using http://accessibilityinsights.io/ to make sure my QML application passes Microsoft requirements for accessibility.
There's only one error that I couldn't resolve.
Qml ComboBoxes don't have the ExpandCollapse pattern.
Code to reproduce:
ComboBox {
model: ["First", "Second", "Third"]
}
Accessibility Insights says "An element of the given ControlType must support the ExpandCollapse pattern. Section 508 502.3.10
And "How to fix":
1. Make sure the element has the appropriate ControlType property for its function.
2. If the current ControlType is correct, modify the element to support the ExpandCollapse pattern.
StackOverflow couldn't help me. Google couldn't help me (I tried looking everywhere...)
So I went to Qt's source code.
I found this: qt-everywhere-src-6.2.1\qtbase\src\plugins\platforms\windows\uiautomation\qwindowsuiamainprovider.cpp
And here's the part that was interesting:
case UIA_ExpandCollapsePatternId:
// Menu items with submenus.
if (accessible->role() == QAccessible::MenuItem
&& accessible->childCount() > 0
&& accessible->child(0)->role() == QAccessible::PopupMenu) {
*pRetVal = new QWindowsUiaExpandCollapseProvider(id());
}
Which means that there must be a MenuItem object (the qml combobox doesn't have that), nor a child that's a PopupMenu.
So I checked, and the ExpandCollapse pattern works fine in a menu (from QWidgets), but not in qml.
I couldn't change the combobox's role (Accessible.MenuItem), it doesn't register for some reason (perhaps because of the c++ backend)
I tried "hacking" this in qml, to see if it could work, with this:
MenuItem
{
text: "test"
Rectangle {
Accessible.role: Accessible.PopupMenu
visible:false // to make the state "collapsed" by default.
}
}
And ... it works. I have the pattern.
But obviously this isn't a combobox (not by name, not for accessibility, and not for functionality).
So here's my question: is there a way to maybe have my custom combobox to have the combobox Accessible.role, while having this structure (Accessible.MenuItem + Accessible.PopupMenu) to make qwindowsuiamainprovider.cpp add the ExpandCollapse pattern?
Or is the only way to find a way to either change qwindowsuiamainprovider.cpp, or override it or something?
I couldn't find any way to change it without recompiling Qt myself with this change (I would also have to check the license if I'm allowed to do it...)

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

How are parameters passed from Qml to C++?

I would like to use call a C++ function from Qml in the following way:
Item{
id: qmlItem
function loadModel(dataModel)
{
// setup my listview
}
MyDataModel{
id: model
}
Button{
onClicked: {
cpp.addValues(model)
loadModel(model)
}
}
}
Now in C++ I have the following function:
void myCpp::addValues(MyDataModel* model)
{
// here I would like to add items to my data model
model->addItem();
}
Now this would be possible if the model was passed as a pointer, but I am not sure if this is the behaviour of C++/Qml interaction. Would what I wrote work or there is another approach I need to use?
EDIT: I have compiled the program and it works fine. However, I would like to know what is going under the hood. I mean does QML sends parameters by pointer? Would the same approach work with a JavaScript array (instead of MyDataModel)?
Actually, if you want to use model for ListView which is passed from C++ you should look at the QAbstractItemModel or QAbstractListModel(maybe better and easier for your case) class from which you inherit and create your own model to use in the qml. To subclass QAbstractListModel from you'll at least need to reimplement rowCount() , data() methods, also you can reimplement insertRows() and removeRows() methods to dynamically add/remove data from model (which will also update the view automatically).
You should look up the http://doc.qt.io/qt-4.8/qabstractlistmodel.html or http://doc.qt.io/qt-4.8/qabstractitemmodel.html
. Also, there were some easy examples in qtcreator on how to implement this kind of model

Why is QML deleting my C++ ListView model?

Using Qt 5.5.1 on iOS 9 I'm trying to assign a dynamically created QAbstractListModel to the model property of a ListView:
Window {
ListView {
model: api.model()
delegate: delegate
}
Component {
id: delegate
Text { text: "Test" }
}
}
api is a C++ object assigned to the QML context with setContextProperty. The model method is a Q_INVOKABLE which returns a QAbstractListModel *. This all works, my ListView is populated with data.
The problem is when I start scrolling. Usually after the second full scroll (to the bottom, back up to the top and down again) my ListView starts to clear itself out. The debugger is telling me the QAbstractListModel is being destroyed.
I don't want to set CppOwnership on the model. Is there another way to prevent the ListView from destroying its model?
QML seems kind of broken in this regard, I've experienced completely arbitrary deletions of objects still in use in multiple scenarios. Objects with parents and referenced by the JS engine are being deleted for no apparent reason while JS garbage still takes hundreds of megabytes of memory instead of being freed. This applies to both objects returned from C++ and objects created in QML. When an object is returned from a C++ function to QML, ownership is passed to the QML engine, which makes the object vulnerable to such arbitrary deletions.
The solution is to force CPP ownership and manage the object's lifetime manually - keep in mind destroy() won't work on such objects, so you have to use a C++ function from QML to delete it.
qmlEngine.setObjectOwnership(obj, QQmlEngine::CppOwnership);
Also, as BaCaRoZzo mentioned, exposing the model as a property to api might be the appropriate form. It depends on whether the function is just an accessor to an existing object or it creates the object itself.
At any rate, keep in mind that QML object lifetime management at this point cannot and should not be trusted.
Even though I've accepted ddriver's answer I've found a solution that seems to better match what I wanted.
By dynamically loading my components and storing the model as a variable, I'm able to get QML to keep my C++ models alive and to destroy them when required, for example:
MyComponent {
property var model: api.createModel()
ListView {
model: model
delegate: delegate
[...]
}
Component { id: delegate [...] }
Component.onDestruction: model.destroy()
}
Unfortunately the model.destroy() call seems to be required. I was expecting the garbage collector to pick this up, but it doesn't seem to.
I've only tested this is toy examples so far, caveat lector.
Just to say - I can confirm the same issue on both Linux x86_64 and Android ARMv7.
MyComponent {
property var model: api.createModel()
ListView {
model: model
delegate: delegate
[...]
}
Component { id: delegate [...] }
}
Seems to be enough if you don't mind the model being destroyed later in time.
I want to add something to ddriver's answer, which is more than a comment.
This same problem came up for me. basically, i wanted to create a dynamic list view model (QAbstractListModel, in fact). The usual way is to put your models up front in main (or somewhere) like this:
QQmlContext* ctxt = engine.rootContext();
ctxt->setContextProperty("myModel", &model);
I have one model per object in this case, so i needed a dynamic solution.
I have a QObject which creates my model for a list. The model created derives from QAbstractListModel. The model is created and given out by my QObject host with a Q_INVOKABLE.
First problem is that the type of the model so generated is not known and must be registered. The usual qmlRegisterType does not work because QAbstractListModels cannot be copied. so you must register with qmlRegisterUncreatableType.
That's the first bit. Now the model works BUT who destroys it?
Turns out both my C++ code and QML both try to destroy the object since ownership was implicitly given to QML as part of the Q_INVOKABLE accessor.
BUT just letting QML clean up was bad. I tracked when this happened and it didn't happen at all in a timely manner. Basically it wouldn't clean up unless I did quite radial things like resize the window. presumably, it would eventually clean up (garbage etc.) but i really wanted these dynamic models to be cleaned up when their host QObject goes out.
So ddriver's idea is the way. but also remember to register with qmlRegisterUncreatableType.
eg,
inline MyModel* MyHostObject::getModel()
{
if (!_model)
{
_model = new MyModel(this);
// retain ownership of this object.
QQmlEngine::setObjectOwnership(_model, QQmlEngine::CppOwnership);
}
return _model;
}

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.