Qt - QML Screen Mirroring in a dual monitor setup - qt

I've developed an application, with the UI done using QML. What I was asked to do now is to make it so that when a second monitor is used, the second monitor shows everything that the program is doing. At first I thought of just telling the client to configure Windows to clone its screens. However when the applications uses some of its functionalities I need for the cloned screen to display certain indicators in the cloned screens but not on the original screen.
So my question is, How can I accomplish this. How can mirror what is happening in one screen, while maintaing enough control to draw in one and not in the other.
My only idea is to use a timer to take as screen shot at regular intervals and show that image in the second screen.
Is this doable?

Taking periodic screenshots, although perfectly doable, is undesired because of the impact to performance. Instead you should make use the main window's onFrameSwapped() signal, to grab images only when a new frame is generated.
Ideally, you'd want to make use of Layer or ShaderEffectSource, as suggested by #dtech, to read and re-render the frame straight from the GPU. Unfortunately, due to limitations in Qt Quick's Scene Graph, it is not possible to accomplish this across separate windows without destabilizing the source window's scene graph.
Update: Nevertheless, I've found a way to emulate the desired features of ShaderEffectSource using ShaderEffect instead. Your other options are to either copy frames through the CPU using Item's grabToImage function, or to re-implement QQuickView (your QML window) in C++ to grab images from the QML Engine when a new frame is generated.
Working Solution 1: Use ShaderEffect (Update: New Solution)
The new approach I've found consists of using ShaderEffect on a child Window and forcing it to update by calling that Window's update() method, which is inherited from QWindow. By using ShaderEffect, you guarantee the source image will be accessed only through the GPU, increasing performance by orders of magnitude over solution #2.
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
id: mainWindow
title: qsTr("Main Window")
visible: true
width: 400
height: 200
color: "#0F0"
Rectangle {
id: viewport
color: "#00F"
width: parent.width/2
height: parent.height/2
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
// Draw QML Item into an off screen layer
layer.enabled: true
// Performance tweaks
layer.smooth: false
layer.mipmap: false
}
onFrameSwapped: {
// Update mirror window manually with each new frame
projectionWindow.update()
}
Window {
id: projectionWindow
property int screenID: 1
transientParent: mainWindow
visible: true
x: Qt.application.screens[screenID].virtualX
y: Qt.application.screens[screenID].virtualY
width: Qt.application.screens[screenID].desktopAvailableWidth
height: Qt.application.screens[screenID].desktopAvailableHeight
flags: Qt.FramelessWindowHint
color: "#000"
visibility: Window.Maximized
ShaderEffect {
id: img
// Set source to copy visuals from
property variant source: viewport
// Setting shader to the same resolution as the source may improve performance
width: viewport.width
height: viewport.height
// Performance tweak
blending: false
}
}
}
Triggering update() manually has the side effect of preventing the child from updating on its own. Calling the update method on the source window from the child Window once whenever needed works around this limitation.
It is worth noting that this solution only works with Qt's basic renderer, which is single threaded. Qt prevents the texture from being accessed from a diferent rendering threads, giving the following error message and returning a null pointer:
QQuickShaderEffectSource::textureProvider: can only be queried on the rendering thread of an exposed window
The performance gained from this method is far greater than the performance loss of using the single threaded renderer. You can force use of the single threaded renderer by setting an environment variable at the start of your application. This can be done from inside the app by adding the following code at the very start of your main function:
#if defined(Q_OS_WINDOWS)
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
putenv("QSG_RENDER_LOOP=windows");
#else
putenv("QSG_RENDER_LOOP=basic");
#endif
#elif defined(Q_OS_MACOS) || defined(Q_OS_LINUX)
setenv("QSG_RENDER_LOOP", "basic", 1);
#endif
Qt's documentation states that using the basic renderer reduces portability, but I can attest this approach works fine on Windows, macOS, and Linux and that it does not work on Haiku OS.
I've switched to this solution on the most recent versions of a project I develop. You can study the full implementation at:
https://github.com/Cuperino/QPrompt/blob/072f0a7808f3a361cb93bca8961e884ac9d8bb56/src/kirigami_ui/PrompterPage.qml#L760
https://github.com/Cuperino/QPrompt/blob/072f0a7808f3a361cb93bca8961e884ac9d8bb56/src/kirigami_ui/main.qml#L847
https://github.com/Cuperino/QPrompt/blob/072f0a7808f3a361cb93bca8961e884ac9d8bb56/src/prompter/ProjectionsManager.qml#L261
Working Solution 2: Use Item's grabToImage (original answer)
Another easy approach is to grab frames from QML, using an Item's grabToImage() function. Since Window is not an item in itself, you'd have to grab the image from one of its elements. In this example, I grab the image from an item called viewport each time a frame is swapped on mainWindow, using the onFrameSwapped() signal. Then the path to that image in memory is set as the source for the image in the second window, named projectionWindow. The second window will open on the screen set by the screenID variable; it is also set to be a frame-less window with its visibility is set to either Maximized or FullScreen, such that it is the only window seen on the second screen.
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
id: mainWindow
title: qsTr("Main Window")
visible: true
width: 400
height: 200
color: "#0F0"
Rectangle {
id: viewport
color: "#00F"
width: parent.width/2
height: parent.height/2
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
}
onFrameSwapped: {
viewport.grabToImage(function(result) {
projectionWindow.frame = String(result.url);
});
}
Window {
id: projectionWindow
property int screenID: 1
property alias frame: img.source
transientParent: mainWindow
visible: true
x: Qt.application.screens[screenID].virtualX
y: Qt.application.screens[screenID].virtualY
width: Qt.application.screens[screenID].desktopAvailableWidth
height: Qt.application.screens[screenID].desktopAvailableHeight
flags: Qt.FramelessWindowHint
color: "#000"
visibility: Window.Maximized
Image {
id: img
anchors.fill: parent
fillMode: Image.PreserveAspectFit
// Performance tweaks
asynchronous: true
cache: false
}
}
}
I used to employed this solution on a project I develop. You can study the full implementation at:
https://github.com/Cuperino/QPrompt/blob/a873a2ec9b0619ec1bfdbde22fe90850e76969a5/src/kirigami_ui/main.qml#L828
https://github.com/Cuperino/QPrompt/blob/a873a2ec9b0619ec1bfdbde22fe90850e76969a5/src/prompter/ProjectionsManager.qml#L260
Alternate, incomplete, solution
The third, more manual, and unfinished solution consists of the following: You'd inherit from QQuickView or QQuickWindow, and then send the image to a second window on either the QQuickWindow::afterRendering() or the QQuickWindow::frameSwapped() signal. You would use a QQuickFramebufferObject or some other rendering pipeline such as DirectX, Metal or Vulkan, to render and grab the frame off screen. Using OpenGL as your renderer, means negating all the performance advantages that come from using the native rendering pipelines supported by Qt 6. You may need to implement the frame grab once per pipeline to get all performance benefits.
https://doc.qt.io/qt-6/qtquick-visualcanvas-scenegraph.html
The following talk by Giuseppe D’Angelo shows how some of this is setup in Qt 5. It doesn't show how to copy the contents to another window/screen but it could aid in this regard.
https://www.youtube.com/watch?v=V_idc9BBRuI
https://www.youtube.com/watch?v=D-7fVGIBz6k

