How to use QScxmlCppDataModel in QScxmlStateMachine? - c++

In my project , i use C++ , QScxmlCppDataModel , there is always occur a error , "No data-model instantiated" when i start the state machine,
I follow the Qt document says
1、Add data model in scxml file
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" binding="early" xmlns:qt="http://www.qt.io/2015/02/scxml-ext" datamodel="cplusplus:DataModel:DataModel.h" name="PowerStateMachine" qt:editorversion="4.6.1" initial="nomal">
2、Create a new data model subclass
#include "qscxmlcppdatamodel.h"
#include <QScxmlEvent>
class DataModel :public QScxmlCppDataModel
{
Q_OBJECT
Q_SCXML_DATAMODEL
public:
// DataModel();
bool isMoreThan50() const;
bool isLessThan50() const ;
int m_power;
QString m_Descript;
QVariant m_var;
};
3、load and start state machine
m_stateMachine = QScxmlStateMachine::fromFile(":/powerStateMachine.scxml");
for(QScxmlError& error:m_stateMachine->parseErrors())
{
qDebug()<<error.description();
}
m_stateMachine->connectToEvent("powerLoss", this, &MainWindow::onPowerLossEvent);
m_stateMachine->connectToEvent("pwoerUp", this, &MainWindow::onPowerUpEvent);
m_stateMachine->connectToState("low", this, &MainWindow::onLowState);
m_stateMachine->connectToState("nomal", this, &MainWindow::onNomalState);
m_stateMachine->connectToState("danger", this, &MainWindow::onDangerState);
m_stateMachine->connectToState("full", this, &MainWindow::onFullState);
DataModel *dataModel = new DataModel;
m_stateMachine->setDataModel(dataModel);
m_stateMachine->init();
m_stateMachine->start();
error image
but still have a error :"No data-model instantiated",when i start the
state machine , anybody know how to fix it ?? thank you

Qt's documentation on QScxmlCppDataModel specifically says:
The C++ data model for SCXML ... cannot be used when loading an SCXML
file at runtime.
And that is exactly what you are doing, so instead of loading the scxml file at runtime:
m_stateMachine = QScxmlStateMachine::fromFile(":/powerStateMachine.scxml");
Use the compiled statemachine directly:
MyStateMachine statemachine;
MyDataModel datamodel;
statemachine.setDataModel(&datamodel);
In the example above:
1- MyStateMachine is the value you have assigned to the <name> attribute of the scxml element (i.e. scxml file/model).
2- MyDataModel is the name you have given to your c++ data model class (i.e. your class derived from QScxmlCppDataModel)
class MyDataModel : public QScxmlCppDataModel
{
Q_OBJECT
Q_SCXML_DATAMODEL
public:
MyDataModel();
};
Too late an answer, but hope it helps others.

Related

C++ member variable class Q_PROPERTY not available in QML

I am attempting to make a new QML QQuick object that will contain a sub-object of QQuickPaintedItem.
Below is a shortened portion of my c++ code
// PDFDocument.h //
class PDFDocument : public QQuickItem
{
public:
Q_OBJECT
Q_PROPERTY( PDFPageView* pageView READ getPageView )
PDFDocument( QQuickItem* parent = nullptr );
~PDFDocument();
PDFPageView* getPageView() { return &m_pageView; }
private:
PDFPageView m_pageView;
};
//PDFDocument.cpp//
PDFDocument::PDFDocument( QQuickItem* parent /*= nullptr*/ )
:QQuickItem( parent )
{
}
//PDFPageView.h//
class PDFPageView : public QQuickPaintedItem
{
public:
Q_OBJECT
Q_PROPERTY( int dpi MEMBER m_dpi NOTIFY dpiChanged )
Q_SIGNALS:
void dpiChanged();
public:
PDFPageView( QQuickItem* parent = nullptr );
~PDFPageView();
void paint( QPainter* painter_p );
private:
int m_dpi = 144; //default dpi to 144
};
Next is the actual QML snippet
PDFDocument
{
id: pdfDocument
anchors
{
fill: parent
centerIn: parent
}
pageView.dpi: 200 //Invalid grouped property access
}
The type is registered in the engine as well
qmlRegisterType<PDFDocument>( "Nordco.TechPubs", 1, 0, "PDFDocument" );
qmlRegisterType<PDFPageView>( "Nordco.TechPubs", 1, 0, "PDFPageView" );
For some reason I am getting an Invalid grouped property access error in the QML. I marked it with a comment in the qml code snippet.
I shortened the code because I have quite a bit, but can edit this post if i forgot to show anything. I feel like I am missing something simple here but cannot seem to get a helpful error. Any ideas?
It appears that I needed to include my namespaces when declaring a Q_Property because it is a macro. Now the custom member variable is accessable.
Q_PROPERTY( TechnicalPublications::PDFPageView* pageView READ getPageView )
Of course in shortening the problem, I excluded the namespaces my classes exist in.

QML can see my Q_GADGET but not Q_OBJECT

