Loader size dependent on source - qt

I'm working on a little system of message dialog windows in QML. For this I'm using a container with a Loader to load the different messages (these are not just text but text and graphical symbols layouted hence loading a QML file for each individual message). By default these message windows have the same size, so I have my size information directly in the container. But some messages can be longer, therefore I'm looking for a way to use the height of my loaded component if it exceeds the default value.
The way I see it my problem can be split into three parts:
How to have a container sized by the dimensions of its children?
How to access the size information of the loaded component through my Loader object?
How to selectively use the larger size?
Any suggestions?

You can access loaded object using item keyword.
Example if your loader id is idLoader, then the created item is idLoader.item, you have 2 solutions to do what you want:
1:How to access the size information of the loaded component through my loader object?
Loader{
id:idLoader
width: (item !== null && typeof(item)!== 'undefined')? item.width : 0
height: (item !== null && typeof(item)!== 'undefined')? item.height: 0
}
2: How to have a container sized by the dimensions of its children? & How to selectivly use the larger size?
Loader{
id:idLoader
width: childrenRect.width
height : childrenRect.height
}

Here's an idea, untested. Take the max of the child's preferred height or a hard-coded minimum, whichever is larger.
Loader {
height: Math.max(item ? item.implicitHeight : 0, 200)
}

Related

Qt widget app - different layouts based on orientation

I have a qt widgets app that needs to rearrange itself if the screen orientation is portrait vs landscape or if the window is resized to be taller than it is wide. The app has all the same widgets and functionality in both modes, just laid out differently.
I realize you can move widgets around in layouts at runtime based on resize events, but this alone is not ideal because you can't see what it will look like beforehand in the designer. I also found this example in the archives: https://doc.qt.io/archives/qt-4.8/qt-widgets-orientation-example.html where they have two separate UIs can choose which to show when the screen resizes, but in this case every single UI change, slot connection, etc. must be done to both UIs which is also extremely cumbersome, not scalable, and error prone.
Is there a clean way to handle this situation? Or at least visualize both modes without running the app? Qt version is 5.12+
You could - theoretically - create 2 files or objects. One would be myLandscapeWidget and other myPortraitWidget. Surround them with a Loader and could do a
Loader {
id: myLoader
anchors.fill: parent
sourceComponent: Screen.orientation === Qt.LandscapeOrientation ? land : port
Component {
id: land
myLandscapeWidget {
width: 500
height: 100
}
}
Component {
id: port
myPortraitWidget {
width: 100
height: 500
visible: Screen.orientation !== Qt.LandscapeOrientation
}
}
}
Well, atleast thats the gist of it...
Some links:
Loader , Screen

Qt Quick Controls 2: Retrieve standard value of a component

I am making a layout in QML and I want to give my Label the same padding as an ItemDelegate.
How can I get the standard padding value of an ItemDelegate?
Thank you in advance!
Firstly, you'll need an instance of an ItemDelegate. If you don't have one, you can create one and set its visible property to false:
ItemDelegate {
id: itemDelegate
visible: false
}
Some of the built-in styles change as the design guidelines they're based on change, so it's not a good idea to hard-code the padding based on a style's current padding values unless you have control over that style.
In addition, each style sets a different default padding, and may also use different properties to do so. The following properties can be used to control padding, starting with the most general and ending with the most specific:
padding
horizontalPadding (available in Qt 5.12)
verticalPadding (available in Qt 5.12)
leftPadding
rightPadding
topPadding
bottomPadding
Because of this, the only way to guarantee that you'll get the correct padding for each side of the control is to use the most specific properties:
Label {
leftPadding: itemDelegate.leftPadding
rightPadding: itemDelegate.rightPadding
topPadding: itemDelegate.topPadding
bottomPadding: itemDelegate.bottomPadding
}

What is best way to load either Rectangle(drawn by code) or Image in Qml?