You can use particular QML elements as texture sources and easily duplicate them via trivial fragment shaders.
You definitely do not want to take screenshots and draw that image back, it is wildly inefficient.
Keep in mind that it will be just a visual duplicate, it will not take user input. If you want it to be interactive in both windows, then you should simply use a single data object and connect it to two individual GUI elements.
OK, here is the code, but unfortunately, it evidently uncovers a bug in QML, as the implementation doesn't seem to work across different windows:
Window {
id: mainw
visible: true
width: 640
height: 480
title: qsTr("main window")
Row {
spacing: 5
Rectangle {
id: source
width: 100
height: 100
color: ma.containsPress ? "red" : "cyan"
Text {
text: "adoy"
anchors.centerIn: parent
}
MouseArea {
id: ma
anchors.fill: parent
}
}
ShaderEffectSource {
width: source.width
height: source.height
live: true
sourceItem: source
}
}
Window {
visible: true
width: 640
height: 480
title: qsTr("another window")
x: mainw.x + width + 10
y: mainw.y
Row {
ShaderEffectSource {
width: source.width
height: source.height
live: true
sourceItem: source
}
Rectangle {
width: 100
height: 100
color: "blue"
}
}
}
}

Related

Sharing data across QML files (properties)

I am writing an App and I already managed to resolve some issues, with the latest one being the Screen.Width/Height for adjusting the window size dynamically on different monitors (I use laptop, phone, PC, it's simply convenient).
To write the code efficiently and nicely, I want to obtain that specific information and put it into a single set of 2 "variables", that would then hold this information.
I tried assigning the ApplicationWindow object an id: mainWindow, in order to call upon it from a different QML file, to obtain the property value as:
mainWindow.height, mainWindow.width
I then was told to use another approach, custom QML properties, that are declared like:
property (type) (name): (value)
I then followed the advice and declared those properties in Main.qml (with AppWindow) and it does work. The properties of AppWindow (ApplicationWindow) contain the width and height of the Screen multiplied by a specific coefficient.
Then those variables are accessed by the object itself, drawing the App Window as I want it to be.
The problem is that this approach was meant to solve the issue of sharing code across .QML files, and it doesn't
[go down]
ApplicationWindow {
id: mainWindow
//Wide screen support
//Screen.desktopAvailableWidth / 4
//Screen.desktopAvailableHeight / 6
//The below is monitor cross-compatible (phone, PC, laptop)
property int globalWidth: Screen.width / 2
property int globalHeight: Screen.height / 3
width: globalWidth
height: globalHeight
visible: true
title: qsTr("Redacted")
//setWindowIcon(QIcon(":/path/to/icon.png"));
The piece of code below resides in main.qml. It is called upon from that file. Just ignoring the IDs (IDs supposedly [as stated by users/docs] can't be accessed outside of local scope...), the properties (especially custom properties) should be accessible inside Page1, Page2, Page3 etc.
SwipeView {
id: swipeView
anchors.fill: parent
currentIndex: tabBar.currentIndex
Page1Form {
}
Page2Form {
}
Page3Form {
}
}
The below is the Page2Form.qml file that is called upon (as class definition) in main.qml.
The properties declared in parent objects in main.qml (imo) should be inherited by child objects (imo).
Page {
id: localPage2
width: globalWidth
height: globalHeight
Rectangle {
id: rectangle
x: (localPage2.width / 2) - (width / 2)
y: (localPage2.height / 4) - (height / 2)
width: localPage2.width / 3
height: localPage2.height / 6
color: "#ffffff"
border.color: "#a45c5c"
border.width: 2
TextInput {
id: textInput
x: 0
y: 0
width: localPage2.width / 3
height: localPage2.height / 6
text: qsTr("Redacted")
font.pixelSize: 12
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.weight: Font.Normal
focus: true
}
}
Ok, so what is the problem?
I can try calling those properties as:
mainWindow.globalHeight
globalHeight
mainWindow.height
etc.
They won't be accessed. The form editor will provide me preview of Page object that has 0 size.
The page does render eventually (when compiled and ran), but there is an issue in passing (accessing) the value of that property.
As you can notice, both IDs and custom properties seem to work just fine locally.
Update:
I haven't fixed that issue, I also tried using aliases (references) and the "foreign" QML file will still fail to be assigned proper size (Page width and height).
I then put another custom property of string type with some text in it, I then managed to access that property in Page2.qml and the property is originally in main.qml.
It's bugged, or I have no idea what it is.
I tried 3 approaches:
ID (it's not global as it turns out)
Custom property (kind of works, just not with Screen size...)
Aliases on object's default properties
(property alias globalWidth: mainWindow.width
property alias globalHeight: mainWindow.height)

How to achieve better caching control for ListView?

I tried to play with cacheBuffer, but it's only help me to increase count of cached delegates, when I want to disable caching at all.
Now with zero caching buffer my example (only one item stretched on all ListView) behaves like this:
At the start ListView creates two delegates: currently visible and
next one.
When I scrolling list forward it creates and keep up to 4 delegates without beginning destroying them.
When I start scrolling list backward it begin immediately destroying delegates without looking on cacheBuffer.
If you replace "height: root.height" to "height: listView.height", it will create delegates for all model items at the start.
Is this behaviour normal? Can I change it some way?
You can tried it yourself:
import QtQuick 2.11
import QtQuick.Controls 2.4
import QtQuick.Window 2.11
import Qt.labs.calendar 1.0
Window {
id: root
visible: true
width: 640
height: 480
title: qsTr("Hello World")
ListView {
id: listView
anchors.fill: parent
snapMode: ListView.SnapOneItem
cacheBuffer: 0
model: 10
delegate: Rectangle {
width: parent.width
height: root.height
// height: listView.height
border.color: "black"
Text {
anchors.centerIn: parent
text: modelData
}
Component.onCompleted: {
console.log("Delegate completed")
}
Component.onDestruction: {
console.log("Delegate destruction")
}
}
}
}
Replace
delegate: MyVeryComplexDelegate {
}
by
delegate: Loader {
width: expectedDelegateWidth
height: expectedDelegateHeight // Otherwise you might create all...
sourceComponent: MyVeryComplexDelegate {
}
active: someCriteriaYouFeelGoodAbout()
}
Now you will only have simple Loaders in your cache and you can decide which ones of those in the cache are active.
Probably better: Have parts of the MyVeryComplexDelegate loaded as the ListView wants, and just hide the most complex parts behind a Loader that turns active only if you really need the full complexity.
On your strange findings as far as I can explain them:
Regarding the difference between root.height and listView.height, the explanation is an issue that is subject to many questions:
While root.height references the property height of the window, which you have explicitly set, listView.height is determined by anchors.fill: parent, which results in setting the height to root.contentItem.height - and that is initially 0. Therefore the delegates, initially all have a height of 0, all of them would fit in the view and therefor have to be created, even if you load as lazy as possible. Later they will resize together with the root.contentItem and some will be destroyed again.
You can see that, when monitoring the height changes of your delegates and your ListView
The next thing is, that even if the delegate really fills the ListView from the beginning, a second delegate is instantiated. The reason for that is, the condition used by the ListView, when to create new delegates. For that the sum of heights - the displacement of the first has to be larger than the ListView. That is not fulfilled when it is equal to the height.
Increase the height of your delegate by a fraction of a pixel, and you are good.
height: root.height + 0.0001

Recursion trick in ShaderEffectSource fails when I destroy the source object in onStateChanged

I want a ShaderEffectSource called snapshotter that updates on every change of a source item, but when the source item is destroyed, I want snapshotter to retain the last state of the source item.
In my code I use a workaround for a Qt deficiency - setting snapshotter.sourceItem = snapshotter when the previous source item gets destroyed. This works fine when I destroy the old source item e.g. on key press. But when I do it in an onStateChanged handler, I get this error:
ShaderEffectSource: 'recursive' must be set to true when rendering recursively.
But I don't want to set recursive: true, because then snapshotter would start repainting as fast as possible, wasting a lot of processing power.
Any idea why that problem happens considering it works fine when using key press, and/or a workaround?
I realize I'm asking why a hacky solution fails in some cases, but since it works in some cases, I'd like to use the solution anyway as it's very useful.
My code: (main.qml)
import QtQuick 2.6
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
Loader {
active: true
id: loader
sourceComponent:
Rectangle {
color: "red"
border.color: "white"
width: 100
height: 100
parent: row
states: [
State {
// the condition is always true, in this example code
name: "theOnlyState"; when: width === 100
}
]
onStateChanged: {
if(state === "theOnlyState") {
loader.active = false;
}
}
}
}
Row {
id: row
ShaderEffectSource {
id: snapshotItem
sourceItem: {
if(loader.status === Loader.Ready) {
return loader.item;
} else {
return snapshotItem;
}
}
live: loader.status === Loader.Ready
width: 100
height: 100
}
}
}
Note: I just had an idea: maybe setting recursive: true will not create the problem I mentioned, considering I'd only set it when live == false. Maybe Qt is smart enough not constantly redraw in that case. But I'm not sure how to check if that's true.
Ok, I found an 99%-authorative answer.
The worry about recursive: true that I expressed in the question was provoked by my vague memory of reading something like that in the Qt docs.
I now went ahead and looked up the relevant passage again, and here's what it says:
Setting both this property and live to true will cause the scene graph to render continuously. Since the ShaderEffectSource depends on itself, updating it means that it immediately becomes dirty again.
Note how they say that for the bad scenario to occur, live has to be true.
So the solution is to simply use recursive: true, which makes the solution much less hacky.
Note: I'm not gonna mark this answer accepted, because I'd like people to still go over it now and then and maybe, just maybe, prove me wrong (e.g. show that I'm misinterpreting the text).
More evidence in favor of the above conclusion:
I set the env var QSG_VISUALIZE to changes and ran this trivival test app:
import QtQuick 2.6
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
ShaderEffectSource {
width: 100
height: 100
id: shaderEffectSource
live: false
sourceItem: shaderEffectSource
recursive: true
}
}
It showed an unchanging colored square. But when I changed live to true in this code, it started flickering in random colors.
As you found out yourself, when you have live = false and recursive = true it won't be redrawn all the time.
I think the problem of yours might arise due to the magic, QML uses for its state machine, going back and forth and so on...
As far as I understand your problem, you want to create an object, take a snapshot, and delete it right after that again.
This is easier achieved by using methods and signals than by some declarative state changes and so on.
Specifically you might be looking for scheduleUpdate() to render the next frame, without the need of setting live = true. The next thing you will be interested in is the signal: scheduledUpdateCompleted to delete your object again.
Something like this:
import QtQuick 2.0
import QtQuick.Controls 2.0
import QtGraphicalEffects 1.0
ApplicationWindow {
width: 1024
height: 800
visible: true
Button {
text: 'Next'
onClicked: {
ses.sourceItem = prototype.createObject(ses)
}
}
ShaderEffectSource {
id: ses
y: 100
width: 50
height: 50
live: false
onSourceItemChanged: if (sourceItem !== this) scheduleUpdate()
onScheduledUpdateCompleted: {
sourceItem.destroy()
sourceItem = this
}
}
Component {
id: prototype
Rectangle {
width: 50
height: 50
color: Qt.rgba(Math.random(-1), Math.random(0), Math.random(1))
visible: false
Component.onCompleted: console.log('Created new Rectangle with color', color)
Component.onDestruction: console.log('Destroy Rectangle with color', color)
}
}
}
Remember: The sourceItem does not have to be visible to be rendered into an ShaderEffectSource. If it is not necessary for other reasons, I would keep it invisible, so it is not rendered twice.

Property Binding on Animated Property vs Multiple Animations

Consider this example:
import QtQuick 2.7
import QtQuick.Controls 2.0
ApplicationWindow {
id: appWindow
width: 1024
height: 800
visible: true
Rectangle {
id: rect1
property bool active: true
opacity: active ? 1 : 0
height: 300 * opacity
width: 300 * opacity
Behavior on opacity { NumberAnimation { duration: 1000 } }
MouseArea { anchors.fill: parent; onClicked: parent.active = false }
color: 'cornflowerblue'
}
Rectangle {
id: rect2
property bool active: true
x: 305
opacity: active ? 1 : 0
height: active ? 300 : 0
width: active ? 300 : 0
Behavior on opacity { NumberAnimation { duration: 1000 } }
Behavior on height { NumberAnimation { duration: 1000 } }
Behavior on width { NumberAnimation { duration: 1000 } }
MouseArea { anchors.fill: parent; onClicked: parent.active = false }
color: 'steelblue'
}
}
I have two Rectangles with the same observable behavior: when clicked, they fade both in opacity and size.
Internally, it differs in the amount of Animations, that are running concurrently - either 1 or 3:
As of now, I mainly use the pattern form rect1 and only in cases where the bindings would get unneccessarily complex rect2. However I wonder, if the animation system has some magic, that optimizes the animation of a single property, while the binding might be less performant.
In which usecases it is beneficial to use pattern rect1 and when it would be wiser to use the method of rect2?
EDIT There is also a third option which moves, what possible, to the render thread via OpacityAnimator. Now I can't bind to the opacity anymore, as it will jump to 0 at the end of the animation.
Rectangle {
id: rect3
property bool active: true
opacity: active ? 1 : 0
height: active ? 300 : 0
x: 610
width: height
Behavior on opacity { OpacityAnimator { duration: 1000 } }
Behavior on height { NumberAnimation { duration: 1000 } }
MouseArea { anchors.fill: parent; onClicked: parent.active = false }
color: 'dodgerblue'
}
EDIT 2 To adress the Answer of Ansh Kumar:
This is an excerpt from the QML Profiler. You can see, that during the animation of rect2 there are neither bindings nor JavaScript running, unlike during the times where height and width are (efficiently) bound to the opacity in rect1 or the width is (efficiently) bound to the height in rect3.
Further the source of the animations shows little trace of JS. I couldn't examine it into all it's depths, but it seems, that only a ScriptAction gets a QQMLScriptString and the rest has only the cost of converting the input from var to the right type (if a type is specified by using a concrete animation such as NumberAnimation).
Further, as far as I can see, there is not a loop per animation involved, but all animations feature some kind of update()-function or so, that is called (when running/registered) by a single loop (AnimationTimer). But this is where I am already unsure about.
Now the question remains: Is the implementation of the animations more efficient than the optimized JS environment especially as multiple objects are created and stuff.
There are two types of bindings in QML: optimized and non-optimized bindings. It is a good idea to keep binding expressions as simple as possible, since the QML engine makes use of an optimized binding expression evaluator which can evaluate simple binding expressions without needing to switch into a full JavaScript execution environment. These optimized bindings are evaluated far more efficiently than more complex (non-optimized) bindings. The basic requirement for optimization of bindings is that the type information of every symbol accessed must be known at compile time.
Bindings are quickest when they know the type of objects and properties they are working with. Animating a property will cause any bindings which reference that property to be re-evaluated. Usually, this is what is desired. The opacity, height and width in rect2 are re-evaluated into a full JavaScript execution environment whereas in rect1; width and height goes through an optimized binding expression evaluator and optimized to give more efficient binding since their type of object is known at compile time. Check binding and also animations for more details.
EDIT
You were right about evaluation being done in C++ environment. I found following informations.
Rendering engine should achieve a consistent 60 frames-per-second refresh rate. 60 FPS means that there is approximately 16 milliseconds (exactly 16.6667 milliseconds) between each frame in which processing can be done, which includes the processing required to upload the draw primitives to the graphics hardware. This shows that the animation is in sync with the vertical refresh, so once every 16.66 ms, and exactly once pr frame.
while (animationIsRunning) {
processEvents();
advanceAnimations();
paintQMLScene();
swapAndBlockForNextVSync();
}
So, in rect1 you have set duration: 1000 and binded height with opacity (height: 300 * opacity) similarly width with opacity, so binding should be called around 60 times ? If you see QML profiler output of statistics you will find following
As expected number of calls are around 60 (exactly 63). Now if you change duration to 2000, number of calls will be doubled.
Since, 300 * opacity has to be calculated, so QML should call JavaScript environment around 60 times (when duration: 1000)
As expected it was called around 60 times.
What about the NumberAnimation, is it implemented in JavaScript or C++ ? Definitely, you were right about it being implemented in C++, Here is the link to its declaration . So, in rect1 we have used NumberAnimation one time and in rect2 we have used it 3 times. So, total of 4 instances of NumberAnimation should be created.
So, rect1 has a total of around 120 bindings and JavaScript calls whereas in in rect2 there is no binding and JavaScript calls, so animation of rect2 should be faster, but the question is, will there be any significant improvements? Since, free version of QtCreator does not comes with CPU analyzer I was not able to study that part of the question (CPU Usage Qt). If anyone has commercial version of Qt, please update me about my hypothesis. I really think that rect2 is the best for usage as number of calls are reduced.

Qt5 QML, when to use a ColumnLayout vs Column?

For example, this works:
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtQuick.Layouts 1.2
ApplicationWindow
{
visible: true
width: 640
height: 480
title: qsTr("Hello World")
function thingWidth()
{
return width*80/100
}
Column
{
spacing: 10;
anchors.horizontalCenter: parent.horizontalCenter
Thing { color: "red"; width: thingWidth(); }
Thing { color: "yellow"; width: thingWidth(); }
Thing { color: "green"; width: thingWidth(); }
}
}
But change Column to ColumnLayout and it doesn't (resizing window causes layout to go wrong).
any help, thanks.
EDIT 1:
Here's also Thing.qml as requested,
import QtQuick 2.0
Item {
property alias color: rectangle.color
width: 50; height: 50
Rectangle
{
id: rectangle
border.color: "white"
anchors.fill: parent
}
}
It looks like my post is mostly code. Yes, nanny it does! that's because people post code on here.
As from the documentation of Column:
Column is a type that positions its child items along a single column. It can be used as a convenient way to vertically position a series of items without using anchors.
Moreover, it eases transitions during insertion, deletion and so on. It also attaches properties to the items to give them notions about their positions.
On the other side, this is the documentation of GridLayout (please, note that ColumnLayout is a convenience utility, but it is nothing more than a grid with one column, as from its documentation).
It has a completely different set of properties, as well as attached properties, completely oriented to the arrangement of the items.
I guess anyway that the most interesting page from the documentation is that one.
I simply cite it:
Positioner items are container items that manage the positions of items in a declarative user interface. Positioners behave in a similar way to the layout managers used with standard Qt widgets, except that they are also containers in their own right.
Positioners make it easier to work with many items when they need to be arranged in a regular layout.
Qt Quick Layouts can also be used to arrange Qt Quick items in a user interface. They manage both the positions and the sizes of items on a declarative user interface, and are well suited for resizable user interfaces.
Please, note that a Column is a Positioner, while a ColumnLayout is a Layout. When to use them depends mainly on your goal, as usual.

Resources