Déplacer et lâcher (drag and drop)

Chapitres traités   

Lorsque vous utilisez le copier-coller pour transmettre des informations entre deux programmes, comme nous l'avons découvert dans l'étude précédente, le Presse-papiers sert d'intermédiaire. La méthaphore déplacer et lâcher supprime cet intermédiaire et permet à deux programmes de communiquer directement.

La plate-forme Java fournit un support simple du déplacer-lâcher, entre des applications Java et des applications natives. Cette étude vous montre comment écrire une application Java qui sert de cible (pour le "lâcher"), avec une autre application servant de source (pour le "déplacer").

Pour revenir sur le fonctionnement du Presse-papiers, je vous invite à revoir l'étude correspondante.
§

Choix du chapitre Qu'est-ce que le déplacer et lâcher ?

Avant d'étudier plus en détail le support de la plate-forme Java pour le déplacer et lâcher, examinons rapidement son interface utilisateur. Nous utiliserons comme exemples l'explorateur de Windows et le programme WordPad.

Sur une autre plate-forme, vous pouvez faire les mêmes expériences sur des programmes natifs disposant des capacités de déplacer-lâcher.
§

Une opération "déplacer" est initiée par un mouvement à l'intérieur d'une source de déplacement. Traditionnellement, cela revient à sélectionner un ou plusieurs éléments, puis à les éloigner de leur position d'origine.

Lorsque vous relâchez le bouton de la souris au-dessus d'une cible qui accepte les opérations de "lâcher", cette cible demande à la source certaines opérations concernant les éléments déplacés, et elle commence diverses opérations :
  1. Si vous lâchez une icône de fichier de l'explorateur de Windows dans Wordpad, ce dernier ouvre le fichier correspondant.
  2. De même si vous amenez l'icône d'un fichier sur l'icône d'un répertoire dans l'explorateur, le fichier sera déplacé vers ce répertoire.
  3. Si vous maintenez enfoncé la touche "Maj" ou "Ctrl" lorsque vous déplacez le curseur de la souris, le type de l'action passe d'un déplacement à une copie, et une copie de fichier est placée dans le répertoire.
  4. Si vous maintenez enfoncées les touches "Maj" et "Ctrl", un raccourci vers ce fichier sera placé dans le répertoire.

    Les autres plates-formes se servent éventuellement d'autres combinaisons de touches pour ces opérations.
    §

Types d'actions

Par conséquent, il existe trois types d'actions, correspondant à trois mouvements :

  1. déplacer ;
  2. copier ;
  3. créer un raccourci.

Le but d'une création de raccourci est d'établir une référence vers l'élément sélectionné. Ces liens ont typiquement besoin d'un support du système d'exploitation hôte (comme des liens symboliques pour des fichiers, ou des liens objets pour les composants d'un document). De plus, ces liens ne sont généralement pas très intéressants pour des programmes travaillant sur plusieurs plates-formes. Pour cette étude, nous nous concentrerons sur l'utilisation du déplacer-lâcher pour copier et déplacer des données.

Tous les programmes ne permettent pas ces types d'actions. Par exemple, si vous amenez un fichier sur WordPad, ce dernier ignore le type d'action et se contente d'ouvrir le fichier.

Il existe généralement un indice visuel indiquant le type d'opération en cours. Au minimum, le curseur de la souris changera. Lorsque le curseur passe au-dessus de cibles éventuelles, sa forme indique si l'opération de lâcher est posible ou non. Si un lâcher est possible, la forme du curseur indique également le type d'action :

Il est également possible de déplacer des éléments en dehors des icônes de fichiers. Par exemple, vous pouvez sélectionner du texte dans WordPad et le déplacer. Essayer de déplacer des fragments de texte dans des cibles acceptant ces fragments et observez leur réaction.

Cette expérience montre un inconvénient du déplacer-lâcher comme mécanisme d'une interface utilisateur. Il peut être difficile de deviner quels éléments vous pouvez déplacer, les endroits sur lesquels vous pouvez les lâcher et ce qui se passe lorsque vous le faites. Comme l'action par défaut "déplacer" peut supprimer l'original, il est normal que la plupart des utilisateurs soient méfiants quant aux expériences de déplacer-lâcher.

 

