How to expose global strings and ints to C++, QML, and JS - qt

Our team is developing a Qt application which makes use of C++, QML, and JS. What is the best way to expose non-language specific strings, representing filenames and ints representing error codes, so that all languages can easily use them?

The most efficient and clean solution would be to implement those strings and ints as properties of a QObject, then create an instance of that object in main.cpp and register it as a singleton.
// before main()
YourType data;
static QObject * getData(QQmlEngine * e, QJSEngine *) {
e->setObjectOwnership(&data, QQmlEngine::CppOwnership); // just in case
return &data;
}
// in main()
qmlRegisterSingletonType<YourType>("Core", 1, 0, "Data", getData);
QQmlApplicationEngine engine;
//...
And now you have a global data for C++, and a global Data for QML. In the first case you will need to declare data as an extern in sources which need to access it, in the second case you will need to import Core 1.0 in order to access Data and its properties.

Related

Qt/QML: std::vector<int> handling

Edit: problem was with 5.6 only, which has a reduced set of supported "native" types according to https://doc.qt.io/qt-5.6/qtqml-cppintegration-data.html vs the latest version ...
According to this page: https://doc.qt.io/qt-5/qtqml-cppintegration-data.html, std::vector<int> is suppported by QML if registered with qRegisterMetaType() and exposed/accessed as a property. However, I cannot get this to work.
My class (which can be instantiated by QML, so this level works) has declarations like:
// prop decl
Q_PROPERTY(std::vector<int> myVector READ myVector NOTIFY myVectorChanged)
// read accessor
Q_INVOKABLE std::vector<int> recordTime() const;
// signal (in signal section)
void myVectorChanged();
Registration via
qRegisterMetaType<std::vector<int> >("std::vector<int>");
When I push something into the vector and try accessing myVector.length or myVector.size, it returns 'undefined' (size() is not callable). How do I iterate over the elements? The page linked above says "Certain C++ sequence types are supported transparently in QML to behave like JavaScript Array types" (and mentions std::vector<int> in the list), so I expected length and index access to work.
The documentation says this container will be converted to a JS array automatically. You don't need to register anything.
Of course, the conversion will be a copy, so modifying it will not modify the original array, and the way you use that is the same way you use a regular JS array. It definitely should have a length (not length()) property and support index access via [].
Update:
After your stories of failure I decided to actually run a simple test:
class Test : public QObject {
Q_OBJECT
public slots:
std::vector<int> test() { return std::vector<int> {1, 2, 3, 4, 5, 6, 7}; }
};
// in main.cpp
qmlRegisterType<Test>("Core", 1, 0, "Test");
// in qml
Test {
Component.onCompleted: {
var t = test()
console.log(t.length, t) // qml: 7 [1,2,3,4,5,6,7]
}
}
As you can see, it gives the expected output, no need to register anything whatsoever.
IIRC there was a problem with Qt that for some reason caused those automatic conversions to not kick in when you use a Q_PROPERTY interface. I suppose that issue is still valid, the solution thankfully is to simply not use a property but a simple return value.
If your problems persist, I suggest to carefully examine your code, or if necessary, clean and rebuild your project, because the conversion is definitely working out as expected, aside from the property related issue.

Access specific object from any class

