Qt5 QML, when to use a ColumnLayout vs Column? - qt

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.

Related

Difference between Window and ApplicationWindow in QML?

https://doc.qt.io/qt-5/qml-qtquick-controls2-menubar.html
MenuBar is supported in ApplicationWindow and not in Window.
Following throws an error "Invalid property name: MenuBar"
Window
{
visible: true
width: 280; height: 280
menuBar: MenuBar {
Menu {}
}
}
whereas following works:
ApplicationWindow
{
visible: true
width: 280; height: 280
menuBar: MenuBar {
Menu {}
}
}
In the new Qt version 5.12, the default code uses Window and not ApplicationWindow.
What is the difference between Window and ApplicationWindow? Which one should be used in which case?
The docs is very clear:
ApplicationWindow is a Window that adds convenience for positioning items, such as MenuBar, ToolBar, and StatusBar in a platform independent manner.
That is, it is an item that inherits from Window but has certain default attributes, it is similar to QMainWindow with respect to QWidget.
When you say: In the new Qt version 5.12, the default code uses Window and not ApplicationWindow I suppose you mean that QtCreator uses Window by default in creating projects, that's just because the developers wanted to and there is no science in it, just a choice.
When you should use one or the other depends on when you want to customize and if you are comfortable with the ApplicationWindow structure since as you can see the latter has a predefined structure.
Note: There are 2 items called ApplicationWindow 1, 2

Why is loop created in this case?

This examples gives me property binding errors:
file:///home/user/qmltests/layouts.qml:22:4: QML Label: Binding loop detected for property "font.pixelSize"
file:///home/user/qmltests/layouts.qml:22:4: QML Label: Binding loop detected for property "font.pixelSize"
file:///home/user/qmltests/layouts.qml:18:4: QML Label: Binding loop detected for property "font.pixelSize"
Code:
import QtQuick 2.11
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.11
Page {
id: root
width: 400
height: 200
StackLayout {
id: main_container
Layout.fillWidth:true
Layout.fillHeight:true
ColumnLayout {
id: sub_container
Layout.fillWidth:true
Layout.fillHeight:true
Label {
text: "One"
font.pixelSize: sub_container.height*0.2
}
Label {
text: "Two"
font.pixelSize: sub_container.height*0.2
}
}
}
}
By logic, this shouldn't happen, because I am copying the width and height down to lower level components by using Layout.fillWidth=true and layout.fillHeight=true
To fix this error, I have to copy the heigth from the root element:
import QtQuick 2.11
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.11
Page {
id: root
width: 400
height: 200
StackLayout {
id: main_container
Layout.fillWidth:true
Layout.fillHeight:true
ColumnLayout {
id: sub_container
Layout.fillWidth:true
Layout.fillHeight:true
Label {
text: "One"
font.pixelSize: root.height*0.2
}
Label {
text: "Two"
font.pixelSize: root.height*0.2
}
}
}
}
Why aren't width and height propagated from the root elements down to children layouts?
How can I reference sub_container.width and sub_container.height (because it is known before items are laid out) without getting binding loop error? I don't want to reference the root item because due to complexity there may be many layouts inside root item and in order to lay out components in a scalable way I need to know the width and height of the parent layout.
If you use layouts, the elements they manage must not change their size based
on size given by the layout. To do what you wish to do, you shouldn’t be using a layout, but anchors, since you want to manage the child sizes manually. The loop is there because the layout uses the size of your item to resize itself, that your item then uses to resize itself, endlessly. If you don’t need that functionality, it will interfere – as you have seen. The reason it worked via root is that root’s size is not managed by the layout: it’s fixed. And that’s what you wanted all along, isn’t it?
Another approach would be for the label not to change its size hint based on font size, so that the layout wouldn’t react to the font size change.
TL;DR: Layouts size themselves based on child sizes, thus there’s a loop if the child sizes itself based on the layout’s size.

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

Qt - QML Screen Mirroring in a dual monitor setup

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

Qt Quick Controls 2 and TableView

Is it OK to use TableView in Quick Controls 2.0 application?
This will require to have both imports:
import QtQuick.Controls 1.4
import QtQuick.Controls 2.0
Will I get any side effects?
Another related question: it seems that TableView belongs to Quick Controls 1.0 set. Is it? Does it mean that if it's possible to use TableView then it's possible to use all the Quick Controls 1.0 controls in Quick Controls 2.0 application?
While it is possible to mix Qt Quick Controls 1 and 2 in the same application, the biggest issue is that Qt Quick Controls 1 are not compatible with Qt's automatic high-DPI scaling, whereas Qt Quick Controls 2 bases its scalability on that. Therefore running such application that mixes the two might not give ideal results on a high-DPI display.
Given that Qt Quick Controls 1 TableView has severe performance issues, one possible alternative is to use plain ListView from Qt Quick core with Row as a delegate. With Qt 5.9 and later, it is possible to explicitly specify the content width and flicking directions so that a vertical ListView can be also flicked horizontally. Here's an overly simple multi-column list example, something you can already try out with the latest Qt 5.9 beta:
import QtQuick 2.9
import QtQuick.Controls 2.2
ApplicationWindow {
id: window
width: 360
height: 360
visible: true
ListView {
id: listView
anchors.fill: parent
contentWidth: headerItem.width
flickableDirection: Flickable.HorizontalAndVerticalFlick
header: Row {
spacing: 1
function itemAt(index) { return repeater.itemAt(index) }
Repeater {
id: repeater
model: ["Quisque", "Posuere", "Curabitur", "Vehicula", "Proin"]
Label {
text: modelData
font.bold: true
font.pixelSize: 20
padding: 10
background: Rectangle { color: "silver" }
}
}
}
model: 100
delegate: Column {
id: delegate
property int row: index
Row {
spacing: 1
Repeater {
model: 5
ItemDelegate {
property int column: index
text: qsTr("%1x%2").arg(delegate.row).arg(column)
width: listView.headerItem.itemAt(column).width
}
}
}
Rectangle {
color: "silver"
width: parent.width
height: 1
}
}
ScrollIndicator.horizontal: ScrollIndicator { }
ScrollIndicator.vertical: ScrollIndicator { }
}
}
Of course, this kind of simplified multi-column list does not provide such features as movable and resizable columns and other bells and whistles that were built into the good old TableView type. On the other hand, the performance is on a whole different level, so if you're targeting something else than classic desktop environments running on computers with endless resources, this route might be worth considering. ;)
import QtQuick.Controls 1.4 as C
import QtQuick.Controls 2.0
C.TableView { //controls 1.4
Button { //controls 2.0
}
}
This will help you avoid any unwanted clashes between the two controls

Resources