How to create a "creatable" qml singleton? - qt

I have a use case where I need to instantiate a singleton in qml (for property bindings).
i.e:
File: main.qml
Item{
MainQuery{id: mainquery
graphql: `query MainQuery{...}`
}
Text{
text: mainquery.data
}
}
This other file should use the query defined in main.qml
File: otherfile.qml
Item{
import MyLib 1.0 as Gql
Text{
text: Gql.MainQuery.data
}
}
However if you use QML_SINGLETON you can't initialize it in QML
and in order to bind to the graphql property above you would have to use
Connection.
Is there a better solution?
Note:
The object should be a singleton in CPP / Python side as well.
currently it is implemented in python like this:
class QSingleton(type(QObject)): # type: ignore
def __init__(cls, name, bases, dict):
super().__init__(name, bases, dict)
cls.instance = None
def __call__(cls, *args, **kw):
if cls.instance is None:
cls.instance = super().__call__(*args, **kw)
return cls.instance
class BaseQuery(QObjcet, QSingleton):
...
#QmlElement
class MainQuery(BaseQuery):
...
And gives this nice error when the qml engine tries to initiate the type:
QEventLoop: Cannot be used without QApplication
Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)
(I do have an eventloop running)

Whether the underlying Python object is or is not a singleton it doesn't matter. We can always declare a singleton wrapper in QML by defining a qmldir and using pragma Singleton in our source file. To demonstrate this approach, I wrap TextMetrics in my singleton object and expose TextMetrics properties as properties of my singleton so that I can use property binding with singleton syntax:
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Page {
Frame {
anchors.centerIn: parent
ColumnLayout {
TextField {
text: "Hello"
onTextChanged: MyWrapper.tmText = text
}
Text {
text: MyWrapper.tmWidth
}
}
}
}
// qmldir
module ExampleWrapper
singleton MyWrapper 1.0 MyWrapper.qml
// MyWrapper.qml
pragma Singleton
import QtQuick
import QtQuick.Controls
Item {
property alias tmText: tm.text
property alias tmWidth: tm.width
TextMetrics {
id: tm
text: "Hello"
}
}
You can Try it Online!
References:
https://doc.qt.io/qt-6/qtqml-modules-qmldir.html

Related

Unable to assign [undefined] to QColor when including QtObject as styling file

I am trying to add set of custom properties to be included by several labels on my QML. The idea is to make a style, but I keep getting these errors:
qrc:/TestPage.qml:18:9: Unable to assign [undefined] to QColor
qrc:/TestPage.qml:17:9: Unable to assign [undefined] to double
qrc:/TestPage.qml:16:9: Unable to assign [undefined] to QString
I was following the thread below but did not work for me.
Unable to assign [undefined] to QColor
Here is my label:
import "Theme"
Label
{
font.family: Theme.tfont
font.pointSize: Theme.tpointSize
color: Theme.tcolor
text: "text"
}
and here is my Theme.qml file:
pragma Singleton
import QtQuick
QtObject
{
readonly property string tfont: {return "Calibri"}
readonly property real tpointSize: {return 28}
readonly property string tcolor: {return "black"}
}
any ideas why it won't resolve the property?
Check that example it uses a Style.qml to define a global theme. As Stephen Quan already said you should create a qmldir to define the singleton.
The important part here is to also include the qmldir and the Theme.qml in your CMakeLists.txt or pro.
CMakeLists.txt
...
qt_add_qml_module(appuntitled
URI untitled
VERSION 1.0
QML_FILES
main.qml
qmldir
Theme.qml
)
...
qmldir
singleton Theme 1.0 Theme.qml
Theme.qml
pragma Singleton
import QtQuick
QtObject {
readonly property string tfont: "Calibri"
readonly property real tpointSize: 28
readonly property string tcolor: "black"
}
main.qml
import QtQuick
import QtQuick.Controls
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Label {
font.family: Theme.tfont
font.pointSize: Theme.tpointSize
color: Theme.tcolor
text: "text"
}
}
I made the following changes to your app:
Declare qmldir
Don't need import "Theme"
Refactor AppLabel
Don't use return when declaring properties declaratively
import QtQuick
import QtQuick.Controls
Page {
AppLabel { text: "text" }
}
//AppLabel.qml
import QtQuick
import QtQuick.Controls
Label {
font.family: Theme.tfont
font.pointSize: Theme.tpointSize
color: Theme.tcolor
}
//qmldir
singleton Theme 1.0 Theme.qml
//Theme.qml
pragma Singleton
import QtQuick
QtObject
{
readonly property string tfont: "Calibri"
readonly property real tpointSize: 28
readonly property string tcolor: "black"
}
You can Try it Online!

