ListView isn't positioned in layout as expected - qt

I have a ColumnLayout with a RowLayout in it. The RowLayout is positioned as expected. This is also true if the windows is being resized. Even if the windows is smaller than the entire ColumnLayout (see second image)
But if I replace the RowLayout by a (horizontal) ListView, this ListView is not positioned as I would expect. I would expect this behaves like the example with the RowLayout but the ListView is positioned higher:
And if the window gets 'to small' the blue rectangle 'moves into' the ListView unlike the first example:
How can I achieve the behaviour of the first example with a ListView?
Source
import QtQuick 2.0
import QtQuick.Layouts 1.1
Rectangle {
width: 360
height: 360
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
Item {
Layout.fillHeight: true
}
/*
ListView {
Layout.fillHeight: true
Layout.fillWidth: true
orientation: ListView.Horizontal
spacing: 5
model: 3
delegate: Rectangle {
width: 50
height: 50
color: "red"
}
}
*/
RowLayout {
Layout.fillHeight: true
Layout.fillWidth: true
Rectangle {
width: 50
height: 50
color: "red"
}
Rectangle {
width: 50
height: 50
color: "red"
}
Rectangle {
width: 50
height: 50
color: "red"
}
}
Item {
Layout.fillHeight: true
}
Rectangle {
width: 50
height: 50
color: "blue"
}
}
}

You just need to define width and height for your ListView. In that way your column layout will consider its size and keep it as a fixed size.
Here your code updated:
import QtQuick 2.0
import QtQuick.Layouts 1.1
Rectangle {
width: 360
height: 360
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
Item {
Layout.fillHeight: true
}
ListView {
//take as much width as possible with a binding
width: parent.width
//keep enought height to display each delegate instance
height: 50
orientation: ListView.Horizontal
spacing: 5
model: 3
delegate: Rectangle {
width: 50
height: 50
color: "red"
}
}
Item {
Layout.fillHeight: true
}
Rectangle {
width: 50
height: 50
color: "blue"
}
}
}

Related

Qt Qml Vertical Tabbar

I would like to add Vertical TabBar to my app in a similar manner of what Qt Creater is doing in their app (as shown in picture).
I have been searching how to simple make the TabBar vertical, yet did not find proper answers (thought its common to have it vertical).
Question: How could I make a Vertical Tab to navigate through the different qml files I have? If there are more suitable options, please suggest.
A TabBar just uses a common ListView to display a bunch of TabButtons. You can customize it by overwriting the contentItem property and making the ListView vertical, like this:
// VertTabBar.qml
TabBar {
id: control
contentItem: ListView {
model: control.contentModel
currentIndex: control.currentIndex
spacing: control.spacing
orientation: ListView.Vertical // <<-- VERTICAL
boundsBehavior: Flickable.StopAtBounds
flickableDirection: Flickable.AutoFlickIfNeeded
snapMode: ListView.SnapToItem
highlightMoveDuration: 0
highlightRangeMode: ListView.ApplyRange
preferredHighlightBegin: 40
preferredHighlightEnd: height - 40
}
}
A complete example using Fusion theme.
It is important to set the width of the TabButton else the width is divided by the number of items.
Notice there is a light colour separator line, that comes from ???
Issue: first item can be partially clipped.
Well, there are a lot of things under the hood with QML...
Thus, we cannot really make a TabBar vertical...
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Templates as T
// import QtQuick.Controls.impl
// import QtQuick.Controls.Fusion
// import QtQuick.Controls.Fusion.impl
Page {
id: root
width: 1800
height: 800
Row {
anchors.fill: parent
TabBar {
id: control
width: 200
height: parent.height
contentItem: ListView {
model: control.contentModel
currentIndex: control.currentIndex
spacing: control.spacing
orientation: ListView.Vertical
boundsBehavior: Flickable.StopAtBounds
flickableDirection: Flickable.AutoFlickIfNeeded
snapMode: ListView.SnapToItem
highlightMoveDuration: 0
highlightRangeMode: ListView.ApplyRange
preferredHighlightBegin: 40
preferredHighlightEnd: width - 40
}
Repeater {
model: 50
TabButton {
id: control2
width: control.width
text: "tab blabla blabla %1".arg(model.index)
contentItem: IconLabel {
spacing: control2.spacing
mirrored: control2.mirrored
display: control2.display
icon: control2.icon
text: control2.text
font: control2.font
color: control2.palette.buttonText
}
background: Rectangle {
y: control2.checked || control2.TabBar.position !== T.TabBar.Header ? 0 : 2
implicitHeight: 21
height: control2.height - (control2.checked ? 0 : 2)
border.color: Qt.lighter(Fusion.outline(control2.palette), 1.1)
border.width: 0
gradient: Gradient {
GradientStop {
position: 0
color: control2.checked ? Qt.lighter(Fusion.tabFrameColor(control2.palette), 1.04)
: Qt.darker(Fusion.tabFrameColor(control2.palette), 1.08)
}
GradientStop {
position: control2.checked ? 0 : 0.85
color: control2.checked ? Qt.lighter(Fusion.tabFrameColor(control2.palette), 1.04)
: Qt.darker(Fusion.tabFrameColor(control2.palette), 1.08)
}
GradientStop {
position: 1
color: control2.checked ? Fusion.tabFrameColor(control2.palette)
: Qt.darker(Fusion.tabFrameColor(control2.palette), 1.16)
}
}
}
}
}
}
StackLayout {
id: stack_layout
width: parent.width - 200
height: parent.height
currentIndex: control.currentIndex
Repeater {
model: 50
Item {
Label {
anchors.centerIn: parent
text: "tab %1".arg(model.index)
font.pixelSize: 50
}
}
}
}
}
}

