将信号连接到槽,但仅调用一次槽,然后自动断开它们的连接。

6

考虑以下JS代码:

function handleSig() {
    emitter.someSig.disconnect(handleSig);
    // do some work here
}

emitter.someSig.connect(handleSig);

是否可以不使用显式断开连接和命名函数来编写代码?

理想情况下,我希望像这样:

emitter.someSig.connect(
    function() {
        // do some work here
    },
    Qt.SingleShotConnection
);

相似度问题: 第一次信号发射后自动断开连接 - 但该问题是关于Python的,而我的问题是关于QML和C++。


调用 disconnect() 真是个问题啊? - folibis
@folibis:1. 然后handleSig名称被复制了三次,2. 阅读起来更困难,3. 无法在connect调用内定义处理程序。 - Stefan Monov
3个回答

7
您可以创建一个小的辅助函数,帮助您进行断开连接,代码如下:
function connectOnce(sig, slot) {
    var f = function() {
        slot.apply(this, arguments)
        sig.disconnect(f)
    }
    sig.connect(f)
}

作为使用示例:
import QtQuick 2.7
import QtQuick.Controls 2.0

ApplicationWindow {
    id: myWindow
    visible: true
    width: 600
    height: 600
    color: 'white'

    signal action(string name)

    function slot(name) {
        console.log(name)
    }

    Button {
        text: 'connect'
        onClicked: {
            connectOnce(action, slot)
        }
    }

    Button {
        y: 80
        text: 'action'
        onClicked: {
            action('test')
        }
    }

    function connectOnce(sig, slot) {
        var f = function() {
            slot.apply(this, arguments)
            sig.disconnect(f)
        }
        sig.connect(f)
    }
}

上面的两个按钮将以单次模式连接 slot 和 slot2 到信号 action。 按钮 action 会触发信号 action,该信号将执行与其连接的插槽相同次数的操作,然后它们将立即断开连接。
您可以将函数 connectOnce 放入库中,以便在需要时随时使用。
通过在闭包中引入计数器,可以将此解决方案轻松扩展为更一般的形式,该形式将连接要执行 n 次的函数。
function connectN(sig, slot, n) {
    if (n <= 0) return
    var f = function() {
        slot.apply(this, arguments)
        n--
        if (n <= 0) sig.disconnect(f)
    }
    sig.connect(f)
}

为什么我没想到这个! :) - Stefan Monov

6

我在这里找到了一个关于C++的答案,尽管有点不太优雅:

https://forum.qt.io/topic/67272/how-to-create-a-single-shot-one-time-connection-to-a-lambda/2

来自该链接的代码:

QMetaObject::Connection * const connection = new QMetaObject::Connection;
*connection = connect(_textFadeOutAnimation, &QPropertyAnimation::finished, [this, text, connection](){
    QObject::disconnect(*connection);
    delete connection;
});

我仍然期待更好的C++答案和QML答案。


3
你可以使用shared_ptr来管理连接,这样就不需要手动delete,因为生命周期在捕获子句中被管理。 - yano

0

最方便的方法可能是创建一个模板,实现与常规 QObject::connect 调用相同的语法。

我使用 Stefan Monov 的答案制作了以下两个模板。第一个基于方法指针,第二个基于 Lambda 表达式。

#ifndef  CONNECT_ONCE_H
# define CONNECT_ONCE_H

# include <QObject>
# include <memory>

template<typename EMITTER, typename SIGNAL, typename RECEIVER, typename... ARGS>
void connectOnce(EMITTER* emitter, SIGNAL signal, RECEIVER* receiver, void (RECEIVER::*slot)(ARGS...), Qt::ConnectionType connectionType = Qt::AutoConnection)
{
  auto connection = std::make_shared<QMetaObject::Connection>();
  auto onTriggered = [connection, receiver, slot](ARGS... arguments){
    (receiver->*slot)(arguments...);
    QObject::disconnect(*connection);
  };

  *connection = QObject::connect(emitter, signal, receiver, onTriggered, connectionType);
}

template<typename EMITTER, typename SIGNAL, typename RECEIVER, typename SLOT, typename... ARGS>
void connectOnce(EMITTER* emitter, SIGNAL signal, RECEIVER* receiver, SLOT slot, Qt::ConnectionType connectionType = Qt::AutoConnection)
{
  std::function<void (ARGS...)> callback = slot;
  auto connection = std::make_shared<QMetaObject::Connection>();
  auto onTriggered = [connection, callback](ARGS... arguments) {
    callback(arguments...);
    QObject::disconnect(*connection);
  };

  *connection = QObject::connect(emitter, signal, receiver, onTriggered, connectionType);
}

#endif

关于基于 lambda 的模板,当我直接声明 std::function 作为参数时,我无法将其编译,因为编译器无法将 lambda 参数识别为有效的候选项...这就是为什么我将 SLOT 声明为模板参数,然后将其转换为 std::function。虽然不像我想象的那样简洁,但它能够工作。

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