I have a QStandardItemModel which I display using a TableView in qml. It uses QVariant to store the data. I want to have custom delegates depending on which type the stored data has, e.g. like this
Component {
id: myDelegate
Loader {
property var roleTwo: model.two
sourceComponent: if( CODE_FOR_MY_ITEM_HAS_BOOL_TYPE) {
checkBoxDelegate}
else { stringDelegate}
}
}
However, I don't know how to check the type of an item in my model. How can this be achieved?
Upon request, I provide more context to this question:
As posted here Custom Model for TableView or QStandardItemModel I want to have a TableView with two columns, a parameter name and a parameter value. The goal is to have a list of editable parameters which control the behaviour of an algorithm. For this, I use a QStandardItemModel defined like this:
class mystandardmodel: public QStandardItemModel
{
public:
mystandardmodel();
enum Role {
role1=Qt::UserRole,
role2
};
explicit mystandardmodel(QObject * parent = 0): QStandardItemModel(parent){}
//explicit mystandardmodel( int rows, int columns, QObject * parent = 0 )
// : QStandardItemModel(rows, columns, parent){}
QHash<int, QByteArray> roleNames() const{
QHash<int, QByteArray> roles;
roles[role1] = "one";
roles[role2] = "two";
return roles;
}
};
This model right now is displayed like this:
TableView {
id: tableView2
x: 69
y: 316
width: 318
height: 150
TableViewColumn {
title: "Parameter Name"
role: "one"
}
TableViewColumn {
title: "Value"
role: "two"
delegate: myDelegate
}
model: myTestModel
}
Component {
id: myDelegate
Loader {
property var roleTwo: model.two
sourceComponent: if(typeof(roleTwo)=='boolean') {
checkBoxDelegate}
else { stringDelegate}
}
}
Component {
id: checkBoxDelegate
CheckBox{text: roleTwo}
}
Component {
id: stringDelegate
Text {text: roleTwo
}
}
So is this the way you would do it? Also, I am happy for hints on who to make the model editable.
If typeof() works for your use-case, then you can roll with it.
What I personally like to do is have an integer member to denote the particular type. This has an array of advantages, here are some:
you can use the same underlying data type to drive different gui types, for example, a string might be a name or an info, one might be editable, the other read only
it works in cases where you might have fundamentally different model items with fundamentally different sets of data
you can use it as an index, and have an array of Components, and in the loader simply use sourceComponent: guitypes[type]
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 have a model that works well with QTreeView. In the model I implemented a sort that looks like this:
void SimpleTreeModel::sort(Node* sortedNode)
{
emit layoutAboutToBeChanged(QList<QPersistentModelIndex>(), VerticalSortHint);
QModelIndexList oldIndices = persistentIndexList();
Node::SortType sortType = Node::Down;
//sort starting node
sortedNode->sortChildren(sortType);
QModelIndexList newIndices;
newIndices.reserve(oldIndices.size());
for(const auto &i : oldIndices)
{
Node* node = const_cast<Node*>(nodeFromIndex(i));
QModelIndex index = indexFromNode(node);
newIndices.push_back(index);
}
changePersistentIndexList(oldIndices, newIndices);
QModelIndex startingIndex = indexFromNode(sortedNode);
emit layoutChanged({ QPersistentModelIndex(startingIndex) }, VerticalSortHint);
}
when I call this function, QTreeView updates the view, but TreeView in QML don't do this.
QML TreeView usage:
TreeView
{
model: treeModel
TableViewColumn
{
title: "Title"
role: "title"
width: 700
}
}
What am I doing wrong? Why the view does not update the layout of the elements after sorting?
I think you need to delegate the tree view item. Data is provided to delegate.
Try changing your QML TreeView as shown below by adding itemDelegate
TreeView
{
model: treeModel
itemDelegate: Item {
Text {
color: styleData.textColor
text: styleData.value
}
}
TableViewColumn
{
title: "Title"
role: "title"
width: 700
}
}
Look into below link to understand the importance of delegate, between model and QML view. There is an image which easily explains.
http://doc.qt.io/qt-5/qtquick-modelviewsdata-modelview.html
Delegate - dictates how the data should appear in the view. The
delegate takes each data in the model and encapsulates it. The data is
accessible through the delegate.
Currently some students and I are programming a little application with QtQuick.
We have the following TableView:
TableView {
model: ListModel {
id: orderListModel
Component.onCompleted: {
var tOrderList = orderController.getOrderList();
for(var i = 0; i < tTicketList.length; ++i){
orderListModel.append(tOrderList[i]);
}
}
}
TableViewColumn {
role: "orderId"
title: "Auftragsnummer"
width: 100
}
TableViewColumn {
role: "customer.name"
title: "Kunde"
width: 100
}
}
getOrderList returns a QList<Object*> with all the orders.
The Order class has a property customer
Q_PROPERTY(Customer* customer READ getCustomer NOTIFY customerChanged)
which in turns has a property called name.
We would like to show the latter property inside the TableView but unfortunately only the orderId property of Order does works.
What value should have the second role? How can we access data of the nested object?
According to the documentation you can use QList<QObject*> as a model for a view directly, without manually copying the data as you did. In particular:
The QObject* is available as the modelData property. As a convenience, the properties of the object are also made available directly in the delegate's context.
Considering the example linked in the documentation we have that a property of the QObject can be used as role in the following way:
ListView {
width: 100; height: 100
model: myModel // injected QList<QObject*> context property
delegate: Rectangle {
height: 25
width: 100
color: model.modelData.color // without "modelData" it would not work
Text { text: name }
}
}
The same applies for nested properties. If our QObject-derived object has a QObject property, like in your case, we can retrieve it via modelData and then access it's properties. So, for instance with the customer property, we would have something like this in the delegate:
Text { text: model.modelData.customer.name }
That's true for ListView et similia whereas I'm not sure it could be done directly with TableView. One possible workaround would be to combine the usage of the QObject-derived role with styleData.value. You can define the usage of the role in the role property and access the inner property inside the styleData.value. A solution for your case would look like the following (assuming myModel is a context property as the example above):
TableView {
width: 100; height: 100
model: myModel
TableViewColumn {
role: "customer"
title: "Kunde"
width: 100
delegate: Text {
text: styleData.value.name // accessing the property
}
}
itemDelegate: Item { } // should be set to empty to avoid warnings
}
A downside of this approach is that you should implement a delegate for each column.
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
I'm very new with Qt / QtQuick.
I have a simple form designed with Qt Designer which consists uniquely of a ListView.
Item {
// ...
ListView {
id: listView1
anchors.fill: parent
model: FooModel
delegate: Item {
// ...
Row {
id: row1
Text {
text: foo
// ...
}
}
}
}
}
This works perfectly fine with the following - either by copy-pasting it inline as model, or by letting qmlscene or Qt Designer notice the FooModel.qml contained in dummydata:
ListModel {
ListElement {
foo: "1"
}
ListElement {
foo: "2"
}
ListElement {
foo: "3"
}
ListElement {
foo: "4"
}
}
However, when I try to replace my dummy model with an actual C++ model that subclasses QAbstractListModel, I have to replace text: foo with text: model.display.foo in the delegate:
delegate: Item {
// ...
Row {
id: row1
Text {
text: model.display.foo // <=== See?
// ...
}
}
}
If I don't, Qt complains that
qrc:/MainForm.ui.qml:23: ReferenceError: foo is not defined
and nothing is displayed.
I set the model property like this, in my main.cpp:
FooListModel* flm= new FooListModel();
QQmlContext *ctxt = engine.rootContext();
ctxt->setContextProperty("FooModel", flm);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
Why is this?
Also - assuming "it's a feature and not a bug" - having to change the text attribute means that I can no longer have my dummy data in QtDesigner or use inline models for prototyping; can I avoid this in any way?
Please find the MWE in question here on Github.
qrc:/MainForm.ui.qml:23: ReferenceError: foo is not defined
Means you have to define foo role in the FooListModel. For example,
class FooListModel : public QAbstractListModel
{
public:
enum FooListModelRoles
{
FooRole = Qt::UserRole + 1,
BarRole,
//...
}
QHash<int, QByteArray> roleNames() const //override
{
QHash<int, QByteArray> roleName;
roleName[FooRole] = "foo"; //define "foo" role for QML
roleName[BarRole] = "bar"; //define "bar" role for QML
return roleName;
}
//...
}
And delegates in QML can access foo role now.
Also - assuming "it's a feature and not a bug"
Yes, text: model.display.foo works fine because display is a pre-defined role in QAbstractItemModel.