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).
V. Remerciements▲
Merci à Louis du Verdier et à Claude Leloup pour leur relecture !