I am trying to access awesome object from another class. I declare this in main()
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QtAwesome* awesome = new QtAwesome(&app);
awesome->initFontAwesome();
app.setWindowIcon(awesome->icon( "clipboard" ));
Login w;
w.show();
return app.exec();
}
Now how do I access awesome object from Login or any another class? Is it best to initialize a class in main() if I want to access from another class?
Declaring awesome in main() makes it scoped to the main() function, and thus only visible from with main(), or functions you explicitly pass it to from main().
The simple way to achieve what you want is to use an "external variable": Put the definition and declaration outside of the main() in your main executable file:
main.cpp:
QtAwesome * awesome = new QtAwesome(&app);
int main(int argc, char *argv[]) {
awesome->do_something();
// ...
}
The above is called the defining declaration of "awesome". In other files you want to use awesome in, use the extern storage specifier:
somerandommodule.cpp:
extern QtAwesome * awesome;
void some_function() {
awesome->do_something();
}
It's important that you have exactly one "defining declaration". The linker will make all extern awesome declarations point to this one definition.
If you are the one writing the QtAwesome class, another way of doing it is keeping the single instance in the QtAwesome class. This is roughly the "singleton pattern". This just makes it easier to tell where and when initialization is happening:
qtawesome.h:
class Awesome {
private:
static Awesome * singleton;
public:
static Awesome * getInstance() {
if(!Awesome::singleton)
Awesome::singleton = new Awesome();
return Awesome::singleton;
};
};
qtawesome.cpp:
#include "awesome.h"
Awesome* Awesome::singleton = 0;
somerandommodule.cpp:
#include "awesome.h"
void some_function() {
Awesome::getInstance()->do_something();
}
You have two options
pass a pointer or reference to awesome to the Login object, e.g. as an argument to the constructor of Login
use a singleton pattern or global variable (as suggested by S.Pinkus)
A global variable or singleton increases the coupling, i.e. the Login object becomes dependent on this specific QtAwesome object to exist.
Passing an object into places of usage enables you to use a specific QtAwesome object for each usage or use the same for multiple usages, etc.
Globally accessible objects, whether global variables or singletons, often make testing more complicated, since the need for such an object is not immediately visible and all tests within the same process access the same globally accessile object, potentially resulting in tests affecting each other in unexpected ways.

Create custom C++ Object in QML and store it in a C++ Model

