How to programatically scroll a ScrollView to the bottom? - qt

I've been trying to create a function that programmatically scrolls to bottom a ScrollView using Qt Quick Controls 2.
I've tried various options, but much of the support I found online refers to Qt Quick Controls 1, not 2. This is what I've tried:
import QtQuick 2.8
import QtQuick.Controls 2.4
ScrollView {
id: chatView
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: inputTextAreaContainer.top
function scrollToBottom() {
// Try #1
// chatView.contentItem.contentY = chatBox.height - chatView.contentItem.height
// console.log(chatView.contentItem.contentY)
// Try #2
// flickableItem.contentY = flickableItem.contentHeight / 2 - height / 2
// flickableItem.contentX = flickableItem.contentWidth / 2 - width / 2
// Try #3
chatView.ScrollBar.position = 0.0 // Tried also with 1.0
}
TextArea {
id: chatBox
anchors.fill: parent
textFormat: TextArea.RichText
onTextChanged: {
// Here I need to scroll
chatView.scrollToBottom()
}
}
}
Does anyone know how this can be achieved using Qt Quick Controls 2?
If no, does anyone have any alternatives to this approach?

Cause
You are trying to set the ScrollBar's position to 1.0:
chatView.ScrollBar.position = 0.0 // Tried also with 1.0
however, you do not consider its size.
Solution
Take into account the size of the ScrollBar when you set its position like this:
chatView.ScrollBar.vertical.position = 1.0 - chatView.ScrollBar.vertical.size
How I came up with this solution?
I was curious of how Qt itself solves this problem, so I took a look at how QQuickScrollBar::increase() is implemented and I saw this line:
setPosition(qMin<qreal>(1.0 - d->size, d->position + step));
Then I took the first argument of qMin, i.e. 1.0 - d->size, and the solution was clear.
Example
Since you did not provide a MCE, I wrote one myself. I hope you can adapt it for your particullar case. Here it is:
import QtQuick 2.8
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.12
ApplicationWindow {
width: 480
height: 640
visible: true
title: qsTr("Scroll To Bottom")
ColumnLayout {
anchors.fill: parent
ScrollView {
id: scrollView
Layout.fillWidth: true
Layout.fillHeight: true
function scrollToBottom() {
ScrollBar.vertical.position = 1.0 - ScrollBar.vertical.size
}
contentWidth: children.implicitWidth
contentHeight: children.implicitHeight
ScrollBar.vertical.policy: ScrollBar.AlwaysOn
clip: true
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
Repeater {
model: 50
Label {
text: "Message: " + index
}
}
}
}
TextField {
Layout.fillWidth: true
}
}
Component.onCompleted: {
scrollView.scrollToBottom()
}
}
Result
The example produces the following result:

Related

QML DropShadow sometimes renders badly

I am using QML's inbuilt DropShadow type (import QtGraphicalEffects) to generate a shadow of some rectangles that are contained within an Item. The DropShadow is also a child of said Item. But sometimes the shadow is rendered very badly. I am dynamically creating the screen and adding it to a SwipeView; the code is as follows:
swipeView.addItem(tasksScreen.createObject(swipeView))
swipeView.incrementCurrentIndex()
"tasksScreen" is the screen that the rectangles and DropShadow are part of.
The following video depicts the issue and the code that generates this behavior:
https://yadi.sk/i/mwl_8IZmm_jetQ
I believe the issue is you are making the DropShadow a child of its source - which is creating a looping dependency.
Instead, try making it a sibling of your Item or even better, set it up as your Item's layer.effect.
You can see these different techniques in the DropShadow documentation:
https://doc.qt.io/qt-5/qml-qtgraphicaleffects-dropshadow.html
The problem is the source property in your code you have set the source as the parent item in your code. Give the source as your visual object(Rectangle). I have attached the code for your reference.
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 2.2
import QtGraphicalEffects 1.0
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Component {
id: swipeviewComponentId
Item {
id: itemId
Rectangle {
id: rectangleId
anchors.fill: parent
anchors.margins: 10
radius: 10
}
DropShadow {
anchors.fill: source
horizontalOffset: 3
verticalOffset: 3
radius: 8.0
samples: 17
color: "#80000000"
source: rectangleId
}
}
}
Column {
anchors.fill: parent
anchors.margins: 10
spacing: 10
SwipeView {
id: swipeViewId
width: parent.width
height: parent.height - addButtonId.height - (2 * parent.spacing) - pageIndicatorId.height
}
PageIndicator {
id: pageIndicatorId
currentIndex: swipeViewId.currentIndex
count: swipeViewId.count
anchors.horizontalCenter: parent.horizontalCenter
}
Button {
id: addButtonId
width: parent.width
height: 40
text: "Add item"
onClicked: {
swipeViewId.addItem(swipeviewComponentId.createObject(swipeViewId,
{height: swipeViewId.height, width: swipeViewId.width}))
swipeViewId.incrementCurrentIndex()
}
}
}
}

