Le modèle OpenGL : modèles distants

| Witold Wysota | Traducteur : Thibaut Cuvelier |

Le framework Interview de Qt se charge du support du paradigme modèle-vue. Il ne gère pas de base la gestion des données distantes. On peut utiliser des solutions basiques, comme les modèles de QtSql, mais ces méthodes ne sont pas les plus efficaces.

Commentez

- Titre : Le modèle OpenGL : modèles distants

- Auteurs : Witold Wysota, Traducteur : Thibaut Cuvelier

- Parution : 6 avril 2011

- Mise à jour : 2 mai 2011

- Licence : Copyright ® 2006 Witold Wysota. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. L'article original

- II. Le problème

- III. Des solutions possibles

- IV. Comment cela fonctionne-t-il ?

- V. Remerciements

I - L'article original

Cet article est la traduction de l'article Remote models de Witold Wysota.

II - Le problème

Qt 4 supporte le paradigme modèle-vue via le framework Interview ; malheureusement, il ne gère pas les modèles qui stockent des données sur des hôtes distants. On pourrait dire qu'il suffit d'opérer sur des modèles avec des sets de données distants, prenant pour exemple les modèles SQL déjà existants. C'est vrai. Mais ce n'est qu'une vérité partielle. Avez-vous déjà utilisé ces modèles (comme QSqlTableModel) sur de grandes bases de données ? Avec des milliers d'enregistrements, contenant quelques images et des données textuelles ? Le problème avec les modèles SQL est que, quand le modèle s'initialise (quand select() est appelé), il récupère toutes les données du SGBDR SQL et bloque la GUI pendant ce temps. Ce n'est pas un problème avec les bases de données locales - 1000 enregistrements de 50 Ko chacun, cela fait 50 Mo de données à transférer, soit l'affaire de quelques secondes sur un réseau domestique Ethernet à 100 Mbps. Par contre, avec une connexion à Internet limitée à 1 Mbps, après quelques minutes, le modèle sera prêt et l'application pourra continuer.

III - Des solutions possibles

Le problème serait simple à dépasser si l'une de ces deux choses était vraie : QSqlQuery travaille de manière asynchrone ou QSqlTableModel commence avec un modèle vide, exécute la requête en arrière-plan et remplit le modèle quand les données sont prêtes. Le résultat de ces deux choses serait similaire - les données seraient ajoutées au modèle dynamiquement, au fur et à mesure qu'elles sont récupérées du serveur distant.

Malheureusement, les classes de modèles du module QtSql ne supportent pas ces comportements. En général, le même problème s'applique à toutes les sources de données bloquantes.

La bonne solution est d'utiliser un thread ou un ensemble de threads pour récupérer les données en arrière-plan et de les insérer dans le modèle en utilisant les signaux et les slots. À cause des connexions mises en file par-delà les threads, mettre à jour le modèle est thread-safe et ne bloque pas la GUI.

Comme preuve du concept, on a implémenté une classe dérivée de QAbstractTableModel qui utilise un thread et un objet simple de mise à jour pour transmettre les données entre l'application et un stockage externe. Le concept utilise des signaux et des événements personnalisés pour effectuer le travail et les développeurs ne doivent plus qu'implémenter l'objet de mise à jour et appeler les bonnes méthodes dans le modèle.

IV - Comment cela fonctionne-t-il ?

Voici un exemple de modèle distant.

class MyModel : public RemoteTableModel {
public:
MyModel() : RemoteTableModel(){
setUpdaterFactory(new MyUpdaterFactory());
start(); // start filling the model
}
int columnCount ( const QModelIndex & parent = QModelIndex() ) const{
if(parent.isValid()) return 0;
return 2; // two column flat model
}
int rowCount( const QModelIndex &parent = QModelIndex() ) const {
if(parent.isValid()) return 0;
return m_rows.count();
}
QVariant data ( const QModelIndex & index, int role = Qt::DisplayRole ) const{
if(!index.isValid() || role!=Qt::DisplayRole) return QVariant();
int row = index.row();
if(row>=m_rows.count()) return QVariant();
if(index.column()==1){
return m_rows.at(row).title;
} else {
return m_rows.at(row).tid;
}
}
protected:
void addRow(const QVariant &data){
// add a row retrieved from a remote data source
beginInsertRows(QModelIndex(), m_rows.size(), m_rows.size());
QVariantList vlist = data.toList();
m_rows << st(vlist.at(0).toString(), vlist.at(1).toInt());
endInsertRows();
}
private:
/**
*  Internal data structure
*/
struct st {
st(QString t, int i){ title = t; tid = i; }
QString title; // column 1 data
int tid;       // column 0 data
};
QList m_rows; // data container
};

On doit aussi implémenter un objet de mise à jour qui effectue la récupération et le stockage. On peut voir un exemple dans les sources attachées à cet article. Tout simplement, il récupère une liste de threads dans le forum Qt Programming de QtCentre en utilisant les fonctionnalités d'archives du forum via HTTP. QHttp(1) fonctionne de manière asynchrone, on peut donc implémenter cet exemple directement, sans utiliser de thread, mais ce n'est qu'un exemple, on peut l'utiliser avec succès pour récupérer et stocker des données dans une base de données SQL, mais il n'y a pas pour le moment de base de données publique à utiliser dans l'exemple, HTTP devra suffire.

La prochaine chose à faire pourrait être de séparer les fonctionnalités de transfert de données de l'interface du modèle, pour qu'elle puisse être utilisée pour des modèles hiérarchiques ou même d'autres choses et nettoyer un peu l'implémentation (actuellement, on utilise un hack pour éviter de créer un QObject dans le contexte d'un mauvais thread).

Les sources de cet article.

V - Remerciements

Merci à Louis du Verdier et à Claude Leloup pour leur relecture !

(1) QHttp est dépréciée dans les versions plus récentes de Qt, au même titre que QFtp. On utilise désormais le gestionnaire d'accès, qui fonctionne aussi de manière asynchrone.