I'm facing a problem regarding top / bottom margin on components inside a layout (GridLayout here, but it seems to be the same with a ColumnLayout) with fillHeight.
Considering the following example:
`
import QtQuick 2.0
import QtQuick.Layouts 1.2
Item {
width : 800
height : 480
GridLayout {
anchors.fill: parent
columns: 2
rows : 2
Rectangle {
color:"red"
Layout.fillHeight: true
width : 400
}
Rectangle {
color:"green"
Layout.rowSpan: 2
Layout.fillHeight: true
width : 400
}
Rectangle {
color:"blue"
Layout.fillHeight: true
width : 400
Layout.bottomMargin: 10
}
}
}
`
The display is as .
Just commenting the line "Layout.bottomMargin: 10" restablish the ratio between red and blue rectangles:
There seem to be a ratio calculated that take into account the margins when my guess is that it shouldn't. Indeed, the height of the blue rectangle seems to be (top+bottom margins)*height of the red one (I've tested with like a dozen values, maybe not enough).
The layout engine indeed uses the implicitHeight to calculate the resulting sizes. Since the implicitHeight of a Rectangle is 0, the blue rectangle will be 10 times as big as the red. You can play a bit with setting implicitHeight on the rectangles:
Item {
width : 800
height : 480
GridLayout {
anchors.fill: parent
columns: 2
rows : 2
Rectangle {
color:"red"
Layout.fillHeight: true
implicitHeight: 200
width : 400
onHeightChanged: console.log("red", height)
}
Rectangle {
color:"green"
Layout.rowSpan: 2
Layout.fillHeight: true
width : 400
}
Rectangle {
color:"blue"
implicitHeight: 200
Layout.fillHeight: true
width : 400
Layout.bottomMargin: 10
onHeightChanged: console.log("blue", height)
}
}
}
However, this feels like a dirty solution. The resulting size depends on the implicitHeight set, but not completely. Basically you alter how much the "weight" of the bottomMargin is in the calculation of the layout engine.
To make a non-dirty solution, you can put the blue Rectangle inside a Item and anchors the blue Rectangle to it with a bottom margin. (so, no Layout.bottomMargin in the Item). But this depends a bit on your desired end-result, as the blue rectangle will be smaller than the red one.
Thanks for the reply !
Yeah, to me, it is definitely a dirty solution to put an implicitHeight and I can't really apply it to my app.
The other solution is almost like the one I thought about. The difference is that I put a ColumnLayout that fills its parent (with a rowSpan: 2) and contains both the red and blue Rectangles and in which I ask for a Layout.bottomPadding. And this way it works.
This is just a workaround that work for what I want to do, but with this, there's no way to have a topMargin on the blue rectangle without using a spacing. But as I said, this is fine for me.
as the blue rectangle will be smaller than the red one
Yes, I'm aware of that, and it is what I expect. And with my solution there's also this difference of height.
Edit : no, I'm completely mistaken for that last point and in fact, with the ColumnLayout, red and blue will be the same size : with a layout
Related
I have a ListView (horizontal orientation) in my qml containing some fixed-size elements. I want items to be spaced out to fill the entiew width of ListView element. So if there are less elements I want them to be spaced out more. Basically what I need is exactly like Layout.fillWidth = true property of RowLayout but for ListView.
I can count how many items I have, then subtract total items width from ListView width, divide by items count and assign the spacing but it seems too silly to do.
Is there a way to do this automatically like in RowLayout?
Or maybe I need to use something different from ListView for this? Something like RowLayout but that I can assign my list data model to?
You can accomplish what you want with a ListView, you just need to adjust the spacing dynamically based on how many delegates you have. This example will break down if your delegates are differently-sized (as this is based only on the width of the first delegate), or if the delegates cumulatively exceed the width of the ListView.
ListView {
width: 500
orientation: Qt.Horizontal
model: 6
spacing: {
if (count > 0) {
return (width - (itemAtIndex(0).width * count))/(count - 1)
} else {
return 0
}
}
delegate: Rectangle {
implicitHeight: 50
implicitWidth: 50
color: "red"
border.width: 1
}
}
ListView may not be the most appropriate container for this task. I say this because it has a built in ScrollView and other behaviors that it sounds like you don't need. If all you need is a simple row of a few identically-sized delegates, I agree with scopchanov and believe that a Repeater inside a RowLayout would be the best option. Here is a simple example:
RowLayout {
width: 500
Repeater {
model: 6
delegate: Rectangle {
implicitHeight: 50
implicitWidth: 50
color: "tomato"
border.width: 1
Layout.alignment: Qt.AlignHCenter // must be set to align the rectangles within their empty space
}
}
}
You may notice that this introduces gaps to the left and right, if these gaps are unacceptable, you may need to set the spacing on the RowLayout in the same manner as the ListView example instead.
I want to update the padding of a ScrollView if there is a scrollbar visible, but on the other hand, the visibility of the scrollbar is dependent on the height/width of the content inside the scrollbar, which changes when the padding changes. The following causes a binding loop:
ScrollView {
id: control
rightPadding: Scrollbar.vertical.visible ? Scrollbar.vertical.width : 0
....
ScrollBar.vertical: ScrollBar {
parent: control
visible: control.height < height
...
}
}
How can I achieve this without a binding loop? Thanks
I was unable to get your code frag to work - it seems like your code should depend on the contents of your ScrollView, but this is not included in your code frag, and it may be missing some other references.
Anyway, I suggest approaching this a little differently - change the ScrollView's content's width based on whether or not the ScrollBar is visible. I also set the ScrollBar policy instead of visibility. I have created a full example where you can add or remove content using a slider for demonstration:
import QtQuick 2.15
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.12
ApplicationWindow {
id: root
visible: true
height: 500
width: 500
ColumnLayout {
anchors {
fill: parent
}
Slider {
// use slider to add delegates to the ScrollView to toggle the scroll bar visibility
id: slider
to: 20
}
ScrollView {
id: scroll
Layout.fillHeight: true
Layout.fillWidth: true
ScrollBar.vertical.policy: scrollBarVisible ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff
property bool scrollBarVisible: scroll.contentHeight > scroll.height
ColumnLayout {
width: scroll.scrollBarVisible ? scroll.width - scroll.ScrollBar.vertical.width : scroll.width // change the width of the
Repeater {
model: slider.value
delegate: Rectangle {
color: "tomato"
Layout.fillWidth: true
Layout.preferredHeight: 150
}
}
}
}
}
}
One thing to note though - your ScrollView content cannot have its height depend on its width, for example, if the content had some Text that wraps if there is not enough width, causing it to get taller when the width decreases. This would get back into infinite-loop territory.
I have a ListView and its delegate has a MouseArea. If the ListView doesn't have a header, everything works well. However, if I set a header, I can only click on the delegates but can't scroll.
What's is more interesting is that when I set the header's width to half the width of the window, I can scroll normally on the right side of the screen (where there's no header), but can't scroll on the left side under the header.
EDIT: Experimented with this a bit and found another thing. The delegate's height is 80 and if I set the header's height to, say, 30, then I can't swipe when the mouse lands on the top 30 pixels of a delegate as if the header is attached to each item in the list?
[edited code] This the full code that recreates the problem for me (can't scroll on the left side but can on the right). I'm using qt 2.15
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
Window {
id: root
width: 640
height: 600
visible: true
ListView {
id: list
anchors.fill: parent
model: 20
delegate: Rectangle {
signal clicked();
implicitHeight: 70
width: root.width
MouseArea {
anchors.fill: parent
z: 2
onClicked: console.log("clicked");
}
Text {
anchors.centerIn: parent
text: "text"
}
}
// Works fine when I comment this out
headerPositioning: ListView.OverlayHeader
header: Rectangle {
z: 2
implicitHeight: 100
implicitWidth: root.width / 2
color: "blue"
}
}
}
Example (the header is blue):
https://bugreports.qt.io/browse/QTBUG-101727 was written up about this; probably the same as QTBUG-89409. And we fixed it already in 5.15.7. It's also OK in Qt 6.
Note that if you press on the header itself and try to flick, you won't be able to, in any version; that's intentional: https://bugreports.qt.io/browse/QTBUG-74046
The problem turns out to be too specific and can't be recreated so I guess I should close the question.
(SO asks me to wait for 18 hours...)
I am using a VideoOutput item, which I have placed inside a Rectangle. This VideoOutput goes on top of the Rectangle, of its border and its rounded corners.
Rectangle{
radius: 12
width: 200
height: width
border.color: black
border.width: 15
VideoOutput {
fillMode: VideoOutput.PreserveAspectCrop
anchors.fill: parent
source: myCamera
autoOrientation: true
}
}
Camera {
id: myCamera
focus {
focusMode: CameraFocus.FocusContinuous
focusPointMode: CameraFocus.FocusPointAuto
}
captureMode: Camera.CaptureViewfinder
}
I intentionaly made the border thick so it really shows what is going on. The border is hidden by the VideoOutput, and the rounded corners are ignored.
I can imagine that the VideoOutput item would use some acceleration tricks, so it might not play well with standard QML item behaviour. But is there a way get this to work, without adding a filter element, or is this the expected behaviour?
From the docs:
The default renderer does not do any CPU-side viewport clipping nor occlusion detection. If something is not supposed to be visible, it should not be shown. Use Item::visible: false for items that should not be drawn. The primary reason for not adding such logic is that it adds additional cost which would also hurt applications that took care in behaving well.
So is there a trick to do it easily, without implementing it myself?
Note that in my case the items that are outside the visible area are there because they are in a ScrollView and they are not scrolled-to.
The reason I want culling is to reduce CPU usage for full-scene redraws.
Here is a trivial example you can extend upon:
Window {
visible: true
width: 640
height: 480
Rectangle {
anchors.centerIn: parent
width: 200
height: 200
color: "yellow"
Flickable {
id: view
anchors.fill: parent
contentWidth: 200
contentHeight: col.height
property real span : contentY + height
Column {
id: col
x: 90
spacing: 2
Repeater {
model: 50
delegate: Rectangle {
width: 10
height: 10
color: inView ? "blue" : "red"
property bool inView: y > view.contentY && y < view.span
}
}
}
}
}
}
Obviously, a full-proof solution would also include the item's height in the calculation. You can also do the check in the x axis if necessary.
To add to dtech's answer, I just learned that there are QML components, such as GridView and ListView, that do culling automatically.