QML layer element "reveals" element above it when they overlap

Is it possible in QML, maybe using shader effects with layers, to create an item that makes another item (with a higher z index) visible only when the two layers overlap? I've been messing aroud with OpacityMask and ThresholdMask but have been unable to figure it out. The effect I'm looking for in the context of the example below would be if the the black circle was only visible when the two red squares are under it:
current:
desired:
Some key points are that the bottom layer (red squares) must be moveable (OpacityMask doesn't seem to let you drag the maskSource) and the bottom layer needs to also be able to contain other elements within it that the black circle responds to. Any guidance towards the right things to learn in order to achieve this would be appreciated. Here is the QML for the red squares and black circle thing. The red squares are draggable as one element:
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Layouts 1.12
import QtGraphicalEffects 1.12
Window {
id: main_window
visible: true
width: 1500
height: 1000
title: qsTr("Hello World")
Item {
width: main_window.width
height: main_window.height
LinearGradient {
anchors.fill: parent
start: Qt.point(0, 0)
end: Qt.point(main_window.width, 0)
gradient: Gradient {
GradientStop { position: 0.0; color: "#003cff" }
GradientStop { position: 1.0; color: "#9afff9" }
}
}
}
Rectangle {
id: sfg
width: 175
height: 75
color: 'transparent'
RowLayout {
width: parent.width
height: parent.height
spacing: 25
Rectangle {
Layout.preferredWidth: 75
Layout.fillWidth: false
Layout.fillHeight: true
color: 'red'
}
Rectangle {
Layout.preferredWidth: 75
Layout.fillWidth: false
Layout.fillHeight: true
color: 'red'
}
}
MouseArea {
cursorShape: Qt.PointingHandCursor
anchors.fill: parent
drag {
target: sfg
}
}
}
Rectangle {
id: mask
color: 'black'
x: 400
y: 200
width: 100
height: 100
visible: true
opacity: 1
radius: 50
}
}
Like this?
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Layouts 1.12
import QtGraphicalEffects 1.12
Window {
id: main_window
visible: true
width: 1500
height: 1000
title: qsTr("Hello World")
Item {
width: main_window.width
height: main_window.height
LinearGradient {
anchors.fill: parent
start: Qt.point(0, 0)
end: Qt.point(main_window.width, 0)
gradient: Gradient {
GradientStop { position: 0.0; color: "#003cff" }
GradientStop { position: 1.0; color: "#9afff9" }
}
}
}
Rectangle {
id: sgfBox
anchors.fill: parent
color: "transparent"
Rectangle {
id: sfg
width: 175
height: 75
color: 'transparent'
RowLayout {
width: parent.width
height: parent.height
spacing: 25
Rectangle {
Layout.preferredWidth: 75
Layout.fillWidth: false
Layout.fillHeight: true
color: 'red'
}
Rectangle {
Layout.preferredWidth: 75
Layout.fillWidth: false
Layout.fillHeight: true
color: 'red'
}
}
MouseArea {
cursorShape: Qt.PointingHandCursor
anchors.fill: parent
drag {
target: sfg
}
}
}
}
Rectangle {
id: mask
anchors.fill: parent
color: "transparent"
Rectangle {
color: 'black'
x: 400
y: 200
width: 100
height: 100
opacity: 1
radius: 50
}
layer.enabled: true
layer.effect: OpacityMask {
maskSource: sgfBox
}
}
}

How to change the position when the parent is resized?

How do I properly change the x, y of an object so that it changes its position when the parent is resized? There is, I will introduce that if I drag the rectangle to the middle, then when the window is resized, it should remain in the middle. (middle for example only, rectangle can be moved freely)
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 2.3
Window {
visible: true
width: 640
height: 480
onWidthChanged: {
block.x -= block.previousWidth - width
block.previousWidth = width
}
onHeightChanged: {
block.y -= block.previousHeight - height
block.previousHeight = height
}
Rectangle {
id: block
color: "red"
width: 50
height:50
x: 100
y: 50
property int previousWidth: 0
property int previousHeight:0
Component.onCompleted: {
previousWidth = parent.width
previousHeight = parent.height
}
MouseArea {
anchors.fill: parent
drag.target: block
}
}
}
I must admit, at first I was not impressed by the question. However, when I thought about it, it represents a very interesting and valid use case. So I would be happy to provide a solution.
Solution
I would approach the problem like this:
Make the frame a child of the background image.
Instead of manually calculating the coordinates, use Item.scale to scale the image, effectively preserving the relative position of the frame with regard to the image.
Example
Here is an example I have prepared for you to demonstrate how the proposed solution could be implemented:
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
visible: true
width: 640
height: 480
Image {
anchors.centerIn: parent
source: "alphabet.png"
scale: parent.width/sourceSize.width
Rectangle {
id: frame
width: parent.width/7
height: parent.height/4
border.color: "black"
color: "transparent"
antialiasing: true
MouseArea {
anchors.fill: parent
drag.target: parent
}
}
}
}
Result
The example produces the following result:
Original window
Resized window
The frame is moved
The window is resized again
As I said in my comment, the best solution is anchoring, for example:
Window {
id: root
width: 600
height: 400
title: qsTr("Parent window")
visible: true
flags: Qt.WindowStaysOnTopHint
Grid {
anchors.fill: parent
Repeater {
model: 16
Rectangle {
width: root.width / 4
height: root.height / 4
color: Qt.rgba(Math.random(),Math.random(),Math.random(),1)
}
}
}
Rectangle {
border {
width: 5
color: "black"
}
color: "transparent"
width: root.width / 4
height: root.height / 4
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.leftMargin: root.width / 4
anchors.bottomMargin: root.height / 4
}
}

