QML - How to append an action to a signal handler? - qt

I have created a custom timer providing a pause function and elapsed time property. When triggered, elapsedTime is incremented by the 'interval' property amount. I already tested it and it works fine.
// CustomTimer.qml
import QtQuick 2.0
Timer {
property double elapsedTimeSec: 0.0
interval: 100
repeat: true
onTriggered: elapsedTimeSec += interval/1000
}
I added it into an existing project as an separate QML file. Now I wish to append action to my onTriggered signal-handler to interact and toggle things in my main application. A little code for example:
Rectangle {
Slider {
id: slider
value: 0.2
}
CustomTimer {
onTriggered: slider.value += 0.1
}
}
How can I do that without deleting already-existing, internal onTriggered handler (since those are necessary to the timer process)?

How can I do that without deleting already-existing actions, since those are necessary to the timer process?
You shouldn't have to worry. The two signal-handlers are executed separately and simultaneously (theoretically). They coexist. So in your code, both the onTriggered handlers in CustomTimer.qml and the one nested under the Rectangle will be executed.
The sort of overwrite behaviour you're concerned about only occurs with properties. For example, the CustomTimer below will change the timer's interval from 100 to 500. Unlike slots, the value is propagated.
Rectangle {
CustomTimer {
interval: 500
}
}

Related

How qml MediaPlayer VideoOutput run in separate thread?

