I'm trying to make an Oscilloscope like Qt Quick application, based on the qtcharts-qmloscilloscope-example here
In this example the traces (a QTQuick ChartView) are pre-allocated in the QML and updated via a timer.
I would like to be able to add and remove traces at runtime.
The existing application passes a reference to the underlying data array as QAbstractSeries of QPointF objects. This action is triggered on a Timer, thus:
Timer {
id: refreshTimer
interval: 1 / 1 * 1000 // 1 Hz
running: dataSource.isRunning
repeat: true
onTriggered: {
dataSource.updateTime();
//dataSource.update(chartView.series(0));
//dataSource.update(chartView.series(1));
//dataSource.update(chartView.series(2));
dataSource.update(chartView);
}
}
And the existing update method looks like this:
void DataSource::update(QAbstractSeries * series)
{
...
}
This is OK if you only want a fixed number of traces and they are all updated individually. But I would like to be able to add traces as they are turned on and off.
I've tried to pass the chartView ID to an update(QChartView *) function but this always breaks with a null pointer.
Q_INVOKABLE void DataSource::update(QChartView * view)
{
...
}
I've also tried using window->findChildren at the top level and passing that to the instance of DataSource. That gets a valid pointer but of type QQuickItem. If I cast that to a QChartView I also get a null pointer.
How to I correctly pass a pointer to a QChartView object to C++?
ChartView is not a QChartView but a QQuickItem so casting does not work.
So you can not access the methods directly but use the QMetaObject as shown below:
helper.h
#ifndef HELPER_H
#define HELPER_H
#include <QObject>
class QQuickItem;
class Helper : public QObject
{
Q_OBJECT
public:
using QObject::QObject;
Q_INVOKABLE void createSerie(QQuickItem *chartview);
Q_INVOKABLE void removeAllSeries(QQuickItem *chartview);
};
#endif // HELPER_H
helper.cpp
#include "helper.h"
#include <QAbstractAxis>
#include <QAbstractSeries>
#include <QLineSeries>
#include <QMetaObject>
#include <QQuickItem>
#include <random>
#include <cstring>
QT_CHARTS_USE_NAMESPACE
void Helper::createSerie(QQuickItem *chartview){
if(!chartview)
return;
const QMetaObject *mo = chartview->metaObject();
if(std::strcmp(mo->className(), "QtCharts::DeclarativeChart") != 0)
return;
int ix = mo->indexOfEnumerator("SeriesType");
QMetaEnum me = mo->enumerator(ix);
int type = me.keyToValue("SeriesTypeLine");
QAbstractAxis *axis_x = nullptr;
QMetaObject::invokeMethod(chartview, "axisX", Qt::DirectConnection,
Q_RETURN_ARG(QAbstractAxis *, axis_x));
QAbstractAxis *axis_y = nullptr;
QMetaObject::invokeMethod(chartview, "axisY", Qt::DirectConnection,
Q_RETURN_ARG(QAbstractAxis *, axis_y));
QAbstractSeries *serie = nullptr;
QMetaObject::invokeMethod(chartview, "createSeries", Qt::DirectConnection,
Q_RETURN_ARG(QAbstractSeries *, serie),
Q_ARG(int, type),
Q_ARG(QString, "serie from c++"),
Q_ARG(QAbstractAxis *, axis_x),
Q_ARG(QAbstractAxis *, axis_y));
if(QLineSeries *line_serie = qobject_cast<QLineSeries *>(serie)){
static std::default_random_engine e;
static std::uniform_real_distribution<> dis(0, 3);
for(int i=0; i < 14; i++){
line_serie->append(i, dis(e));
}
}
}
void Helper::removeAllSeries(QQuickItem *chartview){
if(!chartview)
return;
const QMetaObject *mo = chartview->metaObject();
if(std::strcmp(mo->className(), "QtCharts::DeclarativeChart") != 0)
return;
QMetaObject::invokeMethod(chartview, "removeAllSeries", Qt::DirectConnection);
}
main.qml
import QtQuick 2.14
import QtQuick.Window 2.14
import QtQuick.Layouts 1.14
import QtQuick.Controls 2.4
import QtCharts 2.14
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
ColumnLayout{
anchors.fill: parent
RowLayout{
Button{
text: "Create serie"
Layout.fillWidth: true
onClicked: helper.createSerie(chartview)
}
Button{
text: "Clear series"
Layout.fillWidth: true
onClicked: helper.removeAllSeries(chartview);
}
}
ChartView {
id: chartview
title: "Line"
antialiasing: true
Layout.fillWidth: true
Layout.fillHeight: true
LineSeries {
name: "LineSeries"
XYPoint { x: 0; y: 0 }
XYPoint { x: 3; y: 2.1 }
XYPoint { x: 8; y: 3.3 }
XYPoint { x: 10; y: 2.1 }
XYPoint { x: 11; y: 4.9 }
XYPoint { x: 12; y: 3.0 }
XYPoint { x: 13; y: 3.3 }
}
axes: [
ValueAxis{
id: xAxis
min: 1.0
max: 15.0
},
ValueAxis{
id: yAxis
min: 0.0
max: 5.0
}
]
}
}
}
In the following link is the complete code.
Better variant exists without using QMetaObject::invokeMethod(): it is possible to use QChart directly. Just need to extend following solution "Push QML ChartView updates from c++" a bit:
*.h
public:
Q_INVOKABLE void setSeries(QAbstractSeries *series);
[...]
private:
QXYSeries *mSeries;
QChart *mChart;
[...]
*.cpp
void DataSource::setSeries(QAbstractSeries *series)
{
if (series) {
mSeries = static_cast<QXYSeries *>(series);
mChart = mSeries->chart();
}
}
Related
I am trying to prepare a tree Model and I have done this:
treemodel.h
#include <QStandardItemModel>
class TreeModel : public QStandardItemModel
{
Q_OBJECT
public:
TreeModel( QObject *parent = nullptr );
private:
QVector<TreeModel *> child_items;
TreeModel* parent_item;
static const int DisplayName;
static const int DisplayInfo;
static const int DisplayId;
};
treemodel.cpp
#include "treemodel.h"
#include <QStandardItemModel>
#include <QStandardItem>
#include <QAbstractItemModel>
#include <QString>
#include <QTreeView>
#include <iostream>
#include <QList>
#include <QModelIndex>
const int TreeModel::DisplayName = Qt::UserRole+1;
const int TreeModel::DisplayInfo = Qt::UserRole+2;
const int TreeModel::DisplayId = Qt::UserRole+3;
TreeModel::TreeModel( QObject *parent ) : QStandardItemModel(parent)
{
QStandardItem *parentItem = this->invisibleRootItem();
QHash<int, QByteArray> roles = roleNames();
roles.insert(DisplayName, "A");
roles.insert(DisplayInfo, "B");
roles.insert(DisplayId, "C");
setItemRoleNames(roles);
QStandardItem *c1 = new QStandardItem;
c1->setData("Sudip", DisplayName);
QStandardItem *c2 = new QStandardItem;
c2->setData("Ghimire", DisplayInfo);
QStandardItem *c3 = new QStandardItem;
c3->setData("Bahadur", DisplayId);
QList<QStandardItem *> r1 { c1, c2, c3};
parentItem->insertRow(0, r1);
}
main.qml
import QtQuick 2.12
import QtQuick.Controls 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3
import QtQuick.Window 2.12
import QtQuick 2.0
Window {
visible: true
width: 640
height: 480
title: qsTr("Tree View Example")
id: root
TreeView{
id: tree_view
model: TreeModel
Layout.fillHeight: true
Layout.fillWidth: true
width: root.width
height: root.height
sortIndicatorVisible: true
alternatingRowColors: false
backgroundVisible: false
TableViewColumn{
role: "A"
title: "Elements"
width: 300
}
TableViewColumn{
role: "B"
title: "more"
width: 300
}
TableViewColumn{
role: "C"
title: "I am some title"
width: 300
}
/*
MouseArea{
anchors.fill: tree_view
hoverEnabled: true
onHoveredChanged: function(){
}
}
*/
}
}
main.cpp
QQmlApplicationEngine engine;
TreeModel t_model;
engine.rootContext()->setContextProperty("TreeModel", &t_model);
But the reasult is
But I want to fill all column by a row. I think appendRow in treemodel.cpp should have done the wok. But its wired.
This is because ur model doesn't know how to deal with your custom user role when QStandardItemModel::data() called to bind value in QML. So what you have to do is, override the data() function your TreeModel class and return the data for ur custom roles. You should add below code to ur TreeModel class.
QVariant TreeModel::data(const QModelIndex &index, int role) const
{
QVariant value;
if (index.isValid()) {
if (role <= Qt::UserRole) {
value = QStandardItemModel::data(index, role);
}
else if(role == DisplayName)
{
value = QStandardItemModel::data(index, DisplayName);
}
else if(role == DisplayInfo)
{
int columnIdx = role - Qt::UserRole - 1;
QModelIndex modelIndex = this->index(index.row(), columnIdx);
value = QStandardItemModel::data(modelIndex, DisplayInfo);
}
else if(role == DisplayId)
{
int columnIdx = role - Qt::UserRole - 1;
QModelIndex modelIndex = this->index(index.row(), columnIdx);
value = QStandardItemModel::data(modelIndex, DisplayId);
}
else
{
// undefined role.
}
}
return value;
}
I am connecting a ProgressBar to two Buttons. The ProgressBar will do a computation of a loop as soon as I push the button. The computation will be erased if I will push the other button. I still haven't implemented the pause button.
The print screen of what I am trying to achieve is below and in case needed the whole code of the minimal verifiable example is available here:
The problem is that as soon as I connect the ProgressBar with my main.cpp file I have a bunch of errors all that look like the following:
class ProgressDialog has no member named...
code is below:
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickView>
#include "progressbardialog.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQuickView view;
ProgressBarDialog progressDialog;
// One way but it does not work
// engine.rootContext()->setContextProperty("control", QVariant::fromValue(progressDialog.refModel()));
// Another way but this also does not work
view.setSource(QUrl::fromLocalFile("main.qml"));
QQmlApplicationEngine engine;
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);
return app.exec();
}
progressbardialog.h
#ifndef PROGRESSBARDIALOG_H
#define PROGRESSBARDIALOG_H
#include <QObject>
#include <QFutureWatcher>
class ProgressBarDialog : public QObject
{
Q_OBJECT
Q_PROPERTY(float progress READ progress WRITE setProgress NOTIFY progressChanged)
public:
ProgressBarDialog();
float progress(int &iterator);
// I will use this as a reference to pass to main.cpp using setContextProperty()
QObject &refModel()
{
return m_Model;
}
public Q_SLOTS:
void startComputation();
void cancelComputation();
signals:
void progressChanged();
private:
int m_progressValue;
QObject m_Model;
QFutureWatcher<void> m_futureWatcher;
};
#endif // PROGRESSBARDIALOG_H
progressbardialog.cpp
#include "progressbardialog.h"
#include <QtConcurrent/QtConcurrentMap>
ProgressBarDialog::ProgressBarDialog()
{}
void spin(int &iteration)
{
const int work = 1000 * 1000 * 40;
volatile int v = 0;
for(int j = 0; j < work; ++j)
++v;
}
float ProgressBarDialog::progress(int &iterator)
{
(void) iterator;
const int work = 1000 * 1000 * 40;
volatile int v = 0;
for(int j = 0; j < work; ++j)
++v;
emit progressChanged();
}
void ProgressBarDialog::startComputation()
{
// Prepare the vector
QVector<int> vector;
for(int i = 0; i < 40; ++i)
vector.append(i);
const QFuture<void> future = QtConcurrent::map(vector, spin);
m_futureWatcher.setFuture(future);
}
void ProgressBarDialog::cancelComputation()
{
m_futureWatcher.cancel();
}
and finally the main.qml
import QtQuick 2.12 // for the Item
import QtQuick.Controls 2.12 // for ApplicationWindow
import QtQuick.Layouts 1.12
ApplicationWindow {
visible: true
width: 440
height: 480
title: qsTr("Progress Bar")
ColumnLayout {
spacing: 10
width: parent.width
GroupBox {
id: box1
title: "Start - Stop"
font.pointSize: 20
Layout.alignment: parent.width
spacing: 10
GridLayout {
width: parent.width
columns: 1
RowLayout {
spacing: 200
Layout.fillWidth: true
Layout.fillHeight: false
Button {
id: buttonStart
text: "Start"
font.pointSize: 15
enabled: !progressDialog.active
onClicked: progressDialog.startComputation()
}
Button {
id: buttonFinish
text: "Finish"
font.pointSize: 15
enabled: progressDialog.cancelComputation()
}
}
}
}
GroupBox {
id: boxprogress
title: "Progressbar"
font.pointSize: 20
Layout.alignment: parent.width
spacing: 10
GridLayout {
width: parent.width
columns: 1
RowLayout {
Layout.fillWidth: true
Layout.fillHeight: false
ProgressBar {
id: progressbar_id
Layout.fillWidth: true
Layout.fillHeight: true
width: parent.width
// These are hard-coded values to confirm it is working
from: 0
to: 100
value: 5
onValueChanged: {
console.log("Progressbar value changed: ", progressbar_id.value)
}
onVisibleChanged: {
console.log("Progressbar visibility changed: ", progressbar_id.visible)
}
}
Connections {
target: progressDialog
onProgressChanged: progressbar_id.value = progress;
}
// This is working if clicking on the progressbar
MouseArea {
anchors.fill: parent
onClicked: progressbar_id.value += 5;
}
}
}
}
}
}
What I tried so far :
1) In order to prove myself that the ProgressBarDialog is correctly working I tried it before as a stand alone main.qml using hard-coded values (which I left in the code so you can see what I have done). But as soon as I started implementing it in the .cpp file and setting the Q_PROPERTY I received all the errors I posted on the second screenshot.
2) I am sure about the procedure taken so far because: a) According to the official documentation and from this post because I will need to check the process of a long running operations; b) According to QtConcurrent the process has a better possibility to be checked, and that is exactly what I did using QtConcurrent::map()
3) I am using the following statement in the progressbardialog.h to make sure that the object is correctly connected and listens to the QML file:
QObject &refModel()
{
return m_Model;
}
But this did n't show and substancial improvements. Therefore digging more into the process I was thinking that it should have been the case to use setContextProperty(), whih cled me to the next point.
4) According to the official documentation and according to this post here I thought I would have fix the error, but it still remains.
I am runnign out of ideas and anyone who may have had this problem, please point to the right direction for a possible solution.
Q_PROPERTY is designed to easier read/write/react to variable change (C++ side) on the QML side. To complete your example with variable read/write/notify add correct getter/setter (signal is already ok).
float progress();
void setProgress(float);
If you want to call a function from QML mark it Q_INVOKABLE instead.
Also what is the point of volatile in your code?
So to sum up, mark your iteration function as a Q_INVOKABLE. It will increment some internal progress value and then emit progressChanged(). This should cause QML side to update.
I am creating a game for touch screens that requires 2-4 players to each have access to a pair of slider controls. The problem is that the QML Slider control responds to touch as a mouse event and seizes the focus. Then only one player can access a single control at a time. I need multiple sliders to respond to touch events simultaneously. My question is how to do that?
With the help of a variety of stack overflow posts, I have been able to create my own answer that so far seems to work. I detail the answer below in the answer section to save other newbies like me the trouble.
There is a pure qml solution to this problem. The TouchSlider C++ object in my first answer (elsewhere in this thread) was unnecessary. Here I have modified the code to the TouchSlider qml code to eliminate references to touchslider (the TouchSlider C++ object).
TouchPoint.qml:
import QtQuick 2.0
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.2
Item {
property string sliderTitle
property real sliderMin
property real sliderMax
property real sliderVal
ColumnLayout{
id: column1
Label {
text: qsTr(sliderTitle)
font.pointSize: 10
}
Slider {
id: touchSlider1
minimumValue: sliderMin
maximumValue: sliderMax
orientation: Qt.Vertical
value: sliderVal
onValueChanged: function(){
sliderVal = Math.round(this.value);
labelSliderValue.text = qsTr(JSON.stringify(sliderVal));
}
}
Label {
id: labelSliderValue
text: qsTr(JSON.stringify(sliderVal))
font.pointSize: 10
}
function sliderSetValueFromTouch(position){
// Assume qs a vertical slider
var minvalue = touchSlider1.minimumValue;
var maxvalue = touchSlider1.maximumValue;
// Since this is a vertical slider, by assumption, get the height
var height = touchSlider1.height;
// Compute the new value based on position coordinate
var newvalue = (height-position)/height * (maxvalue-minvalue);
if (newvalue<minvalue) newvalue = minvalue;
if (newvalue>maxvalue) newvalue = maxvalue;
//qDebug() << newvalue;
// Set the value of the slider
touchSlider1.value = newvalue;
}
MultiPointTouchArea{
anchors.fill: touchSlider1
touchPoints: [
TouchPoint {
id: point1
onPressedChanged: function(){
if(pressed){
//console.log("pressed");
//console.log(touchslider.testStringReturn());
//touchslider.sliderSetValueFromTouch(touchSlider1,point1.y);
column1.sliderSetValueFromTouch(point1.y);
}
}
}
]
onTouchUpdated: function(){
//touchslider.sliderSetValueFromTouch(touchSlider1,point1.y);
column1.sliderSetValueFromTouch(point1.y);
}
}
}
}
The touchslider.h and touchslider.cpp files add no value.
I could not find a pure QML way to solve the problem but I wanted to minimize the use of C++. Using C++, I create an object TouchSlider and add it to my qml scene. The TouchSlider object has a simple function to update the value of a vertical slider according to a position argument. Then in the QML code, I add a MultiPointTouchArea on top of a regular slider and respond to the touch events by calling C++ function.
Here are all my files for a project called SliderPair.
SliderPair.pro:
QT += qml quick widgets
QT += quickcontrols2
QT += core
CONFIG += c++11
SOURCES += main.cpp \
touchslider.cpp
RESOURCES += \
qml.qrc
# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH += qml
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
HEADERS += \
touchslider.h
DISTFILES +=
main.cpp:
#include <QApplication>
#include <QQmlApplicationEngine>
// add following includes for exposing new class TouchSlider to QML
#include <QQmlEngine>
#include <QQmlContext>
#include "touchslider.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
//Create an object of type TouchSlider
//When a scoped pointer goes out of scope the object is deleted from memory. Good housekeeping:
QScopedPointer<TouchSlider> touchslider (new TouchSlider);
QQmlApplicationEngine engine;
engine.addImportPath(QStringLiteral("qml"));
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
//QML can now refer to the TouchSlider object using the handle "touchslider":
engine.rootContext()->setContextProperty("touchslider",touchslider.data());
return app.exec();
}
touchslider.h:
#ifndef TOUCHSLIDER_H
#define TOUCHSLIDER_H
#include <QObject>
#include <QDebug>
#include <QtQuickControls2>
class TouchSlider : public QObject
{
Q_OBJECT
public:
explicit TouchSlider(QObject *parent = 0);
//call Q_INVOKABLE macro to set up functions for QML
Q_INVOKABLE void testDebug(); //hello world from C++
Q_INVOKABLE QString testStringReturn(); //hello world to javascript
Q_INVOKABLE void sliderSetValueFromTouch(QQuickItem *qs,int position );//use touch event to set slider value
signals:
public slots:
};
#endif // TOUCHSLIDER_H
touchslider.cpp:
#include "touchslider.h"
TouchSlider::TouchSlider(QObject *parent) : QObject(parent)
{
}
void TouchSlider::testDebug()
{
qDebug() << "Hello from C++";
}
QString TouchSlider::testStringReturn()
{
QString message = "Hi from C++";
return message;
}
void TouchSlider::sliderSetValueFromTouch(QQuickItem *qs, int position)
{
// Assume qs a vertical slider
// Get its properties (its slider properties are accessible even though it is declared as QQuickItem)
// minimumValue and maximumValue are of type QVariant so we need to cast them as double
double minvalue = qs->property("minimumValue").value<double>();
double maxvalue = qs->property("maximumValue").value<double>();
// Since this is a vertical slider, by assumption, get the height
double height = qs->property("height").value<double>();
// Compute the new value based on position coordinate
double newvalue = (height-position)/height * (maxvalue-minvalue);
if (newvalue<minvalue) newvalue = minvalue;
if (newvalue>maxvalue) newvalue = maxvalue;
//qDebug() << newvalue;
// Set the value of the slider
qs->setProperty("value",newvalue);
}
TouchSlider.qml:
import QtQuick 2.0
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.2
Item {
property string sliderTitle
property real sliderMin
property real sliderMax
property real sliderVal
ColumnLayout{
Label {
text: qsTr(sliderTitle)
font.pointSize: 10
}
Slider {
id: touchSlider1
minimumValue: sliderMin
maximumValue: sliderMax
orientation: Qt.Vertical
value: sliderVal
onValueChanged: function(){
sliderVal = Math.round(this.value);
labelSliderValue.text = qsTr(JSON.stringify(sliderVal));
}
}
Label {
id: labelSliderValue
text: qsTr(JSON.stringify(sliderVal))
font.pointSize: 10
}
MultiPointTouchArea{
anchors.fill: touchSlider1
touchPoints: [
TouchPoint {
id: point1
onPressedChanged: function(){
if(pressed){
//console.log("pressed");
//console.log(touchslider.testStringReturn());
touchslider.sliderSetValueFromTouch(touchSlider1,point1.y);
}
}
}
]
onTouchUpdated: function(){
touchslider.sliderSetValueFromTouch(touchSlider1,point1.y);
}
}
}
}
PlayerControls.qml:
import QtQuick 2.0
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.2
Item {
// These properties act as constants, useable outside this QML file
property string playerName
property real priceMin
property real priceMax
property real qualityMin
property real qualityMax
property real priceValue
property real qualityValue
property int sliderWidth
ColumnLayout{
id: columnLayout1
width: 640
height: 480
Layout.minimumWidth: 640
Layout.fillWidth: true
anchors.fill: parent
spacing: 10.2
Label {
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
id: labelPlayer1
text: qsTr(playerName)
font.pointSize: 10
}
RowLayout{
ColumnLayout{
Label {
text: qsTr("")
font.pointSize: 10
width: 50
}
}
TouchSlider {
width: sliderWidth
sliderTitle: "Price"
sliderMin: priceMin
sliderMax: priceMax
sliderVal: priceValue
}
TouchSlider {
width: sliderWidth
sliderTitle: "Quality"
sliderMin: qualityMin
sliderMax: qualityMax
sliderVal: qualityValue
}
}
}
}
main.qml:
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("SliderPair Test")
Item {
PlayerControls{
playerName: "Player 1"
priceMin: 0
priceMax: 200
priceValue: 100
qualityMin: 0
qualityMax: 50
qualityValue: 25
sliderWidth: 200
}
}
}
The result should look like this:
On a touch screen like my Surface Pro, I can control each slider simultaneously with two fingers. Since Windows supports up to 10 simultaneous touches that should mean I can have 2-4 players without a problem. We shall see.
I have a problem with QQuickView instantiating components from C++... Here is my code:
Class definition (vviewerqml.h):
class VViewerQml : public QObject
{
Q_OBJECT
public:
explicit VViewerQml(QSettings &systemSettings, QObject *parent = 0);
~VViewerQml();
protected slots:
void onViewStatusChanged(QQuickView::Status status);
protected:
QString _qmlFolder;
QQuickView _view;
};
Class implementation (vviewerqml.cpp):
#include "vviewerqml.h"
VViewerQml::VViewerQml(QSettings &systemSettings, QObject *parent) :
QObject(parent)
{
// Initialize viewer reading from settings file
_qmlFolder = "/my/path/to/qml/files";
// Initialize the source
connect(&_view, SIGNAL(statusChanged(QQuickView::Status)),
this, SLOT(onViewStatusChanged(QQuickView::Status)));
_view.setSource(QUrl::fromLocalFile(QDir(_qmlFolder).filePath("Main.qml")));
// Show the viewer
_view.show();
}
VViewerQml::~VViewerQml()
{
// Close the viewer
_view.close();
}
void VViewerQml::onViewStatusChanged(QQuickView::Status status)
{
if(status == QQuickView::Ready)
{
QQmlComponent *c =
new QQmlComponent(_view.engine(),
QDir(_qmlFolder).filePath("TextLegacy.qml"));
QQuickItem *i = qobject_cast<QQuickItem*>(c->create());
QQmlEngine::setObjectOwnership(i, QQmlEngine::CppOwnership);
i->setParent(_view.rootObject());
i->setVisible(true);
}
}
Here is my Main.qml:
import QtQuick 2.0
Rectangle {
width: 1024
height: 768
color: "#000000"
Text {
x: 0
y: 0
color: "#ffffff"
text: "Main page"
}
}
And here is my TextLegacy.qml:
import QtQuick 2.0
Item {
Rectangle {
x: 0
y: 0
width: 100
height: 50
color: "#ff0000"
}
}
My code works fine until the load of Main.qml: the QML viewer opens on the screen and I can read the text "Main page" (white on black) on my screen... But unluckily I am not able to load the TextLegacy.qml... if I put a breakpoint in onViewStatusChanged function, the execution reaches that point... no visible errors are shown in the debug console... but I am not able to see on the screen the red rectangle provided by TextLegacy.qml...
What am I missing? Can somebody give some help?
Ok, I found the solution on my own: I have confused setParent with setParentItem... The correct code is:
void VViewerQml::onViewStatusChanged(QQuickView::Status status)
{
if(status == QQuickView::Ready)
{
QQmlComponent *c = new QQmlComponent(_view.engine(),
QUrl::fromLocalFile(QDir(_qmlFolder).filePath("TextLegacy.qml")));
QQuickItem *i = qobject_cast<QQuickItem*>(c->create());
QQmlEngine::setObjectOwnership(i, QQmlEngine::CppOwnership);
i->setParent(this);
i->setVisible(true);
i->setParentItem(_view.rootObject());
}
}
Actually i->setParent(this); defines the parent of i as a QObject (for deleting purposes, for example) while i->setParentItem(_view.rootObject()); actually adds the object to the scene, as a child of the scene root object.
First i created a new c++ class with a member function, which is giving back a string:
#ifndef TESTNAME_H
#define TESTNAME_H
#include <QObject>
#include <QString>
#include <QVariant>
class testname : public QObject
{
Q_OBJECT;
public:
testname();
Q_INVOKABLE QString getName();
};
#endif // TESTNAME_H
#include "testname.h"
testname::testname()
{
}
QString testname::getName() {
return "sometext";
}
I have a qml file with only a text in the middle, like this:
import QtQuick 1.1
Rectangle {
width: 360
height: 360
Text {
id: text1
anchors.centerIn: parent
text: testqml
font.pixelSize: 12
}
}
Notice that the property "text" is a variable named "testqml". This variable contains the string returned by the function of the class i showed above. The code for this is in the main.cpp:
#include <QApplication>
#include "qmlapplicationviewer.h"
#include <testname.h>
#include <QDeclarativeContext>
#include <QDebug>
Q_DECL_EXPORT int main(int argc, char *argv[])
{
QScopedPointer<QApplication> app(createApplication(argc, argv));
// Create instance of "testname"
testname *test = new testname;
QmlApplicationViewer viewer;
viewer.rootContext()->setContextProperty("testqml", test->getName());
viewer.addImportPath(QLatin1String("modules"));
viewer.setOrientation(QmlApplicationViewer::ScreenOrientationAuto);
viewer.setMainQmlFile(QLatin1String("qml/classtest/main.qml"));
viewer.showExpanded();
return app->exec();
}
Using the setContextProperty-Function, the returned string is exposed to the qml file and is correctly shown in the running program.
But i'm actually intending to inherit a model for a GridView. So i created a Gridview with one list element in QML:
import QtQuick 1.1
Rectangle {
width: 360
height: 360
GridView {
id: grid_view1
anchors.centerIn: parent
width: 140
height: 140
cellHeight: 70
delegate: Item {
x: 5
height: 50
Column {
spacing: 5
Rectangle {
width: 40
height: 40
color: colorCode
anchors.horizontalCenter: parent.horizontalCenter
}
Text {
x: 5
text: name
anchors.horizontalCenter: parent.horizontalCenter
font.bold: true
}
}
}
model: ListModel {
ListElement {
name: testqml
colorCode: "grey"
}
}
cellWidth: 70
}
}
The variable "testqml" is now in the "name" Field of the List, which is in the example a string. If i use a string "likethis", it is correctly displayed. But if i run my program (main.cpp and class are remaining unchanged) i get this error:
ListElement: cannot use script for property value
name: testqml
^
Now i'm stuck. I found an reported bug with a similar issue #QTBUG-16289, but i don't know how to solve my problem. Any ideas, how-tos, tutorials or something to solve my problem?
Thanks and regards :)
I ran your code and reproduced your error but what are you ultimately trying to do?
Your delegate indicates that you want to inject a number of model items with name and color from C++, otherwise, why use a GridView, correct?
If that is not the case then perhaps what follows will not be that useful, or perhaps some variant of it might be. So I went ahead and constructed an example of what I thought you might be trying to accomplish.
In summary, on my system, after creating an arbitrary number of model items (in this case 20), the scrollable GridView delegate (midway along the scroll range) looks like this:
As I said, it seems that you want to inject a number of QString items from a C++ model into a QML GridView, noting that using a GridView implies that you would like to have a number of items. In most cases you will want to inherit from a pre-defined Qt Model, which automatically takes care of several important details like keeping the QML views in-sync with the model, and automatically updating the views when items are removed or new ones are added.
With this in mind, the QAbstractListModel is a convenient Class from which to base your model (this is not the only option though, see the help files). At first glance, setting up this model can appear complicated, so I went ahead and defined a minimal version which I hope illustrates what you want to do.
Below is the code for the model (note: I put all of the code in the .h file so no .m file is needed). I also created some "Items" to inject into the model, for simplicity, a struct Item is used as defined below, but these could easily be instances of another suitably defined Class:
#include <QString>
#include <QColor>
#include <QDebug>
#include <QAbstractListModel>
// Create an Item so we have something to put in the model:
struct Item {
QString name;
QString color;
};
class testname : public QAbstractListModel
{
Q_OBJECT
public:
explicit testname(QObject *parent = 0) : QAbstractListModel(parent)
{
// Create some items and then add to the model:
int N = 20;
QStringList colorNames = QColor::colorNames();
Item* items = new Item[N];
for (int i = 0; i < N; i++) {
items[i].name = QString("item"+QString::number(i));
items[i].color = colorNames[i];
//qDebug() << items[i].name << "; " << items[i].color;
_model<<items[i];
}
}
// enum DataRoles for QAbstractListModel:
enum DataRoles {
NameRole = Qt::UserRole + 1,
ColorRole
};
// addData() method for QAbstractListModel:
void addData(const Item& entry) {
beginInsertRows(QModelIndex(), rowCount(), rowCount());
_model << entry;
endInsertRows();
}
// rowCount() method for QAbstractListModel:
int rowCount(const QModelIndex & parent = QModelIndex()) const {
return _model.count();
}
// data() required for QAbstractListModel:
QVariant data(const QModelIndex & index, int role) const {
if ( !index.isValid() || index.row() < 0 || index.row() >= _model.count() )
return QVariant();
Item modelEntry = _model[index.row()];
if (role == NameRole) {return modelEntry.name;}
if (role == ColorRole) {return modelEntry.color;}
return QVariant();
}
// roleNames() method for QAbstractListModel:
QHash<int,QByteArray> roleNames() const {
QHash<int, QByteArray> roles;
roles[NameRole] = "Name";
roles[ColorRole] = "Color";
return roles;
}
private:
// Below are the model items:
QList<Item> _model;
};
Next is the QML code which uses the C++ model defined above and registered as "testqml" in main.cpp, and then defined through the property, model: in GridView.
Note that in the delegate that the Color and Name properties of the model are defined as role names in the class above (these could be any label you like). To help visualize what is going on, the model roles are very similar to the columns of a table, with the row entries corresponding to the model items:
import QtQuick 1.1
Rectangle {
width: 360
height: 360
/* ------------------- */
GridView {
id: grid_view1
anchors.centerIn: parent
width: 140; height: 140
cellHeight: 70
delegate: delegateItem
model: testqml // the C++ model is set here
cellWidth: 70;
}
/* ------------------- */
Component {
id: delegateItem
Item {
x: 5; height: 50
Column {
spacing: 5
Rectangle {
width: 40; height: 40;
color: Color // Note: this a role defined in the C++ model
anchors.horizontalCenter: parent.horizontalCenter
}
Text {
x: 5;
text: Name // Note: this is another role defined in the C++ model
anchors.horizontalCenter: parent.horizontalCenter
font.bold: true
}
}
}
} // end delegateItem
} // end Rectangle
And then my main.cpp is nearly the same as yours, I'll go ahead and post it to avoid any confusion:
#include "qtquick1applicationviewer.h"
#include <QApplication>
#include "testname.h"
#include <QDeclarativeContext>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
testname *test = new testname();
QtQuick1ApplicationViewer viewer;
viewer.rootContext()->setContextProperty("testqml",test);
viewer.addImportPath(QLatin1String("modules"));
viewer.setOrientation(QtQuick1ApplicationViewer::ScreenOrientationAuto);
viewer.setMainQmlFile(QLatin1String("qml/QMLSetProperty/main.qml"));
viewer.showExpanded();
return app.exec();
}
Hope this helps!