将信号连接到插槽,但只调用一次插槽,然后自动断开它们
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 将以单发模式将 slot
和 slot2
连接到信号 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
。它不像我希望的那样简洁,但它确实有效。
考虑这个 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 将以单发模式将 slot
和 slot2
连接到信号 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
。它不像我希望的那样简洁,但它确实有效。