Why can my Q_GADGET be read perfectly in QML (JS) but not my Q_OBJECT?
Running Qt 5.8.0 on Ubuntu 14.04.
I'm attempting to return a list (QVariantMap) of objects to QML. I'm keeping it simple right now, no pointers, etc.. just copies of the objects.
struct Blob
{
Q_GADGET
Q_PROPERTY(QString uuid MEMBER uuid_ CONSTANT)
Q_PROPERTY(QVector3D centroid MEMBER centroid_)
// ...
Blob() {};
~Blob() = default;
Blob(const Blob& blob);
};
Q_DECLARE_METATYPE(Blob)
There are a bunch of QString and QVector3D members on Blob.
In main.cpp, I also register the type:
qmlRegisterType<Blob>("matt", 1, 0, "Blob");
Then my JS code is able to read all the properties (for loop iterating over them) on the object without issue.
But if I use a Q_OBJECT
struct Blob : public QObject
{
Q_OBJECT
Q_PROPERTY(QString uuid MEMBER uuid_ CONSTANT)
Q_PROPERTY(QVector3D centroid MEMBER centroid_)
// ...
explicit Blob(QObject parent = nullptr) : QObjecT(parent) {};
~Blob() = default;
Blob(const Blob& blob);
};
Q_DECLARE_METATYPE(Blob)
Then the JS receives an object where all the properies are the keys from the QVariantMap, but whose values are empty objects.
Note, before sending it to the JS, I convert it to a QVariant and back again just to confirm that that works, e.g.
Blob b;
qDebug() << "Created: " << b;
QVariant var = QVariant::fromValue(b);
qDebug() << " Converted back = " << var.value<Blob>().toQString();
I'd prefer to use a Q_OBJECT such that I have slots/signals.
The reason I was seeing a difference between Q_OBJECT and Q_GADGET was that I was making copies of my object, which is allowed on a Q_GADGET (a value) object, but not on a Q_OBJECT (an identity object.) See identities instead of values.
The solution is to always work on Q_OBJECTs with pointers. This maintains their identity, and avoid copies.
Also, my original intent was to use a smart pointer, but the reasons why that is a bad approach are explained in this answer.
The comment by #dtech also explained that Q_DECLARE_METATYPE is redundant on a Q_OBJECT.
Thus, my final declaration is:
i.e.
class Blob : public QObject
{
Q_OBJECT
Q_PROPERTY(QString uuid MEMBER uuid_ CONSTANT)
Q_PROPERTY(QVector3D centroid MEMBER centroid_)
// ...
explicit Blob(QObject parent = nullptr) : QObjecT(parent) {};
~Blob() = default;
Blob(const Blob& blob);
};
With this, I can easily put a raw pointer to these objects in a QVariantMap, and they can be read on the QML/JS side.

How to create custom Quick QML Item with predefided states

I am quite new to Qt, so I am probably asking a pretty obvious question.
I would like to create a super type for all of my custom QML GUI elements that I want to create in C++.
This super type is supposed to add predefined states to a QML Item. Something alike to this:
import StatedGuiElement 1.0
import QtQuick 2.0
Item {
width: 300; height: 200
StatedGuiElement {
id: aStatedGuiElement
anchors.centerIn: parent
width: 100; height: 100
//some visible Custom Gui Elements
states:[
State {
name: "A_STATE"
},
State {
name: "ANOTHER_STATE"
}]
}
I know how to create a simple custom Item from this tutorial (http://doc.qt.io/qt-5/qtqml-tutorials-extending-qml-example.html). I guess the states could be defined by using an enum in the C++ class which inherits from QQuickItem. However, this tutorial does not show how to create more complex Qt Quick elements like the state list.
class StatedGuiElement : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName)
//pass States via Q_PROPERTY?
public:
//define Enum for fixed States here?
//ENUM STATES {A_STATE, ANOTHER_STATE}
StatedGuiElement( QQuickItem *parent = 0);
QString name() const;
void setName(const QString &name);
private:
QString m_name;
//Some List of States?
signals:
public slots:
};
So the questions I am wondering about are as follows:
Is it even possible to predefine QML State types and use them in multiple elements?
How do I add complex QML types like State Lists in a C++ class such as StatedGuiElement?
First you create your StatedGuiElement as a QQuickItem subclass.
Then you crate a StatedGuiElement.qml, import the package that contains the C++ element, make a StatedGuiElement {} inside, add your states in QML to it, then you can use StatedGuiElement in your project. It will be the one with the QML extra stuff predefined in it.
This assumes the element actually has things you need to implement in C++. If not then it would not make sense to have a C++ element at all. I am not sure whether old C++ state classes will work with QML, probably not, and using the QML states from C++ will be anything but convenient, so you really should do the states in QML, on top of whatever C++ stuff you may have.
It is possible to define your properties once and use them im multiple elements, if you nest your QML elements inside a super-type QML element where you have all your states defined. Child elements can access the parent parameters.
Alternatively, you can also just simply set the context property for each QML which should use the data like this in C++:
QQuickView view;
QStringList data;
// fill the list with data via append()
view.rootContext()->setContextProperty("dataList", QVariant::fromValue(data));
// now the QML can freely use and access the list with the variable name "dataList"
view.setSource(QUrl::fromLocalFile("MyItem.qml"));
view.show();
In the QML-side, you can also declare a custom property which holds the names of the states.
Item {
property variant state_list: ["element1", "element2", "element3"]
// or if you defined a list in the C++ part as a context property
// you can use this instead:
// property variant state_list: dataList
states: [
State {
name: state_list[0]
},
State {
name: state_list[1]
},
// and so on
]
}
If you need a property that is a list of elements, i..e. states being a list of State objects, then you can do this in C++ using the QQmlListProperty type.
You need a QObject derived type for the list element type.
Example
class Entry : public QObject
{
// the list entry element's API
};
class MyItem : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(QQmlListProperty<Entry> entries READ entries)
public:
QQmlListProperty<Entry> entries() const {
return QQmlListProperty<Entry>(this, m_entries);
}
private:
QList<Entry*> m_entries;
};
Register both with qmlRegisterType()
In QML
MyItem {
entries: [
Entry {
},
Entry {
}
]
}

