QML: Dynamic view re-ordering in original model - qt

Implemented QML Dynamic View Ordering by Dragging View Items using this Qt tutorial: QML Dynamic View Ordering Tutorial. Original underlying model is QAbstractListModel descendant in our case. Model stores data in a QList<QObject*> objectList; field type. Works fine, however item ordering changed in proxy DelegateModel only.
How to change items order automatically in original underlying model as well for other C++ and QML consumers where order matters? Or I could we otherway access some resulted (sorted) List Model model from C++ somehow?
Thanks for any help!

In the QML Dynamic View Ordering Tutorial 3 example I've replaced visualModel.items.move() call with my ObjectListModel::move() method like this:
ObjectListModel : public QAbstractListModel:
void ObjectListModel::move(int from, int to)
{
if(0 <= from && from < count() && 0 <= to && to < count() && from != to) {
if(from == to - 1) // Allow item moving to the bottom
to = from++;
beginMoveRows(QModelIndex(), from, from, QModelIndex(), to);
objectList.move(from, to);
endMoveRows();
}
}
Delegate component:
DropArea {
anchors { fill: parent; }
onEntered: {
let from = drag.source.DelegateModel.itemsIndex
let to = mouseArea.DelegateModel.itemsIndex
objectListModel.move(from, to)
}
}
And above works perfectly for the ListView and ObjectListModel itself - I have checked: items (and therefore objects) are moved correctly, indexes are correct, C++ consumers works just fine and take new order into account correctly, etc.
However another consumer like MapItemView fails to use the model after beginMoveRows/endMoveRows calls: moved item disappeared on the map and other manipulations with an item crashes the app.
Map {
...
MapItemView {
model: objectListModel
delegate: SomeItemIndicator {
}
}
}
Reported QTBUG-81076 bug, which is confirmed.
Workaround:
Found workaround for now: created 2nd duplicate model which content will be replaced completely on every change in the 1st model on every add/delete/moving(reordering). Above works since beginResetModel/endResetModel works correctly for MapItemView. So MapItemView now utilizes only the 2nd model. So on every 1st model change this method is called for the 2nd model:
QObjectList ObjectListModel::swapObjectList(const QObjectList& newlist)
{
QObjectList oldlist(_objectList);
beginResetModel();
_objectList = newlist;
endResetModel();
return oldlist;
}

Related

Detect changes in a property after the value is changed

I'm currently working in a project with Qt QML and I have a type that has a property (holds a JavaScript Object), and its reference changes constantly.
What I want to do is to detect the changes in this property and compare with a cached one.
I took a look into Qt Property Binding, but the callback function is executed before the change occurs and the property gets the value of the return of the callback. I want something to be executed after that (the property change), because I need to have the property dataset updated before emitting the signals (in the code the signals are emitted with kind of reflection thing, in the forEach callback).
What I've done so far:
Item {
id: root
property var dataset
Component.onCompleted: {
dataset = Qt.binding(function(){
return MediaWatcherHelper.formatDataset(internals.metadata);
// I want this code to execute after the binding occurs, but I obviously can't execute things after the return.
MediaWatcherHelper.detectChanges(root.dataset, internals.cache)
.forEach(it => root[`${it}Change`](dataset[it]));
internals.cache = root.dataset; // And update the cache
});
}
signal trackIdChange(string trackId)
signal albumChange(string album)
signal artistChange(var artist)
signal titleChange(string title)
signal urlChange(url url)
}
You always can keep the previous value and update it accordingly.
property int myCurrentValue: 10
property int myLastValue: myCurrentValue
onMyCurrentValueChanged: {
console.log("current: " + myCurrentValue + ", last:" + myLastValue)
myLastValue = myCurrentValue;
}

How to access inner item from parent in QML? [duplicate]