Choix du chapitre Cibles de lâcher

Dans ce chapitre, nous allons construire une application Java simple qui sera la cible d'un lâcher. Le programme d'exemple ne fait rien de très élaboré, il démontre simplement comment détecter qu'un utilisateur veut initier une action de lâcher, comment accepter cette action et comment analyser les données lâchées.

N'importe quel composant AWT ou Swing peut être la cible d'un lâcher.
§

Création de la cible

Pour cela, vous devez construire un objet java.awt.dnd.DropTarget et passer au constructeur le composant ainsi qu'un écouteur de cible de lâcher. Le constructeur enregistre alors l'objet avec le système de déplacer-lâcher :

DropTarget cible = new DropTarget(composant, écouteur);

Activation ou désactivation de la cible

Vous pouvez activer ou désactiver la cible avec la méthode setActive() de la classe DropTarget. Par défaut, la cible est active :

cible. setActive(true);

Définir le type d'action

Vous pouvez appeler la méthode setDefaultActions() de la classe DropTarget pour définir les opérations de lâcher que vous souhaitez accepter par défaut. Les opérations sont les suivantes :

  1. DndConstants.ACTION_COPY (la touche "Ctrl")
  2. DndConstants.ACTION_MOVE (la touche "Maj")
  3. DndConstants.ACTION_COPY_OR_MOVE
  4. DndConstants.ACTION_LINK (les touches "Maj" + "Ctrl")

Par défaut, toutes les opérations sont autorisées.
§

Implémenter un écouteur sur le lâcher

Vous devez maintenant implémenter un écouteur de cible de lâcher. L'interface DropTargetListener possède cinq méthodes :

  1. void dragEnter(DropTargetDragEvent événement) : appelée lorsque le curseur entre dans le composant de cible de lâcher. Vous avez la possibilité de modifier la forme du curseur pour indiquer que la cible est prête à accepter l'opération.
  2. void dragExit(DropTargetEvent événement) : appelée lorsque le curseur sort du composant de cible de lâcher. Cette méthode n'est pas souvent utilisée. Cependant, si vous construisez un mécanisme élaboré d'indice graphique, vous pouvez en disposer dans cette méthode.
  3. void dragOver(DropTargetDragEvent événement) : appelée en permanence lorsque l'utilisateur déplace le curseur au-dessus du composant cible. Vous pouvez vous en servir pour fournir un indice graphique sur l'effet d'un lâcher. Par exemple, si la cible d'un lâcher est un arbre, vous pouvez mettre en surbrillance le noeud situé sous le curseur, ou même ouvrir le sous-arbre si le curseur passe au-dessus d'un noeud.
  4. void dragActionChanged(DropTargetDragEvent événement) : appelée si l'utilisateur change d'action. Par exemple, si l'utilisateur presse ou relâche les touches "Maj" ou "Ctrl" en déplaçant le curseur au-dessus du composant cible, cette méthode est appelée. Cela vous permet de modifier l'indice graphique et de le faire correspondre à la nouvelle action.
  5. void drop(DropTargetDropEvent événement) : C'est la méthode la plus importante. Elle est appelée lorsque l'utilisateur a effectué un lâcher, normalement en relâchant le bouton de la souris.

    Notez que cette méthode est appelée, que vous ayez indiqué précédemment ou non que vous acceptiez le lâcher.
    §

Les paramètres des méthodes de l'interface DropTargetListener