QML Calendar not showing week day or month year (however it does seem to be there....)

I am using Qt 5.13 and in general Qt.Controls 2 however the calendar only exists in v1.0 or the labs version (which I can't get to work, so using V1).
My code is
import QtQuick 2.2
import QtQuick.Controls 1.4 as Old
import Tree 1.0
import "."
ApplicationWindow {
id: window
width: 800; height: 1000
title: "TreeView Example"
visible: true
Old.Calendar {
anchors.centerIn: parent
id: calendar
}
}
However when I do this, I don't see the day of the week, nor the month/year in the navivagation bar, although I can see where they should be:
I have tried to add a navigationBar delegate, but this then gets rid of the navigation arrows.
style: CalendarStyle {
navigationBar: Text {
text: "hello"
}
}
So, how do I have the nav arrows, show the month/year and show the days of week? The documentation seems to suggest I would get these out of the box...
Now... I thought I could add drop downs as a work around to choose month/year and place them in the nav bar... however when I do that I can actually see that the days of the week are there, just their text colour is white, so I feel I am missing a trick with regard to the navigation bar...?
Worth reading the comments in response to #Aleph0's post, but to get it working I had to copy the default styles and place them within my element. The final code (stripping out the bits that aren't specifically about the calendar then), looks like
import QtQuick.Controls 2.4
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.3
import QtQuick 2.2
import QtQuick.Controls 1.4 as Old
import QtQuick.Controls.Styles 1.4
import QtQuick.Controls.Private 1.0
ApplicationWindow {
id: window
width: 300; height: 300
title: "Calendar Example"
visible: true
ColumnLayout {
anchors.fill: parent
Old.Calendar {
id: calendar
style: CalendarStyle {
gridVisible: true
dayOfWeekDelegate: Rectangle {
color: gridVisible ? "#fcfcfc" : "transparent"
implicitHeight: Math.round(TextSingleton.implicitHeight * 2.25)
Label {
text: control.locale.dayName(styleData.dayOfWeek, control.dayOfWeekFormat)
anchors.centerIn: parent
}
}
navigationBar: Rectangle {
height: Math.round(TextSingleton.implicitHeight * 2.73)
color: "#f9f9f9"
Rectangle {
color: Qt.rgba(1,1,1,0.6)
height: 1
width: parent.width
}
Rectangle {
anchors.bottom: parent.bottom
height: 1
width: parent.width
color: "#ddd"
}
HoverButton {
id: previousMonth
width: parent.height
height: width
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
source: "./assets/leftanglearrow.png"
onClicked: control.showPreviousMonth()
}
Label {
id: dateText
text: styleData.title
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
font.pixelSize: TextSingleton.implicitHeight * 1.25
anchors.verticalCenter: parent.verticalCenter
anchors.left: previousMonth.right
anchors.leftMargin: 2
anchors.right: nextMonth.left
anchors.rightMargin: 2
}
HoverButton {
id: nextMonth
width: parent.height
height: width
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
source: "./assets/rightanglearrow.png"
onClicked: control.showNextMonth()
}
}
}
}
}
}
That results in
In an ideal world, there would be an alternative to a) using Private controls as that seems like I am using Qt internal modules, but thats an easy fix by using an Image with a mouse area, and if I didn't have to use any 1.x controls either, but there doesn't seem to be a 2.x way of graphically displaying dates etc. Anyway, hopefully this can be useful to someone - it's literally a copy paste job of the default styling so it can be simplified alot...
I'm also using Qt 5.13.0 and just tried you example. After removing some imports it was working with the following files:
main.qml
import QtQuick 2.2
import QtQuick.Controls 1.4 as Old
import QtQuick.Controls 2.12
ApplicationWindow {
id: window
width: 800; height: 1000
title: "TreeView Example"
visible: true
Old.Calendar {
anchors.centerIn: parent
id: calendar
}
}
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
int main(int argc, char* argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
QQmlContext* context = engine.rootContext();
engine.load(QUrl("./data/main.qml"));
return app.exec();
}
I'll obtain the following:
I suggest, that you will try my code and compare it with your application.
I had the same problem - days of week and title were almost invisible, because they were written with white color on light gray background. In my case the problem was in desktop color theme in Ubuntu Studio. After I changed it from Dark to Light the text appeared in black as expected. It seems Qt5 can't handle dark themes well or implementation in Ubuntu Studio (22.04) is buggy. The same was true for text in Button-s.