declaring string in .qml files

I am trying to make a varaibles file in qml which declares several strings to be used by other qml files. for example: this would be Variables.qml
import QtQuick 2.0
Item {
property var mystring: qsTr("hello world")
}
and I want to reference it in another file tab1.ui.qml
import QtQuick 2.4
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.3
import "."
Item {
Rectangle {
id: rectangle
color: "#3e1edd"
anchors.fill: parent
Text {
id: text6
anchors.fill: parent
font.pixelSize: 12
text: Variables.mystring
visible: true
}
}
}
this gives an error: Unable to assign [undefined] to QString
please tell me if there is a better way to manage variables. I want them to be global so they can be used in multiple frames. Thanks
You must use a singleton, for it you must create a folder that contains the .qml with "pragma Singleton" at the top and a qmldir that indicate the files:
Global
├── qmldir
└── Variables.qml
qmldir
singleton Variables 1.0 Variables.qml
Variables.qml
pragma Singleton
import QtQuick 2.0
QtObject {
property var mystring1: qsTr("hello world1")
property var mystring2: qsTr("hello world2")
}
Then you must import and use it in the following way:
// others imports
import "Global"
// ...
Text{
// ...
text: Variables.mystring1
}
In this link you will find an example.

How to put multiple singletons in one qmldir file?

I want to use multiple singletons in one qmldir file but it doesn't seem to work, I don't have an error but the program doesn't launch.
qmldir:
singleton File1 1.0 File.qml
singleton File2 1.0 File2.qml
main:
import QtQuick 2.7
import QtQuick.Controls 2.1
Window {
visible: true
width: 640
height: 480
title: qstr("hello world!")
Button {
onCliked: File2.test();
}
}
File2.qml:
pragma Singleton
import QtQuick 2.7
Item {
signal test;
onTest: console.log("File2 received signal");
}
File1.qml:
pragma Singleton
import QtQuick 2.7
Item {
signal test;
onTest: console.log("File1 received signal");
}
The code works if I delete the second line in qmldir, but then File2 will be undefined.
If your qmldir file is exactly as you have written:
Singleton File1 1.0 File.qml
Singleton File2 1.0 File2.qml
The error might reside within that, as the keyword is singleton, not Singleton.
Try that:
singleton File1 1.0 File.qml
singleton File2 1.0 File2.qml
But that error should not fail silently. There should be some errors:
[main.qml] a component declaration requires two or three arguments, but 4 were provided
[qmldir] a component declaration requires two or three arguments, but 4 were provided
main.qml:
import QtQuick 2.7
import QtQuick.Controls 2.0
import '.'
ApplicationWindow {
id: window
width: 800
height: 600
visible: true
Row {
spacing: 3
Button {
text: 'single1'
onClicked: Single.sig()
}
Button {
text: 'signle2'
onClicked: Single2.sig()
}
}
}
singleton.qml
pragma Singleton
import QtQuick 2.0
QtObject {
signal sig
onSig: console.log('Singleton1 Received')
}
singleton2.qml
pragma Singleton
import QtQuick 2.0
QtObject {
signal sig
onSig: console.log('Singelton2 Received')
}
qmldir
singleton Single 1.0 singleton.qml
singleton Single2 1.0 singleton2.qml
Works like a charm.