Examinez attentivement les paramètres des méthodes de l'interface DropTargetListener. Trois d'entre eux sont un DropTargetDragEvent. Cependant, la méthode drop() reçoit un DropTargetDroptEvent et la méthode dragExit() reçoit un DropTargetEvent.

  1. La classe DropTargetEvent est la superclasse des deux autres classes d'événements. Elle ne possède qu'une seule méthode, getDropTargetContext(), qui renvoie une classe ne contenant aucune méthode publique intéressante. Comme le but de la méthode dragExit() est de faire du ménage, vous ne regarderez probablement même pas son paramètre.
  2. Les classes DropTargetDragEvent et DropTargetDropEvent possèdent chacune les méthodes suivantes :

    1. int getDropAction()
    2. Point getLocation()
    3. DataFlavor[ ] getCurrentDataFlavors()
    4. boolean isDataFlavorSupported(DataFlavor type)

    Vous avez besoin de ces méthodes pour déterminer si vous pouvez autoriser un déplacement ou permettre un lâcher et pour fournir un indice graphique.

    Malheureusement, comme ces méthodes ne sont pas disponibles dans la superclasse commune, vous devrez implémenter la logique de test deux fois, une première fois pour le déplacement et une seconde pour le lâcher.

  3. Si vous ne souhaitez pas encourager une action de déplacement, appelez la méthode rejectDrag() de la classe DropTargetDragEvent dans la méthode dragEnter() ou dropActionChanged(). Le curseur se transforme alors en icône d'avertissement. Si l'utilisateur lâche quand même un élément, en dépit de l'avertissement, vous devez appeler la méthode rejectDrop() de la classe DropTargetDropEvent.
  4. La méthode getDropAction() renvoie l'action de lâcher que l'utilisateur essaie d'exécuter. dans la plupart des opérations de déplacer-lâcher, l'action n'est pas à interpréter au sens littéral. Par exemple, si vous déplacez une icône de fichier dans WordPad, la source du déplacement ne doit pas effacer le fichier. En revanche, la cible du lâcher ne doit pas toujours demander à l'utilisateur de presser certaines touches lors du déplacement. Dans cette situation, la cible du lâcher doit accepter des actions de copie ou de déplacement. Dans la méthode drop(), appelez la méthode acceptDrop() de la classe DropTargetDropEvent avec l'action réelle. Dans l'écriture ci-dessous, la source du déplacement effacera les éléments déplacés :

    événement.acceptDrop(DndConstants.ACTION_MOVE);

    Astuce : Etes-vous sûr que vos utilisateurs comprennent la différence entre des opérations de copie et de déplacement, ainsi que les rôles des touches "Maj" et "Ctrl" ? Savent-ils que l'opération de déplacement par défaut (sans touches de modification) efface les éléments déplacés de la source ? dans le cas contraire, il vaut mieux accepter le lâcher suivant :

    événement.acceptDrop(DndConstants.ACTION_COPY);

    Par ailleurs, si vous souhaitez réellement effectuer une distinction entre une copie et un déplacement, appelez simplement :

    événement.acceptDrop(événement. getDropEvent());

Résumé sur la mise en oeuvre d'un écouteur de lâcher

Voici un résumé d'un écouteur de cible de lâcher typique :

Codage correspondant
class EcouteurLâcher implements DropTargetListener {
   // méthodes pratiques
   public boolean isDragAcceptable(DropTargetDragEvent événement) {
      // examine l'action de lâcher et les types de données disponibles
   }

   public boolean isDropAcceptable(DropTargetDropEvent événement) {
      // effectue le même test que dans isDragAcceptable()
   }   
  
   // méthodes spécifiques de l'écouteur
   public void dragEnter(DropTargetDragEvent événement) {
      if (!isDragAcceptable(événement)) {
         événement.rejectDrag();
         return;
      }
   }

   public void dragOver(DropTargetDragEvent événement) {
      // les indices visuels peuvent être ajoutés ici
   }

   public void dropActionChanged(DropTargetDragEvent événement) {
      if (!isDragAcceptable(événement)) {
         événement.rejectDrag();
         return;
      }
   }

   public void dragExit(DropTargetEvent événement) {   }

   public void drop(DropTargetDropEvent événement) {
      if (!isDropAcceptable(événement)) {
         événement.rejectDrop();
         return;
      }      
      événement.acceptDrop(actionRéelle);
      // traite les données de la source de déplacement
      événement.dropComplete(true);
   }   
}

Récupération des données

Une fois que vous avez accepté un lâcher, vous devez analyser les données de la source de déplacement. La méthode getTransferable() de la classe DropTargetDropEvent renvoie une référence vers un objet Transferable. C'est exactement la même interface que celle que nous avons utilisée pour le copier-coller.