The document said we cannot have the same ID in one file. That means we can have the same id in different file,right? I don't know the scope of ID in QML,so i write the code as following to test it.
//a.qml
Item {
id: a_item
x:20;
y:b_item.x // cannot access this id
y:b1.x1 // can access
Item {
id:a1
x:20
Component.onCompleted : a1.x //this a1 is a.qml's a1 not the a1 in main.qml
}
}
//b.qml
Item {
id: b_item
x:20;
property int x1: 30;
}
//main.qml
Item {
a {
id:a1
Component.onCompleted : b1.x = 1 //can access
}
b {
id:b1
}
function() {
a_item.x = 1; // cannot access this id
}
}
My question:
1.
What is the scope of ID in QML? In my test, the result shows that Item cannot access the id of its childen and his brother 's chilren, but can access his parent or brother, right?
2.
the same id in different file just i show in my code, no error and i worked. But how can i differentiate them.
The canonical answer would be:
The scope of ids is the component scope.
And the component scope is:
Each QML component in a QML document defines a logical scope. Each
document has at least one root component, but can also have other
inline sub-components. The component scope is the union of the object
ids within the component and the component's root object's properties.
Which itself is not overly informative on what the scope exactly is and how can you make optimal use of it. A tad more informative:
In QML, component instances connect their component scopes together to
form a scope hierarchy. Component instances can directly access the
component scopes of their ancestors.
Basically, each id in a qml file is implemented sort of like a property of that source's root item. Except it cannot be accessed via someobj.someId, only via someId.
Which means that this id can be accessed by any object that exists in the branch that extends from the root object thanks to qml's dynamic scoping.
That is as long as it is not shadowed by an identically named id or property.
a_item will be visible in a.qml as well as any object that exists in the branch its root Item grows.
It won't be visible from main.qml as that object is further down the tree, where a_item is not defined.
In the same line of thought, b1 can be accessed from a.qml because b1 is defined in main.qml which is where a.qml is instantiated. But b_item will
not be visible from there.
In fact, since a1 and b1 are defined in main.qml which is the root of the entire application tree, those two ids will be visible from every object of the application, as long as it is a part of the object tree and as long as the identifiers are not shadowed. Note that they will not be visible from singletons or parent-less objects, as those are not part of the application object tree.
obj tree a1 b1 a_item b_item
main.qml D D X X
a.qm V V D X
Item a1 V V V X
b.qml V V X D
D - defined here, V - visible here, X - not available
The same is true for properties, although dynamic scoping only works for properties that are defined for the qml file root element, unlike ids which are visible even if they are on a different sub-branch, which is why in the first sentence of this answer I put it as "implemented sort of a property of that source's root item":
Obj
Obj
Obj
id: objid
property objprop
CustomObj
So objid will be visible in CustomObj, but objprop will not be, since it is not an id and not defined in the root object. The id is identical to doing this:
Obj
property objid : _objid
Obj
Obj
id: _objid
All ids from a given sources are visible in the qml source root object's context and subsequently everything else that will eventually drop down to this context as it lookup fails to resolve the identifier in the "higher" contexts.
Finally, keep in mind the subtle trap - it is only possible to use ids across sources if you know for certain that your application will instantiate the objects in a compatible context tree.
For example:
A.qml {
id: objA
B { } // objA will be visible to this object
}
main.qml
A {
B {} // objA will NOT be visible to this object
}
B {} // objA will NOT be visible to this object
The trap continues - context tree comes before object tree - the context in which an object is created matters, and cannot be changed once set (puts certain limits on reparenting depending on context dependencies).
// ObjA.qml
Item {
id: objA
Component {
id: cm
ObjB {}
}
function create() { cm.createObject(objA) }
}
// ObjB.qml
Item {
Component.onCompleted: console.log(objA)
}
// main.qml
Component {
id: cm
Rect {}
}
Obj {
anchors.fill: parent
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
if (mouse.button === Qt.LeftButton) {
cm.createObject(parent)
} else {
parent.create()
}
}
}
}
As this practical example illustrates, even though in both cases the newly created object is parented to the same object that has the objA identifier, the object created in main.qml cannot resolve it, because it is created in a context where objA is still not defined, but it works if the object is created in the context of objA, and it will work even if it is burred even higher up the tree.
To put it in a more generic way, an id becomes visible in the context of the source's root object and remains visible in every subsequent sub-context until it is shadowed by an identically named object. Visibility cannot reach down the tree to contexts that exist before the context the id is defined in.
Note the subtle difference - a_item refers to an Item whereas a1 refers to an a. And since a1 is visible inside a.qml it will always refer to that one instance of a that is in main.qml, regardless of which instance of a you might be in, whereas a_item will refer to a different object for each different instance of a. a_item is "relative" and will be different in every different instance of a but a1 is absolute and will always refer to a specific instance of a. This is the case because a1 is a concrete instance whereas a_item is a type / prototype.
// Obj.qml
Item {
id: obj
Component.onCompleted: console.log(obj === oid)
}
// main.qml
Obj { } // false
Obj { id: oid } // true
Dynamic scoping of ids can be quite useful and cut the time it takes to implement a workaround to get access to the stuff you need. Which is also why it is a very good idea to give the id descriptive names rather than just main.
For example, if you have a manager that manages a number of views, each with a number of objects in them, you can quickly get access the respective view for each object and also get access to the manager without having to implement any additional stuff. The rule of thumb is that the manager must come first, then each view should be created in the context of the manager, not necessarily directly in it, but in it nonetheless, and each object should be created in the context of a view. And of course take care not to shadow over things. If you break that rule things will not resolve properly.
View.qml { id: view }
manager
view1
object // view is view1
view2
object // view is view2
view3
object // view is view3
Naturally, this makes sense only in specific purpose designs where you know what the general structure of the context tree is gonna be like. If you are making generic elements that may go just about anywhere, you should absolutely not be relying on accessing ids across sources, and you should implement a more generic usage interface via properties, aliases and whatnot.