List delegates driven live by JavaScript data model

I have a QML application where I'm creating lists of elements received from JavaScript. Using the details from this answer I'm populating the model as a JS array, and it works great. However, I'd like it so that when properties of the JavaScript objects change that ListView items driven from them update live.
Here's a simple test app showing the problem. The ListView is properly populated with MyRow instances showing the correct id/title, but when the rand property is changed by the timer, the List rows are unchanged (they show 0 for the last item).
MyRow.qml
import QtQuick 2.0
import QtQuick.Layouts 1.3
Rectangle {
property var obj
color:'#eeeeff'; height:20
RowLayout {
anchors.fill:parent
Text { text:obj.id }
Text { text:obj.title; Layout.fillWidth:true }
Text { text:obj.rand }
}
}
main.qml
import QtQuick 2.7
import QtQuick.Window 2.2
Window {
id:app; visible:true; width:200; height:100
property var database: ({"17":"World","42":"Hello"})
property var objById: ({})
function getObj(id){
if (!objById[id]) objById[id] = { id:id, title:database[id], rand:0 };
return objById[id];
}
ListView {
id:mylist
anchors.fill:parent
model: [42,17] // object ids
delegate: MyRow {
width:parent.width
obj:getObj(mylist.model[index])
}
}
Timer { // Update every object's rand value every second
interval:1000; running:true; repeat:true
onTriggered: {
Object.keys(objById).forEach(function(id){
objById[id].rand = Math.random()*100<<0;
})
}
}
}
How can I get the delegate's Text items to update their text when properties of the objects change?
The easiest (only?) way to get property bindings to work properly is to create real Qt objects to hook the values to. If you don't want to use a ListModel (because you want to quickly populate a model with items from a master library), then you can use createObject() to generate objects and pass them to your delegate.
Here's an updated main.qml that works as desired:
Window {
// ...same as above...
Component { // Creates real Qt objects with bindable properties
id:objFactory
QtObject {
property int id
property string title
property int rand:0
}
}
function getObj(id){
if (!objById[id])
objById[id] = objFactory.createObject( app, {id:id,title:database[id]} );
return objById[id];
}
// ...same as above...
}
Additionally, you may wish to change the property var obj in MyRow.qml to a more specific property QtObject obj (or a more specific object type, depending on what you pass in).
Finally, note that it's slightly cleaner/simpler to use modelData instead of mylist.model[index]:
ListView {
anchors.fill:parent
model: [42,17] // object ids
delegate: MyRow {
width:parent.width
obj:getObj(modelData)
}
}

Using a Loader for a string of QML

In Qt 5.3 I've been using the Loader QML element for loading screens, loading the view from a QML file in the background. Now I'm trying to load a string of QML dynamically. Qt.createQmlObject enables me to do so, but I can't seem to get the Loader element to play along.
Seems like Loader only takes a URL (QUrl) or component (QQmlComponent), but Qt.createQmlObject creates an object (QObject).
I'm new to QML, but from my understanding components are reusable elements, similar to classes, and objects are the instances of those classes. I thus can't seem to wrap my head around why Loader wouldn't work with objects.
How can I show a loading screen while (asynchronously) parsing and initializing a string of QML?
Example QML code:
Item {
Rectangle {id: content}
Loader {id: loader}
Component.onCompleted: {
var obj = Qt.createQmlObject('import QtQuick 2.3; Rectangle {}', content);
loader.source = obj; // Throws error.
}
}
It's not possible using the current API. As Loader's documentation says, it loads objects via a URL that points to a QML file or a Component:
import QtQuick 2.0
Item {
Rectangle {
id: content
anchors.fill: parent
color: "grey"
Loader {
id: loader
sourceComponent: myComponent
anchors.fill: parent
anchors.margins: 40
}
}
property Component myComponent: Qt.createComponent("MyComponent.qml", Component.Asynchronous)
}
MyComponent.qml:
import QtQuick 2.2
Rectangle {
color: "red"
}

Resources