0

For a school project we have to make a 'game', in this game we visualize a maze and find our way through it. This visualization has to be possible in two different ways, in our case a text-based visualization and a more fancy way.

We have to use Qt for this, we are using a QMainWindow with a QWidget in it, this widget will be one or the other visualization. Seeing that during the game we should be able to switch between visualizations, we use the Strategy Pattern, so made an interface (ViewInterface) and both visualizations implement this. Beside implementing ViewInterface, both visualizations inherit another class, in the text-based this is QPlainTextEdit (to have a QWidget with text) and in the Fancy this is QDialog. Probem here is that in my controller, I have a pointer to a ViewInterface which is used to fill the QWidet but to do this ViewInterface also has to inherit from QWidget, which causes this error: QObject is an ambiguous base of 'TerminalView'.

Since the switching between views can be done while playing the game, and update should be called only on the currently-active view, we can't pass the specific view to 'setWidget'.

here is a visualization of the inheritance structure:

Am I doing something wrong or how can I solve this? (I already thought about it but can't come with solutions).

YFrickx
  • 108
  • 1
  • 10

2 Answers2

1

The problem here is that you inherit QObject twice: first in ViewInterface hierarchy, second in QDialog hierarchy (diamond problem). Try using virtual inheritance for your FancyView and TextView classes (DOES NOT WORK, SINCE VIRTUAL INHERITANCE SHOULD BE USED IN ENTIRE HIERARCHY)

But there is another problem with your design: both QDialog and QPlainTextEdit inherit QWidget. To resolve this issue you may do the following:

  1. Make ViewInterface abstract without inheriting from QObject. This abstract class will define the interface of your FancyView and TextView and may or may not implement some common logic.

  2. Implement FancyView and TextView with multiple inheritance from QDialog and ViewInterface and from QPlainTextEdit and ViewInterface respectively.

This way you may not encounter any problems with ambiguous base class.

UPDATE:

Did not see your edit yet: indeed this would solve the issue, but another problem rises: if I do this, I can't use a ViewInterface pointer to set my QWidget. It is possible indeed, but in my opinion this is not really clean

Well, that is a real problem. An obvious solution is not to use ViewInterface* instead of QWidget*. But this means that you need to change quite a lot of code and it may actually be not that great in your case.

With respect to the given comment, I propose another solution:

  1. Inherit ViewInterface from QWidget (with all desired interface functions):

    class ViewInterface: public QWidget {
        Q_OBJECT
        ...
    }
    
  2. In ViewInterface constructor set layout to be used by widget and set it up:

    ViewInterface::ViewInterface (QWidget* i_parent)
        : QWidget (i_parent) {
        auto layout {new QGridLayout (this)};
    
        // Zeroed margins to make full fit.
        layout->setContentsMargins (0, 0, 0, 0);
    
        setLayout (layout);
    }
    
  3. In constructors of derived classes add specific widget to layout:

    class FancyView : public ViewInterface {
        Q_OBJECT
    
        FancyView (QWidget* i_parent) 
            : ViewInterface (i_parent)
            , dialog_widget_ {new QDialog (this)} {
            layout->addWidget (dialog_widget_);
        }
        ...
    
    private:
        QDialog* dialog_widget_;
    }
    
  4. Implement desired interface using target widget. If you want to process events, you may use QObject::eventFilter (). In this case you should set your FancyView object from the code above as event filter for dialog_widget_.

NOTE: In this case you can not use FancyView as QDialog. A workabound for this issue is proxy QDialog signals, slots and public functions and create yet another wrapper class FancyViewDialog, that works as proxy for the methods, signals and slots in FancyView. That is not a fantastic solution, but I do not see any other way around diamond problem that allows "is-a" relation between ViewInterface and QWidget.

  • if I do this, I get the error 'QObject' is an ambiguous base of 'TerminalView' my code: `class TerminalView : public virtual QPlainTextEdit, public virtual ViewInterface { Q_OBJECT ... };` – YFrickx Dec 09 '17 at 15:57
  • Did not see your edit yet: indeed this would solve the issue, but another problem rises: if I do this, I can't use a ViewInterface pointer to set my QWidget. It is possible indeed, but in my opinion this is not really clean – YFrickx Dec 09 '17 at 16:02
0

An interface should be abstract with virtual methods and have no concrete base classes. ViewInterface should not inherit from QWidget. That fixes your problem.

Now there are at least two solutions to converting the ViewInterface instance to QWidget:

  1. Template the users of ViewInterface and have them ensure that the concrete type used does in fact derive from QWidget. That will work if the type is there's no runtime polymorphism.

  2. If there's runtime polymorphism, add a QWidget * widget() = 0 method to the interface, and implement it in the derived methods. It's trivial: QWidget * widget() override { return this; }.

The interface can have both signals and slots - they must be virtual methods, but they certainly will work. See e.g. this answer to get started with virtual signals.

If you want to share some code between the two concrete implementations of ViewInterface, you can have an additional class that derives from ViewInterface to provide the shared code. Both TerminalView and FancyView would derive from that class. The helper class could be parametrized on the base class type, to have it jump through less hoops to access the widget, e.g.: class TerminalView : ViewHelper<QPlainTextEdit> { ... };

Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313