Developpez.com - Qt
X

Choisissez d'abord la catégorieensuite la rubrique :

Viadeo Twitter Facebook Share on Google+   
Logo Documentation Qt ·  Page d'accueil  ·  Toutes les classes  ·  Toutes les fonctions  ·  Vues d'ensemble  · 

Le glisser-déposer

Le glisser-déposer fournit un mécanisme simple que les utilisateurs peuvent employer pour transférer des informations entre applications ou à l?intérieur des applications (dans la littérature, ceci est appelé un « modèle de manipulation directe »). Le glisser-déposer propose la même fonctionnalité que le mécanisme de copier-coller du presse-papier.

Ce document décrit le mécanisme de base du glisser-déposer, et aborde l?approche utilisée pour l?activer dans les widgets personnalisés. Les opérations de glisser-déposer sont aussi prises en charge par les vues d?éléments Qt et par le framework de la vue graphique. Des informations supplémentaires sont disponibles dans Utilisation du glisser-déposer avec les vues d?éléments et Le framework de la vue graphique.

Les classes du glisser-déposer

Ces classes traitent le glisser-déposer et les types MIME nécessaires à l?encodage et au décodage.

QDragEnterEvent Événement qui est envoyé au widget lorsqu?une action de glisser-déposer arrive sur lui.
QDragLeaveEvent Événement qui est envoyé au widget lorsqu?une action de glisser-déposer le quitte.
QDragMoveEvent Événement qui est envoyé lorsqu?une action de glisser-déposer est en cours.
QDropEvent Événement qui est envoyé quand une action de glisser-déposer est terminée.
QMacPasteboardMime Réalise la conversion entre un type MIME et un format Uniform Type Identifier (UTI).
QWindowsMime Association entre le format ouvert MIME et les formats de presse-papier Windows.

Configuration

L?objet QApplication fournit certaines propriétés liées aux opérations de glisser-déposer :

  • QApplication::startDragTime décrit la durée en millisecondes pendant laquelle l?utilisateur doit presser un bouton de souris sur un objet avant qu?un glissement ne démarre ;
  • QApplication::startDragDistance indique à quelle distance l?utilisateur doit bouger la souris tout en pressant un bouton de souris avant que le mouvement ne soit interprété comme un glissement. L?utilisation de grandes valeurs pour cette quantité empêche les glissements accidentels lorsque l?utilisateur voulait simplement cliquer sur l?objet.

Ces quantités fournissent des valeurs par défaut raisonnables si vous autorisez la prise en charge du glisser-déposer dans vos widgets.

Le glissement

Pour démarrer un glissement, on crée un objet QDrag, puis on appelle sa fonction exec(). Dans la plupart des applications, il est judicieux de ne démarrer une opération de glisser-déposer qu'après qu?un bouton de souris a été pressé et que le pointeur a été déplacé d?une certaine distance. Toutefois, la façon la plus simple d?autoriser le glissement depuis un widget est de réimplémenter la fonction mousePressEvent() du widget et de démarrer une opération de glisser-déposer :

 void MainWindow::mousePressEvent(QMouseEvent *event)
 {
     if (event->button() == Qt::LeftButton
         && iconLabel->geometry().contains(event->pos())) {
 
         QDrag *drag = new QDrag(this);
         QMimeData *mimeData = new QMimeData;
 
         mimeData->setText(commentEdit->toPlainText());
         drag->setMimeData(mimeData);
         drag->setPixmap(iconPixmap);
 
         Qt::DropAction dropAction = drag->exec();
         ...
     }
 }

Bien que l?utilisateur puisse prendre un certain temps pour réaliser l?opération de glissement, du point de vue de l?application la fonction exec() est une fonction bloquante qui retourne une de ces valeurs. Celles-ci indiquent comment l?opération s?est terminée et sont décrites plus en détail ci-dessous.

À noter que la fonction exec() ne bloque pas la boucle d?événements principale.

Pour les widgets qui ont besoin de faire la différence entre clics souris et glissements, il est utile de réimplémenter la fonction mousePressEvent() du widget pour enregistrer la position de départ du glissement :

 void DragWidget::mousePressEvent(QMouseEvent *event)
 {
     if (event->button() == Qt::LeftButton)
         dragStartPosition = event->pos();
 }