Pour revenir sur le fonctionnement du Presse-papiers, je vous invite à revoir l'étude correspondante.
§

  1. Un type de données communément utilisé pour le glisser-déposer plutôt que pour le copier-coller est DataFlavor.javaFileListFlavor. Une liste de fichier décrit un ensemble d'icônes de fichiers qui ont été lâchées sur la cible. L'objet Transferable fournit un objet de type java.util.List dont les éléments sont des objets File. Voici le code pour récupérer ces fichiers :
    DataFlavor[] types = transférable.getTransferDataFlavors();
    DataFlavor type = types[i];
    if (type.equals(DataFlavor.javaFileListFlavor)) {
        java.util.List listeFichiers = (java.util.List)transférable.getTransferData(type);
        Iterator itérateur = listeFichiers.iterator();
        while (itérateur.hasNext()) {
            File fichier = (File)itérateur.next();
            // utilisation de fichier
        }
    }
  2. L'autre type de données qui peut être lâché est le texte. Vous pouvez récupérer le texte dans divers types de données. la plus commode est, bien entendu, DataFlavor.stringFlavor.
  3. Les autres types de données ont un type de données MIME de text/plain ainsi que diverses classes de représentation, notamment InputSream et Byte[ ] (tableau d'octets).
  4. Selon la source du déplacement, vous trouverez aussi peut-être des données dans d'autres formats comme : text/html ou text/plain. Si vous souhaitez lire les données sous ce format, choisissez un format commode, par exemple un flot d'entrée, et obtenez les données comme ceci :
    DataFlavor[] types = transférable.getTransferDataFlavors();
    DataFlavor type = types[i];
    if (type.isMimeTypeEqual("text/html") && type.getRepresentationClass()==InputStream.class) {
        String charset = type.getParameter("charset");
        InputStream flux = new InputStreamReader(transférable.getTransferData(type, charset);
       // lire les données du flux
    }

Mise en oeuvre d'un lâcher de texte

Notre programme d'exemple ne fait rien de très élaboré. Il se contente de vous permettre de lâcher des éléments dans une zone de texte. Lorsque vous commencez à déplacer du texte au-dessus de la zone de texte, l'action de lâcher est affichée. Une fois que vous avez initié un lâcher, les données lâchées sont affichées. Si les données sont dans un format de texte, le programme lit à la fois les encodages ascii et unicode.

Dans notre programme d'exemple, nous ne fournissons aucun indice visuel, à part un curseur d'avertissement qui apparaît automatiquement lorsque vous appelez la méthode rejectDrag(). Par ailleurs, la zone de texte est implémentée par la classe JTextArea qui affiche automatiquement une icône spécifique au lâcher si les données sont acceptées, sans avoir à écrire une seule ligne de code particulière.

Le but de ce programme est simplement de vous fournir une cible de lâcher pour vos expérimentations. Essayez de lâcher un ensemble de fichiers texte ou un fragment de texte à partir d'un autre éditeur. Observez aussi comment une tentative de création de lien est rejetée. Si vous relâchez les touches "Maj" et "Ctrl", l'icône d'avertissement apparaît lorsque vous déplacez un élément vers la zone de texte.

A noter que la classe JTexteArea est déjà capable de faire un lâcher de texte issue d'un autre éditeur sans avoir rien à écrire de spécial. Par contre, le lâcher d'un fichier texte au-dessus de la zone d'édition n'est pas pris en compte.


codage correspondant
package déplacer;

import java.awt.datatransfer.*;
import java.awt.dnd.*;
import java.io.*;
import java.util.*;
import javax.swing.*;

public class Lâcher extends JFrame {
   private LâcherTexte éditeur = new LâcherTexte();
     
   public Lâcher() {
      super("Lâcher");
      add(new JScrollPane(éditeur));
      new DropTarget(éditeur, éditeur);
      setSize(400, 300);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setVisible(true);
   }

   public static void main(String[] args) { new Lâcher(); }
   
   private class LâcherTexte extends JTextArea implements DropTargetListener {
      public boolean isDragAcceptable(DropTargetDragEvent événement) {
         return (événement.getDropAction() & DnDConstants.ACTION_COPY_OR_MOVE) != 0;
      }

      public boolean isDropAcceptable(DropTargetDropEvent événement) {
         return (événement.getDropAction() & DnDConstants.ACTION_COPY_OR_MOVE) != 0;
      }   

      public void dragEnter(DropTargetDragEvent événement) {
         if (!isDragAcceptable(événement)) événement.rejectDrag();
      }

      public void dragOver(DropTargetDragEvent événement) {
         // les indices visuels peuvent être ajoutés ici
      }

      public void dropActionChanged(DropTargetDragEvent événement) {
         if (!isDragAcceptable(événement))  événement.rejectDrag();
      }

      public void dragExit(DropTargetEvent événement) { }

      public void drop(DropTargetDropEvent événement) {
         if (!isDropAcceptable(événement)) {
            événement.rejectDrop();
            return;
         }          
        événement.acceptDrop(DnDConstants.ACTION_COPY);
        Transferable transférable = événement.getTransferable();
         DataFlavor[] types = transférable.getTransferDataFlavors();
         for (DataFlavor type : types) {
            try {
               if (type.equals(DataFlavor.javaFileListFlavor)) {
                  List listeFichiers = (List) transférable.getTransferData(type);
                  Iterator itérateur = listeFichiers.iterator();
                  while (itérateur.hasNext()) {
                     File fichier = (File) itérateur.next();
                     read(new FileReader(fichier), null);
                  }
               } 
               else if (type.equals(DataFlavor.stringFlavor)) {
                  String chaîne = (String) transférable.getTransferData(type);
                  append(chaîne+'\n');
               }
            }
            catch (Exception ex) {  setTitle("Coller impossible"); }
         }
         événement.dropComplete(true);
      }
   }
}

Autre exemple de mise en oeuvre

Un deuxième exemple permet de placer un fichier image sur la zone centrale de la fenêtre. Cette zone centrale est représentée par un JLabel qui est capable de gérer une icône, et par là une image.

codage correspondant
package déplacer;

import java.awt.datatransfer.*;
import java.awt.dnd.*;
import java.io.*;
import java.util.*;
import javax.swing.*;

public class Lâcher extends JFrame {
   private LâcherImage photo = new LâcherImage();
     
   public Lâcher() {
      super("Lâcher des photos");
      add(new JScrollPane(photo));
      new DropTarget(photo, photo);
      setSize(400, 300);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setVisible(true);
   }

   public static void main(String[] args) { new Lâcher(); }
   
   private class LâcherImage extends JLabel implements DropTargetListener {
      public void dragEnter(DropTargetDragEvent événement) {
         if (!événement.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) événement.rejectDrag();
      }

      public void dropActionChanged(DropTargetDragEvent événement) {
         if (!événement.isDataFlavorSupported(DataFlavor.javaFileListFlavor))  événement.rejectDrag();
      }

      public void dragExit(DropTargetEvent événement) { }
      public void dragOver(DropTargetDragEvent événement) { }

      public void drop(DropTargetDropEvent événement) {
         if (!événement.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
            événement.rejectDrop();
            return;
         }          
        événement.acceptDrop(DnDConstants.ACTION_COPY);
        Transferable transférable = événement.getTransferable();
         DataFlavor[] types = transférable.getTransferDataFlavors();
         DataFlavor type = types[0];
         try {
            if (type.equals(DataFlavor.javaFileListFlavor)) {
               List listeFichiers = (List) transférable.getTransferData(type);
               File fichier = (File) listeFichiers.get(0);
               setIcon(new ImageIcon(fichier.getPath()));
            } 
         }
         catch (Exception ex) {  setTitle("Récupération photo impossible"); }         
         événement.dropComplete(true);
      }
   }
}

 

Choix du chapitre Sources de déplacement

Maintenant que nous savons comment implémenter un programme contenant une cible de lâcher, nous allons découvrir comment implémenter une source de déplacement.

Pour transformer un composant en source de déplacement, vous devez obtenir un objet de type java.awt.dnd.DragSource. Pour cela, il suffit d'appeler la méthode statique getDefaultDragSource() de cette classe DragSource. Ensuite, appelez la méthode createDefaultDragGestureReconizer() de cette même classe et passez-lui :
  1. Le composant que vous souhaitez transformer en source de déplacement ;
  2. les actions de lâcher que vous désirez autoriser ;
  3. un objet qui implémente l'interface DragGestureListener.
DragSource source = DragSource.getDefaultDragSource();
source.createDefaultDragGestureReconizer(composant, DnDConstants.ACTION_COPY_OR_MOVE, écouteurGesture);

Interface DragGestureListener

L'interface DragGestureListener possède une seule méthode, dragGestureReconized(). L'identificateur de geste appelle cette méthode dès qu'il a remarqué que l'utilisateur souhaite initier une opération de déplacement.

Dans cette méthode, vous devez construire le Transferable qui sera la cible du lâcher dans sa méthode drop(). Une fois que vous avez assemblé le Tranferable, il suffit d'appeler la méthode startDrag() de la classe DragGestureEvent. Un curseur optionnel peut être fourni, ou null si vous préférez le curseur par défaut, suivi par le Transferable et par l'objet qui implémente l'interface DragSourceListener :
événement.startDrag(null, tranférable, écouteurSource);

Vous devez effectuer les opérations habituelles de définition d'une enveloppe Transferable.
§

L'écouteur de source du déplacement DragSourceListener

L'écouteur de la source du déplacement est averti à plusieurs reprises au cours de l'opération de déplacement. L'interface DragSourceListener possède cinq méthodes :

  1. void dragEnter(DragSourceDragEvent événement)
  2. void dragOver(DragSourceDragEvent événement)
  3. void dragExit(DragSourceEvent événement)
  4. void dropActionChanged(DragSourceDragEvent événement)
  5. void dragDropEnd(DragSourceDropEvent événement)
Les quatres premières méthodes peuvent être utilisées pour fournir des indices visuels quant aux opérations de déplacement. Cependant, de manière générale, ces indices reviennent à la cible du lâcher. Seule, la dernière méthode, dragDropEnd(), est très importante. Cette méthode est appelée lorsque la méthode drop() est terminée.
  1. Pour une opération de déplacement, vous devrez vérifier que le lâcher s'est bien déroulé au moyen de la méthode getDropSuccess() de la classe DragSourceDropEvent. Dans ce cas, il faut mettre à jour la source du déplacement.
  2. Pour une opération de copie, vous n'aurez probablement rien à faire.
  3. Dans cette méthode dragDropEnd(), nous devons nous fonder sur la méthode drop() pour indiquer quel type de lâcher s'est réellement produit. Rappelez-vous que la méthode drop() peut modifier une action de déplacement en action de copie, si la source permet ces deux actions. La méthode getDropAction() de la classe DragSourceDropEvent renvoie alors l'action que la cible de lâcher a reçue lors de l'appel à la méthode acceptDrop() de la classe DropTargetDropEvent.
    public void dragDropEnd(DragSourceDropEvent événement) {
       if (événement.getDropSuccess()) {
          int action = événement.getDropAction();
          if (action == DndConstanst.ACTION_MOVE)
             ...
       }
    }
    

Attention : L'action par défaut est un déplacement. Si vous déplacez, par exemple, un fichier d'un composant de liste et que vous le lachiez dans l'explorateur, ce dernier déplace le fichier dans le répertoire cible et n'existe plus dans la source.

Java introduit une classe d'aide intitulé DragSourceAdapter qui implémente toutes les méthodes de l'interface DragSourceListener comme des méthodes inactives.
§

Exemple de mise en oeuvre

Je vous propose de reprendre l'ossature de l'application précédente. Cette fois-ci, il est possible de récupérer l'image qui se trouve sur la partie centrale de la fenêtre afin qu'elle puisse être utilisée par un autre programme capable de prendre en compte une image par glisser-déposer.

codage correspondant
package déplacer;

import java.awt.Cursor;
import java.awt.datatransfer.*;
import java.awt.dnd.*;
import java.io.*;
import javax.swing.*;

public class Déplacer extends JFrame implements DragGestureListener {
   private PanneauImage photo = new PanneauImage(new ImageIcon("C:/Photos/Papillon.jpg"));
     
   public Déplacer() {
      super("Lâcher des photos");
      add(new JScrollPane(photo));
      DragSource source = DragSource.getDefaultDragSource();
      source.createDefaultDragGestureRecognizer(photo, DnDConstants.ACTION_COPY, this);
      setSize(400, 300);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setVisible(true);
   }

   public static void main(String[] args) { new Déplacer(); }
  
   public void dragGestureRecognized(DragGestureEvent événement) {      
      événement.startDrag(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR), photo);
   }
   
   private class PanneauImage extends JLabel implements Transferable {
      public PanneauImage(Icon image) {
         super(image);
      }      
      @Override
      public DataFlavor[] getTransferDataFlavors() {
         return new DataFlavor[] { DataFlavor.imageFlavor };
      }
      @Override
      public boolean isDataFlavorSupported(DataFlavor flavor) {
         return flavor.equals(DataFlavor.imageFlavor);
      }
      @Override
      public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
         if (flavor.equals(DataFlavor.imageFlavor)) {
            ImageIcon icône = (ImageIcon) getIcon();
            return icône.getImage();
         }
         else {
            throw new UnsupportedFlavorException(flavor);
         }
      }   
   }
}

 

