Edit QStandardItemModel via TableView with Custom Delegate - c++

I have a QStandardItemModel which I display via a QML Table view.
Here is the model:
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;
}
};
and this is how the model is displayed using custom delegates:
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
TextEdit {text: roleTwo}
}
I populated the model like this:
mystandardmodel* mysmodel=new mystandardmodel(0);
QStandardItem* it = new QStandardItem();
it->setData("data1", mystandardmodel::role1);
it->setData(true, mystandardmodel::role2);
it->setCheckable(true);
it->setEditable(true);
mysmodel->appendRow(it);
QStandardItem* it2 = new QStandardItem();
it2->setData("data2",mystandardmodel::role1);
it2->setData("teststring",mystandardmodel::role2);
mysmodel->appendRow(it2);
How can I make the model editable, so that using the checkBox or editing the text is transfered back to the model?
Edit: I tried to follow the suggestion in In QML TableView when clicked edit a data (like excel) and use set model:
Component {
id: myDelegate
Loader {
property var roleTwo: model.two
property int thisIndex: model.index
sourceComponent: if(typeof(roleTwo)=='boolean') {
checkBoxDelegate}
else { stringDelegate}
}
}
Component {
id: checkBoxDelegate
CheckBox{text: roleTwo
onCheckedChanged: {
myTestModel.setData(0,"two",false)
console.log('called',thisIndex)
}
}
}
Component {
id: stringDelegate
TextEdit {text: roleTwo
onEditingFinished: {
myTestModel.setData(thisIndex,"two",text)
console.log('called',thisIndex)
}
}
}
The index is OK, but it seems that it does not have an effect (I added a second TableView with the same model, but the data there does not get updated if I edit it in the first TableView)

You can directly set a value to model.two and that will automatically call setData with the correct role and index:
import QtQuick 2.10
import QtQuick.Controls 2.0 as QQC2
import QtQuick.Controls 1.4 as QQC1
import QtQuick.Layouts 1.3
QQC2.ApplicationWindow {
visible: true
width: 640
height: 480
ColumnLayout {
anchors.fill: parent
Repeater {
model: 2
QQC1.TableView {
Layout.fillWidth: true
Layout.fillHeight: true
QQC1.TableViewColumn {
title: "Parameter Name"
role: "one"
}
QQC1.TableViewColumn {
title: "Value"
role: "two"
delegate: Loader {
property var modelTwo: model.two
sourceComponent: typeof(model.two) ==='boolean' ? checkBoxDelegate : stringDelegate
function updateValue(value) {
model.two = value;
}
}
}
model: myModel
}
}
}
Component {
id: checkBoxDelegate
QQC1.CheckBox {
text: modelTwo
checked: modelTwo
onCheckedChanged: {
updateValue(checked);
checked = Qt.binding(function () { return modelTwo; }); // this is needed only in QQC1 to reenable the binding
}
}
}
Component {
id: stringDelegate
TextEdit {
text: modelTwo
onTextChanged: updateValue(text)
}
}
}
And if that's still too verbose and not enough declarative for you (it is for me), you can use something like the following, where most of the logic is in the Loader and the specifics delegates just inform what is the property where the value should be set and updated from :
delegate: Loader {
id: loader
sourceComponent: typeof(model.two) ==='boolean' ? checkBoxDelegate : stringDelegate
Binding {
target: loader.item
property: "editProperty"
value: model.two
}
Connections {
target: loader.item
onEditPropertyChanged: model.two = loader.item.editProperty
}
}
//...
Component {
id: checkBoxDelegate
QQC1.CheckBox {
id: checkbox
property alias editProperty: checkbox.checked
text: checked
}
}
Component {
id: stringDelegate
TextEdit {
id: textEdit
property alias editProperty: textEdit.finishedText // you can even use a custom property
property string finishedText
text: finishedText
onEditingFinished: finishedText = text
}
}

