Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I keep my QAbstractTableModel in sync with my data store?

In my app, I have a class for keeping a list of items:

class Database : public QObject
{
    Q_OBJECT

public:
    Database(QObject *parent, const QString &name);

    const Entry& item(int idx) const { Q_ASSERT(idx < itemCount()); return _items.at(idx); }
    const QString& name() const { return _name; }
    int itemCount() const { return _items.size(); }

    bool addItem(const Entry &item);
    bool addItems(const Database *source, const QList<int> &idxs);
    bool updateItem(int idx, const Entry &updated);
    void removeItem(int idx);
    void removeItems(const QList<int> &idxs);

private:
    QList<Entry> _items;

signals:
    void itemsRemoved(int start, int count);
    void itemsAdded(int count);
    void itemChanged(int index);
    void countUpdate();
};

The item manipulation functions (add, update, remove) emit the corresponding signals when done (items added, changed, removed). I have a list of such Database's and a QTableView for displaying their contents. I also have one object of a custom, QAbstractTableModel-derived model class, which can be made to point to (and display) a different Database when needed:

class DatabaseModel : public QAbstractTableModel
{
    Q_OBJECT

public:
    DatabaseModel(QObject *parent = 0);

    int rowCount(const QModelIndex &parent = QModelIndex()) const { return _data->itemCount(); }
    int columnCount(const QModelIndex &parent = QModelIndex()) const { return 5; };

    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const = 0;

    void setDatabase(const Database *data);
    {
        beginResetModel();
        _data = data;
        endResetModel();
    }

protected:
    const Database *_data;
};

I have a problem with making the model reflect the changes to its current Database. Before, I got it to work by issuing a model reset every time something changed in the Database (triggered by a signal from the Database to the DatabaseModel), but decided that was overkill. Now I have no idea how to connect Database and the model correctly.

Connecting the Database signals to the model and make the model emit dataChanged() doesn't work because the number of items in Database (and hence the model's rows) is changing. There are signals called rowsInserted() and rowsRemoved() in QAbstractTableModel, but the docs say they can't be used in custom classes. There are virtual functions to be reimplemented called removeRows() and insertRows(), but the docs say I must call begin(Remove|Insert)Rows() and end(Remove|Insert)Rows() inside them, which leads to two problems:

  1. begin...Rows() needs a QModelIndex 'parent' parameter, for which I've no idea what to use
    EDIT: Actually it doesnt matter, now I'm passing QModelIndex() for this. This is used by QAbstractTreeModel to identify a parent node in a tree and is apparently not necessary for table models.
  2. the docs say these functions need to be called before changing the underlying data store

How do I make the model keep in sync with the database? Thanks!

like image 840
neuviemeporte Avatar asked Feb 25 '23 08:02

neuviemeporte


1 Answers

I think I see your problem. One the one side you're doing the right thing and trying to keep the data seperate from the model, on the other hand your data isn't aware of the model itself.

There is a reason why you should call begin...Rows() before changing the data and end...Rows() afterwards. Namely the QPersistentModelIndex. Usually you're not supposed to hort QModelIndex objects, but the persistent index is ment to be saved and kept. The model must guarantee its validity. Looking at the code of those begin...Rows() methods it is mainly about those persistent indices.

You have several choices.
a) If you're positivly sure you won't be using persistent indices you could just implement a private slot in your model which listens to a sort of update signal from your data source. This slot would simply call begin...Rows() and end...Rows() with nothing in between. It's not "clean" but it'll work.

b) You could implement more signals in your data source, one that signals the beginning of a data change (removal or adding of a row for example) and one that signals the end of said operation. Of course that would significantly increasy the size of your code.

c) You could make your DataBase class a friend in the model and call the begin... end... methods from within your datasource, but then DataBase would have to be aware of the model.

d) You could rethink the concept. From what I can grasp you're using the DataBase class as a data storage for your model and as an interface for other parts of your code, right? Wouldn't it be easier to use custom items with methods that operate on the model itself and thus avoid the trouble? I've done my fair share of those so I could give you the code if need be.

Hopefully that helps.
Best regards

like image 67
Dariusz Scharsig Avatar answered Apr 29 '23 13:04

Dariusz Scharsig