Choix du chapitre Les composants Swing

Les composants Swing possèdent un support intégré pour le transfert de données qui permet de libérer les programmeurs d'une grande partie de la tâche consistant à implémenter le copier-coller ou le déplacer-lâcher.

Le tableau ci-dessous résume les composants Swing qui sont sources et cibles du transfert de données. Les raccourcis clavier standards pour "Couper", "Copier" et "Coller" sont activés pour tous les composants à l'exception de JColorChooser. Le déplacement n'est pas activé par défaut. Vous devez appeler la méthode setDragEnabled() pour l'activer.

Composant Source de transfert Cible de transfert
JFileChooser Exporte la liste des fichiers Non admis.
JColorChooser Exporte la référence locale à l'objet Color Accepte n'importe quel objet Color
JTextField
JFormattedTextField
Exporte le texte sélectionné Accepte le texte
JPasswordField Non admis (pour des raisons de sécurité) Accepte le texte
JTextArea
JTextPane
JEditorPane
Exporte le texte sélectionné Accepte le texte et les listes de fichiers. Le texte est inséré. Les fichiers sont ouverts.
JList Exporte la description HTML d'une sélection Non admis.
Le paquetage de Swing fournit un mécanisme assez utile pour transformer rapidement un composant en cible de lâcher. Si le composant possède une méthode :