Using setData() could be an option, but it requires an integer value that indicates the role that is not accessible in QML, or rather is not elegant.
A better option is to create a new one that is Q_INVOKABLE. As the update is given in the view it is not necessary to notify it besides causing strange events.
to obtain the row we use the geometry and the rowAt() method of TableView.
The following is an example:
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QStandardItemModel>
class MyStandardModel: public QStandardItemModel
{
Q_OBJECT
public:
enum Role {
role1=Qt::UserRole+1,
role2
};
using QStandardItemModel::QStandardItemModel;
QHash<int, QByteArray> roleNames() const{
QHash<int, QByteArray> roles;
roles[role1] = "one";
roles[role2] = "two";
return roles;
}
Q_INVOKABLE void updateValue(int row, QVariant value, const QString &roleName){
int role = roleNames().key(roleName.toUtf8());
QStandardItem *it = item(row);
if(it){
blockSignals(true);
it->setData(value, role);
Q_ASSERT(it->data(role)==value);
blockSignals(false);
}
}
};
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
MyStandardModel model;
for(int i=0; i< 10; i++){
auto item = new QStandardItem;
item->setData(QString("data1 %1").arg(i), MyStandardModel::role1);
if(i%2 == 0)
item->setData(true, MyStandardModel::role2);
else {
item->setData(QString("data2 %1").arg(i), MyStandardModel::role2);
}
model.appendRow(item);
}
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("myTestModel", &model);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
#include "main.moc"
main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
TableView {
id: tableView2
anchors.fill: parent
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: typeof(roleTwo)=='boolean'? checkBoxDelegate: stringDelegate
}
}
Component {
id: checkBoxDelegate
CheckBox{
checked: roleTwo
onCheckedChanged:{
var pos = mapToGlobal(0, 0)
var p = tableView2.mapFromGlobal(pos.x, pos.y)
var row = tableView2.rowAt(p.x, p.y)
if(row >= 0)
myTestModel.updateValue(tableView2.row, checked, "two")
}
}
}
Component {
id: stringDelegate
TextField {
text: roleTwo
onEditingFinished: {
var pos = mapToGlobal(0, 0)
var p = tableView2.mapFromGlobal(pos.x, pos.y)
var row = tableView2.rowAt(p.x, p.y)
if(row >= 0)
myTestModel.updateValue(tableView2.row, text, "two")
}
}
}
}
The complete example can be found in the following link.

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

Dynamic retranslate UI. Button gets not arranged properly

