Adjusting QML Image display size - qt

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.

Related

How to avoid binding loop when setting padding?

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.

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)

How to make rectangle height fill ScrollView

I have the following QML code:
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Window 2.2
Window {
id: win
width: 1024
height: 768
visible: true
ScrollView {
id:scrollView
anchors.fill: parent
Rectangle{
id:rect
z:5
color:"red"
width: 2048
height: win.height
border{
color: "black"
width: 2
}
}
}
}
In this code the larger Rectangle makes the horizontal scrollbar correctly appear. However, since the scrollbar takes some height from the window, the vertical scrollbar appears too.
How can I make the Rectangle fill only available space in my ScrollView so that vertical scrollbar won't show up? Using something like win.height - <someNumber> is not acceptable. Adding verticalScrollBarPolicy: Qt.ScrollBarAlwaysOff is also not acceptable cause it hides some content on bottom of rect.
Generally speaking ScrollView is not meant for such usage. It is more a container to lay out items and have them shown through the provided scrollbar. Binding loops can pop here and there if bindings are not properly set. Also Flickable + a custom scrollbar (e.g. the ones available here) can perfectly fit your needs.
That said, viewport property provides the desired (cross-platform) workaround for the problem. The documentation states:
The viewport determines the current "window" on the contentItem. In other words, it clips it and the size of the viewport tells you how much of the content area is visible.
Hence the height of the child Item can be set according to the height of the viewport. A final simple example with an Image (cute kitty incoming) would look like this:
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Window 2.2
Window {
id: win
width: 300
height: 300
visible: true
ScrollView {
id:scrollView
anchors.fill: parent
Image{
height: scrollView.viewport.height
source: "http://c1.staticflickr.com/9/8582/16489458700_c9d82954b7_z.jpg"
}
}
}

How to scroll QML ScrollView to center?

I have code like this:
ScrollView {
Image {
source: "..."
}
}
Image is higher than the ScrollView. How can I scroll the latter to the center of Image element?
Despite the appearence, ScrollView is tightly related to Flickable. Indeed, Flickable is used to control the visible area. Such an Item is available as the (readonly) property flickableItem. Flickable has the contentX and contentY properties to control the current visible area. These properties can be combined with the width and height of the ScrollView to position the visible area exactly at the center. Typically you have:
flickableItem.contentY = flickableItem.contentHeight / 2 - height / 2
flickableItem.contentX = flickableItem.contentWidth / 2 - width / 2
The difference is necessary since the first calls just moves the center to the top left point of the visible area (where contentX/contentY are located).
Here is a complete example with an Image as main child of the ScrollView.
Disclaimer: In the simple scenario proposed by the example, with a remotelly loaded image, sizes can still be unset when onCompleted is called, resulting in a centering code that doesn't work. By setting widths and heigths directly into code the problem is avoided. In a real scenario such detail should be unnecessary.
import QtQuick 2.4
import QtQuick.Window 2.2
import QtQuick.Controls 1.2
Window {
id: main
visible: true
width: 600; height: 350
ScrollView {
id: ss
width: 600
height: 350
Image {
id: name
width: 900
height: 600
source: "http://www.joomlaworks.net/images/demos/galleries/abstract/7.jpg"
}
Component.onCompleted: {
flickableItem.contentY = flickableItem.contentHeight / 2 - height / 2
flickableItem.contentX = flickableItem.contentWidth / 2 - width / 2
}
}
}
Here is a solution for QQC2. Tested on Qt 5.12.
The solution is simple, just store the scrollBar pointer and control it however you want.
ScrollView2.qml
import QtQuick 2.0
import QtQuick.Controls 2.4
ScrollView {
id: root
property ScrollBar hScrollBar: ScrollBar.horizontal
property ScrollBar vScrollBar: ScrollBar.vertical
/**
* #param type [Qt.Horizontal, Qt.Vertical]
* #param ratio 0.0 to 1.0
*/
function scrollTo(type, ratio) {
var scrollFunc = function (bar, ratio) {
bar.setPosition(ratio - bar.size/2)
}
switch(type) {
case Qt.Horizontal:
scrollFunc(root.hScrollBar, ratio)
break;
case Qt.Vertical:
scrollFunc(root.vScrollBar, ratio)
break;
}
}
}
main.qml
import QtQuick 2.12
import QtQuick.Window 2.12
Window {
visible: true
width: 500
height: 500
ScrollView2 {
id: scroll
anchors.fill: parent
Text {
width: 1000
height: 1000
text: "ABC"
font.pixelSize: 800
Component.onCompleted: {
scroll.scrollTo(Qt.Horizontal, 0.5)
scroll.scrollTo(Qt.Vertical, 0.5)
}
}
}
}
Since Qt 5.14 (maybe already in 5.12) you can now simply do:
ScrollView {
Component.onCompleted {
// scroll to vertical center
ScrollBar.vertical.position = 0.5
}
// put your content item here:
Image {
// …
}
}

Resources