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
Related
I am trying to control the map in Qt however I keep ending up with the following error:
QGeoTileRequestManager: Failed to fetch tile (291271,152514,19) 5 times, giving up. Last error message was: 'Permission denied'
I have functions in C++ that parse the messages and calculates the position:
Map.qml
import QtQuick 2.12
import QtQuick.Window 2.14
import QtQuick.Controls 2.15
import QtLocation 5.6
import QtPositioning 5.6
import QtQuick.Controls.Material 2.12
import QtQuick.Layouts 1.12
import Qt.labs.location 1.0
Page {
id: mainWindow
visible: true
function addMarker(latitude, longitude)
{
var Component = Qt.createComponent("qrc:/Marker.qml")
var item = Component.createObject(window, {
coordinate: QtPositioning.coordinate(latitude, longitude)
})
map.addMapItem(item)
}
Map {
id: map
width: mainWindow.width
height: mainWindow.height
plugin: mapPlugin
center: QtPositioning.coordinate(59.91, 10.75)
Component.onCompleted: addMarker(59.91, 10.75)
zoomLevel: 60
}
Plugin {
id: mapPlugin
name: "osm" // "mapboxgl", "esri", ...
// specify plugin parameters if necessary
PluginParameter {
name: "osm.mapping.providersrepository.disabled"
value: "true"
}
PluginParameter {
name: "osm.mapping.providersrepository.address"
value: "http://maps-redirect.qt.io/osm/5.6/"
}
}
}
Setting the coordinates is done through Q_PROPERTY:
#include <QObject>
class Data : public QObject
{
Q_OBJECT;
Q_PROPERTY(double gnss_log READ gnss_long WRITE set_gnss_long NOTIFY gnss_long_changed);
Q_PROPERTY(double gnss_lat READ gnss_lat WRITE set_gnss_lat NOTIFY gnss_lat_changed);
public: signals:
void gnss_long_changed();
void gnss_lat_changed();
public slots:
void set_gnss_long(double);
void set_gnss_lat(double);
public:
Data();
double gnss_long();
double gnss_lat();
private:
double m_gnss_long;
double m_gnss_lat;
};
void Data::set_gnss_long(double curr_long)
{
// Checks whether updated baud rate changed
if (curr_long == m_gnss_long)
return;
m_gnss_long = curr_long;
qDebug() << m_gnss_long;
//Emits signal indicating change
emit gnss_long_changed();
}
double Data::gnss_long()
{
return m_gnss_long;
}
I when I run this, I get a blank screen with a bunch of the errors mentioned above.
Since the OP has not provided an MRE my answer will only show a demo. The logic is to create a QProperty that exposes the position, then a binding must be done:
#include <QGeoCoordinate>
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QTimer>
#include <random>
class Data: public QObject{
Q_OBJECT
Q_PROPERTY(QGeoCoordinate gnssPosition READ gnssPosition WRITE setGnssPosition NOTIFY gnssPositionChanged)
public:
const QGeoCoordinate &gnssPosition() const{
return m_gnssPosition;
}
void setGnssLatitude(qreal latitude){
QGeoCoordinate coordinate(latitude, m_gnssPosition.longitude());
setGnssPosition(coordinate);
}
void setGnssLongitude(qreal longitude){
QGeoCoordinate coordinate(m_gnssPosition.latitude(), longitude);
setGnssPosition(coordinate);
}
void setGnssPosition(const QGeoCoordinate &newGnssPosition){
if (m_gnssPosition == newGnssPosition)
return;
m_gnssPosition = newGnssPosition;
emit gnssPositionChanged();
}
signals:
void gnssPositionChanged();
private:
QGeoCoordinate m_gnssPosition;
};
int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
QGuiApplication app(argc, argv);
Data data_out;
data_out.setGnssPosition(QGeoCoordinate(59.91273, 10.74609));
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("data_out", &data_out);
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
QTimer timer;
timer.setInterval(1000);
QObject::connect(&timer, &QTimer::timeout, &data_out, [&data_out](){
std::random_device rd;
std::mt19937 e2(rd());
std::uniform_real_distribution<> dist(-.05, .05);
QGeoCoordinate coord = data_out.gnssPosition();
coord.setLatitude(coord.latitude() + dist(e2));
coord.setLongitude(coord.longitude() + dist(e2));
data_out.setGnssPosition(coord);
// qDebug() << data_out.gnssPosition();
});
timer.start();
return app.exec();
}
#include "main.moc"
import QtQuick 2.15
import QtQuick.Window 2.15
import QtLocation 5.15
import QtPositioning 5.15
Window {
id: mainWindow
width: 640
height: 480
visible: true
title: qsTr("Hello World")
property Component markerProvider: MapQuickItem {
anchorPoint.x: rect.width / 2
anchorPoint.y: rect.height / 2
sourceItem: Rectangle{
id: rect
width: 40
height: 40
color: "salmon"
}
}
function addMarker(coordinate){
var marker = markerProvider.createObject()
console.log(marker)
marker.coordinate = coordinate
map.addMapItem(marker)
}
Map {
id: map
width: mainWindow.width
height: mainWindow.height
plugin: mapPlugin
center: data_out.gnssPosition
zoomLevel: 12
}
Plugin {
id: mapPlugin
name: "osm" // "mapboxgl", "esri", ...
// specify plugin parameters if necessary
PluginParameter {
name: "osm.mapping.providersrepository.disabled"
value: "true"
}
PluginParameter {
name: "osm.mapping.providersrepository.address"
value: "http://maps-redirect.qt.io/osm/5.6/"
}
}
Connections{
target: data_out
function onGnssPositionChanged(){
addMarker(data_out.gnssPosition)
}
}
}
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?
I tried to operate a part of a qt project in Qt\Examples\Qt-5.9\quick\views, I am new to qml and I am trying to open each time a different QDialog window depending on qml pathview component that has been clicked. First of all, I started with creating a class (interfacageQML) which will serve to interface the qml Mainform and the QDialog (qtinterface), the necessary files are included among which interfacageqml.h.
here is the main.cpp :
#include "interfacageqml.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
qmlRegisterType<interfacageQML>("Interfacage", 1, 0,"Component:MouseArea");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
And here's interfacageqml.h :
#ifndef INTERFACAGEQML_H
#define INTERFACAGEQML_H
#include <QObject>
#include "qtinterface.h"
class interfacageQML : public QObject
{
Q_OBJECT
public:
interfacageQML(QObject *parent);
~interfacageQML();
Q_INVOKABLE void mouseClick();
signals:
void clicked();
};
#endif // INTERFACAGEQML_H
interfacageqml.cpp :
#include "interfacageqml.h"
#include <QDebug>
#include <QApplication>
interfacageQML::interfacageQML(QObject *parent)
: QObject(parent)
{
}
interfacageQML::~interfacageQML()
{
}
void interfacageQML::mouseClick()
{
qDebug() << "qmlinterface::mouseClick()";
emit clicked();
}
My project is organised this way :
the qmlinterface.qrc file contains these paths:
main.qml :
import QtQuick 2.6
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
MainForm{
anchors.fill: parent
}
}
MainForm.qml :
import QtQuick 2.6
import QtQuick.Controls 2.0 as QQC2
import Interfacage 1.0
Rectangle {
width: 800
height: 800
color: "white"
ListModel {
id: appModel
ListElement {
name: "Contacts"
icon: "pics/Resources/AddressBook_48.png"
}
ListElement {
name: "Music"
icon: "pics/Resources/AudioPlayer_48.png"
}
ListElement {
name: "Movies"
icon: "pics/Resources/VideoPlayer_48.png"
}
ListElement {
name: "Camera"
icon: "pics/Resources/Camera_48.png"
}
ListElement {
name: "Calendar"
icon: "pics/Resources/DateBook_48.png"
}
ListElement {
name: "Todo List"
icon: "pics/Resources/TodoList_48.png"
}
}
Component {
id: appDelegate
Item {
width: 100
height: 100
scale: PathView.iconScale
Image {
id: myIcon
y: 20
anchors.horizontalCenter: parent.horizontalCenter
source: icon
}
Text {
anchors {
top: myIcon.bottom
horizontalCenter: parent.horizontalCenter
}
text: name
}
MouseArea {
anchors.fill: parent
onClicked: {
view.currentIndex = index
Interfacage.mouseClick
}
}
}
}
Component {
id: appHighlight
Rectangle {
width: 100
height: 80
color: "lightsteelblue"
}
}
PathView {
id: view
anchors.fill: parent
highlight: appHighlight
preferredHighlightBegin: 0.5
preferredHighlightEnd: 0.5
focus: true
model: appModel
delegate: appDelegate
path: Path {
startX: 50
startY: 80
PathAttribute {
name: "iconScale"
value: 2.0
}
PathQuad {
x: 250
y: 200
controlX: 50
controlY: 200
}
PathAttribute {
name: "iconScale"
value: 2.0
}
PathQuad {
x: 600
y: 50
controlX: 400
controlY: 200
}
PathAttribute {
name: "iconScale"
value: 2.0
}
}
}
}
When I run this project, i got an error :
error:C2280
However, when I comment this line : qmlRegisterType<interfacageQML>("Interfacage", 1, 0, "Component:MouseArea"); the project runs and I can navigate between the pathview components in the MainForm.
When you use qmlRegisterType you are registering a new data type in QML, it is not an object, in that case the name "Component: MouseArea" is not suitable.
qmlRegisterType<interfacageQML>("Interfacage", 1, 0, "InterfacageQML");
Another error is that you must pass a parent by default, in this case 0 or nullptr since the items may not have parents.
class interfacageQML : public QObject
{
Q_OBJECT
public:
explicit interfacageQML(QObject *parent = nullptr);
[...]
As I said in the first lines, this is a new type, it is not an object so you must create it.
import QtQuick 2.6
import QtQuick.Controls 2.0 as QQC2
import Interfacage 1.0
Rectangle {
width: 800
height: 800
color: "white"
InterfacageQML{
id: myitem
}
[...]
And in the end if you want to use it you must call the function through the item.
MouseArea {
anchors.fill: parent
onClicked: {
view.currentIndex = index
myitem.mouseClick()
}
}
Since you want to connect your QDialog with the QML through that class, you can not do it since they will be different objects, one solution for this is to use a singleton, for this you must do the following:
interfacageqml.h
#ifndef INTERFACAGEQML_H
#define INTERFACAGEQML_H
#include <QObject>
#include <QQmlEngine>
class interfacageQML : public QObject
{
Q_OBJECT
static interfacageQML* instance;
explicit interfacageQML(QObject *parent = nullptr);
public:
static interfacageQML *getInstance();
~interfacageQML();
Q_INVOKABLE void mouseClick();
signals:
void clicked();
};
#endif // INTERFACAGEQML_H
interfacageqml.cpp
#include "interfacageqml.h"
#include <QDebug>
interfacageQML* interfacageQML::instance = 0;
interfacageQML *interfacageQML::getInstance()
{
if (instance == 0)
instance = new interfacageQML;
return instance;
}
interfacageQML::interfacageQML(QObject *parent) : QObject(parent)
{
}
interfacageQML::~interfacageQML()
{
}
void interfacageQML::mouseClick()
{
qDebug() << "qmlinterface::mouseClick()";
emit clicked();
}
main.cpp
#include "interfacageqml.h"
#include <QGuiApplication>
#include <QQmlApplicationEngine>
static QObject *singletonTypeProvider(QQmlEngine *, QJSEngine *)
{
return interfacageQML::getInstance();
}
int main(int argc, char *argv[])
{
qmlRegisterSingletonType<interfacageQML>("Interfacage", 1, 0, "InterfacageQML", singletonTypeProvider);
// test
interfacageQML *obj = qobject_cast<interfacageQML*>(interfacageQML::getInstance());
QObject::connect(obj, &interfacageQML::clicked,[]{
qDebug()<<"clicked";
});
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
As it is a singleton it is not necessary to create an item, you can do it directly:
import Interfacage 1.0
[...]
MouseArea {
anchors.fill: parent
onClicked: {
view.currentIndex = index
InterfacageQML.mouseClick()
}
}
This last example can be found in the following link.
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.
Here is the issue, I created a C++ class which loads a local file and for every new line it sends out a signal. What I want to do is in my QML file I want to print out these lines into a listview which I have already done but the issue now is that the C++ function loads even before the application starts, it would read the file and populate the listview and then display the page.
Here is my code.
main.cpp
#include <QtCore/QCoreApplication>
#include <QtGui/QGuiApplication>
#include <QtQuick/QQuickView>
#include <QtQml>
#include "boot.h"
int main(int argc, char *argv[])
{
QCoreApplication::setApplicationName("xyz");
QCoreApplication::setAttribute(Qt::AA_X11InitThreads);
qmlRegisterType<Boot>("xyx", 1, 0, "Boot");
QGuiApplication app(argc, argv);
QQuickView quickView;
quickView.setSource(QUrl(QStringLiteral("qrc:/Boot.qml")));
quickView.setResizeMode(QQuickView::SizeRootObjectToView);
quickView.showMaximized();
return app.exec();
}
Boot.qml
import QtQuick 2.0
import "."
import QtQuick.XmlListModel 2.0
import xyz 1.0
Item{
Loader{
id: bootPageLoader
anchors.fill:parent
sourceComponent: bootSystem
focus:true
}
Component{
id:bootSystem
Rectangle{
width: 640
height: 480
color:"black"
focus:true
Component.onCompleted: {
booting.load();
}
Boot{
id:booting
onErrorMsgChanged: {
console.log("New Boot Log Message: " + errorMsg);
//This is where I am adding to the listview every time I receive a signal
logModel.append({msg:errorMsg});
log.positionViewAtEnd();
}
}
ListView {
id:log
anchors.left: parent.left
anchors.leftMargin: 10
anchors.topMargin: 10
anchors.bottomMargin:10
anchors.top: parent.top
width:(parent.width*40)/100
height:parent.height-20
model: BootLogModel{
id:logModel
}
delegate: Text {
text: msg
color: "green"
font.pixelSize: 15
}
}
Text{
id: loading
anchors{
horizontalCenter: parent.horizontalCenter
verticalCenter: parent.verticalCenter
}
text: "LOADING..."
color: "white"
font.pixelSize: Math.round(parent.height/20)
font.bold: true
}
}
}
}
BootLogModel.qml
import QtQuick 2.0
ListModel {
}
Here is the C++ code snippet
In boot.h
#ifndef BOOT_H
#define BOOT_H
#include <QObject>
#include <QString>
#include <string>
class Boot : public QObject{
Q_OBJECT
Q_PROPERTY(QString errorMsg READ errorMsg NOTIFY errorMsgChanged)
public:
explicit Boot(QObject *parent = 0);
Q_INVOKABLE void load();
QString errorMsg();
void setErrorMsg(const QString &errorMsg);
signals:
void errorMsgChanged();
private:
QString m_errorMsg;
};
#endif // BOOT_H
In boot.cpp
Boot::Boot(QObject *parent) : QObject(parent)
{
}
QString Boot::errorMsg()
{
return m_errorMsg;
}
void Boot::setErrorMsg(const QString &errorMsg)
{
m_errorMsg = errorMsg;
emit errorMsgChanged();
}
void Boot::load()
{
int i = 0;
while(i < 10000)
{
setErrorMsg("test: " + QString::number(i));
i++;
}
}
I first see this before the GUI
Then this is the GUI being displayed and already populated
Loops are always a problem in a GUI, it is always better to look for a friendlier alternative to the GUI, in this case a QTimer:
boot.h
int counter = 0;
bool.cpp
void Boot::load()
{
QTimer *timer = new QTimer(this);
connect(timer, &QTimer::timeout, [timer, this](){
setErrorMsg("test: " + QString::number(counter));
counter++;
if(counter > 10000){
timer->stop();
timer->deleteLater();
}
});
timer->start(0); //delay
}