So I have this app were I can change in a settings window dynamically the language. Everything fine except one Issue:
If I change from Spanish:
To german:
You can See that the Button in the bottom right (saying SCHLIEßEN) is not arranged well.
If I reopen the popup. No more issue it gets rearranged correctly:
Is this some kind of bug we have to live with or is there a solution for this?
Here is the code for this popup:
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
import LanguageSelectors 1.0
Dialog {
id: root
x: 100
y: 100
width: 400
height: 600
modal: true
focus: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
title: qsTr("Settings")
property alias countOfQuestions: countOfQuestionsSpinBox.value
property bool darkModeOn
ColumnLayout {
id: columnLayout
RowLayout {
Label {
text: qsTr("Light")
font.pointSize: 13.5
}
Switch {
id: colorModeSwitch
position: darkModeOn ? 1.0 : 0.0
onPositionChanged: {
if (position === 0.0) {
root.darkModeOn = false
} else {
root.darkModeOn = true
}
}
}
Label {
text: qsTr("Dark")
font.pointSize: 13.5
}
}
RowLayout {
Label {
text: qsTr("Count of Questions:")
font.pointSize: 13.5
}
SpinBox {
Layout.fillWidth: true
id: countOfQuestionsSpinBox
from: 0
to: 999
editable: true
}
}
RadioButton {
checked: LanguageSelector.language === LanguageSelector.German
text: qsTr("German")
onPressed: {
LanguageSelector.language = LanguageSelector.German
}
}
RadioButton {
checked: LanguageSelector.language === LanguageSelector.English
text: qsTr("English")
onPressed: {
LanguageSelector.language = LanguageSelector.English
}
}
RadioButton {
checked: LanguageSelector.language === LanguageSelector.Spanish
text: qsTr("Spanish")
onPressed: {
LanguageSelector.language = LanguageSelector.Spanish
}
}
}
standardButtons: Dialog.Close
}
I change the language by assigning to languageSelector:
class LanguageSelector : public QObject {
Q_OBJECT
Q_PROPERTY(Language language READ language WRITE setLanguage NOTIFY
languageChanged)
public:
enum Language { German, English, Spanish };
Q_ENUM(Language)
explicit LanguageSelector(QObject *parent = nullptr);
Language language() const;
void setLanguage(Language newLanguage);
QTranslator *getTranslator() const;
private:
QTranslator *mAppTranslator;
QTranslator *mQtBaseTranslator;
QTranslator *mQtQuickControlsTranslator;
Language mLanguage;
void loadGerman();
void loadEnglish();
void loadSpanish();
void loadLanguage(const QLocale::Language &newLanguage);
signals:
void languageChanged();
};
#include <QDebug>
#include <QGuiApplication>
#include <QLocale>
#include <QMetaEnum>
#include <QTranslator>
LanguageSelector::LanguageSelector(QObject *parent)
: QObject{parent}, mAppTranslator{new QTranslator{this}},
mQtBaseTranslator{new QTranslator{this}}, mQtQuickControlsTranslator{
new QTranslator{this}}
{
}
LanguageSelector::Language LanguageSelector::language() const
{
return mLanguage;
}
void LanguageSelector::setLanguage(Language newLanguage)
{
switch (newLanguage) {
case Language::German:
loadLanguage(QLocale::German);
break;
case Language::English:
qApp->removeTranslator(mAppTranslator);
qApp->removeTranslator(mQtBaseTranslator);
qApp->removeTranslator(mQtQuickControlsTranslator);
break;
case Language::Spanish:
loadLanguage(QLocale::Spanish);
break;
}
mLanguage = newLanguage;
emit languageChanged();
}
QTranslator *LanguageSelector::getTranslator() const
{
return mAppTranslator;
}
void LanguageSelector::loadLanguage(const QLocale::Language &newLanguage)
{
qApp->removeTranslator(mAppTranslator);
if (!mAppTranslator->load(QLocale(newLanguage), QStringLiteral("quiz"),
QStringLiteral("."),
QStringLiteral(":/translations"))) {
auto metaEnum = QMetaEnum::fromType<QLocale::Language>();
qDebug() << tr("load app translator language %1 failed")
.arg(metaEnum.valueToKey(newLanguage));
}
qApp->installTranslator(mAppTranslator);
qApp->removeTranslator(mQtBaseTranslator);
if (!mQtBaseTranslator->load(QLocale(newLanguage), QStringLiteral("qtbase"),
QStringLiteral("."),
QStringLiteral(":/translations"))) {
auto metaEnum = QMetaEnum::fromType<QLocale::Language>();
qDebug() << tr("load qt base translator language %1 failed")
.arg(metaEnum.valueToKey(newLanguage));
}
qApp->installTranslator(mQtBaseTranslator);
qApp->removeTranslator(mQtQuickControlsTranslator);
if (!mQtQuickControlsTranslator->load(
QLocale(newLanguage), QStringLiteral("qtquickcontrols"),
QStringLiteral("."), QStringLiteral(":/translations"))) {
auto metaEnum = QMetaEnum::fromType<QLocale::Language>();
qDebug() << tr("load qt quick controls translator language %1 failed")
.arg(metaEnum.valueToKey(newLanguage));
}
qApp->installTranslator(mQtQuickControlsTranslator);
}
LanguageSelector is registered to qml as a singleton:
qmlRegisterSingletonInstance<LanguageSelector>(
"LanguageSelectors", 1, 0, "LanguageSelector", languageSelector.get());
And connected to retranslate like this:
const QUrl url(QStringLiteral("qrc:/qml/main.qml"));
QObject::connect(
&engine, &QQmlApplicationEngine::objectCreated, &app,
[url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
},
Qt::QueuedConnection);
QObject::connect(languageSelector.get(), &LanguageSelector::languageChanged,
&engine, &QQmlApplicationEngine::retranslate);
engine.load(url);
Am I doing wrong here? Or is there some kind of trick to tell the button to get the proper position?

How to populate listview with QStringList?

