Connexion au WebDate de publication : 18/06/2009. Date de mise à jour : 01/08/2011.
Par
David Boddie traducteur : Thibaut Cuvelier Qt Quarterly
L'apparition de WebKit dans Qt 4.4 ouvre le monde du web aux applications Qt, en effaçant les frontières entre les
applications locales traditionnelles et les services en ligne. Dans cet article, nous allons voir une manière d'utiliser
cette approche hybride dans le développement d'applications.
I. L'article original
II. Introduction
III. Premiers pas avec QtWebKit
IV. Widgets dans une page
V. Un exemple simple
VI. Combler le manque
VII. Lier le tout
VIII. Divers
I. L'article original
Qt Quarterly est une revue trimestrielle électronique proposée par Nokia à destination des développeurs et
utilisateurs de Qt. Vous pouvez trouver les
versions originales.
Nokia, Qt, Qt Quarterly et leurs logos sont des marques déposées de Nokia Corporation en Finlande
et/ou dans les autres pays. Les autres marques déposées sont détenues par leurs propriétaires respectifs.
Cet article est la traduction de l'article
Plugging into the Web de David Boddie
paru dans la Qt Quarterly Issue 26.
Cet article est une traduction d'un des tutoriels écrits par Nokia Corporation and/or its subsidiary(-ies)
inclus dans la documentation de Qt, en anglais. Les éventuels problèmes résultant d'une mauvaise traduction ne sont
pas imputables à Nokia.
II. Introduction
Les navigateurs s'enrichissent sans cesse, les interfaces Web 2.0 se démocratisent : de nombreux développeurs utilisent
le Web comme boîte à outils graphiques. Bien que les interfaces en ligne aient été améliorées depuis les débuts de
l'Internet, de temps en temps, des utilisateurs ont vraiment besoin d'accéder à des widgets natifs.
À travers quelques exemples simples, nous montrerons comment intégrer des widgets Qt à une page web, et jetterons un
oeil à une manière d'intégrer des composants Qt avec du contenu dynamique en ligne.
III. Premiers pas avec QtWebKit
QtWebKit propose une intégration sur deux niveaux de WebKit à Qt. À bas niveau, Qt fournit des widgets sur lequels
les pages web seront rendues. À haut niveau, une série de classes représentent toutes les fonctionnalités d'un
navigateur habituel.
QWebView
est un widget utilisé pour afficher des pages web.
QWebPage
représente le contenu d'une page ;
QWebFrame
représente une frame individuelle dans une page web. Le code pour afficher une page web est très simple.
int main(int argc, char * argv[])
{
QApplication app(argc, argv);
QWebView view;
view.load(QUrl (" http://www.nokia.com/ " ));
view.show();
return app.exec();
}
|
Ce widget propose les fonctionnalités indispensables d'un navigateur : support du
CSS
et du JavaScript.
D'autres technologies peuvent être ajoutées pour une expérience plus complète.
IV. Widgets dans une page
Puisque Qt est utilisé pour rendre des pages web, il est possible d'intégrer des widgets standards et personnalisés.
Tout ce que nous devons faire est de placer quelques balises pour indiquer l'emplacement du widget, et d'un
mécanisme pour savoir quand le créer.
La balise qui est utilisée fait partie du standard HTML4 : <object>, qui sert à intégrer
tous types d'objets dans une page web. Lors de la description du widget à afficher, il y a en général trois paramètres :
data, qui indique l'endroit où les données peuvent être récupérées, width et
height, pour indiquer les dimensions du widget.
Voici la manière dont nous décririons un tel objet.
< object type = " text/csv;header=present;charset=utf8 "
data = " qrc:/data/accounts.csv " width = " 100% " height = " 300 " > < / object >
|
QtWebKit utilise le mécanisme de l' usine de plug-ins pour intégrer
des widgets à des pages web. Les usines sont dérivées de QWebPluginFactory, et peuvent fournir plus qu'un widget.
V. Un exemple simple
Pour montrer la manière d'utiliser l'usine, nous allons implémenter un simple widget qui peut afficher le contenu
d'un fichier CSV ( Comma- Separated Values, format de fichier de stockage de données).
Le widget CSVView est simplement dérivé de QTableView, avec quelques fonctions supplémentaires pour mettre
en place le modèle interne des données. Les instances de l'usine CSVFactory sont responsables de la
création de widgets et de la demande des données en leur nom.
class CSVFactory : public QWebPluginFactory
{
Q_OBJECT
public :
CSVFactory(QObject * parent = 0 );
QObject * create(const QString & mimeType,
const QUrl & url, const QStringList & argumentNames,
const QStringList & argumentValues) const ;
QList < QWebPluginFactory :: Plugin> plugins() const ;
private :
QNetworkAccessManager * manager;
} ;
|
Les fonctions publiques donnent un bon aperçu de la manière dont QtWebKit va utiliser l'usine pour créer des widgets.
Nous allons commencer par le constructeur.
CSVFactory:: CSVFactory(QObject * parent)
: QWebPluginFactory (parent)
{
manager = new QNetworkAccessManager (this );
} ;
|
L'usine contient une gestionnaire d'accès réseau qu'elle va utiliser pour récupérer les données dont elle aura
besoin pour son widget.
La fonction plugins() est utilisée pour récupérer des informations sur les widgets qui peuvent être
construits par l'usine. Notre implémentation renvoie le type MIME
qu'elle attend et fournit une description du plug-in.
QList < QWebPluginFactory :: Plugin> CSVFactory:: plugins()
const
{
QWebPluginFactory :: MimeType mimeType;
mimeType.name = " text/csv " ;
mimeType.description = " Comma-separated values " ;
mimeType.fileExtensions = QStringList () < < " csv " ;
QWebPluginFactory :: Plugin plugin;
plugin.name = " CSV file viewer " ;
plugin.description = " A CSV file Web plugin. " ;
plugin.mimeTypes = QList < MimeType> () < < mimeType;
return QList < QWebPluginFactory :: Plugin> () < < plugin;
}
|
La majeure partie de l'action se déroule dans la fonction create(). Elle est appelée avec un type
MIME qui
décrit le type de données à afficher, une URL qui pointe vers
ces données, et des informations à propos de tous les paramètres passés par la page web. Nous commençons par chercher
l'information basique sur le type MIME
passée en paramètre, et ne continuons que si nous la reconnaissons.
QObject * CSVFactory:: create(const QString & mimeType,
const QUrl & url, const QStringList & argumentNames,
const QStringList & argumentValues) const
{
if (mimeType ! = " text/csv " )
return 0 ;
CSVView * view = new CSVView(
argumentValues[argumentNames.indexOf(" type " )]);
|
Nous construisons un widget en utilisant les informations transmises par le type MIME
pleinement spécifié, ce qui est garanti d'être dans la liste des arguments si un type MIME a été
spécifié.
QNetworkRequest request(url);
QNetworkReply * reply = manager- > get(request);
connect (reply, SIGNAL (finished()),
view, SLOT (updateModel()));
connect (reply, SIGNAL (finished()),
reply, SLOT (deleteLater()));
return view;
}
|
Finalement, nous utilisons le gestionnaire de connexion réseau pour chercher les données spécifiées dans le paramètre
url. Nous connectons son signal finished() au slot updateModel()
de la vue, pour qu'elle puisse utiliser ces données. L'objet de la requête est intentionnellement créé sur le tas.
Le signal finished() est connecté au slot deleteLater(), pour s'assurer que Qt
le détruira dès qu'il n'est plus nécessaire.
La classe CSVView ne propose que peu d'extensions à QTableView, avec un slot public pour s'occuper
des données qui arrivent, et une variable privée pour enregistrer l'information exacte du type MIME.
class CSVView : public QTableView
{
Q_OBJECT
public :
CSVView(const QString & mimeType, QWidget * parent = 0 );
public slots :
void updateModel();
private :
void addRow(bool firstLine, QStandardItemModel * model,
const QList < QStandardItem * > & items);
QString mimeType;
} ;
|
Le nouveau constructeur n'est utilisé que pour stocker le type MIME
des données.
CSVView:: CSVView(const QString & mimeType, QWidget * parent)
: QTableView (parent)
{
this - > mimeType = mimeType;
}
|
Nous allons seulement regarder de plus près quelques parties de updateModel(), qui commence par
rapatrier l'objet QNetworkReply
qui a causé son exécution avant de vérifier s'il y a eu des erreurs.
void CSVView:: updateModel()
{
QNetworkReply * reply =
static_cast < QNetworkReply * > (sender());
if (reply- > error() ! = QNetworkReply :: NoError)
return ;
bool hasHeader = false ;
QString charset = " latin1 " ;
|
En supposant que les données soient correctes, nous devons déterminer si le CSV contient un entête, et deviner
quel encodage a été utilisé. Ces deux informations peuvent être stockées dans l'information complète du type MIME, nous le vérifions donc avant de continuer, ce qui est montré dans cet exemple.
QTextStream stream(reply);
stream.setCodec(
QTextCodec :: codecForName(charset.toLatin1()));
QStandardItemModel * model =
new QStandardItemModel (this );
|
Vu que QNetworkReply dérive de
QIODevice, la réponse peut être lue en
utilisant un flux de texte correctement configuré, les données pouvent être stockées dans un conteneur standard.
Les mécanismes utilisés dépassent le cadre de cet article. À la fin de cette fonction, nous fermons l'objet
de la réponse et appliquons le modèle à la vue.
reply- > close();
setModel(model);
resizeColumnsToContents();
horizontalHeader()- > setStretchLastSection(true );
}
|
Dès que la réponse a été lue, rempli de données, le plug-in n'a presque plus besoin de faire quelque chose.
La possession du widget de la vue est gérée autre part, et nous nous sommes assurés que le modèle sera détruit quand il
ne sera plus nécessaire, en le faisant objet enfant de la vue.
Regardons rapidement le code de MainWindow.
MainWindow:: MainWindow(QWidget * parent)
: QMainWindow (parent)
{
QWebSettings :: globalSettings()- > setAttribute(
QWebSettings :: PluginsEnabled, true );
QWebView * webView = new QWebView ;
CSVFactory * factory = new CSVFactory(webView, this );
webView- > page()- > setPluginFactory(factory);
QFile file(" :/pages/index.html " );
file.open(QFile :: ReadOnly);
webView- > setHtml(file.readAll());
setCentralWidget(webView);
}
|
À part la création de l'usine et son application sur la
QWebPage, la tâche la plus importante
est l'activation des plug-ins web. Si ce paramètre global
n'est pas défini, les plug-ins ne seront pas utilisés et les balises <object> seront
simplement ignorées.
VI. Combler le manque
Pouvoir insérer des widgets dans une page web est assez utile, mais nous pouvons aussi les laisser communiquer avec
le reste de la page, voire même la modifier. Actuellement, cela se fait grâce au moteur JavaScript inclus dans WebKit.
Pour illustrer ceci, nous modifions l'exemple précédent pour que la sélection d'une colonne entraîne la mise à jour
de trois champs de saisie dans un formulaire HTML.
Pour effectuer cette communication, nous devons utiliser une version mise à jour de notre widget CSVView
pour qu'il puisse envoyer un signal dès qu'une colonne est sélectionnée. nous avons besoin d'une fonction JavaScript
pour modifier les éléments de la page. Finalement, d'un peu de code pour effectuer la communication.
Sur la page, le formulaire et le plug-in sont ainsi déclarés.
< object type = " text/csv;header=present;charset=utf8 "
data = " qrc:/data/accounts.csv " width = " 100% " height = " 300 " >
< param name = " related " value = " customers " > < / param >
< / object >
< form >
< input id = " customers_name " name = " name " . . . > ...
< input id = " customers_address " name = " address " . . . > ...
< input id = " customers_quantity " name = " quantity " . . . > ...
< / form >
|
Dans la balise <object>, nous incluons un élément <param>,
qui renvoie aux identifiants des éléments à modifier dans le <form> qui convient.
L'attribut related vaut "customers". Le formulaire qui devra être modifié contient
des éléments <input> avec des id qui sont dérivés de cet identifiant.
Dans cet exemple, quand l'usine crée le widget de la vue, nous saisissons l'opportunité de passer l'identifiant au
constructeur de notre widget.
QObject * CSVFactory:: create(...) const
{
CSVView * view = new CSVView(arguments[" type " ],
arguments[" related " ]);
|
Nous exposons aussi le widget de la vue à la frame dans la page qui contient les éléments, paramétrons une connexion
entre la vue et la fonction JavaScript définie dans l'entête de la page.
QWebFrame * frame = webView- > page()- > mainFrame();
frame- > addToJavaScriptWindowObject(
arguments[" related " ] + " _input " , view);
frame- > evaluateJavaScript(
arguments[" related " ] +
" _input.rowSelected.connect(fillInForm);\n " );
}
|
Pour la page montrée ci-dessus, la vue est ajoutée à la page en tant que customers_input, et le code
de connexion revient à écrire ceci.
customers_input. rowSelected. connect (fillInForm);
|
Où fillInForm est le nom de la fonction JavaScript qui modifiera les éléments du formulaire.
Cette fonction attend 4 arguments : l'identifiant du formulaire à modifier, et les nom, adresse et quantité pour une
ligne de données.
Le signal rowSelected() est un nouveau signal qui doit donner un peu de travail à
fillInForm(). Nous fournissons aussi un slot interne, qui sort les données du modèle et émet le signal.
class CSVView : public QTableView
{
Q_OBJECT
public :
CSVView(const QString & mimeType,
const QString & related, QWidget * parent = 0 );
signals :
void rowSelected(const QString & form,
const QString & name, const QString & address,
const QString & quantity);
public slots :
void updateModel();
private slots :
void exportRow(const QModelIndex & current);
} ;
|
Dans le slot updateModel() de la vue, en plus de stocker les données dans un QStandardItemModel,
le signal currentChanged() du modèle de sélection de la vue est connecté au slot
exportRow().
Ainsi, dès qu'un utilisateur sélectionne une ligne dans le tableau, le slot exportRow() est appelé,
les données de la ligne sélectionnée sont extraites du modèle et émises dans le signal rowSelected()
sous la forme trois QString,
et la fonction JavaScript fillInForm() est appelée avec ces trois paramètres. Les conversions de type
sont automatiquement effectuées, pour s'assurer que tous les QString soient convertis en objets string
du JavaScript.
Voici la fonction JavaScript, pour montrer ce qu'elle fait avec les chaînes qui lui sont données. La fonction est
définie dans l'entête de la page.
function fillInForm (form, name, address, quantity)
{
var nameElement = document. getElementById (form+ " _name " );
var addressElement = document. getElementById (form+ " _address " );
var quantityElement = document. getElementById (form+ " _quantity " );
nameElement. value = name;
addressElement. value = address;
quantityElement. value = quantity;
}
|
Nous utilisons l'identifiant passé dans l'argument form pour dériver les noms pour les attributs
id utilisés dans les éléments du formulaire, et obtenir ces éléments en utilisant l'API
DOM. Les valeurs de ces éléments sont mises à jour avec les chaînes spécifiées.
VII. Lier le tout
Qt fournit une panoplie de widgets qui peuvent être intégrés avec QWebPluginFactory pour créer des navigateurs
spécifiques pour accompagner des applications. Des widgets OpenGL peuvent être utilisés pour créer des navigateurs
permettant de visualiser des modèles 3D. Le framework Graphics View fournit de bonnes bases pour
d'autres types de visionneurs interactifs.
Bien que nous ayons utilisé les widgets pour démontrer l'utilisation des slots et des signaux pour la communication
entre des composants de Qt et des fonctions JavaScript, il n'est pas nécessaire d'inclure des widgets dans une page
pour pouvoir le faire. En insérant des objets dans la page, et en évaluant du JavaScript, des applications Qt peuvent
examiner et utiliser des informations disponibles en ligne.
Puisque WebKit est un moteur web complet, cette approche peut être utilisée pour intégrer des applications et des
services en ligne, en particulier ceux qui utilisent des API JavaScript bien définies. Les techniques montrées peuvent
aussi servir à récolter des informations de différentes sources, de les rassembler et de les combiner.
Le support de Qt pour le contenu web s'améliore en même temps que celui de WebKit, le développement de ce dernier
avançant rapidement. La majorité des nouvelles fonctionnalités sont supportées dans les snapshots journaliers de Qt,
et les développeurs annoncent les plus importantes sur le blog des
Qt Labs.
VIII. Divers
Au nom de toute l'équipe Qt, j'aimerais adresser le plus grand remerciement à Nokia pour nous avoir
autorisé la traduction de cet article !
J'aimerais aussi adresser un immense merci à
IrmatDen pour sa courageuse relecture !
Copyright ©2009 David Boddie.
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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts. Cette page est déposée à la SACD.
|