Does qml supports object oriented programming - qt

I'm using QML for my project, I want to know if am instantiating a file in another file, is it like instantiating object for a c++ class?
File.qml
Rectangle {
id: idRect1
.
.
}
File2.qml
Rectangle {
id: idRect2
File1 {
id:idFile1
.
.
}
}
In File2.qml i have initialized File1, does it means i have created an object of type File1? Please share some knowledge(links) on how all this mechanism works. Thanks in Advance

In QML when creating a file with first letter uppercase, you're creating a component. Components are implemented using OOP aggregation (not subclassing). That mean if I write
// MyButton.qml
import QtQuick 2.0;
Rectangle {
id: base;
width: 120;
height: 40;
color: "lightgray";
Text {
text: "foobar";
anchors.centerIn: parent;
}
}
... I haven't subclassed Rectangle, I just created a component that contains a Rectangle as root object, and configurates it in a certain way, and adds a Text object in it.
As soon as a component is created, it can be instanciated by simply writing :
MyComponent { id: myNewInstance; }
Because that's a way it works in QML.
The component name is a kind of class (but not in the C++ or JS way to define it) and it can also be used as a type for a property :
property MyComponent theComponent : myNewInstance;
Then it can hold the ID of an object created with the given component, acting somewhat like a C/C++ pointer : the property holds a link to the actual object.
But because of the way QML was designed, even if it's more aggregating than subclassing, a property of the type of the root object of a custom component can also hold ID of a derived component, in my case :
property Rectangle theComponent : myNewInstance;
Will work, but if I try to put an ID of an Image or Text or something else, QML engine will throw incompatible types error.
I hope it helps you.

Related

Custom properties don't exist at compile time? "Cannot assign to non-existent property"

In referencing an item's properties from outside its parent component, I can get a "Cannot assign to non-existent property" error that seems to depend on some compile time order-of-operations.
I have created a small example app that shows some various ways to assign a color to this property, where direct assignment fails, but similar assignment to default properties works, or even later assignment works.
Here is my main.qml:
import QtQuick 2.7
import QtQuick.Window 2.12
Window {
id: application_window
visible: true
width: 640
height: 480
Thing {
// colors.backgroundColor: "red" // Direct assignment of custom property: Fails
// thing.color: "red" // Direct assignment of default property: Works
// Component.onCompleted: colors.backgroundColor = "red" // Run time assignment of custom property: Works
}
}
and a file called Thing.qml in the same dir:
Item {
id: root
height: 50
width: 50
property alias colors: colors
property alias thing: thing
Rectangle {
id: thing
anchors.fill: root
color: colors.backgroundColor
}
Item {
id: colors
property color backgroundColor: "blue"
}
}
By individually uncommenting the lines in main.qml, you can see that directly assigning colors.backgroundColor does not work, but the other ways of changing the color do work, even assigning colors.backgroundColor at runtime. I have also moved the 'colors' Item to a different file, which allows direct assignment (I guess the backgroundColor becomes considered like a default property in this case). Is there any way to directly assign the colors.background color without a separate file or waiting until runtime?
Is there any way to directly assign the colors.background color without a separate file or waiting until runtime?
No.
When you are declaratively setting (using it in the left hand side of a binding) a sub property (property of a grouped property) like foo.bar, the QML engine can only do so if the type of foo has a bar property.
In your example when doing thing.color, thing's type is Rectangle so it does have a color property.
When doing colors.backgroundColor, I guess the type of colors is Item and not the implicit type defined in Thing.qml.
When you are creating a 'colors' Item in a different file, the QML engine is aware of the explicit type of the object and the binding then works.
One could argue that the engine could use the implicit type for the alias property, but I'm not sure if it's the correct way, you are essentially exposing an inline implementation detail to the outside.
You could alway open a bug about that, at least to clarify things even if the behaviour is not changed.

Repater: Using C++ property as model