I am developing an app which is to find all the mp4 videos in the sdcard and list them in the listview. A button is made to play all the video in the list or clicking one of the video list is able to play that video.
I don't know how to implement the linking between the QStringlist in C++ and the folderModel in QML. Maybe it should use another way to populate the listview with QStringlist. I have implemented the search part of the mp4 files in C++ side, but don't know how to make the listview populated with that QStringlist which stored the filepath of mp4 videos. Please help.
The source code:
filemodel.cpp
#ifndef FILEMODEL_H
#define FILEMODEL_H
#include <QObject>
#include <QStringList>
#include <QDirIterator>
#include <QString>
class MyObject : public QObject{
Q_OBJECT
public:
explicit MyObject (QObject* parent = 0) : QObject(parent) {}
Q_INVOKABLE QStringList findfile();
};
QStringList MyObject::findfile( ) {
QStringList all_dirs;
QDirIterator it(dir, QStringList() << "*.mp4", QDir::Files, QDirIterator::Subdirectories);
while (it.hasNext()){
all_dirs << it.next();
}
}
#endif // FILEMODEL_H
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
MyObject obj;
engine.rootCtontext()->setContextProperty("MyObject", &obj);
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
main.qml
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.1
import Qt.labs.folderlistmodel 2.1
import QtMultimedia 5.0
import QtQuick.Controls.Styles 1.4
import Qt.labs.platform 1.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
SwipeView {
id: swipeView
anchors.fill: parent
currentIndex: tabBar.currentIndex
Page1 {
ListView {
width: 200; height: 400
FolderListModel {
id: folderModel
nameFilters: ["*.mp4"]
}
Component {
id: fileDelegate
Text { text: fileName }
}
model: folderModel
delegate: fileDelegate
}
Button {
id: button
width: parent.width
text: "Play"
background: Rectangle {
implicitHeight: 40
border.color: "#26282a"
border.width: 2
radius: 4
}
onClicked:
{
player.source = folderModel.get (0, "fileURL")
playTimer.start()
player.play()
swipeView.setCurrentIndex(1)
}
}
}
Page {
MediaPlayer {
id: player
}
VideoOutput {
id: video
anchors.fill: parent
source: player
}
}
}
function setImageIndex(i)
{
index = i;
if (index >= 0 && index < folderModel.count){
player.source = folderModel.get (index, "fileURL");
player.play();
}
else{
player.source = folderModel.get (index, "fileURL");
player.play();
}
}
Timer {
id: playTimer
interval: 2000
repeat: true
running: true
onTriggered: {
var source_name = player.source;
if(source_name.toString().indexOf(".mp4")>0){ //processing .mp4
if (player.status == MediaPlayer.EndOfMedia){
if (index + 1 < folderModel.count){
setImageIndex(index + 1);
}
else{
index = 0;
setImageIndex(index);
}
}
}
}
}
footer: TabBar {
id: tabBar
currentIndex: swipeView.currentIndex
TabButton {
text: qsTr("First")
}
TabButton {
text: qsTr("Second")
}
}
}
You should not use FolderListModel if you want to make your own filter from C ++, to do it there are several possibilities.
one of them is to implement your own model for it, we create a class that inherits from QAbstractListModel:
#ifndef FILEMODEL_H
#define FILEMODEL_H
#include <QAbstractListModel>
#include <QDirIterator>
#include <QUrl>
#include <QMetaType>
#include <QFuture>
#include <QtConcurrent>
struct File
{
Q_GADGET
Q_PROPERTY(QString name MEMBER name)
Q_PROPERTY(QUrl url MEMBER url)
public:
QString name;
QUrl url;
File(const QString& name=""){
this->name = QFileInfo(name).fileName();
this->url = QUrl::fromLocalFile(name);
}
};
Q_DECLARE_METATYPE(File)
class FileModel : public QAbstractListModel
{
enum dashBoardRoles {
NameRole=Qt::UserRole+1,
URLRole
};
Q_OBJECT
Q_PROPERTY(QString folder READ folder WRITE setFolder NOTIFY folderChanged)
Q_PROPERTY(QStringList nameFilters READ nameFilters WRITE setNameFilters NOTIFY nameFiltersChanged)
public:
FileModel(QObject *parent=Q_NULLPTR):QAbstractListModel(parent){
}
Q_INVOKABLE QVariant get(int index){
return QVariant::fromValue(m_all_dirs[index]);
}
int rowCount(const QModelIndex &parent=QModelIndex()) const{
Q_UNUSED(parent)
return m_all_dirs.count();
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const{
if(index.row()<0 && index.row()>= rowCount())
return QVariant();
File file = m_all_dirs[index.row()];
if(role == NameRole)
return file.name;
else if(role == URLRole)
return file.url;
return QVariant();
}
QHash<int, QByteArray> roleNames() const {
QHash <int,QByteArray> roles;
roles [NameRole]="fileName";
roles [URLRole]="url";
return roles;
}
QString folder() const{
return mFolder;
}
void setFolder(const QString &folder)
{
if(mFolder == folder)
return;
mFolder = folder;
emit folderChanged();
findFiles();
}
QStringList nameFilters() const{
return mNameFilters;
}
void setNameFilters(const QStringList &nameFilters){
if(mNameFilters == nameFilters)
return;
mNameFilters = nameFilters;
emit nameFiltersChanged();
findFiles();
}
signals:
void folderChanged();
void nameFiltersChanged();
private:
void findFiles(){
beginResetModel();
m_all_dirs.clear();
if(QDir(mFolder).exists()){
QFuture<QStringList> future = QtConcurrent::run([=]() {
QStringList files;
QDirIterator it(mFolder, mNameFilters, QDir::Files, QDirIterator::Subdirectories);
while (it.hasNext()){
files<<it.next();
}
return files;
});
QStringList fullNames = future.result();
for(const QString& fullName: fullNames){
File file{fullName};
m_all_dirs << file;
}
}
endResetModel();
}
QString mFolder;
QList<File> m_all_dirs;
QStringList mNameFilters;
};
#endif // FILEMODEL_H
and then it is registered and used in the .qml
main.cpp
qmlRegisterType<FileModel>("com.eyllanesc.filemodel", 1,0, "FileModel");
main.qml
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtMultimedia 5.8
import com.eyllanesc.filemodel 1.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Tabs")
SwipeView {
id: swipeView
anchors.fill: parent
currentIndex: tabBar.currentIndex
Page {
ListView {
id: lv
width: 200; height: 400
Component {
id: fileDelegate
Text { text: fileName
MouseArea{
anchors.fill: parent
onClicked: playMusic(index)
}
}
}
model: FileModel{
id: myModel
folder: "/home/eyllanesc"
nameFilters: ["*.mp4"]
}
delegate: fileDelegate
}
Button {
id: button
anchors.top: lv.bottom
width: parent.width
text: "Play"
background: Rectangle {
implicitHeight: 40
border.color: "#26282a"
border.width: 2
radius: 4
}
onClicked: playMusic(0)
}
}
Page {
MediaPlayer {
id: player
onStopped: {
if(status===MediaPlayer.EndOfMedia){
playMusic((lv.currentIndex+1) % lv.count)
}
}
}
VideoOutput {
id: video
anchors.fill: parent
source: player
}
}
}
function playMusic(index){
player.stop()
player.source = myModel.get(index).url
player.play()
swipeView.setCurrentIndex(1)
}
footer: TabBar {
id: tabBar
currentIndex: swipeView.currentIndex
TabButton {
text: qsTr("Page 1")
}
TabButton {
text: qsTr("Page 2")
}
}
}
The other solution is to use QQmlListProperty and expose those properties:
#ifndef FILEMANAGER_H
#define FILEMANAGER_H
#include <QDirIterator>
#include <QFileInfo>
#include <QFuture>
#include <QObject>
#include <QQmlListProperty>
#include <QUrl>
#include <QVector>
#include <QtConcurrent>
class File: public QObject{
Q_OBJECT
Q_PROPERTY(QString fileName READ fileName CONSTANT)
Q_PROPERTY(QUrl url READ url CONSTANT)
public:
File(const QString fullPath="", QObject *parent = nullptr):QObject(parent){
mFullPath = fullPath;
}
QString fileName() const
{
return QFileInfo(mFullPath).fileName();
}
QUrl url() const{
return QUrl::fromLocalFile(mFullPath);
}
private:
QString mFullPath;
};
class FileManager : public QObject
{
Q_OBJECT
Q_PROPERTY(QQmlListProperty<File> files READ files NOTIFY filesChanged)
Q_PROPERTY(QString folder READ folder WRITE setFolder NOTIFY folderChanged)
Q_PROPERTY(QStringList nameFilters READ nameFilters WRITE setNameFilters NOTIFY nameFiltersChanged)
public:
explicit FileManager(QObject *parent = nullptr):QObject(parent){}
QQmlListProperty<File> files(){
return QQmlListProperty<File>(this, this,
&FileManager::filesCount,
&FileManager::file);
}
QString folder() const
{
return mFolder;
}
void setFolder(const QString &folder)
{
if(mFolder == folder)
return;
mFolder = folder;
emit folderChanged();
findFiles();
}
int filesCount() const{
return mFiles.count();
}
File *file(int index) const{
return mFiles.at(index);
}
QStringList nameFilters() const{
return mNameFilters;
}
void setNameFilters(const QStringList &nameFilters){
if(mNameFilters == nameFilters)
return;
mNameFilters = nameFilters;
emit nameFiltersChanged();
findFiles();
}
signals:
void folderChanged();
void filesChanged();
void nameFiltersChanged();
private:
void findFiles( ) {
mFiles.clear();
if(QDir(mFolder).exists()){
QFuture<QStringList> future = QtConcurrent::run([=]() {
QStringList files;
QDirIterator it(mFolder, mNameFilters, QDir::Files, QDirIterator::Subdirectories);
while (it.hasNext()){
files<<it.next();
}
return files;
});
for(const QString& fullName: future.result()){
File* file = new File(fullName);
mFiles << file;
}
}
emit filesChanged();
}
static int filesCount(QQmlListProperty<File>* list){
return reinterpret_cast<FileManager* >(list->data)->filesCount();
}
static File* file(QQmlListProperty<File>* list, int index){
return reinterpret_cast<FileManager* >(list->data)->file(index);
}
QVector<File *> mFiles;
QString mFolder;
QStringList mNameFilters;
};
#endif // FILEMANAGER_H
and then it is registered and used in the .qml
main.cpp
qmlRegisterType<FileManager>("com.eyllanesc.filemanager", 1,0, "FileManager");
main.qml
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtMultimedia 5.8
import com.eyllanesc.filemanager 1.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Tabs")
FileManager{
id: manager
folder: "/home/eyllanesc"
nameFilters: ["*.mp4"]
}
SwipeView {
id: swipeView
anchors.fill: parent
currentIndex: tabBar.currentIndex
Page {
ListView {
id: lv
width: 200; height: 400
Component {
id: fileDelegate
Text { text: fileName
MouseArea{
anchors.fill: parent
onClicked: playMusic(index)
}
}
}
model: manager.files
delegate: fileDelegate
}
Button {
id: button
anchors.top: lv.bottom
width: parent.width
text: "Play"
background: Rectangle {
implicitHeight: 40
border.color: "#26282a"
border.width: 2
radius: 4
}
onClicked: playMusic(0)
}
}
Page {
MediaPlayer {
id: player
onStopped: {
if(status===MediaPlayer.EndOfMedia){
playMusic((lv.currentIndex+1) % lv.count)
}
}
}
VideoOutput {
id: video
anchors.fill: parent
source: player
}
}
}
function playMusic(index){
player.stop()
player.source = manager.files[index].url
player.play()
swipeView.setCurrentIndex(1)
}
footer: TabBar {
id: tabBar
currentIndex: swipeView.currentIndex
TabButton {
text: qsTr("Page 1")
}
TabButton {
text: qsTr("Page 2")
}
}
}
Both examples can be found in the following links
https://github.com/eyllanesc/stackoverflow/tree/master/48534529-1
https://github.com/eyllanesc/stackoverflow/tree/master/48534529-2

