Trying to style Qt QML items by inserting child styling item - qt

I'm trying different approaches to styling a QT's app QML items. My goal is to:
limit the amount of code in the main files (hide all styling stuff in styling files, unlike in the Style Singleton approach)
not have to define every single type of item I'm going to use (unlike in the Custom Component approach)
possibly be able to mix and match different pre-defined styles in a single item.
Maybe there is a clear strategy to get this, I just didn't read about it yet. And maybe it doesn't make any sense anyway :-)
I've been playing with the idea of defining different components, one for each style type I want to define. The idea is to:
- define a component which is going to modify its parent
- insert that component where I want to adopt that specific style
A first approach relies on some javascript calls:
MyStyle.qml:
Item {
Component.onCompleted: {
parent.color = "lightblue"
parent.radius = 5
parent.border.width = 3
parent.border.color = "black"
}
}
And in my main code:
Rectangle {
id: testRectangle
anchors.fill: parent
MyStyle {}
}
This gives me the result I want, but it doesn't feel right:
I'd like for the styling to be applied statically, once and for all
if I start using this all over the place, won't I see artifacts when objects get created, and slow down my interface?
So I tried this too:
MyRectangleStyle.qml:
Item {
property Rectangle styleTarget
styleTarget.color: "lightblue"
styleTarget.radius: 5
styleTarget.border.width: 3
styleTarget.border.color: "black"
}
and in my main code:
Rectangle {
id: testRectangle
anchors.fill: parent
MyStyle {styleTarget: testRectangle}
}
But:
well, it doesn't work :-) (no warnings though, qml simply doesn't load)
and I'm sort of back to having to define every single type of items I'm going to use.
So, is there a better way to achieve what I'm trying to do here? Thanks!

Your second method does not work because your Component sets properties to an item that does not necessarily exist at the time of creating the Component, instead it sets the property when the styleTarget changes. On the other hand, it is not necessary for MyStyle to be an Item since it is not shown, instead use QtObject, and finally to generalize your method change property Item styleTarget toproperty Rectangle styleTarget:
QtObject {
property Item styleTarget
onStyleTargetChanged: {
styleTarget.color = "lightblue"
styleTarget.radius = 5
styleTarget.border.width = 3
styleTarget.border.color= "black"
}
}

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.

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.

Unable to set the parent of a dynamically created QML component

I am creating dynamic component in QML as follows:
var component = Qt.createComponent("PlayerWindow.qml")
if (component.status != component.errorString())
console.log(component.errorString())
var playerWin = component.createObject(rootWindow);
Here rootWindow is my main application window. Now, the PlayerWindow is quite simple as:
Window {
id: playerWindow
width: parent.width
height: parent.height
Component.onCompleted: {
console.log(parent.width)
console.log(rootWindow.height)
}
}
The thing is that the values for parent.width and rootWindow.width are really different and this is also evident when the window is displayed. However, rootWindow is set as the parent in the createObject call. So, I am not sure what is happening there but I wanted to know if this is the correct way to set the component parent when they are being dynamically created.
Try to add console.log(parent) in the code. You will see something like qml: QQuickRootItem(0x1e3e4e0). If you check the Qt doc you will find that Item.parent() returns Item but Windows is not Itemdescendant but QQuickWindow. Also from documentation:
A QQuickWindow always has a single invisible root item ...
So, in your case parent and rootWindow are different objects.
P.S. The dynamic object creation in your code can produce an error since component.createObject will be executed although Qt.createComponent returns error. Just copy the code from Qt documentation.

Does qml supports object oriented programming

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.

asynchronous (kind of) animation in qml

let's say i have the following QML Components:
Foo.qml
import Qt 4.7
Rectangle {
Repeater {
model: myModel
delegate: Bar {
barProp: elemProp
}
}
}
Bar.qml
import Qt 4.7
Rectangle {
property string barProp: ""
Text {
text: barProp
NumberAnimation on x {
from: 0; to: 100
duration: 1000
loops: Animation.Infinite
}
}
}
I maintain myModel from C++, it has the following Q_PROPERTY declaration:
Q_PROPERTY (QDeclarativeListProperty <Bar> myModel READ myModel
NOTIFY myModelChanged)
Now, my problem is that every time I add a new element to the underlying QList, the animation specified in Bar resets, so in practice, the elements always completely overlap. What I want is that the element animations are not synchronous, and each can continue seamlessly regardless of the rest. Is this possible to do?
Cheers
You should use a QAbstractItemModel (QStandardItemModel may be easiest) rather than a QList. QAbstractItemModel notifies the view when new items are inserted/removed/moved and the view reacts appropriately by modifying its content. In contrast, the view knows nothing about the changes made to a QList; only that something has changed. This means that the list has no choice but to destroy and recreate all delegates.

Resources