Plus tard, dans la fonction mouseMoveEvent(), on peut déterminer si un glissement doit commencer et construire un objet de glissement pour gérer l?opération :

 void DragWidget::mouseMoveEvent(QMouseEvent *event)
 {
     if (!(event->buttons() & Qt::LeftButton))
         return;
     if ((event->pos() - dragStartPosition).manhattanLength()
          < QApplication::startDragDistance())
         return;
 
     QDrag *drag = new QDrag(this);
     QMimeData *mimeData = new QMimeData;
 
     mimeData->setData(mimeType, data);
     drag->setMimeData(mimeData);
 
     Qt::DropAction dropAction = drag->exec(Qt::CopyAction | Qt::MoveAction);
     ...
 }

Cette approche particulière utilise la fonction QPoint::manhattanLength() pour avoir une estimation approximative de la distance entre l?endroit où le clic souris a eu lieu et la position courante du curseur. Cette fonction privilégie la vitesse sur la précision, et est généralement adaptée à cet objectif.

La dépose

Pour être capable de recevoir un média déposé sur un widget, on appelle setAcceptDrops(true) pour ce widget et on réimplémente les fonctions de gestion d?événements dragEnterEvent() et dropEvent().

Par exemple, le code suivant autorise les événements de dépose dans le constructeur d?une sous-classe de QWidget, permettant d?implémenter de manière utile les gestionnaires d?événements de dépose :

 Window::Window(QWidget *parent)
     : QWidget(parent)
 {
     ...
     setAcceptDrops(true);
 }

La fonction dragEnterEvent() est typiquement utilisée pour informer Qt sur les types de données que le widget accepte. On doit réimplémenter cette fonction si on souhaite recevoir soit un QDragMoveEvent soit un QDropEvent dans nos réimplémentations de dragMoveEvent() et dropEvent().

Le code suivant montre comment dragEnterEvent() peut être réimplémenté pour dire au système de glisser-déposer que l?on ne peut gérer que le texte brut :

 void Window::dragEnterEvent(QDragEnterEvent *event)
 {
     if (event->mimeData()->hasFormat("text/plain"))
         event->acceptProposedAction();
 }

La fonction dropEvent() est utilisée pour décoder les données déposées et les gérer d'une façon adaptée à votre application.

Dans le code suivant, le texte fourni dans l?événement est passé à un QTextBrowser et une QComboBox est remplie avec la liste des types MIME utilisés pour décrire les données :

 void Window::dropEvent(QDropEvent *event)
 {
     textBrowser->setPlainText(event->mimeData()->text());
     mimeTypeCombo->clear();
     mimeTypeCombo->addItems(event->mimeData()->formats());
 
     event->acceptProposedAction();
 }

Dans ce cas, on accepte l?action proposée sans vérification de son type. Dans les applications du monde réel, il peut être utile de sortir de la fonction dropEvent() sans accepter l?action proposée ou sans gérer la donnée si l?action n?est pas pertinente. Par exemple, on peut choisir d?ignorer les actions Qt::LinkAction si on ne prend pas en charge les liens vers des sources externes à l?application.

Remplacement des actions proposées

On peut aussi ignorer l?action proposée et réaliser une autre action sur la donnée. Pour ce faire, on appelle la fonction setDropAction() de l?objet événement avec l?action préférée choisie dans Qt::DropAction avant d?appeler accept(). Ceci assure que l?action de dépose de substitution est utilisée à la place de l?action proposée.

Pour des applications plus sophistiquées, la réimplémentation de dragMoveEvent() et de dragLeaveEvent() permet de rendre certaines parties du widget réceptives aux événements de dépose, et donne plus de contrôle sur le glisser-déposer dans l?application.

Dérivation de widgets complexes

Certains widgets Qt standard fournissent leur propre prise en charge du glisser-déposer. Lorsque l?on dérive ces widgets, il peut être nécessaire de réimplémenter la fonction dragMoveEvent() en plus de dragEnterEvent() et dropEvent() pour éviter que la classe de base ne fournisse sa gestion par défaut du glisser-déposer, et de pouvoir gérer les cas particuliers qui vous intéressent.