Getting data from C++ model inside delegate

I have a model on C++ end and a treeview
TreeView {
id: view
itemDelegate: Rectangle{
property int indexOfThisDelegate: model.index
Text {
text:???
font.pixelSize: 14
}
}
model: myModel
onCurrentIndexChanged: console.log("current index", currentIndex)
TableViewColumn {
title: "Name"
role: "type"
resizable: true
}
onClicked: console.log("clicked", index)
onDoubleClicked: isExpanded(index) ? collapse(index) : expand(index)
}
How can I get data from my TreeItem? The problem is that indexOfThisDelegate is integer instead of QModelIndex, so
I would like to have something like
Text {
text:model.getDescription(currentlyPaintedModelIndex)
font.pixelSize: 14
}
or should I have a mapping between integer and tree QModelIndex?
Ok, figured it out myself
In the model:
QHash<int, QByteArray> MenuTreeModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[TitleRole] = "Title";
return roles;
}
// of course it could me more complex with many roles
QVariant MenuTreeModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
{
return QVariant();
}
MenuTreeItem *item = itemForIndex(index);
if (role != TitleRole)
{
return QVariant();
}
QString str = item->data(index.column()).toString();
return item->data(index.column());
}
Our custom tree item (for example):
class MenuTreeItem
{
// item data, contains title
QList<QVariant> m_itemData;
};
In qml:
TreeView {
id: view
itemDelegate: Rectangle{
Text {
text:model.Title
font.pixelSize: 14
}
}
model: myModel
}

