如何在QML中创建延迟函数?

40

我想在JavaScript中创建一个延迟函数,它需要一个延迟的时间参数,这样我就可以在我的QML应用程序中执行JavaScript代码时引入延迟。这个函数可能会像这样:

function delay(delayTime) {
  // code to create delay
}

我需要函数delay()的函数体。请注意,JavaScript中的setTimeout()在QML中不起作用。


2
只需使用计时器元素:http://doc.qt.io/qt-5/qtimer.html - MrEricSir
选择在评论中提供。除此之外,动画框架非常丰富,提供了各种控制动画的方式。深入文档或发布(编辑)问题以解决您的动画需求。 - BaCaRoZzo
2
为什么会有人点踩?我认为这是一个完全合理的问题。 - Marcus Ottosson
@MarcusOttosson 感谢您对我的问题的支持! - Sнаđошƒаӽ
7个回答

48

如评论中对您的问题所建议的,定时器组件是解决此问题的好方法。

function Timer() {
    return Qt.createQmlObject("import QtQuick 2.0; Timer {}", root);
}

timer = new Timer();
timer.interval = 1000;
timer.repeat = true;
timer.triggered.connect(function () {
    print("I'm triggered once every second");
})

timer.start();

这是我的当前用法,这是我可能实现你问题中的示例的方式。

function delay(delayTime) {
    timer = new Timer();
    timer.interval = delayTime;
    timer.repeat = false;
    timer.start();
}

(这并没有做任何事情;请继续阅读)

虽然您希望以一种确切的方式来实现它,这表明您希望它在下一行程序执行之前阻塞。但这不是一个很好的方法,因为它会阻塞程序中的所有其他内容,因为JavaScript只在单个执行线程中运行。

另一种选择是传递回调函数。

function delay(delayTime, cb) {
    timer = new Timer();
    timer.interval = delayTime;
    timer.repeat = false;
    timer.triggered.connect(cb);
    timer.start();
}

这将使您能够像这样使用它。

delay(1000, function() {
    print("I am called one second after I was started.");
});

希望能对你有所帮助!

编辑:以上假定你是在一个单独的JavaScript文件中工作,然后将其导入到QML文件中。要直接在QML文件中执行等效操作,可以这样做。

import QtQuick 2.0

Rectangle {
    width: 800
    height: 600

    color: "brown"

    Timer {
        id: timer
    }

    function delay(delayTime, cb) {
        timer.interval = delayTime;
        timer.repeat = false;
        timer.triggered.connect(cb);
        timer.start();
    }

    Rectangle {
        id: rectangle
        color: "yellow"
        anchors.fill: parent
        anchors.margins: 100
        opacity: 0

        Behavior on opacity {
            NumberAnimation {
                duration: 500
            }
        }
    }

    Component.onCompleted: {
        print("I'm printed right away..")
        delay(1000, function() {
            print("And I'm printed after 1 second!")
            rectangle.opacity = 1
        })
    }
}

我并不确定这是解决你实际问题的方法;如果想要延迟动画,你可以使用PauseAnimation


我也认为Timer是一个不错的解决方案。 - folibis
1
如果您使用此功能超过一次,请小心。请参阅下面Bumsik Kim的答案。我花了很长时间才意识到我的计时器仍然连接着多个函数,而没有意识到这一点。 - DaveK
我认为你需要调用timer.destroy()方法; ... timer.triggered.connect(cb); timer.triggered.connect(function () { timer.destroy(); }); ... - Israel Lins Albuquerque
延迟现在正在冻结 QML 2.15 应用程序。 - Ingo Mi
我刚开始学习JavaScript,因为有一些QML的工作要做。你不应该使用timer = new Timer();代替timer = Timer();吗?new会创建一个新对象并将其注入到Timer中作为this,但是你没有使用它。因此,在这里使用new关键字似乎是多余和令人困惑的。 - Johannes Schaub - litb

19

Marcus的答案解决了问题,但是存在一个大问题

问题在于回调函数即使在触发一次后仍然保持连接到triggered信号。这意味着,如果您再次使用该延迟函数,定时器将再次触发所有先前连接的回调函数。因此,应该在触发后断开回调函数的连接。

这是我增强版的延迟函数:

Timer {
    id: timer
    function setTimeout(cb, delayTime) {
        timer.interval = delayTime;
        timer.repeat = false;
        timer.triggered.connect(cb);
        timer.triggered.connect(function release () {
            timer.triggered.disconnect(cb); // This is important
            timer.triggered.disconnect(release); // This is important as well
        });
        timer.start();
    }
}

...

timer.setTimeout(function(){ console.log("triggered"); }, 1000);

1
我不再使用QML,但你所说的似乎是正确的,所以点赞。 - Sнаđошƒаӽ
1
实际上,你也应该断开这个匿名函数的连接吧?timer.triggered.connect(function() { ... }); - manicaesar
1
@manicaesar 对的,我已经修复了。谢谢你提醒我! - Bumsik Kim

4

这里是另一种变化,它利用Component对象来存储Timer对象。

我们实现了一个类似于setTimeout的函数来动态创建和调用这个Timer对象。

注意:本答案假定使用Qt5.12.x,其中包括ECMAScript 7(因此包括ECMAScript 6),可以利用参数快捷方式、剩余参数和展开语法:

    function setTimeout(func, interval, ...params) {
        return setTimeoutComponent.createObject(app, { func, interval, params} );
    }

    function clearTimeout(timerObj) {
        timerObj.stop();
        timerObj.destroy();
    }

    Component {
        id: setTimeoutComponent
        Timer {
            property var func
            property var params
            running: true
            repeat: false
            onTriggered: {
                func(...params);
                destroy();
            }
        }
    }