Why is QObject ::findChildren returning children with a common base class?

I am using QObject as a base class for a composite pattern.
Say I have a parent class File (in a contrived example) to which I am adding children of different types, HeaderSection and PageSection. File, HeaderSection and PageSection are all Sections. The constructor for Section takes a parent object which is passed through to QObject's constructor, setting the parent.
e.g:
class Section : public QObject {
Q_OBJECT
// parent:child relationship gets set by QObject
Section(QString name, Section *parent=NULL) : QObject(parent)
{ setObjectName(name);}
QString name(){return objectName();}
};
class File: public Section {
public:
// probably irrelevant to this example, but I am also populating these lists
QList<Section *> headers;
QList<Section *> pages;
};
class Header : public Section {
Header(QString name, File *file) : Section(name, file){}
};
class Page: public Section {
Body(QString name, File *file) : Section(name, file){}
};
Syntax for construction in the definition may be incorrect, apologies, I'm used to doing it outside. Anyway, when I do this:
File *file = new file();
Header *headerA = new Header("Title", file);
Header *headerB = new Header("Subtitle", file);
Page *page1 = new Page("PageOne", file);
Page *page2 = new Page("PageTwo", file);
QList<Page*> pages = file->findChildren<Page*>();
for(int i=0; i < pages.size(); i++)
qDebug() << pages.at(i)->name();
I get the following output:
Title
Subtitle
PageOne
PageTwo
What am I missing here? Surely if findChildren looked for common base classes then it would only ever return every single child of a Widget (for example), which I know it doesn't in normal use.
Also, if I iterate over the list of children returned and use dynamic_cast<Page*> on each returned child, I get the expected two Page items.
The answer is as #Mat and #ratchet freak tell me - I needed Q_OBJECT in every subclass, not just the base class.

Qt invoking qml function with custom QObject type

I want to invoke a qml method from c++ passing as a parameter a custom type.
This is my custom type (*Note that all the code I post is not exactly what i'm using, i've extracted the relevant parts, so if you try to compile it is possible that some changes have to be made)
Custom.h
class Custom : public QObject
{
Q_OBJECT
Q_PROPERTY(QString ssid READ getSSID WRITE setSSID)
public:
Q_INVOKABLE const QString& getSSID() {
return m_ssid;
}
Q_INVOKABLE void setSSID(const QString& ssid) {
m_ssid = ssid;
}
}
private:
QString m_ssid;
};
Q_DECLARE_METATYPE(NetworkInfo)
Custom.cpp
Custom::Custom(): QObject() {
setSSID("ABC");
}
Custom::Custom(const Custom & other): QObject() {
setSSID(other.getSSID());
}
This is where I call the qml method
void MyClass::myMethod(const Custom &custom) {
QMetaObject::invokeMethod(&m_item,
"qmlMethod", Q_ARG(QVariant, QVariant::fromValue(custom)));
}
where QQuickItem& m_item
I have declared Custom as QML and QT meta types
qmlRegisterType<Custom>("com.mycustom", 1, 0, "Custom");
When i try to access the parameter inside the qml
import com.mycustom 1.0
function(custom) {
console.log(custom.ssid)
}
I get
qml: undefined
If i do console.log(custom) I get
qml: QVariant(Custom)
so, i'd say my type is correctly imported into Qt (I have used it without problems instantiating it directly in Qml with
Custom {
....
}
Now, the question :)
Why can't I access the ssid property ?
Since you seem to have registered everything correctly, you might be encounting a bug that sometimes happen with QML where objects seem to be stuck in a QVariant wrapping. Try the following and see if it work.
import com.mycustom 1.0
property QtObject temp
function(custom) {
temp = custom;
console.log(temp.ssid);
}
Sometimes forcing your instance to be a QtObject will remove the annoying "QVariant wrapper" and solve the issue.