I have a C++ property
Q_PROPERTY(QList<qreal> XTickPos MEMBER _xTickPos);
which I want to use in a Repeater. In the same QML file, the c++ class has been given the id
id: pw
The repeater looks like this
Item {
anchors.fill: parent
visible: true
Repeater {
model: pw.XTickPos.length
Rectangle{
height: 50
width: 2
x: pw.XTickPos[index]
y:10
visible: true
color: "black"
border.width: 2
}
}
}
However, nothing is drawn on the screen. If instead I make property in the QML file:
var xTickPos = []
and set it via a Q_Invokable function in c++
Q_INVOKABLE QList<qreal> getXTickPositions();
and in QML
root.xTickPos=pw.getXTickPositions();
and use the QML property xTickPos as model in the above repeater it is working. I checked that pw.XTickPos is correctly filled via a console.log
What am I missing here?
This one is kinda tricky.
The documentation states that you can use a JS array as a model, and it does state that QList<qreal> is automatically converted to a JS array when returned to QML.
But it seems that you can't use a QList<qreal> that is automatically converted to a JS array as a model. Go figure...
Naturally, it is preferable to have a proper model with its proper notifications for top efficiency. But in case you really want to go for the list property, it appears you will have to do the conversion manually in a getter:
QVariantList model() {
QVariantList vl;
for (auto const & v : yourList) vl.append(v);
return vl;
}
Amazingly, although Qt supposedly makes that conversion automatically, it doesn't seem to be able to make a QVariantList from a QList<qreal>.
That's Qt for you...

pass data from one window to another (inside same QML file)

i got two Windows inside the same .qml file.
Window1 has a textinput1 and a button, and when I press the button, i'd like to send the string value from that textinput to the Window2 textInput2.
I'm new to Qt and QML, been reading a lot on signals, Loaders, properties and can't materialize this kind of transfer. Can anyone do a simple 10-line example of such transfer please?
Window {
id:window
TextInput {
id:text1
text: "This value is needed in the second Window!"
}
Button {
onClicked: window2.open()
}
Window {
id.window2
function open(){
visible = true
}
Text {
text: text1.text
}
}
}
If I do this it gives me ReferenceError: text1 is not defined, how can I reference the text1 from the first Window?
I would prefer to use signals in such case:
Window {
id: window1
title: "window 1"
visible: true
width: 600
height: 600
signal someSignal()
Button {
anchors.centerIn: parent
text: "Open window"
onClicked: window1.someSignal()
}
Window {
id: window2
title: "window 2"
width: 400
height: 400
// you can use this instead of Connections
//Component.onCompleted: {
// window1.someSignal.connect(show);
//}
}
Connections {
target: window1
onSomeSignal: {
window2.show();
}
}
}
I think this is more ... how do you say? ... more imperative -)
i got two Windows inside the same .qml file.
If you did then your code will work. Since it doesn't work, I will assume each window is defined in its own qml file, and you only instantiate the two windows in the same qml file.
If I do this it gives me ReferenceError: text1 is not defined, how can
I reference the text1 from the first Window?
You will have to be able to reference the window first, and it should provide an interface to reference the text.
Keep in mind that ideally ids should only be used to reference stuff in the same source. On rare occasions you could go further, and reference ids down the object tree, but only parents, and none of their out-of-line children, it will however work for in-line children that are given ids in that same source. Meaning that if window2 is created inside window then you will be able to reference window from inside window2. But if both windows are siblings in another object, the id won't resolve.
Obj1
Obj2
Obj4
Obj3
In this example object tree, Obj1 will resolve from any of the objects. However, Obj3 will not be able to resolve Obj2 if the id is given inside Obj2, but will resolve if the id for Obj2 is given inside Obj1. But there is no way to resolve Obj4 from Obj3. because the id doesn't act like a property, you can't do someId.someOtherId, that's only possible for properties. You cannot do somePropertyObject.someId neither. You can only begin with either an id or a property, and continue only with sub-properties.
When the id is not applicable, can expose objects or properties either as properties or property aliases. The first is useful when you want to expose a whole object, the second when you want to expose a particular property of an object:
Item {
property Item innerItem: inner // gives you access to the "entire" inner object
property alias innerWidth: inner.width // gives you access to a property of inner
Item {
id: inner
}
}
You can also have aliases to aliases.

QML send signals between anonymous grandchildren