Pass QStandardItemModel from C++ to QtQuick / QML TableView and display it

I'm currently trying to pass a QStandardItemModel to a QtQuick TableView and then display it. This is basically my code (just a simplified extract, so I hope I haven't added any extra mistakes here).
The C++ / Qt part:
foo.cpp
[...]
QStandardItemModel *Foo::getModel()
{
QStandardItemModel *model = new QStandardItemModel(this);
QList<QStandardItem*> standardItemList;
QList<QString> data;
data.append("Cat");
data.append("Dog");
data.append("Mouse");
foreach (QString cell, comInputData->getHeadings()) {
QStandardItem *item = new QStandardItem(cell);
standardItemList.append(item);
}
// we only add one row here for now; more will come later
model->appendRow(standardItemList);
standardItemList.clear();
return model;
}
[...]
main.cpp
Foo f;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("myModel", f.getModel());
engine.load(QUrl(QStringLiteral("qrc:///InputView.qml")));
The QtQuick / QML part:
InputView.qml
TableView {
id: monitorInputVectorsTable
[... positioning and sizing ...]
model: myModel
}
I guess, I'm still missing some important parts in the QML part, am I? I found some examples with inline-model-defintions like this:
ListModel {
id: libraryModel
ListElement{ title: "A Masterpiece" ; author: "Gabriel" }
ListElement{ title: "Brilliance" ; author: "Jens" }
}
... displayed this way (the following added inside the TableView-item):
TableViewColumn{ role: "title" ; title: "Title" ; width: 100 }
TableViewColumn{ role: "author" ; title: "Author" ; width: 200 }
My guess: I need to add such a line as well. However, I could not figure out where to get the role from the C++ QStandardItemModel? Is it even necessary to set a role? At least the QWidgets examples with the "classic" QTreeView just set the model and everything was fine...
You can subclass QStandardItemModel and re-implement roleNames() to define your desired role names :
#include <QStandardItemModel>
class MyModel : public QStandardItemModel
{
public:
enum Role {
role1=Qt::UserRole,
role2,
role3
};
explicit MyModel(QObject * parent = 0): QStandardItemModel(parent)
{
}
explicit MyModel( 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";
roles[role3] = "three";
return roles;
}
};
After adding items you can set data to the model like :
model->setData(model->index(0,0), "Data 1", MyModel::role1);
model->setData(model->index(0,1), "Data 2", MyModel::role2);
model->setData(model->index(0,2), "Data 3", MyModel::role3);
Now in the qml you can access different columns by the role names :
TableView {
TableViewColumn {title: "1"; role: "one"; width: 70 }
TableViewColumn {title: "2"; role: "two"; width: 70 }
TableViewColumn {title: "3"; role: "three"; width: 70 }
model: myModel
}
Adding data to the model as in Nejats answer did not work in my case (Qt 5.5). The data had to be added as follows:
QStandardItem* it = new QStandardItem();
it->setData("data 1", MyModel::role1);
it->setData("data 2", MyModel::role2);
it->setData("data 3", MyModel::role3);
model->appendRow(it);
A complete example for the answer of Nejat with this modificaiton follows (also avilable here: https://github.com/psimona/so-qt-QStandardItemModel-Example)
main.cpp
#include <QApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
// QStandardItemModel derived class
#include <QStandardItemModel>
class MyModel : public QStandardItemModel {
public:
enum Role {
role1=Qt::UserRole,
role2,
role3
};
explicit MyModel(QObject * parent = 0): QStandardItemModel(parent){}
explicit MyModel( 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";
roles[role3] = "three";
return roles;
}
};
// Main
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MyModel* model = new MyModel;
QStandardItem* it = new QStandardItem();
it->setData("data 1", MyModel::role1);
it->setData("data 2", MyModel::role2);
it->setData("data 3", MyModel::role3);
model->appendRow(it);
it = new QStandardItem();
it->setData("more data 1", MyModel::role1);
it->setData("more data 2", MyModel::role2);
it->setData("more data 3", MyModel::role3);
model->appendRow(it);
QQmlApplicationEngine engine;
// register cpp model with QML engine
engine.rootContext()->setContextProperty("myModel", model);
// Load qml file in QML engine
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
main.qml
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Window 2.2
import QtQuick.Dialogs 1.2
ApplicationWindow {
title: qsTr("Hello World")
width: 640
height: 480
visible: true
TableView {
anchors.fill: parent
TableViewColumn {title: "1"; role: "one"; width: 150 }
TableViewColumn {title: "2"; role: "two"; width: 150 }
TableViewColumn {title: "3"; role: "three"; width: 150 }
model: myModel
}
}