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▲
Le code source de cet article.
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 !