I have an application that stores and edits notes. The list of notes is displayed in a listview like this:
Page {
id: noteList
title: i18n.tr("QNote")
visible: false
Column {
anchors.fill: parent
ListView {
anchors.fill: parent
model: notes
delegate: ListItem.Standard {
text: Title
onClicked: editNote(NoteText, Title, modelData);
progression: true
}
}
}
}
function editNote(text, title, item) {
pageStack.push(noteEdit, {title: title, text: text});
handler.setActiveItem(item);
}
The notes item is a NoteListModel that subclasses the QAbstractListModel and contains NoteListItems. What I would like to do is to store the currently selected NoteListItem so I could easily access the Note object inside when the user wants to save the modified note. However, I don't know how to access the backing NoteListItem from the qml delegate. the modelData seems to be something else. Is there any way to do so? If i could wrap the Note object in a QVariant I could access it easily through roles but when I tried it like this
QVariant NoteListItem::data(int role) {
switch (role) {
case Title:
return note.getTitle();
case NoteText:
return note.getText();
case NoteObject:
return QVariant::fromValue(note);
default:
return QVariant();
}
}
it resulted in a compiler error saying
qmetatype.h:642: error: invalid application of 'sizeof' to incomplete type 'QStaticAssertFailure'
Or should i try to access the selected list item from the backing code? Is there any way for that? Dou you have any ideas?
Thanks for your time. Regards,
Peter
This took me a very long time to find, as there are many incorrect solutions on Stackoverflow.
The pure QML way is to use a DelegateModel and access it from QML as follows:
import QtQuick 2.4
import QtQml.Models 2.1
ListView {
property var currentSelectedItem
onCurrentItemChanged{
// Update the currently-selected item
currentSelectedItem = myDelegateModel.items.get(currentIndex).model;
// Log the Display Role
console.log(currentSelectedItem.display);
}
model: DelegateModel {
id: myDelegateModel
model: myAbstractItemModel
delegate: {
// Define delegates here
}
}
}
This line returns an object (var) that you can access in the same way as within a delegate:
myDelegateModel.items.get(currentIndex).model
This example assumes you are only using the default DelegateModelGroup.
See http://doc.qt.io/qt-5/qml-qtqml-models-delegatemodel.html
and http://doc.qt.io/qt-5/qml-qtqml-models-delegatemodelgroup.html#get-method method
Related
I am building a mapping program that eventually wants to display a different item on the map based on a value within the model. To facilitate this, I'm using Loader in my MapItemView delegate.
Unfortunately, when I tested this, the Loader method displayed nothing.
MapItemDelegate.qml
MapQuickItem {
id: waypoint
anchorPoint.x: image.width/2
anchorPoint.y: image.height/2
coordinate: task.waypoint //task extends QObject with members taskname and waypoint
sourceItem: Grid: {
columns: 1
rows:2
horizontalItemAlignment: Grid.AlignHCenter
Image {
id: image
source: "Waypoint.png"
height: 32
width: 32
}
Text {
id: text
text: task.taskname
}
}
}
main.qml
MapItemView {
id: taskview
model: tasklistmodel //Extends AbstractListModel to contain task objects
delegate: Component {
//Desired method, fails to display.
Loader {
source:"MapItemDelegate.qml"
// Will eventually be:
// source: task.typename+"MapItemDelegate.qml" to facilitate several types.
}
//Control method. Works fine, but does not meet my requirements.
MapItemDelegate {
//Nothing further needed.
}
}
}
How do I get MapItemView to actually display MapQuickItems loaded with Loader? Is there another method of dynamically loading delegates for display?
As it turns out, using a Loader within a delegate of MapItemView is not possible, as the API demands the Component contain a single mapping object. While Loader did load a mapping object, it is not in itself a mapping object, which caused the object to fail to display.
What I ended up doing is implementing a custom QSortFilterProxyModel that overrides filterAcceptsRow to filter tasklistmodel to match the task.typename property, then editing my MapItemView as follows:
MapItemView {
id:taskview
model: TaskListFilterModel { // custom extension of QSortFilterProxyModel
acceptType:"dothisthingtype"
sourceModel: tasklistmodel
}
delegate:MapItemDelegate {
}
}
With that, I can add a MapItemView for each type of task I plan to add in the future. While it's far less elegant and way more work than I had hoped to achieve with Loader, it does at least meet my requirements.
Prologue
QAbstractListModel might be a solution, but I think it is a bit overkill for me ... not sure
Intro
I'm making a vector inside C++ to be accessible from within QML:
class MyClass : public QObject
{
Q_OBJECT
Q_PROPERTY(QVector<QString> items READ items WRITE setItems NOTIFY itemsChanged)
// ...
}
As I have tested, I can access my vector from within QML:
console.log("myClass.items >>> ", myClass.items) // Logs vector of strings
Question
On my QML UI, I intend to show a list of the strings inside the vector. I try to use ListModel and ListView but I don't know how to do it. Can anybody help?
ListModel {
id: myListModel
// How to compose my model here according to vector of strings
// i.e. myClass.items
}
ListView {
model: myListModel
delegate: {
// ... show the strings inside the vector
}
}
UPDATE
As suggested by #Amfasis and #mohabouje, I modified my code like this, but it is not working for some reason.
C++ side:
class MyClass : public QObject
{
Q_OBJECT
Q_PROPERTY(QStringList items READ items WRITE setItems NOTIFY itemsChanged)
//...
}
QML side:
Row {
ListView {
id: items
model: myClass.items // Directly connect to C++ string list
delegate: Text {
text: modelData // Strings are NOT displayed on QML UI
// Log of modelData shows it is empty
}
}
}
On QML I log myClass.items and I see my string list:
console.log("myClass.items >>> ", myClass.items)
// logs:
// qml: myClass.items >>> [item 0,item 1,item 2]
Finally Fixed
When setting or modifying items, I need to emit the modification signal otherwise it won't work:
m_items = /* set items here */;
emit itemsChanged(m_items); // This is required!
You should expose your class interface to the QML engine. In this case, you can replace your QVector<QString> by an QStringList.
class MyModel: public QObject {
Q_OBJECT
Q_PROPERTY(QStringList model READ model NOTIFY modelChanged)
...
};
If you take a look into the docs:
Models that do not have named roles (such as the ListModel shown below) will have the data provided via the modelData role. The modelData role is also provided for models that have only one role. In this case the modelData role contains the same data as the named role.
So, you should use the property modelData:
ListView {
model: mymodel.model
delegate: Text {
text: modelData
}
}
To expose your C++ class, take a look into this page.
I enriched a TextField (displaying a float) so that it can be changed by mouse wheel, all while being still editable by hand.
I found the quirk with forceActiveFocus and onClicked here (I wish I could just let all events pass down the widget stack like in Qt) and use onWheel to change the value (please let me know if this is not the best way to do this):
TextField{
text: cxxObject.floatAttribute.toString()
onEditingFinished: { cxxObject.floatAttribute=parseFloat(text); }
MouseArea{
anchors.fill: parent
propagateComposedEvent: true
onClicked: { parent.forceActiveFocus(); }
onWheel: { parent.text=parseFloat(parent.text)-.5*wheel.angleDelta.y/120;
parent.editingFinished();
}
}
I would like to re-use this component instead of TextField in several places (without copy&paste), so I tried to declare the component like this:
Component{
id: wheeledFloatTextField
property real initValue: 0.
property real dWheel: 0.5
signal editingFinished(real value);
TextField{
text: parent.initValue.toString();
// re-emit signal to the component
// so that user-defined slot can be defined when re-used
onEditingFinished: parent.editingFinished(parseFloat(text));
// validator: ...
MouseArea{
anchors.fill: parent
propagateComposedEvents: true
onClicked: { parent.forceActiveFocus(); }
onWheel: {
parent.text=parseFloat(parent.text)-parent.parent.dWheel*wheel.angleDelta.y/120;
parent.editingFinished();
}
}
}
}
and re-use:
Loader{
sourceComponent: wheeledFloatTextField
initValue: cxxObject.floatAttribute;
onEditingFinished: { cxxObject.floatAttribute=value; }
}
I am however getting (at the line where Component is used):
Component objects cannot declare new properties.
What is wrong? I was some posts (like How do you assign a QML Item to a component property in QML and then use that object inside the component? and https://developer.blackberry.com/native/documentation/dev/custom_components/index.html) from which I am gathering I might need to wrap the inside of Component (which is as-if it were a separate .qml file and does not define a scope) in something like Item or Container but I am not sure what to do. Any hint?
I would like to keep the definition inline first, later move to a separate file.
If you have the component declared in a separate file, you can (should) omit the top-level Component. For maximum reusability of components, it is reccomended to declare them in a separate file.
A Component can not have any properties declared. It is basically stopping the object creation in a prototypical state. That is useful if you want to configure the object, for later creation, for example lazy initialization (delegates).
If you have a property of type Component and you use the myProp: SomeType {...} syntax, it will automatically just create a component from that.
I think the best solution is to put your TextField in a seperate file, and add the properties to the root-node so it is customizable.
File1 (e.g. "CustomTextField.qml")
TextField{
property real initValue: 0.
property real dWheel: 0.5
signal editingFinished(real value);
text: initValue.toString();
// re-emit signal to the component
// so that user-defined slot can be defined when re-used
onEditingFinished: editingFinished(parseFloat(text));
// validator: ...
MouseArea{
anchors.fill: parent
propagateComposedEvents: true
onClicked: { parent.forceActiveFocus(); }
onWheel: {
parent.text=parseFloat(parent.text)-parent.parent.dWheel*wheel.angleDelta.y/120;
parent.editingFinished();
}
}
}
You can then reuse the Component in all known ways like in a Loader:
Loader {
sourceComponent: CustomTextField { // Property type is component, so it automatically creates a Component instead of the full-blown object, until it is loaded.
initValue: 12
dWheel: 42
}
...
}
or without Loader
CustomTextField {
...
}
Of course you can keep it inline, but even then, you have to add the properties to the root-element inside the Component.
Component {
id: componentId // only thing you can set besides one Object in a Component
TextField{
id: componentRoot // You can't reference this id from outside the Component!!!
property real initValue: 0.
property real dWheel: 0.5
signal editingFinished(real value);
text: initValue.toString();
// re-emit signal to the component
// so that user-defined slot can be defined when re-used
onEditingFinished: editingFinished(parseFloat(text));
// validator: ...
MouseArea{
anchors.fill: parent
propagateComposedEvents: true
onClicked: { parent.forceActiveFocus(); }
onWheel: {
parent.text=parseFloat(parent.text)-parent.parent.dWheel*wheel.angleDelta.y/120;
parent.editingFinished();
}
}
}
}
This has the down-side that you will always need a separate object to instantiate the Component, like a Loader which adds overhead and complicates communication in the file between the objects, since to address it, you will need to use: loaderId.item.property which might be expensive in lookup, you need to ensure that item is defined e.t.c.
I have a QML application where I'm creating lists of elements received from JavaScript. Using the details from this answer I'm populating the model as a JS array, and it works great. However, I'd like it so that when properties of the JavaScript objects change that ListView items driven from them update live.
Here's a simple test app showing the problem. The ListView is properly populated with MyRow instances showing the correct id/title, but when the rand property is changed by the timer, the List rows are unchanged (they show 0 for the last item).
MyRow.qml
import QtQuick 2.0
import QtQuick.Layouts 1.3
Rectangle {
property var obj
color:'#eeeeff'; height:20
RowLayout {
anchors.fill:parent
Text { text:obj.id }
Text { text:obj.title; Layout.fillWidth:true }
Text { text:obj.rand }
}
}
main.qml
import QtQuick 2.7
import QtQuick.Window 2.2
Window {
id:app; visible:true; width:200; height:100
property var database: ({"17":"World","42":"Hello"})
property var objById: ({})
function getObj(id){
if (!objById[id]) objById[id] = { id:id, title:database[id], rand:0 };
return objById[id];
}
ListView {
id:mylist
anchors.fill:parent
model: [42,17] // object ids
delegate: MyRow {
width:parent.width
obj:getObj(mylist.model[index])
}
}
Timer { // Update every object's rand value every second
interval:1000; running:true; repeat:true
onTriggered: {
Object.keys(objById).forEach(function(id){
objById[id].rand = Math.random()*100<<0;
})
}
}
}
How can I get the delegate's Text items to update their text when properties of the objects change?
The easiest (only?) way to get property bindings to work properly is to create real Qt objects to hook the values to. If you don't want to use a ListModel (because you want to quickly populate a model with items from a master library), then you can use createObject() to generate objects and pass them to your delegate.
Here's an updated main.qml that works as desired:
Window {
// ...same as above...
Component { // Creates real Qt objects with bindable properties
id:objFactory
QtObject {
property int id
property string title
property int rand:0
}
}
function getObj(id){
if (!objById[id])
objById[id] = objFactory.createObject( app, {id:id,title:database[id]} );
return objById[id];
}
// ...same as above...
}
Additionally, you may wish to change the property var obj in MyRow.qml to a more specific property QtObject obj (or a more specific object type, depending on what you pass in).
Finally, note that it's slightly cleaner/simpler to use modelData instead of mylist.model[index]:
ListView {
anchors.fill:parent
model: [42,17] // object ids
delegate: MyRow {
width:parent.width
obj:getObj(modelData)
}
}
Similar to the this keyword in C++, I’d like to either have a QML element to pass itself into a JS function, or have it set a property on another element to itself. Is this possible?
For example:
Rectangle{
id:theParent
property var theElement
SomeElement{
id:theChild
MouseArea {
anchors.fill:parent
onClicked: {
someJsFunction(*whatGoesHere*)
parent.theElement=*whatGoesHere*
}
}
Or, Consider this:
Rectangle{
id:theParent
property var theElement
SomeElement{
id:theChild
}
Then, in SomeElement.qml:
Rectangle{
MouseArea {
anchors.fill:parent
onClicked: {
someJsFunction(*whatGoesHere*)
parent.theElement=*whatGoesHere*
}
}
}
In this case, the *whatGoesHere* would be the instance of SomeElement where these are being called from.
Is this possible in QML? I would think the id property would make sense, but according to the docs, you cannot query the value of the id field, and anyway the id wouldn't be available if my SomeElement was described in a separate file, and the whatGoesHere handling above appeared in that separate file rather than in a particular instance.
I have two complementary proposals :
First, for a single usage, pass the ID as it's basically a pointer to the item :
MouseArea {
id: myClicker;
onClicked: { callFunc (myClicker); }
}
Then if you need multiple items to share this behavior, that means you're using MVC so the ID will work exactly the same :
Repeater {
model: 100;
delegate: MouseArea {
id: myClicker;
onClicked: { callFunc (myClicker); }
}
}
That is the classical part.
But to todo even better if you create your own components, keep in mind to create a 'self' helper property that does the 'this' job properly :
MouseArea { // component root definition
id: base;
property var self : base; // bind self on the I
}
Then use it like this :
Repeater {
model: 100;
delegate: MyComponent {
onClicked: { callFunc (self); }
}
}
Use this as often as you want !
Instance of your SomeElement is its id property value i.e. theChild. You can use it as this. No other built-in way exists as far as I can tell. But you can try to add your own QML item hierarchy with property which will return this.
Another way is to get children of some parent and use them. So you get children, locate the child you need and use that particular instance
If you define your element in a separate file, then you can simply assign an id and use it. It will be valid just within the context of that instance:
SomeElement.qml
Rectangle{
id: thisElement
MouseArea {
anchors.fill: parent
onClicked: {
someJsFunction(thisElement);
parent.theElement = thisElement;
}
}
}