How do I have declarative, bidirectional bindings involving QML MouseAreas?

I've created a QML UI that has a dial and a custom control. The custom control is basically a progress bar with a MouseArea to allow the user to set the value by clicking it. As Qt's property binding docs point out, as soon as I assign to the custom control's value from Javascript in the MouseArea click handler, I lose the declarative binding between it and the dial.
Is it possible to make this binding bidirectional, or even better, to link the values of both controls to a single value above both of them in the QML hierarchy? And is it possible to do this with declarative syntax so I don't have complex event handler code in every control?
import QtQuick 2.9
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.3
import QtQuick.Window 2.2
import QtQuick.Shapes 1.0
Window {
id: window
visible: true
width: 800
height: 200
readonly property int range: 10
RowLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
spacing: 5
Dial {
id: dial1
live: true
from: 0
to: window.range
stepSize: 1
snapMode: Dial.SnapAlways
}
Control {
id: dut
implicitWidth: 200
implicitHeight: 50
property int range: window.range
property int value: dial1.value
onValueChanged: {
console.log("New value: " + value);
}
Rectangle {
width: parent.width
height: parent.height
color: Qt.rgba(0,0,0,0)
border.color: Qt.rgba(0,0,0,1)
border.width: 1
}
Rectangle {
width: parent.width * dut.value/dut.range
height: parent.height
color: Qt.rgba(0,0,0,1)
}
MouseArea {
anchors.fill: parent
onClicked: {
dut.value = Math.round(mouseX/width * dut.range);
}
}
}
}
}
Note that if I reverse the relationship ie. have dial1.value: dut.value, then the binding isn't broken (although it's not quite bidirectional).
I realise that this example basically reinvents the scrollbar, but I'm trying to work my way up to more complex controls, for which declarative relationships between values would make life much easier.
Elaboration from a comment: What I don't understand, but want to, is how it's done for other QML components. For example, with a Dial I can set its value property to be bound to some other component's property, and clicking on the dial doesn't remove that binding. I don't have to hook into its mouse events to do that. Despite looking through the source for how this is done, I'm not really any closer to understanding it.
There are other questions about bidirectional property bindings in QML, but I haven't been able to apply them to my problem because (a) I really, really want something declarative, and (b) the MouseArea properties and events don't seem to work well with Binding objects (as in, I can't figure out how to integrate the two things).
I would have done this:
import QtQuick 2.9
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.3
import QtQuick.Window 2.2
import QtQuick.Shapes 1.0
Window {
id: window
visible: true
width: 800
height: 200
readonly property int range: 10
property int commonValue
RowLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
spacing: 5
Dial {
id: dial1
live: true
from: 0
to: window.range
stepSize: 1
snapMode: Dial.SnapAlways
onValueChanged: {
commonValue = dial1.value
console.log("New value: " + value);
}
}
Rectangle {
width: 200
height: 50
color: Qt.rgba(0,0,0,0)
border.color: Qt.rgba(0,0,0,1)
border.width: 1
MouseArea {
anchors.fill: parent
onClicked: {
commonValue = Math.round(mouseX/width * window.range)
dial1.value = commonValue
}
}
Rectangle {
width: parent.width * window.commonValue/window.range
height: parent.height
color: Qt.rgba(0,0,0,1)
}
}
}
}
Use a Binding QML Type:
MouseArea {
id: mouseArea
anchors.fill: dut
}
Binding {
target: dut
property: 'value'
value: Math.round(mouseArea.mouseX/mouseArea.width * dut.range);
when: mouseArea.pressed && mouseArea.containsMouse
}
Note that the when property on the Binding means it's only active as a binding when those conditions are fulfilled ie. the mouse is over the area and one of the "accepted buttons" is pressed.
This does not mean that the value reverts when the conditions aren't met, just that the value stops updating when they're not met. However, if you have another binding active somewhere else, that one may cause the the value to "snap back" because it will "take over" when this Binding ceases to apply.
Depending on the other components you use, this might not even be enough, and you might need to implement your properties in C++ to get them to work as you expect.

