7

My application stores a few objects of a type that inherits from QAbstractListModel objects.

This generates quite a lot of duplicated code when wrapping a simple std::vector<T> or a QList<T> into model with the general add, delete and multi-selection functionality.

Is that the way QAbstractListModel is supposed to be used or is there some adapter class that can remove the duplicated code (at least for containers that are part of Qt)?

Example: I want to wrap vector<ObjectA> and vector<ObjectB> into a model. The code for insertRows, deleteRows, columnCount etc. is always going to be the same and I would like to consolidate that (with a little meta-programming that could even work for with tuple and data).

pmr
  • 58,701
  • 10
  • 113
  • 156
  • Did you mean `QAbstractListModel` or `QAbstractItemModel`? – Karlson Jan 17 '12 at 14:37
  • @Karlson Indeed, the multitude of similar names in the ModelView framework got me confused. I'll leave the tag `abstractitemmodel` tag as it seems the main tag for the framework. – pmr Jan 17 '12 at 14:42

2 Answers2

7

You have to do this in two separate classes because Qt's extensions to c++ (SIGNALS, SLOTS, etc.) do not play well with templates. The rationale and workaround for this can be found at: https://doc.qt.io/archives/qq/qq15-academic.html

Here is a rough outline of a solution. (This is based on code we are using in our application and that is working fine.)

1. Abstract list class that does Qt stuff

class FooListModelQt : public QAbstractTableModel {
  Q_OBJECT

public:
  // Non-template methods, signals, slots, etc. can be used here. For example...
  QSet<int> SelectedRows() const;
  // ... etc. ...

signals:
  void SelectionChanged();
  // ... etc. ...

protected:
  explicit FooListModelQt(QObject *parent = NULL);
  virtual ~FooListModelQt() = 0;
  // ... etc. ...

};

2. Abstract class that does template stuff

template <typename T>
class FooListModel : public FooListModelQt {
public:
  const T* at(int index) const { return items_.at(index); }
  int count() const { return items_.count(); }
  void Append(T *item);
  // ... etc. ...

protected:
  explicit FooListModel(QObject *parent = NULL);
  virtual ~FooListModel();

private:
  QList<T*> items_;
};

3. Actual list class

class BarListModel : public FooListModel<Bar> {
  Q_OBJECT

public:
  explicit BarListModel(QObject *parent = NULL);
  int columnCount(const QModelIndex &parent) const;
  QVariant data(const QModelIndex &index, int role) const;
  // ... etc. ...
};
user240515
  • 3,056
  • 1
  • 27
  • 34
Dave Mateer
  • 17,608
  • 15
  • 96
  • 149
  • So, if I skip the `Q_OBJECT` macro `moc` is always going to ignore the class and let me go about my template business? (OT The article is just apologetic writing and gives no reasoning for taking away core language features. Especially in face of implementations of signals and slots that don't impose those constraints.) – pmr Jan 17 '12 at 16:22
  • That would be my assumption, yes. If you are not using anything that requires Q_OBJECT (signals, slots, translation, etc.) then you should be OK. – Dave Mateer Jan 17 '12 at 16:35
  • I've just lost a few hours on that, so word of advice: it you are going to expose the final model object to QML/Declarative GUI, make sure that the exact class of the specific model is marked with Q_OBJECT. It itself must be marked with it, it is not 'inherited' from base. See the BarListModel in Dave's example? There's Q_OBJECT repeated. Also, qmlRegisterType will also be needed. This means that your final class cannot be a template, it must be a plain class, like BarListModel. – quetzalcoatl Sep 21 '12 at 23:36
1

Normally I would implement my own model inheriting from QAbstractItemModel directly and provide my own implementation for the presentation functions such as data() to handle the data storage container I give the model.

If you have code duplication for using QList<T> and std::vector<T> then I would suggest converting one to the other by doing either:

QList<T> list = QList::fromVector(QVector::fromStdVector(vector));

or the other way.

std::vector<T> vector = qlist.toVector().toStdVector();

I'd do the latter but you can choose either.

Based on your additional comments there are 2 paths of action you can take:

Path 1:

Implement objectA and objectB as follows:

class objectA : baseObject

and

class objectB : baseObject

where baseObject is:

struct baseObject
{
    virtual std::string toString() = 0;
};

Probably easier to convert to string then anything else.

Path 2 will basically involve inside the model using std::vector<boost::any>() as your data storage container, this way you can implement a single model subclassing QAbstractListModel.

The thing that you have to consider that if your data storage container you can probably make common the data presentation you are limited at what you can do because data() function that will give your view the element has to return QVariant and it's limited at what you can construct it from.

Karlson
  • 2,958
  • 1
  • 21
  • 48
  • The crux is that the base class that implements the boiler plate must be a template. I polished my question a little to better reflect my problem. – pmr Jan 17 '12 at 15:22