Qml timer not triggered in the right interval - qt

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.

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 how force update / redraw

In my application I have a process which is started by clicking on a button. The button already has a property to indicate when it is busy, this changes the colours to reflect the busy state. When the process is finished the busy property is set back to false.
The problem is that although the steps are:
button.busy = true
... Do something ...
button.busy = false
In reality the button does not change to reflect the busy state until the process is almost complete, it then changes back to non-busy.
Is there anyway I can insert something after setting the busy state to true and before doing something to get the GUI to update and reflect the state?
My button QML:
Button {
id: root
property bool busy: false
property bool showDropShadow: true
property color bottomColour: MerlinStyle.greenButtonBottom
property color iconColour: "white"
property color topColour: MerlinStyle.greenButtonTop
property string icon: ""
opacity: (pressed || !enabled) ? 0.5 : 1.0
onBusyChanged: {
//Set the colours according to busy state
if ( root.busy == true ) {
root.bottomColour = MerlinStyle.indicatorOrange;
root.topColour = MerlinStyle.indicatorOrange;
} else {
root.bottomColour = MerlinStyle.greenButtonBottom;
root.topColour = MerlinStyle.greenButtonTop;
}
}
background: Item {
RadiusRectangle {
id: rect
anchors.fill: parent
radius: MerlinStyle.rectRadius
topLeftPointed: true
gradient: Gradient {
GradientStop { position: 0.0; color: root.topColour }
GradientStop { position: 1.0; color: root.bottomColour }
}
}
DropShadow {
visible: showDropShadow && !pressed && enabled
anchors.fill: rect
horizontalOffset: 1
verticalOffset: 2
color: "#80000000"
source: rect
}
}
contentItem: Item {
ColoredImage {
anchors.centerIn: parent
height: parent.height * 0.85
width: parent.width * 0.85
source: icon
color: root.iconColour
}
}
}
I've tried to trigger an update using:
idOfButton.update
This always results in :
Button_QMLTYPE_28 : Update called for a item without content
The update function takes no parameters.
When you call that function, it simply blocks the GUI thread, and those events which have been put to the event queue will wait until the program returns to the event loop again. That's why you cannot see that the button is updated based on the property changes.
This happens because of bad design. As per Qt documentation:
use asynchronous, event-driven programming wherever possible
use worker threads to do significant processing
never manually spin the event loop
never spend more than a couple of milliseconds per frame within blocking functions
You should not call a blocking function from within the GUI thread. You need to run that function from another thread or if you have a purpose to do that, you can call your function with a Timer which is a dirty hack.
Timer{
id: dummyTimer
interval:1
repeat: false
running: false
onTriggered: {
control.someLazyBlockingFunction();
idOfButton.busy = false;
}
}
Button{
id: anotherButton
...
onClicked:{
idOfButton.busy = true;
dummyTimer.running= true;
}
}
Late answer, but might help someone.
In my case, my application is running on python (PyQt5 + QML), the event comes from QML code, and is handled in a slot (nothing new here).
The thing is, as stated in the qt documentation, you cannot block the main thread, so, the best way I found to handle it was spinning a new daemon thread to run the event code without hanging the frontend.
#pyqtslot(name="doMyButtonCallback")
def do_mybuttoncallback():
def mybuttonwork():
time.sleep(3)
print("i did not hang your code!"
t=threading.Thread(target=mybuttonwork)
t.daemon=True
t.start()

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

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
}
}

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
}

Unable to loop video using QML MediaPlayer

I am trying to create a simple video player that just plays a specified video on loop. While the video plays as expected, it does not loop.
The following is the code I am using:
import QtQuick 2.0
import QtMultimedia 5.0
Rectangle
{
width : 320
height : 240
signal buttonPressed(string msg)
property string currentVideo
function playVideo(videoName)
{
currentVideo = videoName
videoPlayer.source = videoName
videoPlayer.seek(1)
videoPlayer.play()
}
function loopVideo()
{
if(videoPlayer.duration === 0)
{
playVideo(currentVideo)
}
}
function stopVideoPlayback()
{
videoPlayer.stop()
}
MediaPlayer {
id: videoPlayer
source: ""
autoPlay: false
autoLoad: false
loops: 100
}
VideoOutput {
id: videoOutput
source: videoPlayer
anchors.fill: parent
visible: true
}
}
I call playVideo from C++. It starts playing as expected. However, once it completes, the frame freezes on the last one. I tried looping it by calling the loopVideo function in a QTimer. That does not work either.
What might I be doing wrong?
Your code is ok. (slight tip: you might want to use MediaPlayer.Infinite instead 100 for looping)
I believe that your situation is same like mine.
I have played a little with MediaPlayer component and at my end I am unable to seek video because seekable is always false. And seekable is false because somehow QML uses my file as live stream, and that results duration property to be 0.
Also note that onPaused and onStopped are never triggered and position is just increasing after end of video (live stream never ends).
Now I think that this is related to looping, because basically looping seeks back to 0. Because there is no duration (MediaPlayer thinks it is live stream) it cannot seek (and loop).
One nasty workaround that I found is this (append to your code):
Rectangle {
id: root
//...
MediaPlayer {
//...
onPositionChanged: {
if (position > VIDEO_LENGTH) {
root.stopVideoPlayback()
root.playVideo(root.currentVideo)
}
}
}
}
Where VIDEO_LENGTH is length of your video file in milliseconds.
Click here for MediaPlayer element documentation
UPDATE:
It looks like that is bug in Qt for mingw version (closed bug report).
UPDATE 2: I have downloaded MSVC version of Qt and media player works as it is supposed.
So it is bug in Qt for mingw.
Either use this workaround (which I wouldn't recommend) or download MSVC version.
I have created new bug report here.
Using stopped signal try this code :
MediaPlayer {
id: mediaplayer
source: "groovy_video.mp4"
onStopped: play()
}

Resources