How to avoid binding loop when setting padding? - qt

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.

Related

Setting header on ListView prevents scrolling when the delegate has MouseArea

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...)

QML Scroll View does not allow scrolling of its content

I need to create components dynamically add added to an area of the screen that, of course, needs to be scrollable. I found out that no matter how many of components I added with the scroll bar as its parent, the scroll bars would not appear and the element would not be scrollable.
I did a little fiddling and I think I came up with a minum working example of what I am talking about:
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
ScrollView {
width: 200
height: 200
clip: true
Label {
text: "ABC"
font.pixelSize: 224
}
// Rectangle {
// color: "#ff0000"
// width: 100
// height: 100
// }
}
}
This is a modified version of the example used int he official documentation. However when I uncomment the square the screen is no longer scrollable (scroll bars never appear).
If I remove the label and leave the rectangle (making it larger so that there is something to scroll to) it still doesn't work.
I am using Qt 5.10.
So the code below worked for me. I defined a rectangle as a backgroud to get border lines to a scrollable table that I need to create.
Rectangle {
id: tableBackground
color: "#ffffff"
border.width: 2
border.color: "#EDEDEE"
radius: 4
anchors.top: tableHeader.bottom
anchors.left: tableHeader.left
width: vmTableWidth
height: vmTableHeight - tableHeader.height
ScrollView {
id: tableArea
anchors.fill: parent
clip: true
ListView {
id: patientListView
anchors.fill: parent
model: patientList
delegate: VMPatientEntry {
onFetchReport: {
// This is a signal emitted by my VMPatientEntry.
}
}
onCurrentIndexChanged: {
// Do stuff when the current index changes.
}
}
}
}
So I hope this answer allows someone to fix their problem as well.

Why does my QML background transparency break depending on width setting?

I'm running into some strange QML behavior. Basically, I have a TabBar header with several tabs running across it. I'd like the background element to be mostly the same for each of them, but some of them I want to be able to dynamically change the color of. So I have a component:
Component {
id: standardBackground
Rectangle {
opacity: parent.parent.checked ? 0 : (parent.parent.pressed ? 0.8 : 1)
color: tabColor
}
}
And for each TabButton, I'm doing:
TabButton {
text: qsTr("Tab 1")
background: Loader { sourceComponent: standardBackground }
height: 60
}
This works perfectly, but I'm running into some really strange errors. First off, running it this way gives me the following QML warning:
QML TabButton: Binding loop detected for property "implicitWidth"
So I figured I could fix this by adding: width: parent.width to the Rectangle in my component. This does silence the warning, but for some reason, it makes it so that the first tab will always be transparent regardless of whether or not it's clicked. This only affects the first tab. I have no clue why this would happen.
However, when I set width: <anything>, then this fixes both problems: No warnings and correct transparency. Playing around with different settings for the width causes no noticeable changes, as long as it's positive. So I have it set to 1. If I set it to 0, I get the same "implicit width" warnings.
So a couple different questions:
Why does the transparency of the component break when I set width: parent.width?
Why can I set width to any constant value without it affecting the GUI at all?
Is there a better way of silencing the warning about implicit width?
Here is my full code (simplified to less tabs):
import QtQuick 2.6
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.0
import QtQuick.Controls.Material 2.0
import QtQuick.Controls.Universal 2.0
import Qt.labs.settings 1.0
import QtQuick.VirtualKeyboard 2.1
import QtQuick.VirtualKeyboard.Settings 2.1
import "DataEntry"
ApplicationWindow {
id: window
width: 1280
height: 1024
visible: true
title: "Hello World"
property var tabColor: "#353637"
property var dummy: InputContext.focus
Settings {
id: settings
property string style: "Universal"
}
Component {
id: standardBackground
Rectangle {
opacity: parent.parent.checked ? 0 : (parent.parent.pressed ? 0.8 : 1)
color: tabColor
width: 1
}
}
header: TabBar {
id: bar
width: parent.width
height: 60
TabButton {
text: qsTr("Tab 1")
background: Loader { sourceComponent: standardBackground }
height: 60
}
TabButton {
text: qsTr("Tab 2")
background: Loader {
sourceComponent: standardBackground
function getTabColor(error){
if (error)
return '#cccc00'
return window.tabColor
}
property var tabColor: getTabColor(hasError)
}
height: 60
}
}
StackLayout {
id: viewStack
width: parent.width
anchors.fill: parent
currentIndex: bar.currentIndex
tab1 {
}
tab2 {
}
}
}
As we are on SO I tend to answer only one question. For you, I choos the question for the binding loop.
The reason for that binding loop is documented here.
You do not specify a size for the Loader so the implicit width of the Loader is set to the width specified by the loaded Item. Here you set the size to be the same as the Loader's size. Now this would not be a problem, and the result would just be 0
Now we stir in the Button which also has an implicitSize set to its styling items. Here the Loader is instantiated widht width 0 and then resized to fill the implicitWidth of the Button which is (without a sized background) depending on the text and the paddings.
And now we update the round. So, the implicitWidth of the Rectangle is depending on the width of the Loader whose implicitWidth is depending on the Rectangles width. Further the Loaders width is depending on the Buttons width, which is depending on its implicitWidth and which is in turn depending on its childrenRect.width...
A binding loop is easily detected even if there are no direct problems, as the system is stabilizing in the first iteration.