在下面的代码片段中,我们将在0-1000ms的随机时间延迟后调用console.log(31)console.log(32)console.log(33)

console.log("Started");
setTimeout(console.log, Math.floor(1000 * Math.random()), 31);
setTimeout(console.log, Math.floor(1000 * Math.random()), 32);
setTimeout(console.log, Math.floor(1000 * Math.random()), 33);

另请参阅:https://community.esri.com/groups/appstudio/blog/2019/05/22/ecmascript-7-settimeout-and-arrow-functions


这篇文章涉及到IT技术,主要讲解ECMAScript 7中的SetTimeout和箭头函数。希望该文章能够为您提供帮助,谢谢!

2
除了在 setTimeoutComponent.createObject(app, …) 中提到的 app 引用之外,这段代码写得很好。在这里传递 null 是安全的(参见)。由于对象是自我销毁的,所以垃圾回收也不是问题。 - tanius
至于QML 6,@tanius的更正只能正常工作。 - undefined

1

这是我对之前答案https://dev59.com/7F4b5IYBdhLWcg3w9FkI#62051450https://dev59.com/7F4b5IYBdhLWcg3w9FkI#50224584的持续演变。

将此文件/组件添加到您的项目中:

Scheduler.qml

import QtQuick 2.0

Timer {
    id: timer

    property var _cbFunc: null
    property int _asyncTimeout: 250

    // Execute the callback asynchonously (ommiting a specific delay time)
    function async( cbFunc )
    { delay( cbFunc, _asyncTimeout ) }

    // Start the timer and execute the provided callback ONCE after X ms
    function delay( cbFunc, milliseconds )
    { _start( cbFunc, milliseconds, false ) }

    // Start the timer and execute the provided callback repeatedly every X ms
    function periodic( cbFunc, milliseconds )
    { _start( cbFunc, milliseconds, true ) }

    function _start( cbFunc, milliseconds, isRepeat ) {
        if( cbFunc === null ) return
        cancel()
        _cbFunc        = cbFunc
        timer.interval = milliseconds
        timer.repeat   = isRepeat
        timer.triggered.connect( cbFunc )
        timer.start()
    }

    // Stop the timer and unregister the cbFunc
    function cancel() {
        if( _cbFunc === null ) return
        timer.stop()
        timer.triggered.disconnect( _cbFunc )
        _cbFunc = null
    }
}

然后,在另一个组件中实现,例如:
...
Scheduler { id: scheduler; }
scheduler.delay( function(){ console.log('Delayed'); }, 3000 );

你可以像这里展示的那样使用匿名函数,或者回调到命名函数中。如果您不太关心确切的时间,可以使用简单的async以非阻塞方式启动代码。请注意,虽然使用0毫秒超时作为“异步”回调(就像使用C ++ QTimer一样)很诱人,但这不是QML Timer的正确方法!这些计时器似乎不会在事件循环上排队事件,其中屏幕重绘被赋予优先级。因此,如果您的目标是推迟给定操作以首先实现“即时”UI更改,则需要按照此处所示的方式增加延迟间隔。使用0ms通常会导致代码在重绘之前触发。
请注意,这些“Scheduler”实例之一只能绑定到一个回调函数,在一个给定的时间间隔内,一次只能绑定一个。如果您需要“重叠”延迟事件,则需要多个实例。

1
Bumsik Kim的答案很棒,这个答案稍微修改了一下,使得定时器可以循环使用,需要时停止并重新使用。
在需要的地方添加定时器的QML代码。
// Allow outside access (optional)
property alias timer: timer

Timer {
    id: timer

    // Start the timer and execute the provided callback on every X milliseconds
    function startTimer(callback, milliseconds) {
        timer.interval = milliseconds;
        timer.repeat = true;
        timer.triggered.connect(callback);
        timer.start();
    }

    // Stop the timer and unregister the callback
    function stopTimer(callback) {
        timer.stop();
        timer.triggered.disconnect(callback);
    }
}

这可以按如下方式使用。
timer.startTimer(Foo, 1000); // Run Foo every 1 second
timer.stopTimer(Foo); // Stop running Foo

timer.startTimer(Bar, 2000); // Run Bar every 2 seconds
timer.stopTimer(Bar); // Stop running Bar

function Foo() {
    console.log('Executed Foo');
}

function Bar() {
    console.log('Executed Bar');
}

0

你可以使用 QtTest

import QtTest 1.0
import QtQuick 2.9

ApplicationWindow{
    id: window

    TestEvent {
        id: test
    }

    function delay_ms(delay_time) {
        test.mouseClick(window, 0, 0, Qt.NoButton, Qt.NoModifier, delay_time)
    }
}

其实是个相当不错的计时器,不知道为什么没有更多的点赞。 - Ingo Mi

0

这应该足够了:

void QmlUtils::singleShot(int msec, QJSValue callback)
{
    QTimer::singleShot(msec, this, [callback] () mutable {
        if (callback.isCallable())
            callback.call();
    });
}

然后在 QML 中调用它,无论你在哪里:

qmlUtils.singleShot(5000, () => console.log("Hello!"))

完成。

如果你想的话,你甚至可以不用写this就使用它。只需通过以下方式将其暴露给QML:

ctx->setContextProperty("lqtUtils", new lqt::QmlUtils(qApp));

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接