void setName(Type t)

Vous la transformez ensuite en cible de lâcher pour les types de données avec une classe de représentation Type en appelant simplement la méthode :

composant.setTransferHandler("nom"); // "nom" représente le nom de la propriété du composant que l'on désire utiliser

Au moment du lâcher, le gestionnaire vérifie alors si l'un des types de données possède une classe de représentation Type. Si c'est le cas, il appelle la méthode setName().

Supposons, par exemple, que vous souhaitiez utiliser le déplacer-lâcher pour modifier la couleur de fond d'un champ de texte. Vous avez besoin d'un gestionnaire de transfert qui invoque la méthode :

void setBackground(Color couleur)

lorsqu'un objet Color est déplacé sur le champ de texte. Appelez simplement :

champTexte.setTransferHandler(new TransferHandler("background"));

Attention, en installant ce gestionnaire de transfert dans le champ de texte, vous désactivez le gestionnaire de transfert standard. Vous ne pouvez plus couper, copier, déplacer ou lâcher du texte dans le champ de texte. Un composant Swing peut uniquement disposer d'un gestionnaire de transfert.

L'exemple suivant montre ce comportement. Comme nous pouvons le voir, le haut du bloc contient un sélecteur de couleur, alors qu'un champ de texte se trouve en bas du bloc contenant le message "Faites glisser la couleur ici". Vous devez faire glisser la couleur depuis l'intérieur du panneau "Aperçu", et non à partir des nuances des couleurs. Lorsque vous la faites glisser dans le champ de texte, son fond change de couleur.

codage correspondant
package déplacer;

import java.awt.*;
import javax.swing.*;

public class Lâcher extends JFrame {
   private JTextField saisie = new JTextField("Faites glisser la couleur ici");
   private JColorChooser choix = new JColorChooser();
   
   public Lâcher() {
      super("Lâcher de couleur");      
      choix.setDragEnabled(true);
      add(choix);
      saisie.setDragEnabled(true);
      saisie.setTransferHandler(new TransferHandler("background"));
      add(saisie, BorderLayout.SOUTH);
      pack();
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setVisible(true);
   }

   public static void main(String[] args) { new Lâcher(); }
}