Scroll two or more List views in QML

I need to scroll two or more list view at once using a single scrollBar. Initially, i used Column inside a Flickable but scroll was not happening as expected. Later, I used ListView and even that was not scrolling correctly.
So how to scroll a listview/layout content item with a scroll bar? Should I use ScrollView or Flickable or something else?
The stock scrollbar will only hook to a single scrollable item. However, it is trivial to make a custom scroller and hook multiple views to it:
Row {
Flickable {
width: 50
height: main.height
contentHeight: contentItem.childrenRect.height
interactive: false
contentY: (contentHeight - height) * scroller.position
Column {
spacing: 5
Repeater {
model: 20
delegate: Rectangle {
width: 50
height: 50
color: "red"
Text {
anchors.centerIn: parent
text: index
}
}
}
}
}
Flickable {
width: 50
height: main.height
contentHeight: contentItem.childrenRect.height
interactive: false
contentY: (contentHeight - height) * scroller.position
Column {
spacing: 5
Repeater {
model: 30
delegate: Rectangle {
width: 50
height: 50
color: "cyan"
Text {
anchors.centerIn: parent
text: index
}
}
}
}
}
Rectangle {
id: scroller
width: 50
height: 50
color: "grey"
property real position: y / (main.height - 50)
MouseArea {
anchors.fill: parent
drag.target: parent
drag.minimumY: 0
drag.maximumY: main.height - 50
drag.axis: Drag.YAxis
}
}
}
Note that it will work adequately even if the the views are of different content height, scrolling each view relative to the scroller position:
Realizing the question was not put that well, just in case someone wants to actually scroll multiple views at the same time comes around, I will nonetheless share another interesting approach similar to a jog wheel, something that can go indefinitely in every direction rather than having a limited range like a scrollbar. This solution will scroll the two views in sync until they hit the extent of their ranges. Unlike GrecKo's answer, this never leaves you with an "empty view" when the view size is different:
Row {
Flickable {
id: f1
width: 50
height: main.height
contentHeight: contentItem.childrenRect.height
interactive: false
Connections {
target: jogger
onScroll: f1.contentY = Math.max(0, Math.min(f1.contentHeight - f1.height, f1.contentY + p))
}
Column {
spacing: 5
Repeater {
model: 20
delegate: Rectangle {
width: 50
height: 50
color: "red"
Text {
anchors.centerIn: parent
text: index
}
}
}
}
}
Flickable {
id: f2
width: 50
height: main.height
contentHeight: contentItem.childrenRect.height
interactive: false
Connections {
target: jogger
onScroll: f2.contentY = Math.max(0, Math.min(f2.contentHeight - f2.height, f2.contentY + p))
}
Column {
spacing: 5
Repeater {
model: 30
delegate: Rectangle {
width: 50
height: 50
color: "cyan"
Text {
anchors.centerIn: parent
text: index
}
}
}
}
}
MouseArea {
id: jogger
width: 50
height: main.height
drag.target: knob
drag.minimumY: 0
drag.maximumY: main.height - 50
drag.axis: Drag.YAxis
signal scroll(real p)
property real dy: 0
onPressed: dy = mouseY
onPositionChanged: {
scroll(dy - mouseY)
dy = mouseY
}
onScroll: console.log(p)
Rectangle {
anchors.fill: parent
color: "lightgrey"
}
Rectangle {
id: knob
visible: parent.pressed
width: 50
height: 50
color: "grey"
y: Math.max(0, Math.min(parent.mouseY - 25, parent.height - height))
}
}
}
Another advantage the "jog" approach has it is it not relative but absolute. That means if your view is huge, if you use a scroller even a single pixel may result in a big shift in content, whereas the jog, working in absolute mode, will always scroll the same amount of pixels regardless the content size, which is handy where precision is required.
You could just use a Flickable with your Columns.
I don't know how your Columns are laid out horizontally but if they are inside a Row it's pretty straightforward:
import QtQuick 2.7
import QtQuick.Controls 2.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Multi Column")
Flickable {
anchors.fill: parent
contentWidth: row.implicitWidth
contentHeight: row.implicitHeight
Row {
id: row
Column {
spacing: 5
Repeater {
model: 20
delegate: Rectangle {
width: 50
height: 50
color: "red"
Text {
anchors.centerIn: parent
text: index
}
}
}
}
Column {
spacing: 5
Repeater {
model: 30
delegate: Rectangle {
width: 50
height: 50
color: "cyan"
Text {
anchors.centerIn: parent
text: index
}
}
}
}
}
ScrollBar.vertical: ScrollBar { }
}
}
Even if they are not in a Row you could do :
contentHeight: Math.max(column1.height, column2.height, ...)
Demonstration :