What is the scope of ID in QML?

The document said we cannot have the same ID in one file. That means we can have the same id in different file,right? I don't know the scope of ID in QML,so i write the code as following to test it.
//a.qml
Item {
id: a_item
x:20;
y:b_item.x // cannot access this id
y:b1.x1 // can access
Item {
id:a1
x:20
Component.onCompleted : a1.x //this a1 is a.qml's a1 not the a1 in main.qml
}
}
//b.qml
Item {
id: b_item
x:20;
property int x1: 30;
}
//main.qml
Item {
a {
id:a1
Component.onCompleted : b1.x = 1 //can access
}
b {
id:b1
}
function() {
a_item.x = 1; // cannot access this id
}
}
My question:
1.
What is the scope of ID in QML? In my test, the result shows that Item cannot access the id of its childen and his brother 's chilren, but can access his parent or brother, right?
2.
the same id in different file just i show in my code, no error and i worked. But how can i differentiate them.
The canonical answer would be:
The scope of ids is the component scope.
And the component scope is:
Each QML component in a QML document defines a logical scope. Each
document has at least one root component, but can also have other
inline sub-components. The component scope is the union of the object
ids within the component and the component's root object's properties.
Which itself is not overly informative on what the scope exactly is and how can you make optimal use of it. A tad more informative:
In QML, component instances connect their component scopes together to
form a scope hierarchy. Component instances can directly access the
component scopes of their ancestors.
Basically, each id in a qml file is implemented sort of like a property of that source's root item. Except it cannot be accessed via someobj.someId, only via someId.
Which means that this id can be accessed by any object that exists in the branch that extends from the root object thanks to qml's dynamic scoping.
That is as long as it is not shadowed by an identically named id or property.
a_item will be visible in a.qml as well as any object that exists in the branch its root Item grows.
It won't be visible from main.qml as that object is further down the tree, where a_item is not defined.
In the same line of thought, b1 can be accessed from a.qml because b1 is defined in main.qml which is where a.qml is instantiated. But b_item will
not be visible from there.
In fact, since a1 and b1 are defined in main.qml which is the root of the entire application tree, those two ids will be visible from every object of the application, as long as it is a part of the object tree and as long as the identifiers are not shadowed. Note that they will not be visible from singletons or parent-less objects, as those are not part of the application object tree.
obj tree a1 b1 a_item b_item
main.qml D D X X
a.qm V V D X
Item a1 V V V X
b.qml V V X D
D - defined here, V - visible here, X - not available
The same is true for properties, although dynamic scoping only works for properties that are defined for the qml file root element, unlike ids which are visible even if they are on a different sub-branch, which is why in the first sentence of this answer I put it as "implemented sort of a property of that source's root item":
Obj
Obj
Obj
id: objid
property objprop
CustomObj
So objid will be visible in CustomObj, but objprop will not be, since it is not an id and not defined in the root object. The id is identical to doing this:
Obj
property objid : _objid
Obj
Obj
id: _objid
All ids from a given sources are visible in the qml source root object's context and subsequently everything else that will eventually drop down to this context as it lookup fails to resolve the identifier in the "higher" contexts.
Finally, keep in mind the subtle trap - it is only possible to use ids across sources if you know for certain that your application will instantiate the objects in a compatible context tree.
For example:
A.qml {
id: objA
B { } // objA will be visible to this object
}
main.qml
A {
B {} // objA will NOT be visible to this object
}
B {} // objA will NOT be visible to this object
The trap continues - context tree comes before object tree - the context in which an object is created matters, and cannot be changed once set (puts certain limits on reparenting depending on context dependencies).
// ObjA.qml
Item {
id: objA
Component {
id: cm
ObjB {}
}
function create() { cm.createObject(objA) }
}
// ObjB.qml
Item {
Component.onCompleted: console.log(objA)
}
// main.qml
Component {
id: cm
Rect {}
}
Obj {
anchors.fill: parent
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
if (mouse.button === Qt.LeftButton) {
cm.createObject(parent)
} else {
parent.create()
}
}
}
}
As this practical example illustrates, even though in both cases the newly created object is parented to the same object that has the objA identifier, the object created in main.qml cannot resolve it, because it is created in a context where objA is still not defined, but it works if the object is created in the context of objA, and it will work even if it is burred even higher up the tree.
To put it in a more generic way, an id becomes visible in the context of the source's root object and remains visible in every subsequent sub-context until it is shadowed by an identically named object. Visibility cannot reach down the tree to contexts that exist before the context the id is defined in.
Note the subtle difference - a_item refers to an Item whereas a1 refers to an a. And since a1 is visible inside a.qml it will always refer to that one instance of a that is in main.qml, regardless of which instance of a you might be in, whereas a_item will refer to a different object for each different instance of a. a_item is "relative" and will be different in every different instance of a but a1 is absolute and will always refer to a specific instance of a. This is the case because a1 is a concrete instance whereas a_item is a type / prototype.
// Obj.qml
Item {
id: obj
Component.onCompleted: console.log(obj === oid)
}
// main.qml
Obj { } // false
Obj { id: oid } // true
Dynamic scoping of ids can be quite useful and cut the time it takes to implement a workaround to get access to the stuff you need. Which is also why it is a very good idea to give the id descriptive names rather than just main.
For example, if you have a manager that manages a number of views, each with a number of objects in them, you can quickly get access the respective view for each object and also get access to the manager without having to implement any additional stuff. The rule of thumb is that the manager must come first, then each view should be created in the context of the manager, not necessarily directly in it, but in it nonetheless, and each object should be created in the context of a view. And of course take care not to shadow over things. If you break that rule things will not resolve properly.
View.qml { id: view }
manager
view1
object // view is view1
view2
object // view is view2
view3
object // view is view3
Naturally, this makes sense only in specific purpose designs where you know what the general structure of the context tree is gonna be like. If you are making generic elements that may go just about anywhere, you should absolutely not be relying on accessing ids across sources, and you should implement a more generic usage interface via properties, aliases and whatnot.