Spacer Item in QML Layouts

I want to create a layout in QML and I'd like to add a spacer item (the bottom selected item from the image below) just as you do using widgets like so:
But I couldn't find anything to suit this on the QtQuick side of things...is it possible to have this kind of layout in QML w/o using the anchoring system?
I'd prefer the layouts approach...
You can simply use an Item with Layout.fillHeight: true :
import QtQuick 2.0
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.3
ApplicationWindow {
visible: true
ColumnLayout {
anchors.fill: parent
Button {
Layout.fillWidth: true
text: "PushButton"
}
Button {
Layout.fillWidth: true
text: "PushButton"
}
Label {
Layout.fillWidth: true
text: "TextLabel"
}
Item {
// spacer item
Layout.fillWidth: true
Layout.fillHeight: true
Rectangle { anchors.fill: parent; color: "#ffaaaa" } // to visualize the spacer
}
}
}
EDIT: Alternatively here, you could have used a Column with no spacer item since a Column just positions its children from top to bottom and don't spread them to take all the available space.
For those coming from Qt widgets and for comparison: the intended solution in QML for this situation is the anchoring system that the question mentions. In this case, it would look as follows, and I think it's not so bad :)
import QtQuick 2.0
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.3
ApplicationWindow {
visible: true
ColumnLayout {
// anchors.fill sets all four directional anchors.
// Loosening one yields the space at the bottom.
anchors.fill: parent
anchors.bottom: undefined
// Alternative approach: only set the three anchors we want.
// anchors.top: parent.top
// anchors.left: parent.left
// anchors.right: parent.right
Button {
Layout.fillWidth: true
text: "PushButton"
}
Button {
Layout.fillWidth: true
text: "PushButton"
}
Label {
Layout.fillWidth: true
text: "TextLabel"
}
}
}

Take snapshot of video in Qt Multimedia

Is it possible to take snapshot of a video in Qt Multimedia? how?
It depends on the platform but what you can probably do is to use a QMediaPlayer, set a subclassed video surface via setVideoOutput, and get the frame data from the QVideoFrame passed in the present method. You'll then have to deal with the frame format and to map if those are not in CPU memory.
However, depending on your need, I would use ffmpeg/libav to get a frame from a specific position.
Try this (Documentation here: http://doc.qt.io/qt-5/qml-qtquick-item.html#grabToImage-method)
import QtQuick 2.5
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.1
import QtMultimedia 5.0
Window {
id: mainWindow
visible: true
width: 480
height: 800
MediaPlayer {
id: player
source: "file:///location/of/some/video.mp4"
autoPlay: false
}
ColumnLayout {
anchors.fill: parent
VideoOutput {
id: output
source: player
Layout.fillHeight: true
Layout.fillWidth: true
}
Row {
id: buttonsRow
height: 100
spacing: 20
anchors.horizontalCenter: parent.horizontalCenter
Layout.margins: 10
Button {
id: playPauseButton
text: player.playbackState === MediaPlayer.PlayingState ? "Pause" : "Play"
onClicked: {
var playing = player.playbackState === MediaPlayer.PlayingState;
playing ? player.pause() : player.play();
}
}
Button {
text: "Snapshot"
onClicked: {
output.grabToImage(function(image) {
console.log("Called...", arguments)
image.saveToFile("screen.png"); // save happens here
});
}
}
}
}
}

Resources