I think I've run into a kind of diamond inheritance problem here.
Qt provides a couple of spin boxes, for integer values, for doubles and also for dates/times. They all derive from QAbstractSpinBox
:
#include <QtWidgets/QSpinBox>
class QSpinBox:
public QAbstractSpinBox {
};
#include <QtWidgets/QDoubleSpinBox>
class QDoubleSpinBox:
public QAbstractSpinBox {
};
Now I'd like to add some functionality common to all spin boxes, in this concrete example, a button to revert the spin box to its minimum (hence specialValueText).
So I also derived from QAbstractSpinBox
and came up with something like this:
class AbstractRevertibleSpinBox:
public QAbstractSpinBox {
public:
RevertibleSpinBox() {
/* Use abstract base: */
QAction *revertAction = new QAction(this);
QAbstractSpinBox::lineEdit()->addAction(
revertAction, QLineEdit::TrailingAction);
/* ... */
}
public slots:
virtual void revert() = 0;
}
This containts the pure revert()
that should implement how to revert the different spin boxes. For example, using setValue(double)
for the QDoubleSpinBox
or setDate(QDate)
for the QDateEdit
.
And then I went the obvious way and derived the appropriate classes for all the spin boxes I needed, like these:
class RevertibleSpinBox:
public QSpinBox,
public AbstractRevertibleSpinBox {
protected:
void revert() {
/* Revert 'int' */
setValue(0);
}
};
class RevertibleDoubleSpinBox:
public QDoubleSpinBox,
public AbstractRevertibleSpinBox {
protected:
void revert() {
/* Revert 'double' */
setValue(0.0);
}
};
This obviously does not work as anything in QAbstractSpinBox is ambiguous now. I thought I could resolve it using virtual inheritance and this would work if, e.g. QDoubleSpinBox
would virtually derive from its own QAbstractSpinBox
. But it doesn't. Also, it would fail with QObject because Qt seems to do a lot of static_cast
upwarts there, which also does not work with virtual inheritance.
I also thought of resolving it by making the AbstractRevertibleSpinBox
a template class that is passed the distinct spin box type as a template class parameter. The construction would then look like this:
template<class Base>
class AbstractRevertibleSpinBox:
public Base {};
class RevertibleSpinBox:
public AbstractRevertibleSpinBox<SpinBox> { };
This would work, however Qt's moc is very unhappy about template classes. So for example, I cannot connect any signals and slots from within the template class. At least not using the conventional string based SIGNAL()/SLOT()
syntax.
Is there any other reasonably elegant way to overcome this problem..?
The diamond problem occurs when two superclasses of a class have a common base class. For example, in the following diagram, the TA class gets two copies of all attributes of Person class, this causes ambiguities. For example, consider the following program. In the above program, constructor of ‘Person’ is called two times.
Multiple Inheritance is a feature of C++ where a class can inherit from more than one classes. The constructors of inherited classes are called in the same order in which they are inherited.
Whether you are handling navigation, common modal alert UI, or anything else, using the inheritance model granted via TypeScript to extend components is a powerful tool to keep in our toolbox. The code for this tutorial is available on GitHub.
You can access the variables of extending (Base) class if they are not private (protected or internal or public) can override them if they are not final (open variables) or you can override the functions also. Here we added a property i.e. rollNumber and override the function.
As indicated right upfront in my comment, I think this is a clear case for the Decorator Pattern if you want an easily extensible feature system, otherwise just inherit from QObject rather than from the base "interface" with pretty much the same code.
I will start with the IMHO worse approaches, supplied in the other answers given:
This is obviously tiresome and even more important, you will not be able to support any QSpinBox subclass with those as you would always need to create a new subclass for each addition. It is simply an unflexible approach.
This looks like an unnecessary coupling of two different things, and so you would not be able to reuse spin boxes easily should you trigger them any other way later than through buttons. I think the two concepts should remain distinct and separately managed.
Furthermore, dynamic_cast
ing is wrong as suggested as you should use qobject_cast
if any.
Let us have a closer look at the decorator approach:
This is not yet the solution for your case, but it shows pretty well how features can be added (i.e. "decorated") into existing hierarchies. To get a bit more concrete about your use case, let us see what would be what in your particular scenario:
Component: QAbstractSpinBox
Concrete components
Decorator: AbstractSpinBoxDecorator (this step can be left out in your case)
Concrete Decorator: RevertibleSpinBoxDecorator
Let us get our hands dirty with implementing this design:
#include <QAbstractSpinBox>
#include <QSpinBox>
#include <QDoubleSpinBox>
#include <QDateTimeEdit>
#include <QDateEdit>
#include <QTimeEdit>
#include <QPushButton>
#include <QApplication>
#include <QMainWindow>
#include <QHBoxLayout>
#include <QWidget>
#include <QShowEvent>
class RevertibleSpinBoxDecorator : public QAbstractSpinBox
{
Q_OBJECT
public:
explicit RevertibleSpinBoxDecorator(QAbstractSpinBox *abstractSpinBox, QAbstractSpinBox *parent = Q_NULLPTR)
: QAbstractSpinBox(parent)
, m_abstractSpinBox(abstractSpinBox)
{
}
public slots:
void revert(bool)
{
QSpinBox *spinBox = qobject_cast<QSpinBox*>(m_abstractSpinBox);
if (spinBox) {
spinBox->setValue(spinBox->minimum());
return;
}
QDoubleSpinBox *doubleSpinBox = qobject_cast<QDoubleSpinBox*>(m_abstractSpinBox);
if (doubleSpinBox) {
doubleSpinBox->setValue(doubleSpinBox->minimum());
return;
}
QDateEdit *dateEdit = qobject_cast<QDateEdit*>(m_abstractSpinBox);
if (dateEdit) {
dateEdit->setDate(dateEdit->minimumDate());
return;
}
QTimeEdit *timeEdit = qobject_cast<QTimeEdit*>(m_abstractSpinBox);
if (timeEdit) {
timeEdit->setTime(timeEdit->minimumTime());
return;
}
QDateTimeEdit *dateTimeEdit = qobject_cast<QDateTimeEdit*>(m_abstractSpinBox);
if (dateTimeEdit) {
dateTimeEdit->setDateTime(dateTimeEdit->minimumDateTime());
return;
}
Q_ASSERT_X(false, "decorator", "concrete component unimplemented");
}
protected:
void showEvent(QShowEvent *event) Q_DECL_OVERRIDE
{
m_abstractSpinBox->show();
event->ignore();
hide();
}
private:
QAbstractSpinBox *m_abstractSpinBox;
};
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = Q_NULLPTR) : QMainWindow(parent)
{
connect(pushButton, &QPushButton::clicked, revertibleSpinBoxDecorator, &RevertibleSpinBoxDecorator::revert);
QHBoxLayout *layout = new QHBoxLayout(centralWidget);
layout->addWidget(revertibleSpinBoxDecorator);
layout->addWidget(pushButton);
setCentralWidget(centralWidget);
}
private:
QWidget *centralWidget{new QWidget(this)};
QDoubleSpinBox *doubleSpinBox{new QDoubleSpinBox(this)};
RevertibleSpinBoxDecorator *revertibleSpinBoxDecorator{new RevertibleSpinBoxDecorator(doubleSpinBox)};
QPushButton *pushButton{new QPushButton(this)};
};
#include "main.moc"
int main(int argc, char **argv)
{
QApplication application(argc, argv);
MainWindow mainWindow;
mainWindow.show();
return application.exec();
}
If you want to get rid of the QAbstractSpinBox
inheritance, you will need to apply a bit more glue and IMHO for not much gain, while losing the flexibility. You would start off with something like this:
#include <QAbstractSpinBox>
#include <QSpinBox>
#include <QDoubleSpinBox>
#include <QDateTimeEdit>
#include <QDateEdit>
#include <QTimeEdit>
#include <QPushButton>
#include <QApplication>
#include <QMainWindow>
#include <QHBoxLayout>
#include <QWidget>
#include <QShowEvent>
class RevertibleSpinBoxDecorator : public QObject
{
Q_OBJECT
public:
explicit RevertibleSpinBoxDecorator(QAbstractSpinBox *abstractSpinBox, QObject *parent = Q_NULLPTR)
: QObject(parent)
, m_abstractSpinBox(abstractSpinBox)
{
}
public slots:
void revert(bool)
{
QSpinBox *spinBox = qobject_cast<QSpinBox*>(m_abstractSpinBox);
if (spinBox) {
spinBox->setValue(spinBox->minimum());
return;
}
QDoubleSpinBox *doubleSpinBox = qobject_cast<QDoubleSpinBox*>(m_abstractSpinBox);
if (doubleSpinBox) {
doubleSpinBox->setValue(doubleSpinBox->minimum());
return;
}
QDateEdit *dateEdit = qobject_cast<QDateEdit*>(m_abstractSpinBox);
if (dateEdit) {
dateEdit->setDate(dateEdit->minimumDate());
return;
}
QTimeEdit *timeEdit = qobject_cast<QTimeEdit*>(m_abstractSpinBox);
if (timeEdit) {
timeEdit->setTime(timeEdit->minimumTime());
return;
}
QDateTimeEdit *dateTimeEdit = qobject_cast<QDateTimeEdit*>(m_abstractSpinBox);
if (dateTimeEdit) {
dateTimeEdit->setDateTime(dateTimeEdit->minimumDateTime());
return;
}
Q_ASSERT_X(false, "strategy", "strategy not implemented");
}
private:
QAbstractSpinBox *m_abstractSpinBox;
};
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = Q_NULLPTR) : QMainWindow(parent)
{
connect(pushButton, &QPushButton::clicked, revertibleSpinBoxDecorator, &RevertibleSpinBoxDecorator::revert);
QHBoxLayout *layout = new QHBoxLayout(centralWidget);
layout->addWidget(doubleSpinBox);
layout->addWidget(pushButton);
setCentralWidget(centralWidget);
}
private:
QWidget *centralWidget{new QWidget(this)};
QDoubleSpinBox *doubleSpinBox{new QDoubleSpinBox(this)};
RevertibleSpinBoxDecorator *revertibleSpinBoxDecorator{new RevertibleSpinBoxDecorator(doubleSpinBox)};
QPushButton *pushButton{new QPushButton(this)};
};
#include "main.moc"
int main(int argc, char **argv)
{
QApplication application(argc, argv);
MainWindow mainWindow;
mainWindow.show();
return application.exec();
}
TEMPLATE = app
TARGET = main
QT += widgets
CONIG += c++11
SOURCES += main.cpp
qmake && make && ./main
I don't think I would use inheritance here at all; instead I'd use composition: create an unrelated class (e.g. derived from QWidget) that uses a QBoxLayout (or similar) to arrange the spin-box and the button as child widgets. The appropriate spin-box object-pointer could be passed to the class's constructor, which would also do the necessary connect() commands to forward the various signals back and forth. You'd probably want to redeclare equivalents for the QAbstractSpinBox class's slots/signals/methods in your class, of course, but the upside is that it would work with any of the QAbstractSpinBox sub-classes.
(As for getting revert() to do the right thing, the easiest approach might be just some ugly special-case logic using dynamic_cast<> -- since there are only three QAbstractSpinBox subclasses you need to support, that will be manageable, and at least that way the ugliness is hidden inside a private method body rather than being exposed to the class's users)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With