Qtableview operations on filtered sets

I have a working Qtableview with custom model subclassed QAbstractTableModel and QAbstractItemModel.
I have a Qlineedit, onclicked it will filter the view:
// model.cpp
setFilter(QString strFilter) function searches trough my intern QList (this Qlist is actually attached to model) and if match found then: m_filterSet.insert(i);
This all works great. Problem is, i have CRUD operations for the tableview (insert row, delete row..) which also work great! But when selecting a row from a filtered set, i need to somehow know where in my QList exactly is this selected row from the filtered set (QSet ).
ui.myView->selectionModel()->currentIndex().row();
obvious gives the wrong indexes counting for the current view.
How can i somehow extract the value (int) from the selected row in the QSet?
Because when i added this function to model:
foreach (const int &value, m_filterSet)
qDebug() << value;
It has printed out successfully all the i values, e.g: 3410, 3411, 3412 (those are my client id's)
If i could extract this ID for the selected row in Qset, i could write a function that iterates my intern QList, and find a matching, so to speak:
if(m_Intern[i].nClientID == nId){ // nId = value inside Qset for selected row in view
return nIdx;
}
Qt has a solution for your problem - just use QSortFilterProxyModel. You will need to:
Subclass it and write your own filtering function (filterAccpetsRow)
Proxy your original model through filtering one
Attach filtering model to a view
use QSortFilterProxyModel::mapToSource() to convert between indexes in filtered and original model.
This allows you to have more than one view with just one source data model, each view may have different filters.
I solved it after a while re thinking, i just needed to implement another function inside my model:
int myClass::screenIndex2DataIndex(int nIdxScreen)
{
if(m_bUseFilter)
{
int nIdx =-1;
for(int i=0;i<m_lstIntern.size();i++)
{
if(m_filterSet.contains(i))
{
nIdx++;
if(nIdx == nIdxScreen){
return i;
}
}
}
return -1; //not found
}
else{
return nIdxScreen;
}
}
This way i can find out for the present index on the filtered view, where it is in my intern list.
After this it's easy to get my nClientID trough a return: return m_lstIntern[idx].nClientId

Property binding not updating

I am continuously getting data for my application as it runs, but I am having a bit of trouble displaying the data once I have read it in and stored it in a map.
When I try to display the data in the QML, it simply displays zero, despite the fact that I can see it updating in the application output.
I access the value in QML using property bindings (I was under the impression that these led headingSensor to be updated whenever carData.headingSensor changed?):
property int headingSensor: carData.headingSensor
Text { text: "Heading: " + headingSensor }
In my data class I have:
Q_PROPERTY(int headingSensor READ getHeadingSensor NOTIFY headingSensorChanged)
int headingSensor;
In the c++ implementation I originally had:
int data::getHeadingSensor(){
return data.value(heading)[headingSensorReading];
}
Where it returns the value in the map which is being updated with the incoming information.
This I realized, probably doesn’t work, because the property is dependent upon the headingSensor variable, which is itself not being updated despite the correct value being returned. So, I thought if I changed it to update the headingSensor value and return that it might work.
So in my data aquisition logic I wrote a method to update the variables as well.
data.insert(key, value);
updateVariables();
}
}
}
void data::updateVariables(){
headingSensor = data.value(heading)[headingSensorReading];
}
int data::getHeadingSensor(){
return headingSensor;
}
While this led to the headingSensor variable being updated in addition to the value in the map, the correct value is still not displayed in the QML display. It simply displays 0 (its default value when it is initially displayed since it has not gotten a value from incoming data yet).
So, I am wondering, how can I get the value of sensorHeading displayed in the QML to update as the value of it and/or the value in the map changes in C++? Do I need to do something like:
Connections {
target: carData
onSensorHeadingChanged: updateValues
}
EDIT:
Trying something like this, the onSensorHeadingChanged never fires. I am not sure why, since the value of sensorHeading clearly changes as I watch it in the application output
Connections{
target: carData
onHeadingSensorChanged: console.log("It's noting the change!")
}
It is the responsibility of the C++ element writer to emit headingSensorChanged() in order to cause the binding to be updated.
This tutorial is a good place to start when implementing a C++ element.
In your case you need to do something like this:
void data::updateVariables(){
int sensorReading = data.value(heading)[headingSensorReading];
if (headingSensor != sensorReading) {
headingSensor = sensorReading;
emit headingSensorChanged();
}
}
Note that we don't emit the change notifier unless there really is a change. This prevents needless JS evaluations, and also removes the possibility of binding loops.

Resources