QML TreeView display nodes by levels or custom delegate - c++

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
}
}

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
}
}
}
}
}
}
}
}
}

Return from child qml to main one

I have several QML files, main one is the one that opens the ApplicationWindow when ever I try to get back from child QML to main, new window is opened again!
how can I prevent of doing this?
I thought that enabling a flag in child QML, but there may be other ways!
I tried stackview in QML
Is there any way to prevent from opening new page when I get back to main QML?
create a loader in main window and call every page into that loader when ever you need to change page just change loader's source
Window {
Loader{
id:myLoader
anchors.fill: parent
source: "LoginPage.qml"
}
Connections{
target: myLoader.item
onBack_clicked:{
loginid = ""
myLoader.source = "LoginPage.qml"
}
onSetting_clicked:{
myLoader.source = "Setting.qml"
}
}
}
and for child qml files : (for me Setting.qml)
Item {
signal back_clicked()
Button {
id: button1
anchors.right: parent.right
anchors.rightMargin: 15
onClicked: {
back_clicked()
}
}
}
but if you want to not destroy old page use SwipeView Or StackView:
SwipeView {
id: swipeView
clip: true
currentIndex: 0
Item{
id:firstPage
clip:true
//your page
}
Item{
id:secondPage
clip:true
//your page
}
}
and to change pages just change currentIndex
swipeView.currentIndex = 1
UPDATE:
StackView {
id: stackView
initialItem: one
}
Component {
id: one
Item{//your first page }
}
Component {
id: two
Item{//your second page }
}
and to push your pages :
stackView.push({ item: two})
//or
stackView.push("MySecondView.qml")
to get back to old or main page just pop it :
stackView.pop()

QML ItemSelectionModel does not select?

I have a TreeView of a QFileSystemModel and an ItemSelectionModel:
ItemSelectionModel {
id: sel
model: fileSystemModel
onSelectedIndexesChanged: {
model.setSelected(selectedIndexes)
}
}
TreeView {
id: view
anchors.fill: parent
sortIndicatorVisible: true
model: fileSystemModel
rootIndex: rootPathIndex
selection: sel
selectionMode: 2
TableViewColumn {
id: namecolumn
title: "Name"
role: "fileName"
resizable: true
width: parent.width-sizeWidth-dateWidth-scrollBarWidth
delegate: mycomp
}
... other columns
}
When I sort the TreeView for the name or size of a file, the selection does not get updated. Therefore, in my fileSystemModel (derived from QFileSystemModel) I created a Q_INVOKABLE function which should store the selected indices:
Q_INVOKABLE void setSelected(QModelIndexList list) {
_selectedIndices.clear();
for(int i=0;i<list.count();i++) {
_selectedIndices.append(QPersistentModelIndex(list[i]));
}
}
However, I am not sure if this would work, as I am not sure whether the ItemSelectionModel indices are the same as the model indices.
After sorting, I thought I try to set the selection via retrieving this list, on the c++ side:
Q_INVOKABLE QVariantList getSelected() {
return static_cast<QVariantList>(_selectedIndices);
}
and in QML
onSortIndicatorOrderChanged: {
var list = model.getSelected())
console.log('new selection:')
for(var idx in list) {
console.log(list[idx])
selection.select(list[idx],ItemSelectionModel.Select)
console.log('now selected')
console.log(selection.selectedIndexes)
}
}
The debug output in onSortIndicatorChanged shows
qml: new selection:
qml: QPersistentModelIndex(5,0,0x1e2ad30,DisplayFileSystemModel(0x1544890))
qml: now selected
qml:
sort by column 0
qml: new selection:
qml: QPersistentModelIndex(1,0,0x1e2ad30,DisplayFileSystemModel(0x1544890))
qml: now selected
qml:
if I toggle the sortIndicator. So it seems that the row of the QPersistentModelIndex gets updated correctly. However, selection.selectedIndexes is empty and no selection is shown. What am I missing?
A different way (maybe better) would be to access the QItemSelectionModel directly in c++, but I don't know how I would access that (without searching for its object name, which hurts reusability).

Qt/QML: Putting WebEngineView in a ListView and calling loadHtml()

If I have a WebEngineView in a ListView delegate, how can I call loadHtml when the delegate is loaded? For example:
ListView
{
model: myModel
delegate: Component
{
Item:
{
WebEngineView
{
id: myWebView
text: myWebView.loadHtml(model.modelData.htmlText)
}
}
}
}
The above shows the idea of what I would like to do. Is there a signal I could hook into for each delegate Item where I could call myWebView.loadHtml()?
I do not know about a text-property of the WebEngineView but I have never used it sofar.
I think, what you want is the Component.onCompleted-handler like this:
ListView {
model: myModel
delegate: Component {
Item {
WebEngineView {
id: myWebView
Component.onCompleted: loadHtml(model.modelData.htmlText, baseURL)
}
}
}
}
I don't know about your usecase, but the loadHtml-method has a second argument for the baseURL to look for ressources such as CSS or Images, which might be needed by you.

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
}
}