How to get notified when a QML item's dimensions change? - qt

I understand that I can get to know if the width or height of a QML item changed using the slots onWidthChanged and onHeightChanged. This is by doing something like below.
import QtQuick 2.12
Item {
id: my_item
onWidthChanged: {
if (my_item.visible) {
console.log("Dimension chnaged")
}
}
onHeightChanged: {
if (my_item.visible) {
console.log("Dimension chnaged")
}
}
}
Above works well. But I am only interested to know if the dimension of my QML item changed. I just need a callback when width or the height changed. I don't need a callback for both.
Is there a QML signal to listen only for a dimension change?
I am using Qt 5.15.7 commercial version.

As a workaround, you can create a property which is bound to both width and height and connect a handler to the changed signal of that property:
property double dimensions: width * height
onDimensionsChanged: {
if(my_item.visible)
console.log("Dimension changed")
}
There is a small risk, namely the number won't change when swapping width & height, but that might be worth it in your situation

As mentioned in the comments no such signal currently exists by default.
You can create a size or rect type property and react on that instead though:
property size dimensions: Qt.size(width, height)
onDimensionsChanged: console.log("Dimension changed")
Here's a WASM example of it working to power an emitter:
https://www.canonic.com/#https://playground.canonic.com/525f6691-fe05-4824-a7f1-574bb8cabd8b/dimension-changed-signal-example

Related

Trying to style Qt QML items by inserting child styling item

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"
}
}

How to remove QML binding on property from C++?

With QML a property value can be based on another object property's value, it is called binding and it will update your value everytime the property you depend on is updated.
Like in this example, the implicitWidth of CppItem is half of the parent's width and the Text will fill the layout. If you resize the window, the CppItem width is updated.
Window
{
RowLayout {
anchors.fill: parent
CppItem { implicitWidth: parent.width * 0.5 }
Text { Layout.fillWidth: true }
}
}
In my problem CppItem is a QQuickItem with some C++ code, and in some specific cases the C++ code will set the implicitWidth with the following code.
I know I could use setImplicitWidth but I am not sure it would make a difference for my problem, and anyway you can not do that if the property is not declared in C++ but in a loaded QML file.
setProperty("implicitWidth", 100);
The property value is set but the QML binding is not removed. So the C++ code above is not equivalent to the QML code :
cppItem.implicitWidth = 100
With the C++ code, any change on parent.width will trigger an update on cppItem.implicitWidth and set again the value cppItem.implicitWidth.
How can I remove this binding from C++ ?
Using QObject::setProperty in C++ does not break the binding previously made in QML.
But using QQmlProperty::write does break the binding.
// setProperty("implicitWidth", 100); --> Does not break binding
QQmlProperty::write(this, "implicitWidth", 100);

Zero width or height versus visible property in QML

Does setting an component's width or height to zero has the same effect as setting its visible property to false?
An example use-case:
I have an item, which slides into the window. The sliding happens by animating its height from 0 to x and when I dismiss this item from x to 0. Don't want to go in depth why I am not animating the position of the item instead. When the item has 0 height, should I set its visible property to false or it doesn't make any difference?
Not really, unless you clip. And it is better to avoid clipping as much as possible.
An Item with zero size will still have its children visible.
Whereas setting visible to false will hide the entire object tree.
In your particular case it seems like it doesn't matter as long as it doesn't cause you to have unwanted visible leftovers. You certainly do not want to have a binding such as visible: height as that would needlessly execute on every step of the animation.
Just to be on the safe side, you can install handlers on the animation to toggle visibility:
// in the animation
onStarted: if (!item.height) item.visible = true // show if start at 0
onStopped: if (!item.height) item.visible = false // hide if end at 0
This will avoid the continuous reevaluations you'd get if you bind visibility to height directly, but will still ensure visibility on before your object begins expanding and off after it has finished contracting.
As dtech already pointed out, the dimensions of the root node of a component do not automatically represent the dimensions of the underlying object tree. As an example take this:
Item {
id: root
Text {
id: txt
text: 'some text produces implicit width'
}
}
In this example the text of txt will be shown, though the dimensions of root are width: 0; height: 0.
As dtech already mentioned, you might set clip to true, but this is not advisable, as then it would be passed to the renderer, which renders the Item and its tree and finally applies clipping to it - in a seperate batch.
If you have something like that:
Item {
Rectangle {
anchors.fill: parent
color: 'red'
}
}
The renderer would do nothing extra when rendering, as it could be processed in the same batch as the rest. However as a developer it is hard to tell, whether something is visible when the size is set to 0 or not. Therefore it is adivsable to always set visible properly.
We might simply set
visible: width > 0 && height > 0 && opacity > 0
which works fine, as long as we don't animate on any of those properties or change them frequently. At least for animations we might have good knowledge, when the any of those properties might become 0 and use this information to reduce the amount of evaluations.
The nice thing about QML is, that the logical expression is evaluated from the left to the right, which means in our last example:
If width === 0 and height changes, it wont trigger reevaluation
If height === 0 and width changes, each change triggers reevaluation.
This means, we need to put the most stable condition first. This might be our information about when any of those values might change. I propose, using the animation.running property, to prevent reevaluation of the binding, while the animation is running.
Let's take a more complete example: Upon click, this Rectangle will shrink from width: 800 to width: 0 - which shall set it invisible.
Or three additional properties binding1, binding2, binding3 are bound to expressions, that we might use to set visible. When ever a particular part of the binding is reeavluated, we log this.
Rectangle {
id: rect
color: 'red'
width: 800
height: 600
NumberAnimation {
id: ani1
target: rect
property: 'width'
from: 800
to: 0
duration: 3000
}
}
MouseArea {
anchors.fill: parent
onClicked: ani1.running = true
}
property bool binding1: {console.log("1", !rect.width); return !rect.width}
property bool binding2: {!ani1.running && (function() { console.log("2", !rect.width); return !rect.width })()}
property bool binding3: {(function() { console.log("3", !rect.width); return !rect.width })() && !ani1.running}
// equivalent, stripped of the logging:
// property bool binding1: !rect.width
// property bool binding2: !ani1.running && !rect.width
// property bool binding3: !rect.width && !ani1.running
As we can see, binding1 is constantly reevaluated, when ever the width changes. This is not desirable.
We can see, that binding2 is only evaluated once at creation, and whenever ani stops running.
In binding3 we have it the other way around and we first evaluate the width, and then whether the ani is running. This means, we have a reevaluation whenever the width is changing.
We could also use the signal handlers ani.onStarted and ani.onStopped and explicitly set the visiblity then, but that would not be declarative and QML encourages you to always strive to stay declarativ.

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.

