c – Qt:如何为所有小部件和小部件类型(通过虚拟基类槽)实现通用的基类信号/插槽功能?
作者:互联网
我想从基类小部件派生我的所有小部件,该小部件自动在类的插槽和(很少调用的)信号之间建立信号/插槽连接.
插槽是一个虚拟功能,因此我希望实现自定义功能的任何小部件都可以从虚拟插槽功能派生.在所需的场景中,我的所有小部件都将从具有虚拟插槽的基类派生,因此默认情况下,我的所有小部件实例都将连接到所需的信号,并为该对象定义一个插槽(具有基类的默认行为) ).
我知道Qt允许虚拟插槽.但是,不支持从两个QObject类派生,因此,例如,不允许使用以下代码:
class MySignaler : public QObject
{
Q_OBJECT
public:
MySignaler : QObject(null_ptr) {}
signals:
void MySignal();
}
MySignaler signaler;
class MyBaseWidget: public QObject
{
Q_OBJECT
public:
MyBaseWidget() : QObject(null_ptr)
{
connect(&signaler, SIGNAL(MySignal()), this, SLOT(MySlot()));
}
public slots:
virtual void MySlot()
{
// Default behavior here
}
}
// Not allowed!
// Cannot derive from two different QObject-derived base classes.
// How to gain functionality of both QTabWidget and the MyBaseWidget base class?
class MyTabWidget : public QTabWidget, public MyBaseWidget
{
Q_OBJECT
public slots:
void MySlot()
{
// Decide to handle the signal for custom behavior
}
}
如示例代码所示,似乎不可能同时获得(在此示例中)QTabWidget的好处,以及从所需信号功能到虚拟插槽功能的自动连接.
在Qt中,是否有某种方法让我的所有应用程序的窗口小部件类共享公共基类槽和connect()功能,同时允许我的窗口小部件从Qt窗口小部件类派生,例如QTabWidget,QMainWindow等?
解决方法:
有时,当继承存在问题时,可以用组合替换它或其中的一部分.
这是Qt 4中所需的方法:而不是从QObject派生,而是从非QObject类(MyObjectShared)派生,该类携带辅助QObject,该辅助QObject用作将信号连接到其插槽的代理;帮助程序转发调用非QObject类.
在Qt 5中,根本不需要从QObject派生:信号可以连接到任意仿函数. MyObjectShared类保持不变.
如果Qt 4兼容性在代码的其他区域通常是有用的,那么可以使用通用连接函数将信号连接到Qt 4和Qt 5中的仿函数(在Qt 4中,它将使用隐式辅助QObject).
// https://github.com/KubaO/stackoverflown/tree/master/questions/main.cpp
#include <QtCore>
#include <functional>
#include <type_traits>
class MySignaler : public QObject {
Q_OBJECT
public:
Q_SIGNAL void mySignal();
} signaler;
#if QT_VERSION < 0x050000
class MyObjectShared;
class MyObjectHelper : public QObject {
Q_OBJECT
MyObjectShared *m_object;
void (MyObjectShared::*m_slot)();
public:
MyObjectHelper(MyObjectShared *object, void (MyObjectShared::*slot)())
: m_object(object), m_slot(slot) {
QObject::connect(&signaler, SIGNAL(mySignal()), this, SLOT(slot()));
}
Q_SLOT void slot() { (m_object->*m_slot)(); }
};
#endif
class MyObjectShared {
Q_DISABLE_COPY(MyObjectShared)
#if QT_VERSION < 0x050000
MyObjectHelper helper;
public:
template <typename Derived>
MyObjectShared(Derived *derived) : helper(derived, &MyObjectShared::mySlot) {}
#else
public:
template <typename Derived, typename = typename std::enable_if<
std::is_base_of<MyObjectShared, Derived>::value>::type>
MyObjectShared(Derived *derived) {
QObject::connect(&signaler, &MySignaler::mySignal,
std::bind(&MyObjectShared::mySlot, derived));
}
#endif
bool baseSlotCalled = false;
virtual void mySlot() { baseSlotCalled = true; }
};
class MyObject : public QObject, public MyObjectShared {
Q_OBJECT
public:
MyObject(QObject *parent = nullptr) : QObject(parent), MyObjectShared(this) {}
// optional, needed only in this immediately derived class if you want the slot to be a
// real slot instrumented by Qt
#ifdef Q_MOC_RUN
void mySlot();
#endif
};
class MyDerived : public MyObject {
public:
bool derivedSlotCalled = false;
void mySlot() override { derivedSlotCalled = true; }
};
void test1() {
MyObject base;
MyDerived derived;
Q_ASSERT(!base.baseSlotCalled);
Q_ASSERT(!derived.baseSlotCalled && !derived.derivedSlotCalled);
signaler.mySignal();
Q_ASSERT(base.baseSlotCalled);
Q_ASSERT(!derived.baseSlotCalled && derived.derivedSlotCalled);
}
int main(int argc, char *argv[]) {
test1();
QCoreApplication app(argc, argv);
test1();
return 0;
}
#include "main.moc"
要在两个QObject之间共享一些代码,您可以将QObject作为类的成员,这是一个插入的非对象类,它使用仅在基类型上进行参数化的泛型类.泛型类可以有插槽和信号.它们必须仅在直接派生类中对moc可见 – 而不是在任何进一步派生的类中.
唉,你通常无法在类的构造函数中连接任何泛型类的信号或槽,因为此时派生类尚未构造,并且其元数据不可用 – 从Qt的角度来看,信号和槽不存在.因此,Qt 4样式的运行时检查连接将失败.
编译时检查的连接甚至不会编译,因为它所使用的this指针具有不正确的编译时类型,并且您对派生类的类型一无所知.
Qt-4样式连接的解决方法只是具有派生构造函数必须调用的doConnections方法,在该方法中建立连接.
因此,让我们在基类和派生类上使泛型类参数化 – 后者称为Curiously Recurring Template Pattern,或简称为CRTP.
现在您可以访问派生类的类型,并可以使用辅助函数将其转换为指向派生类的指针,并在Qt 5样式的编译时检查连接中使用它.
仍需要从doConnections调用Qt 4样式的运行时检查连接.所以,如果你使用Qt 5,那不是问题.你不应该在Qt 5代码中使用Qt 4样式的连接.
槽需要稍微不同的处理,这取决于直接派生自泛型类的类是否覆盖它们.
如果一个槽是虚拟的并且在直接派生类中有一个实现,你应该以正常方式将它暴露给moc – 使用槽部分或Q_SLOT宏.
如果一个槽在直接派生类中没有实现(无论是否为虚拟),那么它在泛型类中的实现应该只对moc可见,而不是对编译器可见 – 你不希望覆盖它,毕竟.因此,插槽声明包含在#ifdef Q_MOC_RUN块中,该块仅在moc读取代码时才有效.生成的代码将引用插槽的通用实现.
由于我们希望确保这确实有效,我们将添加一些布尔值以跟踪是否调用了插槽.
// main.cpp
#include <QtWidgets>
template <class Base, class Derived> class MyGenericView : public Base {
inline Derived* dthis() { return static_cast<Derived*>(this); }
public:
bool slot1Invoked, slot2Invoked, baseSlot3Invoked;
MyGenericView(QWidget * parent = 0) : Base(parent),
slot1Invoked(false), slot2Invoked(false), baseSlot3Invoked(false)
{
QObject::connect(dthis(), &Derived::mySignal, dthis(), &Derived::mySlot2); // Qt 5 style
QObject::connect(dthis(), &Derived::mySignal, dthis(), &Derived::mySlot3);
}
void doConnections() {
Q_ASSERT(qobject_cast<Derived*>(this)); // we must be of correct type at this point
QObject::connect(this, SIGNAL(mySignal()), SLOT(mySlot1())); // Qt 4 style
}
void mySlot1() { slot1Invoked = true; }
void mySlot2() { slot2Invoked = true; }
virtual void mySlot3() { baseSlot3Invoked = true; }
void emitMySignal() {
emit dthis()->mySignal();
}
};
泛型类使用起来非常简单.请记住在仅moc防护中包装任何非虚拟覆盖的插槽!
还要记住适用于所有Qt代码的一般规则:如果你有一个插槽,它应该只被声明为moc一次.因此,如果您有一个进一步派生自MyTreeWidget或MyTableWidget的类,则您不希望在任何必要的虚拟插槽覆盖前面有Q_SLOT或slot宏.如果存在,它将巧妙地破坏事物.但你绝对想要Q_DECL_OVERRIDE.
如果您使用的是Qt 4,请记得调用doConnections,否则该方法是不必要的.
QTreeWidget和QTableWidget的特定选择完全是任意的,没有意义,不应该被认为意味着这种使用有意义(它可能没有).
class MyTreeWidget : public MyGenericView<QTreeWidget, MyTreeWidget> {
Q_OBJECT
public:
bool slot3Invoked;
MyTreeWidget(QWidget * parent = 0) : MyGenericView(parent), slot3Invoked(false) { doConnections(); }
Q_SIGNAL void mySignal();
#ifdef Q_MOC_RUN // for slots not overridden here
Q_SLOT void mySlot1();
Q_SLOT void mySlot2();
#endif
// visible to the C++ compiler since we override it
Q_SLOT void mySlot3() Q_DECL_OVERRIDE { slot3Invoked = true; }
};
class LaterTreeWidget : public MyTreeWidget {
Q_OBJECT
public:
void mySlot3() Q_DECL_OVERRIDE { } // no Q_SLOT macro - it's already a slot!
};
class MyTableWidget : public MyGenericView<QTreeWidget, MyTableWidget> {
Q_OBJECT
public:
MyTableWidget(QWidget * parent = 0) : MyGenericView(parent) { doConnections(); }
Q_SIGNAL void mySignal();
#ifdef Q_MOC_RUN
Q_SLOT void mySlot1();
Q_SLOT void mySlot2();
Q_SLOT void mySlot3(); // for MOC only since we don't override it
#endif
};
最后,这个小测试案例表明它确实按预期工作.
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MyTreeWidget tree;
MyTableWidget table;
Q_ASSERT(!tree.slot1Invoked && !tree.slot2Invoked && !tree.slot3Invoked);
emit tree.mySignal();
Q_ASSERT(tree.slot1Invoked && tree.slot2Invoked && tree.slot3Invoked);
Q_ASSERT(!table.slot1Invoked && !table.slot2Invoked && !table.baseSlot3Invoked);
emit table.mySignal();
Q_ASSERT(table.slot1Invoked && table.slot2Invoked && table.baseSlot3Invoked);
return 0;
}
#include "main.moc"
此方法为您提供以下内容:
>公共代码类派生自基类,因此可以轻松调用或覆盖基类的行为.在此特定示例中,您可以重新实现QAbstractItemView方法等.
>完全支持信号和插槽.即使信号和槽在派生类的元数据中声明为这样,您仍然可以在泛型类中使用它们.
标签:c,qt,signals-slots 来源: https://codeday.me/bug/20191006/1858326.html