Nested ScrollView in QML doesn't respond to mousewheel

I have a nested ScrollView, similar to the following QML:
import QtQuick 2.0
import QtQuick.Controls 1.1
Rectangle {
width: 200
height: 600
ScrollView {
id: sView
anchors.fill: parent
ListView {
id: list
boundsBehavior: Flickable.StopAtBounds
clip: true
focus: true
interactive: true
model: 5
delegate: Component {
MouseArea {
id: hoverArea
width: 100
height: 200
onClicked: list.currentIndex = index;
Rectangle {
id: fauxParent
anchors.fill: parent
border.width: 1
border.color: "black"
Rectangle {
anchors.top: parent.top
anchors.left: parent.left
height: parent.height
width: parent.width / 2
border.width: 1
border.color: "purple"
color: "green"
Text {
anchors.centerIn: parent
text: "stuff"
}
}
ScrollView {
//parent: sView
anchors.top: fauxParent.top
anchors.right: fauxParent.right
height: fauxParent.height
width: fauxParent.width / 2
ListView {
model: 3
delegate: Component {
Rectangle {
radius: 10
height: 100
width: 100
color: "blue"
}
}
}
}
}
}
}
}
}
}
It seems to run correctly, except that the inner ScrollView won't respond to the mousewheel: the outer ScrollView intercepts that event. The only fix I've found in research for this, is to set the inner scrollview's parent directly to the outer scrollview (uncomment the parent: sView line). Unfortunately, this re-positions all five scrollview delegates onto the top right corner of the outer scrollview. It seems that ScrollView positions itself based on its parent?
For the record, my actual application is wrapping a large section of the page in a scrollview so as to allow the user to access sections of it that may be out of bounds for the current window size. The content of this section, though, has a variety of different controls for a variety of different purposes, including some scrollviews. So I'd also accept an alternate way of moving around a set of generic content that's too large for the window.
This is a Windows desktop app, so I don't need to consider mobile-specific issues.
You nested four elements that handle scroll Events.
Why do you put a ScrollView arround a ListView?
If you remove the ScrollViews the Mousewheel work fine.
Rectangle {
width: 200
height: 600
ListView {
anchors.fill: parent
id: list
boundsBehavior: Flickable.StopAtBounds
clip: true
focus: true
interactive: true
model: 5
delegate: Component {
MouseArea {
id: hoverArea
width: 100
height: 200
onClicked: list.currentIndex = index;
Rectangle {
id: fauxParent
anchors.fill: parent
border.width: 1
border.color: "black"
Rectangle {
anchors.top: parent.top
anchors.left: parent.left
height: parent.height
width: parent.width / 2
border.width: 1
border.color: "purple"
color: "green"
Text {
anchors.centerIn: parent
text: "stuff"
}
}
ListView {
anchors.top: fauxParent.top
anchors.right: fauxParent.right
height: fauxParent.height
width: fauxParent.width / 2
model: 3
delegate: Component {
Rectangle {
radius: 10
height: 100
width: 100
color: "blue"
}
}
}
}
}
}
}
}
If you miss the Scrollbar look at this:
How to create scrollbar in QtQuick 2.0?

Resources