md612
md612

Reputation: 330

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

Upvotes: 1

Views: 1397

Answers (1)

eyllanesc
eyllanesc

Reputation: 244282

You should not use FolderListModel if you want to make your own filter from C ++, to do it there are several possibilities.

  1. 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")
        }
    }
}
  1. 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

Upvotes: 4

Related Questions