Following case:
Let's say there is a binary library which defines the class "Base", and many subclasses ("Derivative1", "Derivative2" etc) of it.
I want to extend these subclasses in my own code, but because my extensions are the same for all subclasses as they only deal with parts of Base, it would be tedious to subclass every Derivative class and add the same code over and over again.
My first idea was to simply write a class template which would do the work for me, but because the library I'm dealing with is Qt, QObject has foiled me.
My second idea was to use macros to generate each class structure, but this was also thwarted by moc.
The "reparent" in the title is because I thought of deriving from Base and creating BaseExtended, and then somehow tell the compiler to reparent every Derivative to this extended class. Isn't there a way to for example declare the "Base" in "BaseExtended" virtual, and then just write
class Derivative1Extended : public Derivative1, public BaseExtended {}
and have the virtual Base in BaseExtended point to the Base in Derivative1, thus basically "squeezing ing" my extensions between Base and Derivative1?
(By the way, I tried to keep the above as generic as possible, but what I'm actually doing is trying add signals for "focusIn" and "focusOut" to every QWidget derivative without writing the same code over and over again for every QWidget subclass I use)
EDIT:
For reference, here's my current implementation:
// qwidgetfs.h
class QLineEditFS : public QLineEdit
{
Q_OBJECT
private:
void focusInEvent(QFocusEvent *);
void focusOutEvent(QFocusEvent *);
public:
QLineEditFS(QWidget *parent = 0);
signals:
void focusReceived(QWidgetFS::InputType);
void focusLost();
};
// qwidgetfs.cpp
QLineEditFS::QLineEditFS(QWidget *parent /*= 0*/)
: QLineEdit(parent)
{}
void QLineEditFS::focusInEvent(QFocusEvent *e)
{
QLineEdit::focusInEvent(e);
emit focusReceived(QWidgetFS::InputText);
}
void QLineEditFS::focusOutEvent(QFocusEvent *e)
{
QLineEdit::focusOutEvent(e);
emit focusLost();
}
And this repeated for QSpinBoxFS, QComboBoxFS, QCheckBoxFS and so on...
Instead I would like to just define this logic in a common class QWidgetFS, and then "inject" it into other classes derived from QWidget
I'm not sure you'll really be able to do what you are suggesting without modifying Qt and recompiling it.
Perhaps to do what you want, you could use event filters installed on the objects that you want to handle focus events from?
little test app:
header:
class FocusEventFilter : public QObject
{
Q_OBJECT
public:
FocusEventFilter(QObject* parent)
: QObject(parent)
{}
Q_SIGNALS:
void focusIn(QWidget* obj, QFocusEvent* e);
void focusOut(QWidget* obj, QFocusEvent* e);
protected:
bool eventFilter(QObject *obj, QEvent *e);
};
class testfocus : public QMainWindow
{
Q_OBJECT
public:
testfocus(QWidget *parent = 0, Qt::WFlags flags = 0);
~testfocus();
public Q_SLOTS:
void onFocusIn(QWidget*, QFocusEvent*);
void onFocusOut(QWidget*, QFocusEvent*);
private:
Ui::testfocusClass ui;
};
Implementation
#include <QFocusEvent>
#include "testfocus.h"
bool FocusEventFilter::eventFilter(QObject *obj, QEvent *e)
{
if (e->type() == QEvent::FocusIn) {
bool r = QObject::eventFilter(obj, e);
QFocusEvent *focus = static_cast<QFocusEvent*>(e);
QWidget* w = qobject_cast<QWidget*>(obj);
if (w) {
emit focusIn(w, focus);
}
return r;
}
else if (e->type() == QEvent::FocusOut) {
bool r = QObject::eventFilter(obj, e);
QFocusEvent *focus = static_cast<QFocusEvent*>(e);
QWidget* w = qobject_cast<QWidget*>(obj);
if (w) {
emit focusOut(w, focus);
}
return r;
}
else {
// standard event processing
return QObject::eventFilter(obj, e);
}
}
testfocus::testfocus(QWidget *parent, Qt::WFlags flags)
: QMainWindow(parent, flags)
{
ui.setupUi(this);
FocusEventFilter* filter = new FocusEventFilter(this);
ui.lineEdit->installEventFilter(filter);
ui.lineEdit_2->installEventFilter(filter);
connect(filter, SIGNAL(focusIn(QWidget*, QFocusEvent*)), this, SLOT(onFocusIn(QWidget*, QFocusEvent*)));
connect(filter, SIGNAL(focusOut(QWidget*, QFocusEvent*)), this, SLOT(onFocusOut(QWidget*, QFocusEvent*)));
}
testfocus::~testfocus()
{
}
void testfocus::onFocusIn(QWidget* obj, QFocusEvent*)
{
obj->setStyleSheet("background-color:#aaaaff;");
}
void testfocus::onFocusOut(QWidget* obj, QFocusEvent*)
{
obj->setStyleSheet("background-color:#ffaaaa;");
}
Of course, YMMV. You could always have a separate filter per object. This method means you can avoid deriving from everything. Not as efficient but it should work.
You may be able to do what you want in the event filter itself rather than using signals/slots.
I have done stuff like this in the past with templates. The problem is that you can't use signals.
I'm typing this up without a compiler so please be kind :):
template<typename T>
class FSWidget: public T
{
public:
FSWidget()
{
_delegate = NULL;
}
setDelegate(FSDelegate *delegate)
{
_delegate = delegate;
}
protected:
virtual void focusInEvent(QFocusEvent *e)
{
T::focusInEvent(e);
if (_delegate) {
_delegate->focusInEvent(this);
}
}
virtual void focusOutEvent(QFocusEvent *e)
{
T::focusOutEvent(e);
if (_delegate) {
_delegate->focusOutEvent(this);
}
}
private:
FSDelegate *_delegate;
};
So, the advantage is when you need to use this you can basically create a class like this:
FSWidget<QLineEdit *> lineEdit = new FSWidget<QLineEdit *>;
lineEdit->setDelegate(delegate);
You can put in whatever you want instead of QLineEdit and it will work.
And then teh FSDelegate could be just an interface that you mix into whatever class needs to act on the info. It could be one of these:
class FSDelegate
{
public:
virtual void focusInEvent(QWidget *w) = 0;
virtual void focusOutEvent(QWidget *w) = 0;
};
If you're always doing the same thing in on focusInEvent and focusOutEvents, you can implement these functions and make a real Mixin class.
Hopefully this can avoid some code duplication for you.
Related
I have the following code:
void AppMPhase::closeEvent(QCloseEvent *closeEvent) {
QMessageBox* dialog = new QMessageBox(this);
dialog->setText("Warning: Initial configuration changed\nDo you want to restore it ?");
dialog->setIcon(QMessageBox::Warning);
dialog->addButton(QMessageBox::Yes);
dialog->addButton(QMessageBox::No);
connect(dialog, SIGNAL(buttonClicked(QAbstractButton*)), this, SLOT(restoreInitialConfig(QAbstractButton*)));
dialog->exec();
}
void AppMPhase::restoreInitialConfig(QAbstractButton *button) {
if(!button->text().compare(QString("Yes"))) {
// restore Config
}
else {
// don't restore
}
MainWindow::closeEvent(closeEvent);
}
with AppMPhase the derived class of MainWindow
I would like to call the method closeEvent of the base class MainWindow in the "restoreInitialConfig" method, to close the window after we restore the config.
Is it possible ? it's different from the other questions I have seen because the method closeEvent is overriden in the AppMPhase class.
the AppMPhase class:
class AppMPhase : public QtUi::MainWindow
{
Q_OBJECT
public:
AppMPhase(QWidget *const parent = Q_NULLPTR, const Qt::WindowFlags flags = 0);
~AppMPhase();
int readConfigFromExternFile(QString path);
int readCalibrationDate(QString path);
QSize sizeHint() const Q_DECL_OVERRIDE;
QtWrapperTestManager * testManager;
public Q_SLOTS:
void show();
public slots:
void currentTestFinished();
void createTest(unsigned int,QString);
void restoreInitialConfig(QAbstractButton* button);
signals:
void notifyPageTestCurrentTestFinished();
private:
enum AppPage
{
PAGE_START,
PAGE_ABOUT
};
bool isTestMangaerCreated;
std::map<QString, std::map<std::string, std::string> > conversion_Table_Cable_Ref_sensorParamMap;
Pages::GenericAppPage * appPage(const int index) const;
QToolButton * appPageButton(const int index) const;
virtual void closeEvent(QCloseEvent *closeEvent) Q_DECL_OVERRIDE;
The MainWindow class:
class SHARED_EXPORT_LIB_QT_UI MainWindow : public QMainWindow
{
Q_OBJECT
signals:
void aboutToClose();
public slots:
virtual void show();
private slots:
void changeLanguage(const QString &language);
public:
MainWindow(QWidget *const parent = Q_NULLPTR, const Qt::WindowFlags flags = 0);
virtual ~MainWindow();
protected:
void showCopyright(const QString ©RightHeader);
void showLanguageSelector(const QStringList &languages);
void setupUi(const MainPageList &pageList);
void setupUi(QWidget *centerWidget = Q_NULLPTR);
virtual void closeEvent(QCloseEvent *closeEvent) Q_DECL_OVERRIDE;
const Ui::MainWindow *const _ui;
MainPageItemList _mainPageItemList;
};
Thanks in advance.
Flo
There is a far easier way to achieve what you're wanting to do, without having to use signals and slots, and call base class functions from your slot.
It is possible to do the restoration directly from within your closeEvent handler.
This is made possible by the fact that QMessageBox::exec returns an integer code which matches one of the values in the StandardButton enum, depending on what button was pressed.
That then allows you to call restoreInitialConfig directly from within your closeEvent handler, as you know which button was pressed.
void AppMPhase::closeEvent(QCloseEvent* ev)
{
int res = QMessageBox(
QMessageBox::Icon::Warning,
"Restore configuration?",
"Warning: Initial configuration changed\nDo you want to restore it?",
QMessageBox::Yes | QMessageBox::No,
this).exec();
if (res == QMessageBox::Yes)
restoreInitialConfig();
MainWindow::closeEvent(ev);
}
Note that this also simplifies your restoreInitialConfig function, as there is no need to check button text, you know the answer was yes.
Note also I made use of this QMessageBox constructor which makes it very easy to create simple message boxes.
I have a class which I'd like to hide using PIMPL type approach. This is because it is a UI form which introduces uic generated header dependancies that I don't want other parts of code to require.
So far I have renamed it PrivateClass, for illustration:
PrivateClass: public QWidget, public Ui_Form
{
Q_OBJECT:
public: // doesn't need to be public but I'm trying to leave as-is apart from name change
PrivateClass(QWidget *parent=0) : QWidget(parent)
{
setupUi(this); // Ui_Form function
}
// etc
void do_something();
};
MyClass: public QWidget
{
Q_OBJECT:
public:
MyClass(QWidget *parent=0) : QWidget(parent)
{
_prvt = new PrivateClass(this); // or pass in parent?
}
~MyClass()
{
delete _prvt;
}
// a bunch of interface functions like this
void do_something(){ _prvt->do_something();}
private:
PrivateClass *_prvt;
};
I am aware that Qt provides a macro based PIMPL implementation, but I'd like to do this manually here, it's not a huge class.
So the question is what to do about the QWidget side of things. To leave PrivateClass unchanged but still allow the new wrapper MyClass to slot in, they both have to be QWidgets. Any QWidget calls on MyClass won't get propagated to _prvt unless I code an interface for QWidget, which doesn't sound right to me.
I have temporarily reconfigured PrivateClass so that it is no longer a QWidget and takes a pointer to MyClass as a constructor argument, any improvements on this?
See this question for an example of how to do it using Qt's PIMPL macros. Since we don't use these macros in the code below, there's some code that has to be written by hand to maintain type safety.
Suppose you started with this class:
Original Interface
#include <QWidget>
#include "ui_widget.h"
class Widget : public QWidget, Ui::Widget {
int m_something;
public:
explicit Widget(QWidget * parent = nullptr);
void do_something();
int something() const;
~Widget();
};
Original Implementation
#include "widget.h"
Widget::Widget(QWidget * parent) :
QWidget{parent},
m_something{44}
{
setupUi(this);
}
void Widget::do_something() {
hide(); // just an example of doing something
}
int Widget::something() const {
return m_something;
}
Widget::~Widget() {}
Can I wrap a QWidget subclass PIMPL style without modifying it
Perhaps. Let's see how it'd work. We can leave Widget alone, treat it as an implementation detail, and "expose" it via a Public interface. We need to use an intervening layout to forward resizing and size constraints from the interface to implementation.
Wrapper Widget Interface
#include <QWidget>
class Widget;
class Public : public QWidget {
Widget * const d_ptr;
Widget * d();
const Widget * d() const;
public:
explicit Public(QWidget * parent = nullptr);
void do_something();
int something() const;
};
Wrapper Widget Implementation
#include "public.h"
#include "widget.h"
Public::Public(QWidget * parent) :
QWidget{parent},
d_ptr{new Widget{this}}
{
auto const layout = new QVBoxLayout{this};
layout->setMargin(0);
}
Widget * Public::d() { return d_ptr.data(); }
const Widget * Public::d() const { return d_ptr.data(); }
void Public::do_something() {
d()->do_something();
}
int Public::something() const {
return d()->something();
}
This has some problems:
You're paying the price of an extra widget and layout instances.
The intervening layout can subtly break the behavior of the enclosed and enclosing widget. Qt layouts aren't perfect; due to their design they suffer from numerically approximate behavior and imperfect forwarding of the encapsulated entities' behavior. Adding additional layers underscores this deficiency.
Instead, you really want to modify the original class. It's much simpler to just PIMPL it and be done with it. If you prepare yourself right, it can be a very mechanical code transformation that will generate a sensible diff.
So, now you want it PIMPL-ed. It will be simplest to push all methods from Widget to WidgetPrivate, and only add forwarder methods to the public interface.
The interface loses all private members, and gets d_ptr and d() added.
PIMPL-ed Interface
#include <QWidget>
class WidgetPrivate;
class Widget : public QWidget {
QScopedPointer<WidgetPrivate> const d_ptr;
WidgetPrivate* d();
const WidgetPrivate* d() const;
public:
explicit Widget(QWidget * parent = nullptr);
void do_something();
int something() const;
~Widget();
};
The PIMPL accesses the QWidget via the q-pointer.
PIMPL-ed Implementation
#include "widget.h"
#include "ui_widget.h"
class WidgetPrivate : public Ui::Widget {
public:
Widget * const q_ptr;
inline Widget * q() { return q_ptr; }
inline const Widget * q() const { return q_ptr; }
int m_something;
WidgetPrivate(Widget * q)
void init();
void do_something();
int something() const;
};
///vvv This section is from "original.cpp" with simple changes:
WidgetPrivate(Widget * q) :
q_ptr{q},
m_something{44}
{}
/// Can only be called after the QWidget is fully constructed.
void WidgetPrivate::init() {
auto const q = q(); // Widget * - we can use a local `q`
// to save on typing parentheses
setupUi(q);
}
void WidgetPrivate::do_something() {
q()->hide(); // we can use q() directly
}
int WidgetPrivate::something() const {
return m_something;
}
///^^^
WidgetPrivate * Widget::d() { return d_ptr.data(); }
const WidgetPrivate* Widget::d() const { return d_ptr.data(); }
Widget::Widget(QWidget * parent) :
QWidget{parent},
d_ptr{new WidgetPrivate{this}}
{
d()->init();
}
void Widget::do_something() {
d()->do_something();
}
int Widget::something() const {
return d()->something();
}
// If the compiler-generated destructor doesn't work then most likely
// the design is broken.
Widget::~Widget() {}
The d() and q() methods are needed to return the const-correct PIMPL and Q when accessed from const methods.
After you check in this rather mechanical change into your version control system, you can then optionally get rid of trivial methods from the PIMPL and move them into the interface:
Reworked PIMPL-ed Implementation
#include "widget.h"
#include "ui_widget.h"
class WidgetPrivate : public Ui::Widget {
public:
Widget * const q_ptr;
inline Widget * q() { return q_ptr; }
inline const Widget * q() const { return q_ptr; }
int m_something;
WidgetPrivate(Widget * q) : q_ptr{q} {}
void init();
};
WidgetPrivate * Widget::d() { return d_ptr.data(); }
const WidgetPrivate* Widget::d() const { return d_ptr.data(); }
WidgetPrivate(Widget * q) :
q_ptr{q},
m_something{44}
{}
/// Can only be called after the QWidget is fully constructed.
void WidgetPrivate::init() {
setupUi(q());
// let's pretend this was a very long method that would have
// much indirection via `d->` if it was moved to `Widget`'s constructor
}
void Widget::do_something() {
hide();
}
int Widget::something() const {
return d()->m_something;
}
Widget::Widget(QWidget * parent) :
QWidget{parent},
d_ptr{new WidgetPrivate{this}}
{
d()->init();
}
Widget::~Widget() {}
I've a C++ library. This library uses the publish-subscriber pattern.
namespace mylib {
typedef unsigned MyLibType;
class IEvent
{
virutal ~IEvent() {}
virtual void event1() = 0;
virtual void event2(MyLibType) = 0;
};
class IMyClass
{
public:
virtual ~IMyClass() {}
// operations
};
} // mylib
// C interface
int CreateMyClass( mylib::IMyClass** class, mylib::IEvent* listener );
// implementation
class MyClass : public IMyClass { // ...
When MyClass is create uses a thread that generate some events.
Now on Qt:
class QtMyApplication : public QMainWindow, public mylib::IEvent
{
Q_OBJECT
public:
explicit QtMyApplication(QWidget *parent = 0);
~QtMyApplication();
signals:
void MyLibEvent1();
void MyLibEvent2(mylib::MyLibType);
private slots:
void OnMyLibEvent1();
void OnMyLibEvent2(mylib::MyLibType);
private: // IEvent interface
void event1()
{
emit MyLibEvent1();
}
void event2(mylib::MyLibType i)
{
emit OnMyLibEvent2( i );
}
private:
Ui::QtMyApplication* ui;
mylib::IMyLib mMyLib;
};
// implementation
QtMyApplication::QtMyApplication( QWidget *parent ) :
QMainWindow(parent),
ui(new Ui::QtMyApplication)
{
ui->setupUi( this );
CreateMyLib( &mMyLib, this );
connect( this, SIGNAL(MyLibEvent1()),
this, SLOT(OnMyLibEvent1()) );
connect( this, SIGNAL(MyLibEvent2(mylib::MyLibType)),
this, SLOT(OnMyLibEvent2(mylib::MyLibType)) );
}
When I run the application event2() is called inside QtMyApplication but OnMyLibEvent2 is never called, instead event1() is called and OnMyLibEvent1 is called too.
What am I wrong? Is the argument parameter the issue?
edit 1
I discovered that the connect has some restriction on params.... because if I change MyLibEvent2(mylib::MyLibType) in MyLibEvent2(int) all works properly...
You need to register your datatype to QTs metatype system to make it work:
qRegisterMetaType<mylib::MyLibType>();
QTs metatype system does not know about typedefs, and therefore does not know how to transfer a mylib::MyLibType.
I'm not exactly shure if you need Q_DECLARE_METATYPE(mylib::MyLibType) too, try it if you have no success.
If you use QT5s new connection syntax then it might also work without any additonal effort.
connect( this, &QtMyApplication::MyLibEvent2,
this, &QtMyApplication::OnMyLibEvent2);
The problem is that the old connect syntax is working around the metadata system and string comparisons and is fairly fragile. It also won't work if you decide to update your slot to void OnMyLibEvent2(::mylib::MyLibType)
I would like to derive all of my widgets from a base class widget that automatically establishes a signal/slot connection between a slot for the class and a (rarely called) signal.
The slot is a virtual function, so that any widgets for which I wish to implement custom functionality can derive from the virtual slot function. In the desired scenario, all my widgets would derive from this base class with the virtual slot, so that by default all of my widget instances would be connected to the desired signal with a slot defined for the object (with default behavior from the base class).
I know that virtual slots are allowed in Qt. However, deriving from two QObject classes is not supported, so that, for example, the following code is disallowed:
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
}
}
As the sample code demonstrates, it seems impossible to gain both the benefits of (in this example) the QTabWidget, and also the automatic connection from the desired signal function to the virtual slot function.
Is there some way, in Qt, to have all my application's widget classes share common base-class slot and connect() functionality while allowing my widgets to nonetheless derive from Qt widget classes such as QTabWidget, QMainWindow, etc.?
Sometimes when inheritance is problematic, one can replace it, or a part of it, with composition.
That's the approach needed in Qt 4: instead of deriving from a QObject, derive from a non-QObject class (MyObjectShared) that carries a helper QObject that is used as a proxy to connect the signal to its slot; the helper forwards that call to the non-QObject class.
In Qt 5, it is not necessary to derive from a QObject at all: signals can be connected to arbitrary functors. The MyObjectShared class remains the same.
Should Qt 4 compatibility be generally useful in other areas of the code, one can use a generic connect function that connects signals to functors in both Qt 4 and Qt 5 (in Qt 4, it would use an implicit helper 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"
To share some code between two QObjects, you could have the QObject as a member of the class,an interposing non-object class that uses generic class that's parametrized only on the base type. The generic class can have slots and signals. They must be made visible to moc only in the immediately derived class - and not in any further derived ones.
Alas, you generally cannot connect any of the generic class's signals or slots in the constructor of the class, since at that point the derived class isn't constructed yet, and its metadata isn't available - from Qt's perspective, the signals and slots don't exist as such. So the Qt 4-style runtime-checked connect will fail.
The compile-time-checked connect will not even compile, because the this pointer it works on has an incorrect compile-time type, and you know nothing about the type of the derived class.
A workaround for Qt-4 style connect only is to have a doConnections method that the derived constructor has to call, where the connections are made.
Thus, let's make the generic class parametric on the base and the derived class as well - the latter is known as the Curiously Recurring Template Pattern, or CRTP for short.
Now you have access to the derived class's type, and can use a helper function to convert this to a pointer to the derived class, and use it in the Qt 5-style compile-time-checked connects.
The Qt 4-style runtime checked connect still needs to be invoked from doConnections. So,if you use Qt 5, that's not an issue. You shouldn't be using Qt 4-style connect in Qt 5 code anyway.
The slots require slightly different treatment depending on whether the class immediately derived from the generic class overrides them or not.
If a slot is virtual and has an implementation in the immediately derived class, you should expose it to moc in the normal fashion - using a slots section or the Q_SLOT macro.
If a slot doesn't have an implementation in the immediately derived class (whether virtual or not), its implementation in the generic class should be made visible to moc only, but not to the compiler - you don't wish to override it, after all. Thus the slot declarations are wrapped in #ifdef Q_MOC_RUN block that is only active when moc is reading the code. The generated code will refer to the generic implementations of the slots.
As we wish to make sure this indeed works, we'll add some booleans to track whether the slots were invoked.
// 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();
}
};
The generic class is very simple to use. Remember to wrap any non-virtual overridden slots in a moc-only guard!
Also recall the general rule that applies to all Qt code: if you have a slot, it should be declared to moc only once. So, if you had a class that further derives from MyTreeWidget or MyTableWidget, you don't want a Q_SLOT or slots macro in front of any necessarily virtual slot overrides. If present, it'll subtly break things. But you definitely want Q_DECL_OVERRIDE.
If you're on Qt 4, remember to call doConnections, otherwise the method is unnecessary.
The particular choice of QTreeWidget and QTableWidget is completely arbitrary, meaningless, and shouldn't be taken to mean that such use makes any sense (it likely doesn't).
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
};
Finally, this little test case shows that it indeed works as desired.
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"
This approach gives you the following:
The common code class derives from the base class, and can thus easily invoke or override the behavior of the base class. In this particular example, you can reimplement the QAbstractItemView methods etc.
There is full support for signals and slots. Even though the signals and slots are declared as such in the metadata of the derived class, you can still use them in the generic class.
In this situation you may make use of composition rather than multiple inheritance. Something like this:
class MySignaler : public QObject
{
Q_OBJECT
public:
MySignaler : QObject(NULL) {}
signals:
void MySignal();
}
MySignaler signaler;
class MyBaseWidgetContainer: public QWidget
{
Q_OBJECT
public:
MyBaseWidgetContainer() : QObject(NULL), widget(NULL)
{
connect(&signaler, SIGNAL(MySignal()), this, SLOT(MySlot()));
}
public slots:
virtual void MySlot()
{
// Default behavior here
}
private:
QWidget *widget;
}
class MyTabWidgetContainer : public MyBaseWidgetContainer
{
Q_OBJECT
public:
MyTabWidgetContainer() {
widget = new QTabWidget(this);
QLayout *layout = new QBoxLayout(this);
layout->addWidget(widget);
}
public slots:
void MySlot()
{
// Decide to handle the signal for custom behavior
}
}
using Qt 5.0.0
The following is roughly an Observer pattern (the code is stripped to bare minimum to explain only the problem):
class A : public QObject
{
Q_OBJECT
public:
void registerListner(Observer *pObs);
static A* getInstance();
signals:
void sig();
};
void A::registerListner(Observer *pObs)
{
connect(this, SIGNAL(sig()), pObs, SLOT(slo));
}
////////////////////////////////////////////////////////////////
class Observer : public QObject
{
Q_OBJECT
public slots:
virtual void slo() = 0;
};
class ConcreteObserver : public Observer , public QWidget
{
Q_OBJECT
public: //re-mentioning "slots" is not necessary
virtual void slo();
};
ConcreteObserver *pCObs = new ConcreteObserver;
A::getInstance()->registerListner(pCObs);
/////////////////////////////////////////////////////////////
problem (apart from dreaded-diamond):
Can't inherit multiple times from QObject - moc does not allow it.
One possible solution is derive Observer from QWidget and then ConcreteObserver from Observer alone. However this is putting a constraint on ConcreteObserver. Maybe ConcreteObserver_2 needs to derive from QDialog instead etc.
How do i solve this design problem? Is there anything specific to Qt 5.0.0 Signal-Slot (in addition to earlier versions) that can solve this, or what would you suggest?
If runtime warnings are not enough for you, you can add a bit of compile-time type checking by making registerListener a function template and avoid multiple inheritance of QObject by not defining an Observer class per-se.
Here's what this could look like: (Note: my SFINAE skills are non-existent, this could probably be made nicer.)
#include <QObject>
#include <QDebug>
#include <type_traits>
class A : public QObject
{
Q_OBJECT
public:
template <typename T>
void registerListener(T *pObs)
{
static_assert(std::is_base_of<QObject, T>::value,
"Listener must be a QObject");
static_assert(std::is_same<void,
decltype(std::declval<T>().slo())
>::value,
"Slot slo must have signature void slo();");
connect(this, SIGNAL(sig()), pObs, SLOT(slo()));
}
static A* getInstance() { return instance; }
static void init() { instance = new A; }
void doStuff() { emit sig(); }
signals:
void sig();
private:
static A *instance;
};
A few test cases:
class BadObject1 : public QObject
{
Q_OBJECT
public:
BadObject1() {}
public slots:
void slo(int){}
};
class BadObject2 : public QObject
{
Q_OBJECT
public:
BadObject2() {}
public slots:
int slo(){return 0;}
};
struct BadObject3 {
void slo();
};
class ObservedObject : public QObject
{
Q_OBJECT
public:
ObservedObject(QString const& name): QObject() {
setObjectName(name);
}
public slots:
virtual void slo(){
qDebug() << objectName();
}
};
class ObservedObject2 : public ObservedObject
{
Q_OBJECT
public:
ObservedObject2(QString const& name)
: ObservedObject(name + " (derived)") {}
};
And a main file:
#include "A.h"
A* A::instance = 0;
int main(int , char **)
{
A::init();
A::getInstance()->registerListener(new BadObject1);
A::getInstance()->registerListener(new BadObject2);
A::getInstance()->registerListener(new BadObject3);
A::getInstance()->registerListener(new ObservedObject("foo"));
A::getInstance()->registerListener(new ObservedObject2("bar"));
A::getInstance()->doStuff();
}
You'll get compiler errors for all the BadObjectN cases. If you comment them out, the output will look like:
"foo"
"bar (derived)"
A warning though: this will not check if the void slo(); member is indeed a slot. You can check that at runtime with something like:
if (pObs->metaObject()->indexOfSlot("slo()") == -1) {
qDebug() << "Class" << pObs->metaObject()->className()
<< "doesn't have a slo slot.";
::exit(1);
}
This will work and do what is expected (unless you've got a class hierarchy where the slot wasn't declared virtual - then strange things will happen in derived classes that omit the slots "specifier". So I advocate that your docs not have the comment you have above about that specifier: it is always a good idea to have it when overloading a slot).
I don't believe this last check is achievable at compile-time, "slot resolution" is done with a runtime walk of the QObject meta-data and involves parsing moc-generated strings. Even if it was with some recursive template magic, I don't think it's work the effort. You'll get a runtime error message at registration type in which you can include the actual class name of the faulty object. That's a very accurate error message, and should be caught by the simplest testcases.