Les actions de glisser-déposer

Dans le cas le plus simple, la cible d?une action de glisser-déposer reçoit une copie de la donnée en cours de glissement, et la source décide si elle supprime l?original. Ceci est décrit par l?action CopyAction. La cible peut aussi choisir de gérer d?autres actions, en particulier les actions MoveAction et LinkAction. Si la source appelle QDrag::exec() et qu?elle retourne MoveAction, la source est responsable de la suppression de toute donnée d?origine si elle choisit de le faire. Les objets QMimeData et QDrag créés par le widget source ne doivent pas être détruits ? ils seront détruits par Qt. La cible a pour responsabilité de prendre possession de la donnée envoyée par l?opération de glisser-déposer ; ceci est habituellement fait en gardant des références vers la donnée.

Si la cible comprend l?action LinkAction, elle devrait stocker sa propre référence vers l?information d?origine ; la source n?a besoin de réaliser aucun autre traitement ultérieur sur la donnée. L?utilisation la plus commune d?une action de glisser-déposer est la réalisation d?un déplacement à l?intérieur du même widget ; voir la section sur Les actions de dépose pour plus d?informations sur cette fonctionnalité.

L?autre utilisation principale des actions de glissement est l?utilisation de références de type text/uri-list, lorsque les données glissées sont en fait des références à des fichiers ou des objets.

Ajout de nouveaux types de glisser-déposer

Le glisser-déposer n?est pas limité au texte et aux images. Tout type d?information peut être transféré dans une opération de glisser-déposer. Pour glisser une information entre des applications, ces applications doivent être capables de s?indiquer entre elles quels formats de données elles peuvent accepter et produire. Ceci est réalisé en utilisant les types MIME. L?objet QDrag construit par la source contient une liste de types MIME qui sont utilisés pour représenter la donnée (triée du plus approprié au moins approprié), et la cible de dépose utilise un de ces types pour accéder à la donnée. Pour les types de données courants, les fonctions utilitaires gèrent les types MIMES utilisés de manière transparente, mais, pour des types de données personnalisés, il est nécessaire de les déclarer explicitement.

Pour implémenter les actions de glisser-déposer pour un type d?information non couvert par les fonctions utilitaires de QDrag, la première et plus importante étape est de chercher des formats existants appropriés : l?organisation Internet Assigned Numbers Authority (IANA) fournit une liste hiérarchique de types de media MIME à l?institut Information Sciences Institute (ISI). L?utilisation des types MIME standard augmente l?interopérabilité de l?application avec les autres logiciels présents et à venir.

Pour prendre en charge un type de média supplémentaire, définissez simplement les données de l?objet QMimeData avec la fonction setData(), en fournissant le type MIME complet et un QByteArray contenant les données dans le format approprié. Le code suivant extrait une pixmap depuis un libellé et la stocke en tant que fichier Portable Network Graphics (PNG) dans un objet QMimeData :

     QByteArray output;
     QBuffer outputBuffer(&output);
     outputBuffer.open(QIODevice::WriteOnly);
     imageLabel->pixmap()->toImage().save(&outputBuffer, "PNG");
     mimeData->setData("image/png", output);

Bien sûr, dans ce cas on aurait pu simplement utiliser setImageData() à la place pour fournir les données de l'image dans des formats variés :

     mimeData->setImageData(QVariant(*imageLabel->pixmap()));

L?approche QByteArray reste utile dans ce cas car elle fournit un plus grand contrôle sur la quantité de données stockée dans l?objet QMimeData.

À noter que les types de données personnalisés utilisés dans les vues d?éléments doivent être déclarés en tant que métaobjets, et que les opérateurs de flux doivent être implémentés pour eux.

Les actions de dépose