Flex/AS3: changing width/height doesn't affect contents

I thought I had a handle on AS3, DisplayObjectContainers, etc. but this basic thing is really confusing me: changing the width/height of a sprite does not affect it's visual contents - either graphics drawn within it or any children it may have.
I have searched around and found an Adobe page that represents my own little test code. From that page, I would expect the sprite to increase in visual size as it's width increases. For me, it doesn't. (http://www.adobe.com/livedocs/flash/9.0/ActionScriptLangRefV3/flash/display/DisplayObject.html#width)
width property
width:Number [read-write]
Indicates the width of the display object, in pixels. The width is calculated based on the bounds of the content of the display object. When you set the width property, the scaleX property is adjusted accordingly, as shown in the following code:
My code below doesn't affect the visual display at all - but it does set the width/height, at least according to the trace output. It does not affect the scaleX/scaleY.
What the heck am I missing here??
My setup code:
testSprite = new SpriteVisualElement();
var childSprite:SpriteVisualElement = new SpriteVisualElement();
childSprite.graphics.beginFill(0xFFFF00, 1);
childSprite.graphics.drawRect(0, 0, 200, 100);
childSprite.graphics.endFill();
childSprite.name = "child";
testSprite.addChild(childSprite);
container.addElement(testSprite);
testSprite.addEventListener(MouseEvent.CLICK, grow);
}
public function grow(event:MouseEvent):void
{
event.target.width += 5;
event.target.height += 5;
trace("grow", event.target.width);
}
If I understand the code correctly; you are changing the width / height of the sprite. But you are doing nothing to change the width/ height of the sprite's children.
In the context of a Flex Application, you can use percentageWidth and percentageHeight on the child to resize the child when the parent is resized. You could also add a listener to the the resize event and adjust sizing that way; preferably tying in to the Flex Component LifeCycle methods somehow.
I believe these approaches are all Flex specific, and dependent upon the Flex Framework. Generic Sprites, as best I understand, do not automatically size themselves to percentages of their parent container; and changing the parent will not automatically resize the parent's children.
I bet something like this would work:
public function grow(event:MouseEvent):void
{
event.target.width += 5;
event.target.height += 5;
childSprite.width += 5;
childSprite.height += 5;
trace("grow", event.target.width);
}
First, if you have a problem with a flex component, you can look over its source code.
In my environment, (as I installed flex SDK to C:\flex\flex_sdk_4.1), the source code for SpriteVisualElement is located at
C:\flex\flex_sdk_4.1\frameworks\projects\spark\src\spark\core\SpriteVisualElement.as
In the source code, you'll find that width property is overridden :
/**
* #private
*/
override public function set width(value:Number):void
{
// Apply to the current actual size
_width = value;
setActualSize(_width, _height);
// Modify the explicit width
if (_explicitWidth == value)
return;
_explicitWidth = value;
invalidateParentSizeAndDisplayList();
}
So, you cannot expect the auto-scaling of the component.
Making custom components will be one solution.
Here is a sample implementation of custom component.
Custom Component Example - wonderfl build flash online

Resources