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.
Related
I have 3 nested ListViews.The first is the month, second is day and third is hour of day.All of them constructs a calendar.There is a loader that loads a window when I click in the hour ListView, and set text in a Label.This text can be displayed in the hour view and can be deleted or edited through the above window .Unfortunately the text can not be saved because when I scroll the ListViews, delegates changing and not keeping the context of the label(That is normal I suppose).The objective is to be able to save those texts in the label(store the data) and restore them when the application is closed and re-opened.
Below is a generic code sample for 3 ListViews:
ApplicationWindow{
id:appwindow
............
Item{
id:dayView
...........
ListView{
id:monthofdayCalendar
orientation:Qt.Horizontal
model:12
delegate: Item{
ListView{
id:dayCalendar
orientation: Qt.Horizontal
model:32
delegate: Item{
...............
ListView{
id:daylistView
orientation: Qt.Vertical
model:24
delegate:Item{
id:hourItem
property string hourTime:hourweeklistviewLabel
property string notetaking:notesLabe
.............
MouseArea{
anchors.fill:parent
onClicked:{
windowLoader.active =true
daylistView.currentIndex=index
}
}
Rectangle{}
Label{
id:hourweeklistviewLabel
}
Label{
id:notesLabel
anchors.left:hourweeklistviewLabel.right
anchors.leftMargin: 30
text:""
}//Label
}//delegate:Item
}//ListView
} //delegate:Item
}//ListView
}//delegate:Item
}//Listview
}//Item
Below is the code of loader:
Loader {
id:windowLoader
focus: true
active:false
sourceComponent: Window{
id:inputWin
title:"Enter Note"
width:500
height:300
visible:true
onClosing:{
windowLoader.active=false
monthofdayCalendar.currentItem.daycalendarAlias.currentItem.dayList.currentIndex = calendarMonth.selectedDate.getDate() === new Date().getDate()
&& calendarMonth.selectedDate.getDay() === new Date().getDay()
&& calendarMonth.selectedDate.getMonth() === new Date().getMonth()?getHour():12
}
TextField {
id:title
x:50
y:20
placeholderText :'Enter Note'
text:monthofdayCalendar.currentItem.daycalendarAlias.currentItem.dayList.currentItem.notetaking.text
}
TextField{
id:timeDate
anchors.horizontalCenter: title.horizontalCenter
anchors.top:title.bottom
anchors.topMargin:10
placeholderText : calendarMonth.selectedDate.getDate() +"-"
+ (calendarMonth.selectedDate.getMonth()+1)+"-"
+ calendarMonth.selectedDate.getFullYear() + " "
+ monthofdayCalendar.currentItem.daycalendarAlias.currentItem.dayList.currentItem.hourTime.text
}
Button {
id: button
text: qsTr("Add Note")
anchors.centerIn:parent
onClicked: {
if (title.text !==""){monthofdayCalendar.currentItem.daycalendarAlias.currentItem.dayList.currentItem.notetaking.text= title.text}
else{}
}
}
}
}
The big question is how to save (store) the data of notesLabel.text and be able to display it and restore it every time I close and re-open the application.
As you can see the model for each ListView is not a ListModel so I think I can not use those models to save the data if I am right.If I am wrong please advise.
Anyway your help will be appreciateed.
EDIT
I've changed the integer models with ListModel dynamically created.The code of the ListModels is below:
ListModel{
id:hourlistModel
Component.onCompleted:{
for (var i = 0; i <25; i++){
append(createListElement())
}
}
property int h:0
function createListElement(){
return {
hour : h++
}
}
}
ListModel{
id:daylistModel
Component.onCompleted:{
for (var j=0; j <= 31; j++){
append(createListElement())
}
}
property int dD:0
function createListElement(){
return {
day : dD++
}
}
}
ListModel{
id:monthlistModel
Component.onCompleted:{
for (var k=0; k <=11; k++){
append(createListElement())
}
}
property int mN:0
function createListElement(){
return {
monthName : mN++
}
}
}
Can I store the data from Label notesLabel, now I've changed the models of ListViews with ListModels?
Thanks in advance.
I would create an exposed C++ class.
Using the exposed C++ class you have a range of options to pass data from the front/backend
Q_Property/Member
Q_Invokable
Signal/Slots
Given that you are using strictly strings, I would use an exposed series of QStrings or a QStringList.
QString
QStringList
To tackle to file read/write use your now exposed C++ class. You can either stick to file I/O via standard c++ or QFile system.
Constructor - Read the .txt file and save the data to your property data.
Exchange data as needed updating either the QML or C++ property member
Deconstructor - save the property member data back to file.
Brief example code:
someQML.qml
Import MyExposedClass 1.0
Item {
MyExposedClass {
id: myExposedClassID
text: myExposedClassID
}
Text{
id: yearTextID
text: myExposedClassID.year
}
Text{
id: monthTextID
text: myExposedClassID.month
}
Text{
id: dayTextID
text: myExposedClassID.day
}
Button {
id: myButtonID
onButtonPressed {
var finalStr = yearTextID + monthTextID + dayTextID
// If you used a QMember of qstring lets say
myExposedClassID.saveFile = finalStr
// If you used a QInvokable
myExposedClassID.saveFile_INVOK(finalStr)
}
}
}
myClass.h
class myClass : public QObject {
Q_OBJECT
// Serial Dev
Q_PROPERTY(QString day READ getDay WRITE setDay NOTIFY dayChanged)
Q_PROPERTY(QString month READ ... WRITE ... NOTIFY ...)
Q_PROPERTY(QString year READ ... WRITE ... NOTIFY ...)
...
// On construction, read the text file and update the qproperty variables
//implement getters and setters for qproperties
// On deconstruction, write the properties to file
}
If you have issues with MVC and QStrings/QStringlist. You may have to look into QVariants or QAbstracts.
I find QML to be a risky hole to dive into. Its easy to add more and more functionality to a QML file. But if you try and redesign it or change up some logic, it can very quickly ruin the QML. Splitting the QML into a QML & C++ is a nice way to achieve modularity and control.
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.
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)
}
}
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