Dans le modèle presse-papier, l?utilisateur peut couper ou copier l?information source, puis la coller plus tard. De même, dans le modèle glisser-déposer, l?utilisateur peut glisser une copie de l?information, ou il peut glisser l?information elle-même à une nouvelle position (en la déplaçant). Le modèle glisser-déposer entraîne une complication supplémentaire pour le programmeur : le programme ne sait pas si l?utilisateur souhaite couper ou copier l?information tant que l?opération n'est pas terminée. Ceci ne fait généralement pas de différence lors du glissement d?information entre applications, mais à l?intérieur d?une application il est important de vérifier quelle action de dépôt a été utilisée.

On peut réimplémenter la fonction mouseMoveEvent() pour un widget et démarrer une opération de glisser-déposer avec une combinaison des actions de dépose possibles. Par exemple, on peut vouloir s?assurer que le glissement déplace toujours les objets dans le widget :

 void DragWidget::mouseMoveEvent(QMouseEvent *event)
 {
     if (!(event->buttons() & Qt::LeftButton))
         return;
     if ((event->pos() - dragStartPosition).manhattanLength()
          < QApplication::startDragDistance())
         return;
 
     QDrag *drag = new QDrag(this);
     QMimeData *mimeData = new QMimeData;
 
     mimeData->setData(mimeType, data);
     drag->setMimeData(mimeData);
 
     Qt::DropAction dropAction = drag->exec(Qt::CopyAction | Qt::MoveAction);
     ...
 }

L?action retournée par la fonction exec() peut être par défaut une CopyAction si l?information est déposée dans une autre application mais, si elle est déposée dans un autre widget de la même application, on peut obtenir une action de dépose différente.

Les actions de dépose proposées peuvent être filtrées dans la fonction dragMoveEvent() d?un widget. Toutefois, il est possible d?accepter toutes les actions proposées dans la fonction dragEnterEvent() et laisser l?utilisateur décider de celles qu?il souhaite accepter plus tard :

 void DragWidget::dragEnterEvent(QDragEnterEvent *event)
 {
     event->acceptProposedAction();
 }

Lorsqu?une dépose se produit dans le widget, la fonction de gestion dropEvent() est appelée et l'on peut traiter chaque action possible une par une. Premièrement, on traite les opérations de glisser-déposer à l'intérieur d'un même widget :

 void DragWidget::dropEvent(QDropEvent *event)
 {
     if (event->source() == this && event->possibleActions() & Qt::MoveAction)
         return;

Dans ce cas, on refuse de traiter les opérations de déplacement. Chaque type d?action de dépose que l?on accepte est vérifiée et traitée en conséquence :

     if (event->proposedAction() == Qt::MoveAction) {
         event->acceptProposedAction();
         // Traiter les données de l'événement.
     } else if (event->proposedAction() == Qt::CopyAction) {
         event->acceptProposedAction();
         // Traiter les données de l'événement.
     } else {
         // Ignorer la dépose.
         return;
     }
     ...
 }

À noter que l?on vérifie les actions individuellement dans le code ci-dessus. Comme mentionné plus haut dans la section Dérivation des widgets complexes, il est parfois nécessaire de remplacer l?action de dépôt proposée et d?en choisir une différente parmi l'ensemble des actions de dépose possibles. Pour ce faire, on a besoin de vérifier la présence de chaque action dans la valeur fournie par possibleActions() de l?événement, de définir l?action de dépose avec setDropAction() et d'appeler accept().

Les rectangles de dépose

La fonction dragMoveEvent() du widget peut être utilisée pour limiter les déposes à certaines parties du widget en acceptant seulement les actions de dépôt proposées lorsque le curseur est à l?intérieur de certaines zones. Par exemple, le code suivant accepte toutes les actions de dépose lorsque le pointeur est sur un widget enfant (dropFrame) :

 void Window::dragMoveEvent(QDragMoveEvent *event)
 {
     if (event->mimeData()->hasFormat("text/plain")
         && event->answerRect().intersects(dropFrame->geometry()))
 
         event->acceptProposedAction();
 }

La fonction dragMoveEvent() peut aussi être utilisée si vous devez fournir un retour visuel durant une opération de glisser-déposer, faire défiler la fenêtre ou exécuter une autre action appropriée.

Le presse-papier

Les applications peuvent également communiquer entre elles en mettant des données dans le presse-papier. Pour accéder à celui-ci, on a besoin d?obtenir un objet QClipboard depuis l?objet QApplication :

     clipboard = QApplication::clipboard();