Suppose there is a code like this (sorry for the contrived code)
Window {
id: window
visible: true
width: 700
height: 700
MediaPlayer {
id: mediaplayer
source: "test.avi"
autoPlay: true
}
VideoOutput {
anchors.fill: parent
source: mediaplayer
}
Repeater {
id: repeater
property int n: 1
model: 1
Label {
function getRandomInt(max) {
return Math.floor(Math.random() * Math.floor(max))
}
id: label
y: getRandomInt(window.width)
x: getRandomInt(window.height)
text: "label"
}
}
Timer {
interval: 10
running: true
repeat: true
onTriggered: {
repeater.n += 1
if (!(repeater.n % 100)) {
repeater.model = repeater.n
}
}
}
}
When the number of labels increases, the video starts to break. How to start a video in a separate thread so that manipulations with any widgets do not interrupt it (video).
You should not overload main thread (with it's event loop). Otherwise, whole the software will lag, not only VideoOutput.
Don't move to another thread well-optimized and efficient controls. Move to another thread huge things, hard & long computations.
So
avoid using timers with low interval or if you use them, don't attach to them hard things
if you should create many or hard controls in QML, then use Loader or QQmlIncubator, they allow to create QML controls in a separate threads. Also Loader allows dynamically load and unload needed/unneeded controls. So, QML engine will not render too many controls, most of which even are not visible.
Note about Loader/QQmlIncubator: they create control in a separate thread, not run it there.
avoid writing custom properties and functions inside QML controls, place them in some single QtObject: QtObject { id: internals; ...all your custom data... }
Use ListView instead of Repeater. It's more efficient because it instantiates only visible items (and several out of visible area) - probably the most important for your sample
More advice and samples you can find here:
Performance Considerations And Suggestions: https://doc.qt.io/qt-5/qtquick-performance.html
QML performance tips and tricks: https://katastrophos.net/harmattan-dev/html/guide/html/Developer_Library_Best_practices_for_application_development_QML_performance_tips_and_tricks.html
QtObject: https://doc.qt.io/qt-5/qml-qtqml-qtobject.html

QMl QtControls.Slider returns value with decimal points

I am using a Slider with below properties set for it -
Item {
id: root
signal sliderMoved
property alias value: control.value
QtControls.Slider {
orientation: Qt.Vertical
snapMode: QtControls.Slider.SnapOnRelease
from: 59.0
to: 86.0
stepSize : 1.0
onMoved: root.sliderMoved()
}
}
From user QML where this slider is used thru loader
Connections {
target: sliderLoader.item
onSliderMoved: {
console.warn(sliderLoader.item.value);
}
}
But eachtime when the slider is dragged, it returns the value with decimal points. I am expecting value like 59,60,61, 62.....till 86 not like 59.12, 63.45 etc. How to get rid of these decimal points from the returned slider value when the slider is moved
The reason you are seeing decimals is because your slider snapMode is set to snapOnRelease. This means the slider value will not honor your step size until the user releases the slider. Changing to snapAlways will fix this, but it will cause the slider to appear "choppy" during use. If you would still like to use snapOnRelease; I see two options for you moving forward.
Instead of using onMoved to emit sliderMoved, you may use.
onPressedChanged: {
if (!pressed) {
root.sliderMoved()
}
}
This way the signal will only be emitted once the slider has been released/snapped, and you will log the whole number value the user has decided upon.
If it is necessary to log every change the user makes as you have it written currently, round up or down as desired in your log statement to prevent decimals in the logs.

Binding Type: Is it possible to listen to a temporarily overridden binding?

I'd like to create a widget that has a value that can be bound to a value outside of itself, but can also internally set this value. A scenario I'd like to be able to support:
Developer using the widget binds the widget's value to some external value
At run-time, widget value follows any external value changes via this binding
User interacts with widget, setting their own value
Some time later, external value is updated
Ideally, widget value would then return to being bound to the external value
Point 5 does not seem possible when using only bindings. Here is an example where 'textItem' is our imaginary widget:
import QtQuick 2.7
import QtQuick.Controls 2.0
ApplicationWindow {
visible: true
width: 640
height: 480
property real externalValue: (Math.random()* 50).toFixed(0)
Timer {
running: true
repeat: true
interval: 1000
onTriggered: {
// Simulate externalValue changes out of our control
externalValue = (Math.random()* 50).toFixed(0)
}
}
Component.onCompleted: {
// Unknown external binding set by developer using textItem widget
textItem.text = Qt.binding(function(){return externalValue})
}
Column {
Text {
id: textItem
text: ""
property real internalValue: (Math.random()* 50).toFixed(0)
Binding on text {
id: binding
value: textItem.internalValue
when: false
}
}
Button {
text: "Change Internal Value"
onClicked: {
textItem.internalValue = (Math.random()* 50).toFixed(0)
binding.when = true
}
}
}
}
So textItem.text listens to the externalValue binding until a user interacts with the button, which then binds textItem.text to the user-set internal value. Assuming textEdit is a black-box widget, and it has no concept of externalValue before its runtime binding, is there any way for textEdit to internally listen to the overridden externalValue binding and restore it (by setting binding.when = false) the next time that externalValue is updated by the timer?
One way to make the scenario work would be to use direct assignments in place of all of the bindings, but this seems like it would cause a confusing widget API since I can't stop users from trying to use bindings which would confusingly get broken by the widget's black-box internal assignments...
You can temporarily override bindings using States, like in the code below.
The real problem here is detecting when the external value has changed, in my solution I'm using a Timer for this, but your requirements ask for the external value to change again. Since you are externally binding to property text and also overriding the binding to text you temporarily loose the change signals from the external binding, hence cannot undo the temporary binding.
If you have control over the widget, I would implement a property which should be set externally and internally assign that value to where it should go. This gives you the ability to receive the changes and for example stop the tempBoundedTimer (Since I still think you should have a timer to not indefinitely override the binding in case the external value fails to update).
If you don't have control over the widget, I would settly down on a suitable interval for tempBoundedTimer
(In any case, I don't adding a Timer in each instance of the widget is very nice)
import QtQuick 2.7
import QtQuick.Controls 2.0
ApplicationWindow {
visible: true
width: 640
height: 480
property real externalValue: (Math.random()* 50).toFixed(0)
Timer {
running: true
repeat: true
interval: 1000
onTriggered: {
// Simulate externalValue changes out of our control
externalValue = (Math.random()* 50).toFixed(0)
}
}
Component.onCompleted: {
// Unknown external binding set by developer using textItem widget
textItem.text = Qt.binding(function(){return externalValue})
}
Column {
Text {
id: textItem
text: ""
property real internalValue: (Math.random()* 50).toFixed(0)
Timer {
id: tempBoundedTimer
repeat: false
interval: 2000
}
states: [
State {
when: tempBoundedTimer.running
PropertyChanges {
target: textItem
text: "internal:" + internalValue
}
}
]
}
Button {
text: "Change Internal Value"
onClicked: {
textItem.internalValue = (Math.random()* 50).toFixed(0)
tempBoundedTimer.start()
}
}
}
}
BTW, I think your Binding object should actually also work if the when is bound to tempBoundedTimer.running, but I couldn't get it to play nice. It seems the Qt.binding has priority

QML: is it possible to change the long duration of a mouse Area

QML provides in its MouseArea component a PressAndHold signal, when a mouse area is pressed for a "long duration"
http://doc.qt.io/qt-5/qml-qtquick-mousearea.html#pressAndHold-signal
this duration is set to 800ms, and I find nowhere a way to modify this duration.
Can it be done and if so, how can I do that?
Thanks!
If you will see the MouseArea source (Src/qtdeclarative/src/quick/items/qquickmousearea.cpp) you find this line:
d->pressAndHoldTimer.start(qApp->styleHints()->mousePressAndHoldInterval(), this);
The durations value came from QStyleHints but it's read-only since the value is platform specified. So the answer to your question: "No", if you are not going to change the source.
But you still can emulate this events, for example:
MouseArea {
property int pressAndHoldDuration: 2000
signal myPressAndHold()
anchors.fill: parent
onPressed: {
pressAndHoldTimer.start();
}
onReleased: {
pressAndHoldTimer.stop();
}
onMyPressAndHold: {
console.log("It works!");
}
Timer {
id: pressAndHoldTimer
interval: parent.pressAndHoldDuration
running: false
repeat: false
onTriggered: {
parent.myPressAndHold();
}
}
}
Yes, this can be directly configured with setMousePressAndHoldInterval() (added in November 2015), e.g.:
int pressAndHoldInterval = 2000; // in [ms]
QGuiApplication::styleHints()->setMousePressAndHoldInterval(pressAndHoldInterval);
Put the above at the beginning in your main(), along with
#include <QStyleHints>
and it will globally set the interval as desired.
NOTE #1: As per the Qt bug report, this is a system-wide configuration, so individual MouseArea components cannot be fine-tuned.
NOTE #2: In the source code, the doxygen lists this as \internal so this might be removed/refactored without warning.
Since Qt 5.9, the property pressAndHoldInterval overrides the elapsed time in milliseconds before pressAndHold is emitted.
Documentation
import QtQuick 2.9 // 2.9 or higher
MouseArea {
pressAndHoldInterval: 100 // duration of 100ms
}

Qml timer not triggered in the right interval

I've created a timer QML app, and I'm using Timer qml component.
The interval is set to 1000 milliseconds (the default one)... but it seems to be working properly only when the app is with focus on it.
When I put it in background it seems that it's not triggered every time, and because of that I got some mistakes in the app.
I've tried to find anything related to that in the documentation, but I couldn't
The timer code is really simple:
Timer {
id: timer
repeat: true
onTriggered: {msRemaining -= 1000; Core.secondsToText(type);}
}
Anyone has any idea about that and how to fix it?
Versions:
Qt 5.2
QML 2.0
OS X 10.9
The QML Timer element is synchronized with the animation timer. Since the animation timer is usually set to 60fps, the resolution of Timer will be at best 16ms. You should also note that in Qt Quick 2 the animation timer is synced to the screen refresh (while in Qt Quick 1 it is hard-coded to 16ms). So when your app runs in background i think the refreshing is stopped and consequently your timer which is synced to screen refresh will stop working properly.
If you want to show elapsed time using a timer as you did is not a good idea because it is not precise. You can use javascript Date() function like:
import QtQuick 2.0
Item {
id: root
width: 200; height: 230
property double startTime: 0
property int secondsElapsed: 0
function restartCounter() {
root.startTime = 0;
}
function timeChanged() {
if(root.startTime==0)
{
root.startTime = new Date().getTime(); //returns the number of milliseconds since the epoch (1970-01-01T00:00:00Z);
}
var currentTime = new Date().getTime();
root.secondsElapsed = (currentTime-startTime)/1000;
}
Timer {
id: elapsedTimer
interval: 1000;
running: true;
repeat: true;
onTriggered: root.timeChanged()
}
Text {
id: counterText
text: root.secondsElapsed
}
}
I have a QML app with a Timer object, running on Android:
With Qt 4.8, the Timer works fine when the QML app is in the background.
With Qt 5.4, the Timer no longer works when the QML app is in the background. For example, the QML app can no longer receive onTriggered() signal. When the QML app is brought back to the foreground again, the Timer starts working again. It appears that the Qt signals are blocked while the QML app is in the background.
So this looks like a regression in Qt. And the best solution would be to wait until this regression is fixed.
This the code snippet working for me, It should inside ApplicationWindow{} or Item{} elements.
Timer{
id: closeTimer
interval: 10000
repeat: true
running: true
onTriggered: {
drawer.close()
}
}
Both repeat and running should be true.

Resources