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

Connect signal to slot, but call the slot only once and then auto-disconnect them

考虑这个 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
);

近乎重复:Automatically disconnect after first signal emission - 但那个问题是关于 Python 而我的问题是关于 QML 和 C++。

我在这里找到了 C++ 的答案,虽然它有点不优雅:

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

来自 link 的代码:

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

我仍在等待更好的 C++ 答案和 QML 答案。

您可以创建一个小的辅助函数,它会为您断开连接,如下所示:

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

上面的两个 Button 将以单发模式将 slotslot2 连接到信号 action。 Button 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)
}

制作一个实现与常规 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。它不像我希望的那样简洁,但它确实有效。