La classe QMimeData est utilisée pour représenter les données transférées vers et depuis le presse-papier. Pour mettre une donnée dans le presse-papier, on peut utiliser les fonctions utilitaires setText(), setImage() et setPixmap(). Ces fonctions sont similaires à celles de la classe QMimeData, sauf qu?elles prennent un argument supplémentaire qui contrôle l?endroit où la donnée est stockée : si Clipboard est spécifié, la donnée est placée dans le presse-papier ; si Selection est spécifié, la donnée est placée dans la sélection de souris (sous X11 seulement). Par défaut, la donnée est mise dans le presse-papier.

Par exemple, on peut copier le contenu d?un QLineEdit vers le presse-papier avec le code suivant :

     clipboard->setText(lineEdit->text(), QClipboard::Clipboard);

Des données avec des types MIME différents peuvent aussi être mises dans le presse-papier. On construit un objet QMimeData, et on fixe la donnée avec la fonction setData() de la façon décrite dans la section précédente ; cet objet peut ensuite être placé dans le presse-papier avec la fonction setMimeData().

La classe QClipboard peut informer l?application sur les changements de la donnée qu?elle contient via son signal dataChanged(). Par exemple, on peut surveiller le presse-papier en connectant ce signal au slot d?un widget :

     connect(clipboard, SIGNAL(dataChanged()), this, SLOT(updateClipboard()));

Le slot connecté à ce signal peut lire la donnée dans le presse-papier en utilisant un des types MIME pouvant être utilisés pour la représenter :

 void ClipWindow::updateClipboard()
 {
     QStringList formats = clipboard->mimeData()->formats();
     QByteArray data = clipboard->mimeData()->data(format);
     ...
 }

Le signal selectionChanged() peut être utilisé sous X11 pour surveiller la sélection de souris.

Exemples

Interopérabilité avec les autres applications

Sous X11, le protocole public XDND est utilisé, sous Windows Qt utilise le standard OLE et Qt pour Mac OS X utilise le Carbon Drag Manager. Sous X11, XDND utilise MIME, donc aucune traduction n?est nécessaire. L?API Qt est la même indépendamment de la plateforme. Sous Windows, les applications compatibles MIME peuvent communiquer en utilisant les noms de formats du presse-papier qui sont des types MIME. Certaines applications Windows utilisent déjà les conventions de nommage MIME pour leurs formats de presse-papier. En interne, Qt utilise QWindowsMime et QMacPasteboardMime pour traduire les formats de presse-papier propriétaires vers et depuis les types MIME.

Sous X11, Qt prend aussi en charge les déposes via le protocole de glisser-déposer Motif. L?implémentation intègre du code écrit à l?origine par Daniel Dardailler et adapté pour Qt par Matt Koss et Nokia. Voici l?avis de droit d?auteur original :

Copyright 1996 Daniel Dardailler. Copyright 1999 Matt Koss

Permission to use, copy, modify, distribute, and sell this software for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of Daniel Dardailler not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. Daniel Dardailler makes no representations about the suitability of this software for any purpose. It is provided « as is » without express or implied warranty.

À noter : le protocole de glisser-déposer Motif autorise uniquement les receveurs à faire une requête de données en réponse à un événement QDropEvent. Si vous essayez de faire une requête de données en réponse par exemple à un événement QDragMoveEvent, un QByteArray vide est retourné.

Remerciements

Merci à Lo?c Leguay pour la traduction, ainsi qu'à Ilya Diallo, Dimitry Ernot et Claude Leloup pour la relecture !

Cette page est une traduction d'une page de la documentation de Qt, écrite par Nokia Corporation and/or its subsidiary(-ies). Les éventuels problèmes résultant d'une mauvaise traduction ne sont pas imputables à Nokia. Qt 4.7
Copyright © 2018 Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. 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.
Vous avez déniché une erreur, une redirection cassée ou tout autre problème, quel qu'il soit ? Ou bien vous désirez participer à ce projet de traduction ? N'hésitez pas à nous contacter ou par MP !
Responsable bénévole de la rubrique Qt : Thibaut Cuvelier -