I'm trying to communicate between qml components in a tree structure. I have a main.qml component with and id of root. It has two children, and each of those children has an arbitrary number of children dynamically created from a repeater and a model.
When one of the grandchildren is clicked I would like the others to know, and be able to take action. So if I could send signals between the grandchildren that would be fine.
The problem is none of them have their id property set because they are made dynamically, and some of them are in different scopes. To communicate between them I have done the following:
Created a function in root, every grandchild can see that, and can call it with a message as parameter. The root function then emits a signal with the message as parameter. All the grandchildren can connect to the signal because they know the id of root.
What do people think of that? I'm getting the feeling that I've missed the point of signals in qml, feels like i've implemented a crude system and missed the whole point or something.
Also, I want to stay out of the C++ world, but do people think it would be best to use a C++ class so that I can use signals and slots.
What I'm aiming at is an MVC structure with very loose coupling, and a centralised Controller. What do people think about communicating between QML components in MVC.
The only similar questions I found here were about C++ or using hard-coded id's on components.
I don't think id's can be set dynamically, not even once at creation; am I wrong about that?
Also, the components are in different scopes, so id's can't be resolved; am I wrong about that?
I've written some code:
//qml.main
import QtQuick 2.4
import QtQuick.Controls 1.3
ApplicationWindow {
id: root
visible: true
menuBar: MenuBar {
Menu {
title: qsTr("File")
MenuItem {
text: qsTr("&Open")
onTriggered: console.log("Open action triggered");
}
MenuItem {
text: qsTr("Exit")
onTriggered: Qt.quit();
}
}
}
property string thisName: "root"
signal rootSays(string broadcastMessage)
function callRoot(message) {
var response = message
print("Root received: " + message)
print("Root broadcasting: " + response)
rootSays(response)
}
MajorComponent{//this is root's child A
property string thisName: "A"
thisModel: [{name:"First Grandchild of A", color:"red", y:0},
{name:"Second Grandchild of A", color:"green", y:80}]
}
MajorComponent{//this is root's child B
property string thisName: "B"
thisModel: [{name:"First Grandchild of B", color:"blue", y:210},
{name:"Second Grandchild of B", color:"yellow", y:290}]
}
}
//qml.MinorComponent
import QtQuick 2.0
Rectangle {
property string thisName: ""
property string thisColor: ""
color: thisColor
height: 50; width: 200
Text {
anchors.centerIn: parent
text: thisName
}
MouseArea {
anchors.fill: parent
onClicked: {
print(thisName + " clicked")
print("Root called with: " + thisName)
root.callRoot("Hello from " + thisName)
print("---")
}
}
}
//qml.MajorComponent
import QtQuick 2.0
Rectangle {
property var thisModel: []
Repeater {
model:thisModel
MinorComponent {
y: modelData.y
thisName: modelData.name
thisColor: modelData.color
function handleResponse(response) {
print(thisName + " received: " + response);
}
Connections {
target: root
onRootSays: handleResponse(broadcastMessage)
}
}
}
}
I don't think id's can be set dynamically, not even once at creation;
am I wrong about that?
ids are purely "compile" time construct. That being said, there is nothing preventing you from implementing and managing your own object registry system. A simple empty JS object would do, it can effectively be used as a QMap to lookup objects based on a key (the property name). If you set the map object as a property of the root object, it should be resolvable from every object in the tree because of dynamic scoping.
The approach with the signal is a sound one IMO. I've used something similar, combined with functors and capture by reference, allowing access to arbitrary and optionally existing objects in an arbitrary tree structure, filtering candidates by various criteria they must meet. You can do some very tricky stuff with this technique.
That being said, a practical example which illustrates what you actually want to achieve will be useful for providing a more specific answer.

QML: Communicating with great grandparents through signals

I'm currently in the process of learning Qt for some cross platform development, and I'm trying to do everything using QML. I know there are lots of ways to solve my problem using C++, but I want to stay true to the model and use QML.
Here it is: If I am using a loader to display qml files as seen in the following code, how do I communicate with main.qml from secondPage.qml?
I assumed this would be through signals, but upon further reading it seems all actions on a signal are within the class that sent it ( using the connected method). Here is my resource on this: http://qt-project.org/doc/qt-4.8/qmlevents.html#connecting-signals-to-methods-and-signals
Alternatively, this may be the wrong design for a QML application. I'm trying to break away from using a single source file before things get too out of hand...
main.qml:
Rectangle {
id: background
...
Item{
id: item1
Loader {
....
id:pageLoader;
source : "secondPage.qml"
focus:true;
}
}
You can declare some signal in SecondPage.qml and connect those to Main.qml's function.
Like below, suppose you have secondPageSignal() defined in secondPage.qml and you want to call doSomething() function in Main.qml on secondPage's signal, you can connect that in onLoaded handler of Loader.
Rectangle {
id: background
...
function doSomething() {
}
Item{
id: item1
Loader {
....
id: pageLoader;
source : "secondPage.qml"
focus:true;
onLoaded:{
pageLoader.item.secondPageSignal.connect(background.doSomething);
}
}
}
}
In secondPage.qml you have access to your background element directly because children have access to any of its parents in the hierarchy.
Another possibility is to declare a signal in your secondPage component, and then to connect the signal from this component to a signal in your great-grandparent in the onLoaded handler of your Loader. It makes it easier and cleaner if you want your component to be re-usable multiple times in your app without assu,ing what is parent is.

Resources