I have a Qml file with one 'Rectangle' and an 'Image'. I want to load either one based on the property set in my.cpp file.
Please help me to find a best way to do this.
Actually I could think two possible ways to do the same:
1) First approach is to have both the element (the image and rectangle), defined in the respective QML, and to control their visibility from my.cpp file. I can have a property, this property can control the visibility of either of the two. Drawback in this approach is that even though only one element has to be displayed, two will be created.
2) Second approach is that we can have two components and load either one using "Loader" depending on the property set from the my.cpp.
Ex:
'
Component
{
id:img
Image
{
id: myImage
source:currentdir + "/img_production/Separator/myImage.png"
width: 10
height: 79
}
}
Component
{
id:rect
Rectangle
{
id:re
height: 82
width: 10
color: "#FFFFFF"
}
}
Loader
{
id: itemDisplay
sourceComponent: style.flag? rect : img
anchors.fill: parent.fill
}
'
Looking for some expert suggestions.
PS: style.flag is property set by my.cpp to Qml.
In this case, where both items are simple base types, I would go for the visibility change.
Having both elements instantiated directly makes it easier to refer to them in bindings or bind to their properties.
It also means their allocation only happens once, reducing the chance of memory fragmentation
If you are worried about the image consuming too much memory while the rectangle is shown you could still make the image`s source property depend in the visiblity value, i.e. unload the image when not showing the Image element.

Why is a dynamically by C++ generated QML Item not aware of other pre-existing Items?

I have a QQuickView that displays a QML file which itself consists of several QML Items (in separate files). I'd like to add Items dynamically using C++ code. The dynamically added item should resize with the parent one, i.e. width and height properties reference parent.
For example, my target Item in QML looks like this:
// TargetContainer.qml
Grid {
id: fillMeWithItemsContainer
objectName: "fillMeWithItemsContainer"
}
The Item I want to add dynamically (maybe multiple times) looks like this:
// DynamicItem.qml
Rectangle {
color: "white"
height: fillMeWithItemsContainer.height
width: height * 4/3
}
Note that the rectangle references the container it is intended to reside in regarding height.
quickView is populated with TargetContainer:
QQuickView *quickView = new QQuickView();
quickView->setSource(QUrl("qrc:/foo/bar/TargetContainer.qml"));
So I load a component
QQmlComponent dynamicallyLoadedComponent(
quickView->engine(),
QUrl("qrc:/foo/bar/DynamicItem.qml")
);
And I create an Object out of it.
QObject *dynamicallyLoadedObject = dynamicallyLoadedComponent.create();
Here I get an error (in application output view):
DynamicItem.qml:4: ReferenceError: fillMeWithItemsContainer is not defined
quickView should be aware of the existence of fillMeWithItemsContainer, because it has been created before. However, fillMeWithItemsContainer is not a parent of dynamicallyLoadedObject (yet) and this could be the problem.
So I find the target Item by
QQuickItem *targetItem = quickView->rootObject()->findChild<QQuickItem*>("fillMeWithItemsContainer");
And reparent the previously created object
dynamicallyLoadedObject->setProperty("parent", QVariant::fromValue<QObject*>(targetItem ));
Note: I tried dynamicallyLoadedObject->setParent() before, but this seems to be a different kind of parent (QObject vs. parent property).
However, the width and height properties of dynamicallyLoadedObject are set to 0 (because of the reference error, I assume) and won't change. Even if I set them again programatically
dynamicallyLoadedObject->setProperty("height", "fillMeWithItemsContainer.height;");
dynamicallyLoadedObject->setProperty("width", "height * 4/3");
nothing changes.
If I define DynamicItem directly in QML it works:
Grid {
id: fillMeWithItemsContainer
objectName: "fillMeWithItemsContainer"
DynamicItem {}
}
How do I make sure that dynamically added items can access Items that have been in the QML view before? Alternatively: What am I doing wrong?
dynamicallyLoadedObject->setProperty("height", "fillMeWithItemsContainer.height;");
dynamicallyLoadedObject->setProperty("width", "height * 4/3");
This will not actually set a JavaScript binding on the properties. Instead, it will try to assign e.g. the string "fillMeWithItemsContainer.height;" to the property, which will fail since the property is of type int, not of type QString.
Assigning bindings to properties is actually not possible from within C++ (with some exceptions like QQmlBinding).
dynamicallyLoadedObject->setProperty("parent", QVariant::fromValue<QObject*>(targetItem ));
As Sergei mentioned, you need to call QQuickItem::setParentItem instead of setting the parent property. That is also a bit more typesafe than the general string-based setProperty API. Without a parent item, a QQuickItem will not be visible.
Reparenting will only change the parent item, which will affect layouting and a few other things. It will not change the context of the object. A context defines which objects/IDs are in the scope. The context can not be changed after an item has been created. Even if changing the parent would change the context, it is too late - the object has been created, and IDs/object are only looked up in the creation phase.
The solution is to pass the correct context to QQmlComponent::create(), which actually has an optional argument. You need to create your item in the context of fillMeWithItemsContainer, so you need to get a pointer to it (you did that already with findChild) and then retrieve its context, which is possible with QQmlEngine::contextForObject(). That should give you enough to figure out how to make it work.
I agree with Sergei though, you should prefer to dynamically create objects in JavaScript instead. Changing QML from within C++ is a layering violation, you should never access QML from C++, only the other way around, to have a nicer separation between UI and program logic.
I believe, QML engine treats DynamicItem instance as a non-graphical item since it is casted to QObject*. Thus it is not rendered. It has to be at least QQuickItem* to be rendered.
I believe you experience same problem with setParent() since parent property refers to parent QQuickItem and might no be the same as QObject parent.
Two questions:
Why wouldn't you create dynamic objects in JS?
Is it possible to use relative parent instead of absolute fillMeWithItemsContainer?
p.s. I assume you understand this is rather irregular way of using QML and have strong reasons for such hacky approach.

Why are width & height of loaded SWFLoader zero?

I have a class which extends SWFLoader, I use it like a normal SWFLoader:
var loader:MySWFLoader = new MySWFLoader();
loader.load("myFile.SWF");
myScene.addChild(loader);
The loading works OK, except that it remains 0 because the width & height never change from 0. I had to override the width/height get properties to make it work:
class MySWFLoader extends SWFLoader
{
public override function get width():Number{ return contentWidth; }
public override function get height():Number{ return contentHeight; }
}
As well as being a big hack this isn't quite correct; especially since the SWF has multiple frames and the frames are of different width/height.
Why aren't width/height working in the first place? Shouldn't they be automatically set during loading? Is it possible the issue lies with the SWF itself somehow?
Update: Changing scaleContent to true or false makes no difference.
I'm not sure if this applies for SWF loading, but whenever I'm loading content, i cannot access width and height before the whole thing is loaded.
So make an event listener that listens when the loading is completed, and then read the height/width.
Also take a look at the loaderInfo class in AS3
I'm not sure what part of the cycle you're trying to grab the height and width (and is it of the loader object or the loaded content) but you can can access the height and width of the loaded object using SWFLoader.loaderInfo.height and SWFLoader.loaderInfo.width.
By default the SWFLoader scales the content to the size of the loader, so you have to set the size of the loader. If you want the loader to scale to the size of the content then you have to set the scaleContent property to false.
SWFLoader is a UIComponent.
It needs to be run through the UIComponent framework to set its dimensions correctly
In other words, it is waiting for calls to commitProperties(), updateDisplayList(), etc.
If you drop it in your application (so that it is part of the layout framework) it should be fine.

Resources