Update c++ model on qml delegate visual model change - c++

There's a simple QStandardItemModel defined in c++ which I am displaying in a QML ListView via custom Delegates and a DelegateModel. The ListView can be reordered via Drag'n Drop:
// The DropArea is part of the delegate `comp_container`
DropArea{
anchors{fill: parent}
keys: ["pageitem"]
onEntered: {
let from = drag.source.DelegateModel.itemsIndex
let to = dragAreaPage.DelegateModel.itemsIndex
if ( pageItemDragOperationStartIndex === -1 ){
pageItemDragOperationStartIndex = from
}
pageItemDragOperationFinalIndex = to
console.log(from + "->" + to)
visualModel.items.move(from,to)
}
}
Here is the delegate model and pageproxymodel is the c++ model.
DelegateModel {
id: visualModel
model: pageproxymodel
delegate: comp_container
}
How do I want to update the c++ model?
The delegate's top level item is a MouseArea and I handle the reordering in the release handler:
onReleased: {
if ( pageItemDragOperationStartIndex !== -1 && pageItemDragOperationFinalIndex !== -1 ){
console.log("Page item final drag operation: " + pageItemDragOperationStartIndex + "->" + pageItemDragOperationFinalIndex)
pageproxymodel.move(pageItemDragOperationStartIndex, pageItemDragOperationFinalIndex)
pageItemDragOperationStartIndex = -1
pageItemDragOperationFinalIndex = -1
}
}
The c++ model's move function forwards the call to this handler:
bool PageModel::moveRow(const QModelIndex &sourceParent,
int sourceRow,
const QModelIndex &destinationParent,
int destinationChild)
{
if ( sourceRow < 0 || sourceRow > rowCount()-1 ||
destinationChild < 0 || destinationChild > rowCount() )
{
return false;
}
beginMoveRows(sourceParent, sourceRow, sourceRow, destinationParent, destinationChild);
QList<QStandardItem*> rowItems = takeRow(sourceRow);
insertRow(destinationChild, rowItems);
endMoveRows();
return true;
}
With the above c++ model code, it crashes at the release handler in QML:
I've tried other things to see the effect, no crashes, but also not the expected behaviour.
deleting a single row (which deletes 2 (!) rows in the QML ListView)
deleting a single row without begin/end calls (deletes 1 rows in the QML ListView, but can't be right)
remove and insert a single row without begin/end calls (QML ListView looks fine for a while but comes out of sync after a few moves)
Basically all I want to do is to save the ListView state via the c++ model, after all that is a standard use case and something simple must be wrong on my side, yet I can't see it.

One thing I like to do with DelegateModel makes use of DelegateModelGroup. By declaring a group named "all", it introduces an attached property allIndex which is useful for tracking an item after it has been reordered. The following example implements a DelegateModel with both MouseArea and DropArea. When in dragging mode, I disable all MouseArea so that the DropArea can have a chance at responding.
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Page {
property int activeMouseArea: -1
ListView {
id: listView
width: 420
height: parent.height
model: SampleDelegateModel { }
ScrollBar.vertical: ScrollBar {
width: 20
policy: ScrollBar.AlwaysOn
}
}
footer: Text { id: dbg }
}
// SampleData.qml
import QtQuick
import QtQuick.Controls
ListModel {
ListElement { name: "Steve Jobs" }
ListElement { name: "Jeff Bezos" }
ListElement { name: "Bill Gates" }
ListElement { name: "Elon Musk" }
}
// SampleDelegateModel.qml
import QtQuick
import QtQuick.Controls
import QtQml.Models
DelegateModel {
id: delegateModel
model: SampleData { }
delegate: SampleDelegate { }
groups: [
DelegateModelGroup {
id: allItems
name: "all"
includeByDefault: true
}
]
filterOnGroup: "all"
function moveItem(from, to) {
dbg.text = `Debugging: moveItem(${from},${to})`;
allItems.move(from, to);
}
}
// SampleDelegate.qml
import QtQuick
import QtQuick.Controls
import QtQml.Models
Rectangle {
property int allIndex: DelegateModel.allIndex
width: 400
height: labelText.height + 20
border.color: "grey"
z: mouseArea.drag.active || mouseArea.pressed ? 2 : 1
property int dragTo: -1
Drag.active: mouseArea.drag.active
Text {
id: labelText
anchors.centerIn: parent
text: allIndex + ": [" + index + "] " + name
}
DropArea {
anchors.fill: parent
onEntered: drag.source.dragTo = allIndex
}
MouseArea {
id: mouseArea
anchors.fill: parent
drag.target: parent
property point startPoint
enabled: activeMouseArea === -1
onPressed: {
activeMouseArea = allIndex;
dragTo = -1;
startPoint = Qt.point(parent.x, parent.y);
}
onReleased: {
activeMouseArea = -1;
[parent.x,parent.y] = [startPoint.x, startPoint.y];
Qt.callLater(delegateModel.moveItem, allIndex, dragTo);
}
}
}
You can Try it Online!

Found the mistake:
The pageItemDragOperationStartIndex and pageItemDragOperationFinalIndex variables where part of each delegate, but not of the page.
Also, as was pointed out in the comments, using a QStandardItemModel it is not necessary to call the begin/end functions. Now it works like a charm.

Related

How to access nested QStandardItemModel's items from QML?

Background
I have a tree-like QStandardItemModel, whose items I would like to access from QML.
Here is how the model is defined on the C++ side:
backend.h
class Backend : public QObject
{
Q_OBJECT
Q_PROPERTY(QStandardItemModel *model READ model CONSTANT)
public:
explicit Backend(QObject *parent = nullptr);
QStandardItemModel *model() const;
private:
QStandardItemModel *m_model;
};
backend.cpp
Backend::Backend(QObject *parent) :
QObject(parent),
m_model(new QStandardItemModel(this))
{
auto *itemFirst = new QStandardItem(tr("First"));
auto *itemSecond = new QStandardItem(tr("Second"));
auto *subItem = new QStandardItem(tr("First_02"));
subItem->appendRow(new QStandardItem("First_02_01"));
itemFirst->appendRow(new QStandardItem(tr("First_01")));
itemFirst->appendRow(subItem);
itemFirst->appendRow(new QStandardItem(tr("First_03")));
itemSecond->appendRow(new QStandardItem(tr("Second_00")));
itemSecond->appendRow(new QStandardItem(tr("Second_01")));
m_model->appendRow(itemFirst);
m_model->appendRow(itemSecond);
}
QStandardItemModel *Backend::model() const
{
return m_model;
}
The model is exported to QML in main.cpp like this:
Backend backend;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("backend", &backend);
qmlRegisterUncreatableType<QStandardItemModel>("QStandardItemModel", 1, 0, "QStandardItemModel", "The model should be created in C++");
Using TreeView from QtQuick.Controls 1.4 in main.qml like this:
TreeView {
anchors.fill: parent
model: backend.model
TableViewColumn {
title: "Name"
role: "display"
}
}
I get the desired results, i.e. all items nested correctly:
Problem
When I try to manually iterate over the nested items using Repeater and DelegateModel like this:
ColumnLayout {
anchors.fill: parent
Repeater {
model: backend.model
Item {
Layout.fillWidth: true
Layout.fillHeight: true
ColumnLayout {
anchors.fill: parent
Text {
color: "blue"
text: model.display
}
Repeater {
model: DelegateModel {
model: backend.model
rootIndex: modelIndex(index)
Item {
Layout.fillWidth: true
Layout.fillHeight: true
ColumnLayout {
anchors.fill: parent
Text {
color: "green"
text: model.display
}
Repeater {
model: DelegateModel {
model: backend.model
rootIndex: modelIndex(index)
Text {
color: "red"
text: model.display
}
}
}
}
}
}
}
}
}
}
}
the main branches (marked with blue) and the items on the first nesting level (marked with green) are the right ones, but I get the wrong items on the second nesting level (marked with red):
How to fix the code to correctly iterate over the QStandardItemModel's items on each nesting level?
The problem is in these two lines: rootIndex: modelIndex(index).
index is the index of the 'parent' model, but modelIndex(...) is the method of the current model.
I've tried it with this (slightly modified) piece of code and it worked:
Repeater {
model: DelegateModel {
id: model1
model: backend.model
delegate: ColumnLayout{
Text {
text: "Data: " + display
}
Repeater {
model: DelegateModel {
id: model2
model: backend.model
// 'index' comes from 'model1', so use the 'modelIndex' method from 'model1'
rootIndex: model1.modelIndex(index)
delegate: ColumnLayout{
Text {
text: "- Data: " + display
}
Repeater {
model: DelegateModel {
id: model3
model: backend.model
// 'index' comes from 'model2', so use the 'modelIndex' method from 'model2'
rootIndex: model2.modelIndex(index)
delegate: Text {
text: "-- Data: " + display
}
}
}
}
}
}
}
}
}

StackPanel Equivalent In Qt Quick 2 / QML - Problem With Width

I'm trying to make an equivalent to wpf stackpanel, I already had a logic and implemented it but something is wrong about width, I don't know how to create new components without getting into width loop binding, here is my stackpanel:
StackPanel.qml
import QtQuick 2.12
import QtQuick.Controls 2.12
import KiMa.Models 1.0
Item {
id:root
property var orientation : UOrientation.Horizontal
property int itemSpacing : 10
default property list<Item> pageData
Loader{
property var childs
anchors.fill: parent
id:loader
onChildsChanged: {
if(root.pageData != null){
for(var z = 0;z<root.pageData.length;++z){
root.pageData[z].parent = loader.childs
}
}
}
}
state: orientation == UOrientation.Horizontal ? "row": "col"
states: [
State {
name: "row"
PropertyChanges {
target: loader
sourceComponent : row
}
},
State{
name: "col"
PropertyChanges {
target: loader
sourceComponent : col
}
}
]
Component{
id:col
Column{
Component.onCompleted: {
childs = _col;
}
id:_col
width: parent.width
spacing: root.itemSpacing
}
}
Component{
id:row
Row{
Component.onCompleted: {
childs = _row
}
id:_row
width: parent.width
layoutDirection: Qt.RightToLeft
spacing: root.itemSpacing
}
}
}
and my orientation enum is like this:
#ifndef UORIENTATION_H
#define UORIENTATION_H
#include<QObject>
class UOrientation
{
Q_GADGET
public:
explicit UOrientation();
enum Orientation{
Horizontal,
Vertical
};
Q_ENUM(Orientation)
};
#endif // UORIENTATION_H
and usage example should be like this:
StackPanel{
x: 320
height: 50
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: 25
Button{
}
Button{
}
}
you need to add this into main.cpp:
qmlRegisterUncreatableType<UOrientation>("KiMa.Models",1,0,"UOrientation","its not creatable type!");
This code is working , if you have anything to suggest me to change or you think I made a mistake let me know, the only problem I can see here is width binding.
I already tried using childrenRect but it is not working:
width: childrenRect.width
height: childrenRect.height
Note : stackpanel allowing you to stack item after item on top of each other you can set orientation to horizontal or vertical so in qt its a column and row together which i made it already.
vertical one :
horizontal one :
You can do this easily with a Grid by setting the number of columns.
If you want a separate component, you can create your StackPanel.qml with the following:
import QtQuick 2.0
Grid {
property int orientation: Qt.Horizontal
columns: orientation === Qt.Horizontal ? -1 : 1
}
If you want a scrollable object, you could also use a ListView with an ObjectModel model. ListView has an orientation property.

QML TreeView display nodes by levels or custom delegate

I have a tree model derived from a QAbstractItemModel. And I can display the data in a tree like way.
What I want is to display teh data by the layers. To display only one level of a layer at a time AND put each layer on a stack and navigate backwards by poping the layer from the stack.
I guess I have to implement a custom delegate? Any advice would be highly appreciated. Thank you.
I recently implemented something similar, based on a QFileSystemModel, set as a qml contextProperty, named treeModel in the example below.
The idea was to keep track of the current QModelIndex, and to use the data() & rowCount() functions of the QAbstractItemModel to get the actual model data, and to use a recursive stack view for the navigation
General layout
ApplicationWindow {
id: main
visible: true
width: 640
height: 480
ColumnLayout
{
anchors.fill: parent
// Breadcrumb
SEE BELOW
// View
StackView
{
id: stackView
Layout.fillHeight: true
Layout.fillWidth: true
initialItem: TreeSlide {}
}
}
}
TreeSlide
The view itself is pretty simple. I didn't used anything fancy here, and it displays only one role, but you could extend it without trouble. Note that the view's model is NOT your treeModel, but instead just the rowCount for the rootIndex.
ListView
{
Layout.fillHeight: true
Layout.fillWidth: true
model: treeModel.rowCount(rootIndex)
clip: true
snapMode: ListView.SnapToItem
property var rootIndex
// I used a QFileSytemModel in my example, so I had to manually
// fetch data when the rootIndex changed. You may not need this though.
onRootIndexChanged: {
if(treeModel.canFetchMore(rootIndex))
treeModel.fetchMore(rootIndex)
}
Connections {
target: treeModel
onRowsInserted: {
rootIndexChanged()
}
}
delegate: ItemDelegate {
property var modelIndex: treeModel.index(index,0, rootIndex)
property bool hasChildren: treeModel.hasChildren(modelIndex)
width: parent.width
text: treeModel.data(modelIndex)
onClicked: {
if(hasChildren)
{
// Recursively add another TreeSlide, with a new rootIndex
stackView.push("TreeSlide.qml", {rootIndex: modelIndex})
}
}
}
}
Breadcrumb
To navigate the model, instead of a simple back button, I used a kind of dynamic breadcrumb
// Breadcrumb
RowLayout
{
Repeater
{
id: repeat
model: {
var res = []
var temp = stackView.currentItem.rootIndex
while(treeModel.data(temp) != undefined)
{
res.unshift(treeModel.data(temp))
temp = temp.parent
}
res.unshift('.')
return res
}
ItemDelegate
{
text : modelData
onClicked: {
goUp(repeat.count - index-1)
}
}
}
}
the goUp function simply goes up the stack by poping items
function goUp(n)
{
for(var i=0; i<n; i++)
stackView.pop()
}
To to do it completely by guides we should use DelegateModel and DelegateModel.rootIndex
DelegateModel {
id: delegateSupportPropConfigModel
model: supportModel
delegate: SupportPropConfigListItem {
id: currentItem
width: scrollRect2.width - 60
fieldName: model.fieldName
fieldValue: model.value
onClick:{
delegateSupportPropConfigModel.rootIndex = supportPropConfigModel.index(0, 0, supportPropConfigModel)
}
}
}
Column {
id: columnSettings
spacing: 2
Repeater {
model: delegateSupportPropConfigModel
}
}

Signal emit Issue - Listview is not showing full list

I am binding a ListView with values passed from the cpp.
Issue: Listview displays only one row, mean first value, The rest of the rows are not appeared.
Checked:
I created an ListModel/ListElement in main.qml as test and bind with ListView, Now the Listview just working fine, display all values
I suspect after the signal emit, the error occurs.
Code snippet:
main.qml
ListView {
id: idListView
anchors {
left: parent.left
leftMargin: 10 * scaleFactor
right: parent.right
rightMargin: 10 * scaleFactor
top: rectangleToolBar.bottom
topMargin: 10 * scaleFactor
bottom: rectangleStatusBar.top
bottomMargin: 10 * scaleFactor
}
// model: objHomeController.detailsModel // Display only one row
//model: idListmodel //Working fine
delegate: comsearchDelegate
spacing: 10 * scaleFactor
clip: true
highlight: Rectangle {
color: 'grey'
Text {
anchors.centerIn: parent
color: 'white'
}
}
focus: true
}
Component {
id: comsearchDelegate
Row {
spacing: 10 * scaleFactor
Column {
Layout.alignment: Qt.AlignTop
Text { text: title; font { pixelSize: 14 * scaleFactor; bold: true } }
Text { text: description; font { pixelSize: 14 * scaleFactor; bold: true } }
}
}
}
ListModel {
id: idListModel
ListElement{
title : "sdfsdf";
description:"sdfsdfs";
}
ListElement {
title : "sdfsdf";
description:"sdfsdfs";
}
ListElement {
title : "sdfsdf";
description:"sdfsdfs";
}
ListElement {
title : "sdfsdf";
description:"sdfsdfs";
}
}
HomeController.h
Q_PROPERTY(Model* detailsModel READ get_detailsModel WRITE set_detailsModel NOTIFY detailsModelChanged )
HomeController.cpp
void HomeController::set_detailsModel(Model* value)
{
m_detailsModel = value;
//value has correct values - checked.
emit detailsModelChanged(value);
}
Model* HomeController::get_detailsModel(void)
{
return m_detailsModel;
}
void HomeController::getAllData()
{
m_detailsModel->clear();
m_detailsModel->updateModel(eveReadXML());
set_detailsModel(m_detailsModel);
}
Model.cpp
void Model::updateModel(const QList<Details> & details)
{
if(this->rowCount() > 0) {
this->clear();
}
beginInsertRows(QModelIndex(),rowCount(),rowCount());
m_modelData.append(details);
endInsertRows();
}
Since I came from .Net background, I would like to understand binding a Listview/GridView to a DataTable or an XML. Here I followed, Created class called Details [Details.h] and created Model.h/Model.cpp and fetching the value from there and binding to ListView. Am I doing right, Or do we have other flow. Any tutorial/Codesnippet/Link for projects highly appreciated.
To define ListModel from c++, you need to subclass QAbstractListModel
https://doc.qt.io/qt-5/qabstractlistmodel.html
You can take example on QQmlObjectListModel in this project : http://gitlab.unique-conception.org/qt-qml-tricks/qt-qml-models
Or clone it and use it in your project as follow :
Q_PROPERTY(QQmlObjectListModel<Details>* detailsModel READ get_detailsModel WRITE set_detailsModel NOTIFY detailsModelChanged)

QML-Listview (Cpp-Model) Details-Dialog

EDIT: 26.08.2014 08:20 - Completely reworked question!
What I want to do is:
Fill a qml-listview with data from a cpp-listmodel (QAbstractListModel).
Open a Dialog that show's more data from the cpp-listmodel by click on a listview-item.
I have two cpp-Classes:
DataModelItem with two attributes (listData (displayed in the listview) and detailsData (displayed in the Dialog))
DataModel which inherits QAbstractListModel with an attribut QList itemList.
DataModel.cpp:
QVariant DataModel::data(const QModelIndex &index, int role) const
{
DataModelItem *item = m_itemList.at(index.row());
switch (role) {
case ListDataRole:
return QString().sprintf("%.2f", item->listData());
break;
case DetailsDataRole:
return QString().sprintf("%.4f", item->detailsData());
break;
default:
qDebug () << "role not handled";
}
return QVariant();
}
What I now wanna do is, to display the listData in the ListView. When I click on one ListItem a dialog should appear with the detailsData.
I figured out, that I can't write model.detailsData in my main application, but just detailsData works (I also tried listview.model.detailsData with no effect). Probably someone know why this does not work.
Anyway I found a solution.
Here's the working example:
main.qml
import QtQuick 1.1
Rectangle {
width: 200
height: 400
ListView {
id: listView
model: dataModel
delegate: listDelegate
}
Component {
id: listDelegate
Item {
id: delegateItem
width: listDataText.width
height: listDataText.height
Text {
id: listDataText
text: listData
}
MouseArea {
anchors.fill: parent
onClicked: {
console.log(detailsData)
itemDetails.details = model.detailsData
itemDetails.visible = true
}
}
}
}
DetailsDialog {
id: itemDetails
visible: false
anchors.centerIn: parent
}
}
DetailsDialog.qml
import QtQuick 1.1
Rectangle {
property alias details: detailsText.text
width: 100
height: 62
Text {
id: detailsText
}
}