I have written a customer Class (inheriting from QObject) in C++ and registered it's type successfully with QML. Currently I'm creating objects of this class statically in C++ and storing a pointer to them in a Model which implements QAbstractListModel. In QML in I can access the objects perfectly as items of the Model.
The customObject is a non-visual object.
I'm visualising in another part of the GUI application (QML) the objects in a ListView with a delegate.
However now I would like to create objects from my custom Class dynamically in QML and store them also in the Model. This is where I'm struggling. I hoped I could create a customObject like this:
import com.myProject.myCustomStuff 1.0
...
Button{
id: createObjBtn
text: "create new CustomObj"
onClicked:{
var obj = MyCustomObj;
myObjectManager.addObj(obj); // object holding the implemented QAbstactListModel
console.log(typeof(obj)); // returns [Object object]
console.log(Qt.isQtObject(obj)) // returns false
}
}
I would appreciate your thoughts. Maybe someone knows a way to do this correctly?
Thanks!
Update 1
As requested by Simon-Warta, here is the Constructor implementation of MyCustomObj.
MyCustomObj.cpp
MyCustomObj::MyCustomObj(QObject *parent) : QObject(parent)
{
QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
}
You are confusing the functionality intent of the classes. The QAbstractListModel is intended as a wrapper around a container, yes, you could put the container inside the QAbstractListModel derived class, but you don't really have to, the container can be just about any C++ class, not necessarily even QObject derived, it can be just a QVector<Something> that you can reach from the model via a pointer. Which is good for cases where you have many objects, and not all need to have models at all the time, since those models are pretty heavy.
You don't really need to concern yourself with the ownership, leave that at the C++ side, the same goes for the actual object creation, have a slot called that adds the new object to the container while also using the model's beginInsertRows() and endInsertRows() so that any views will be notified to update efficiently, the new object should also be created in that slot, you can pass any data needed for it from QML, just make sure all data is registered with the Qt meta system so it can work with QVariant for QML-C++ interop.
So it should be something like:
myObjectManager.create(/* any needed data goes here */)
And create() passes eventual data to the C++ side, where you create the object, call beginInsertRows(), add the object to the model's underlying storage, then call endInsertRows() and you are done.
I'd prefer to keep the ownership in the C++ side (and I don't mean explicitly), where I have control over it. Qt kind of sucks in a big way when dealing with object ownership shared between C++ and QML. Ideally, there should be a single shared pointer class that will work across both C++ and QML, so the object is deleted once all reference to it are gone. But that is just not the "Qt way" - the Qt shared pointers do not work with QML, nor do the standard C++ shared pointers, there is actually an entirely different shared reference class for QML, which is not even part of the public API IIRC, very ugly and shortsighted design that only widens the gap between C++ and QML and the associated inconvenience factor.
I don't know if this is the shortest way but this should do for you. I am starting with the basics for those other up-voters.
MyCustomObj.h
class MyCustomObj : public QObject
{
Q_OBJECT
public:
// ...
Q_INVOKABLE void funfunction();
MyCustomObj.cpp
void MyCustomObj::funfunction()
{
qDebug("Fun with QML");
}
main.cpp
qmlRegisterType<MyCustomObj>("com.myProject.myCustomStuff", 1, 0, "MyCustomObj");
app.qml
import com.myProject.myCustomStuff 1.0
ApplicationWindow {
id: mainWindow
Component {
id: myComponent
MyCustomObj {
}
}
Component.onCompleted: {
var obj = myComponent.createObject(mainWindow)
if (!obj) console.error("Error creating object")
console.log(typeof(obj))
console.log(Qt.isQtObject(obj))
obj.funfunction()
}
}
createObject optionally takes properties to be passed to the component.
Storing
Since you are responsible for deleting the objects now, I'd recommend to use shared pointers, such that the objects get destroyed when the List is destroyed.
Your implementation of QAbstactListModel, let's call it MyModel has an adder function like that:
#include <memory> // for std::shared_ptr
class MyModel : public QAbstractListModel
{
Q_OBJECT
public:
// ..
Q_INVOKABLE addObj(MyCustomObj* obj)
{
objectlist_.append(std::shared_ptr<MyCustomObj>(obj));
}
private:
QList<std::shared_ptr<MyCustomObj>> objectlist_
}

Convert a QStandardItemModel to a QVariant

I'm trying to send a QStandardItemModel-derived object to PythonQt, but I'm a little confused on how it needs to be sent. When I was using boost::python I had several controls like boost::noncopyable to ensure I wasn't recreating this object, but sharing it with python. I also had constructs to provide a boost shared pointer to python from inside python.
class Scene : public boost::enable_shared_from_this<Scene>, public QStandardItemModel
In PythonQt, however, I'm not sure what's available. The function call takes a QVariantList for all the function parameters.
QVariant PythonQt::call(PyObject* object, const QString &callable, const QVariantList &args = QVariantList))
What I'm confused about now is how to get my object to python via a QVariant. Since its derived from QStandardItemModel, I figured it would already be register
void MyObject::someFunction(QString fileName)
{
QVariant myObjectV = qVariantFromValue(this);
// send to python
...
}
But this gives me the following error:
'qt_metatype_id' : is not a member of 'QMetaTypeId<MyObject>'
I've tried registering it after I declare my class, but this throws a different error.
class MyObject : public QStandardItemModel
{
Q_OBJECT
...
};
Q_DECLARE_METATYPE(MyObject)
QStandardItemModel::QStandardItemModel(const QStandardItemModel&) is private within this context.
I actually get the error twice--once in header where I add the Q_DECLARE_METATYPE and in another header, which has a class which always derives from QStandardItemModel but is otherwise unrelated.
Is Q_DECLARE_METATYPE even the correct way to go about converting this object to a QVariant?
BOOST_PYTHON_MODULE(scene)
{
class_("Scene");
}
Yes, by default, QVariant can take one of te following types - http://doc.qt.io/qt-4.8/qvariant.html#Type-enum - and they are not enough for your task. You should declare additional types by yourself via qmetatype system. Thus you shoud call qRegisterMetaType() function.

Passing unmanaged pointer to unmanaged object in managed class in C++/CLI

Let's assume the following situation:
class A
{
public:
void MyMethod()
{
a->AnotherMethod(b);
}
private:
MyType* a;
MyAnotherType* b;
};
and
typedef std::vector< int >MyAnotherType;
I want to pass a pointer to std::vector allocated (and filled in) in C++/CLI code to unmanaged C++. If I simply write a->AnotherMethod(b) then the vector is empty in unmanaged code (e.g. 4 elements in C++/CLI and 0 elements after passing to a.
What is the proper way to do that?
Try pragma managed and unmanaged.
Make sure the definition of the class you are passing into the DLL is defined in unmanaged section.

Resources