QML ApplicationWindow: set minimum size to fit content

I would like to set the minimum width and height of my QML Application window, so that the content item is fully visible (not clipped).
import QtQuick 2.5
import QtQuick.Controls 1.4
ApplicationWindow {
visible: true
width: 100
height: 100
title: "test"
minimumWidth: circle.width
minimumHeight: circle.height // + menuBar.height
menuBar: MenuBar {
Menu {
title: qsTr("File")
MenuItem {
text: qsTr("Exit")
onTriggered: Qt.quit();
}
}
}
Rectangle {
id: circle
anchors.centerIn: parent
width: 200
height: 200
color: "red"
radius: width * 0.5
}
}
Here is the result:
As you can see, setting the minimum width works fine. The minimum height seems to be off by the height of the menu bar. The problem is, adding something like menuBar.height does not work as this property does not exist.
So the question is: how do I set the size of the ApplicationWindow, so that the content item (given by width/height or implicitWidth/implicitHeight) is not clipped?
Note: In reality, instead of a red circle, the content item serves as a game canvas, which I would like to resize dynamically.
As always with the old QtQuick.Controls 1.x the only way to help yourself is, to look at the (undocumented/internal) properties. For the MenuBar those are:
objectName
menus
__contentItem
__parentWindow
__isNative
style
__style
__menuBarComponent
objectNameChanged
menusChanged
nativeChanged
contentItemChanged
styleChanged
__styleChanged
__menuBarComponentChanged
__contentItem seems to be interesting, and it features a height - as soon as it is instantiated.
So we can define the height of the ApplicationWindow as such:
minimumHeight: contentItem.childrenRect.height
+ (menuBar.__contentItem ? menuBar.__contentItem.height : 0)

Adjusting QML Image display size

I have a QML window with a nested RowLayout. In the inner row I have two images. The source .png files for these images are (intentionally) rather large. When I attempt to set the height property on these images to make them smaller, they are still drawn large.
Desired Appearance:
Actual Appearance:
The only way I have been able to get them to be small is to set the sourceSize.height:100 instead of height:100; however, this is not what I want. I want them to be able to scale up and down without reloading.
How can I fix my QML so that the images take on the height of their containing RowLayout?
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3
ApplicationWindow {
width:600; height:300
visible:true
Rectangle {
color:'red'
anchors { top:header.bottom; bottom:footer.top; left:parent.left; right:parent.right }
}
header:RowLayout {
id:header
spacing:0
height:100; width:parent.width
RowLayout {
id:playcontrol
Layout.minimumWidth:200; Layout.maximumWidth:200; Layout.preferredWidth:200
height:parent.height
Image {
// I really want these to take on the height of their row
source:'qrc:/img/play.png'
width:100; height:100
fillMode:Image.PreserveAspectFit; clip:true
}
Image {
source:'qrc:/img/skip.png'
width:100; height:100
fillMode:Image.PreserveAspectFit; clip:true
}
}
Rectangle {
color:'#80CC00CC'
Layout.minimumWidth:200
Layout.preferredWidth:parent.width*0.7
Layout.fillWidth:true; Layout.fillHeight:true
height:parent.height
}
}
footer:Rectangle { height:100; color:'blue' }
}
When using layouts, never specify the width or height of the item; use the Layout attached properties instead. The layout itself will set the width and height, effectively overriding whatever you set.
So, for your images, replace
width:100; height:100
with
Layout.preferredWidth: 100
Layout.preferredHeight: 100
This is documented here. Specifically, the width and height are only used as a "final fallback", and they won't behave as you'd expect.
There are other places in your code where this occurs:
playcontrol sets height: parent.height (filling the width and height of the parent is the default behaviour for layouts, so this shouldn't be necessary anyway).
The Rectangle within the playcontrol layout also sets height: parent.height.

Resources