Presse-papiers

Chapitres traités   

L'un des mécanisme les plus puissants et les plus pratiques d'une interface utilisateur graphique est le copier-coller. Vous pouvez ainsi sélectionner des données dans un programme et les copier ou les couper dans le Presse-papiers. Ensuite, il suffit de sélectionner un autre programme et de coller le contenu du Presse-papiers dans cette application.

Grâce au Presse-papiers, vous pouvez transférer du texte, des images ou d'autres données d'un document à un autre, ou naturellement d'un endroit d'un document à un autre endroit du même document. Le copier-coller est tellement naturel que la plupart des utilisateurs d'ordinateurs n'y pensent même pas.

Choix du chapitre Problématiques du Presse-papiers

Même si le principe du Presse-papiers est assez simple, l'implémentation des services du Presse-papiers est en fait bien plus complexe que ce que vous pourriez imaginer. Supposons que vous copiiez du texte à partir d'un traitement de texte vers le Presse-papiers. Si vous collez ce texte dans un autre traitement de texte, vous êtes en droit de vous attendre à ce que les polices et le format de ce texte restent intacts. C'est-à-dire que le texte du Presse-papiers doit conserver les informations de formatage.

Mais, si vous collez le même texte dans un champ de texte simple, vous ne voulez conserver que le texte lui-même, sans les codes de formatage. Pour supporter cette flexibilité, le fournisseur des données peut proposer les données du Presse-papiers sous divers formats parmi lesquels l'utilisateur choisira.

Les implémentations du Presse-papiers système de Microsoft Windows, OS/2 ou Macintosh sont similaires, mais il existe naturellement de légères différences. De plus le mécanisme du Presse-papiers système X Windows est bien plus limité : le copier-coller des éléments différents du texte simple n'est bien supporté que sporadiquement. Il conviendra de prendre ces limites en compte lorsque vous testerez les programmes de cette étude.

Souvent, les programmes doivent supporter le copier-coller pour des types de données que le Presse-papiers Système ne peut gérer. L'API de transfert de données Java supporte le transfert de références d'objets arbitraires dans la même machine virtuelle. Entre différentes machines virtuelles, il est possible de transférer des objets sérialisés et des références à des objets distants.

Le tableau suivant résume les possibilités et les capacités de transfert de données de Java :

Transfert Format
Entre le programme écrit dans le langage de programmation Java et un programme natif. Texte, images, listes de fichiers, etc. (en fonction de la plate-forme hôte).
Entre deux programmes coopérants et écrits dans le langage de programmation Java. Objets sérialisés et distants.
A l'intérieur d'un programme écrit dans le langage de programmation Java. N'importe quel objet.

 

Choix du chapitre Classes et interfaces pour le transfert de données

Dans la technologie Java, le transfert de données est implémenté dans une bibliothèque appelée java.awt.transfert. Voici un aperçu des classes et des interfaces les plus importantes de ce paquetage.

  1. Les objets qui peuvent être transférés via un Presse-papiers doivent implémenter l'interface Transferable.
  2. La classe Clipboard décrit un Presse-papiers. Les seuls objets qui peuvent être transférés sont des éléments qui peuvent être placés dans un Presse-papiers, ou en être retirés. Le Presse-papiers système est un bon exemple de ce type de Clipboard.
  3. La classe DataFlavor décrit le type de données qui peuvent être placées dans un Presse-papiers.
  4. La classe StringSelection est une classe concrète qui implémente l'interface Transferable. Cette classe est utilisée pour transférer des chaînes de texte.
  5. Une classe doit implémenter l'interface ClipboardOwner si elle souhaite être avertie du moment où le contenu du Presse-papiers a été écrasé par une autre personne. Le propriétaire du Presse-papiers permet la "mise en forme retardée" de données complexes. Si un programme transfère des données simples (comme une chaîne de caractères), il définit simplement le contenu du Presse-papiers et passe à l'action suivante. Toutefois, si un programme souhaite placer des données complexes qui peuvent être mises en forme dans plusieurs types de données, puisqu'il y a de grandes chances que la plupart d'entre elles ne soient jamais nécessaires. Cependant, il doit alors attendre les données du Presse-papiers afin de créer les types de données par la suite, au moment où ils seront nécessaires. Le propriétaire du Presse-papiers est averti (par un appel à sa méthode lostOwnership()) du moment où le contenu du Presse-papiers a changé. Cela lui indique que les informations ne sont plus nécessaires.

 

Choix du chapitre Transférer du texte

La meilleure manière de se familiariser avec les classes de transfert de données est de commencer avec la plus simple des situations supportées : transférer du texte avec le Presse-papiers système. Nous devons commencer par obtenir une référence au Presse-papiers système :

Clipboard pressePapiers = Toolkit.getDefaultToolkit().getSystemClipboard();

Copier le texte

Pour que des chaînes puissent être transférées vers le Presse-papiers, elles doivent être emballées dans des objets de type StringSelection. Le constructeur prend le texte que vous souhaitez transférer :

String texte = ...
StringSelection sélection = new StringSelection(texte);

Le transfert est réellement effectué par un appel à la méthode setContents() du Presse-papiers (la classe Clipboard), qui prend un objet StringSelection et un ClipboardOwner comme paramètres. Si vous ne souhaitez pas désigner le propriétaire d'un Presse-papiers, vous pouvez choisir null comme second paramètre :

pressePapiers.setContents(sélection, null);

Coller le texte

Intéressons-nous maintenant à l'opération inverse, la lecture d'une chaîne de caractère à partir du Presse-papiers. Appelez la méthode getContents() du Presse-papiers (la classe Clipboard) pour obtenir un objet Transferable.

Transferable contenu = pressePapiers.getContents(this);

Le paramètre de l'appel à la méthode getContents() est une référence Object de l'objet qui affecte la requête.
§

La valeur de retour de getContents() peut être null. Cela indique que le Presse-papiers est vide ou qu'il ne contient aucune donnée que la plate-forme Java puisse récupérer.

L'objet Transferable peut indiquer le format des données du Presse-papiers. Nous reviendrons un peu plus tard sur les détails de l'identification des types de données. Pour l'instant, nous voulons simplement le type standard appelé DataFlavor.stringFlavor. Utilisez la méthode isDataPlavorSupported() pour déterminer si un type donné est réellement disponible.

Pour terminer, le type choisi est passé à la méthode getTransferData() de l'interface Transferable. Cet appel de méthode doit se trouver dans un bloc try-catch parce que la méthode a tendance à déclencher une UnsupportedFlavorException si le type spécifié n'est pas disponible, ou une IOException si elle ne peut pas lire les données.

DataFlavor flavor = DataFlavor.stringFlavor;
if (contenu.isDataFlavorSupported(flavor)) {
  try {
    String texte = (String)contenu.getTransfertData(flavor);
    ... // utilisattion du texte
  catch (UnsupportedFlavorException ex) { ... }
  catch (IOException ex) { ... }
}

Exemple de mise en oeuvre

Je vous propose de mettre en pratique ces nouvelles connaissances en fabriquant un tout petit éditeur de texte simple, par l'intermédiaire de la classe JTextArea, qui est capable de faire du copier-coller au travers du Presse-papiers système.

codage correspondant

import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.event.ActionEvent;
import javax.swing.*;

public class PressePapiers extends JFrame {
   private JTextArea éditeur = new JTextArea();
   private JToolBar boutons = new JToolBar();
   
   public PressePapiers() {
      super("Editeur simple");
      éditeur.setBackground(Color.ORANGE);
      add(new JScrollPane(éditeur));
      add(boutons, BorderLayout.NORTH);
      boutons.add(new AbstractAction("Copier") {
         @Override
         public void actionPerformed(ActionEvent e) {
            Clipboard pressePapiers = Toolkit.getDefaultToolkit().getSystemClipboard();
            String texte = éditeur.getSelectedText();
            if (texte==null) texte = éditeur.getText();
            StringSelection sélection = new StringSelection(texte);
            pressePapiers.setContents(sélection, null);
         }
      });
      boutons.add(new AbstractAction("Coller") {
         @Override
         public void actionPerformed(ActionEvent e) {
            Clipboard pressePapiers = Toolkit.getDefaultToolkit().getSystemClipboard();
            Transferable contenu = pressePapiers.getContents(this);
            if (contenu==null) return;
            DataFlavor flavor = DataFlavor.stringFlavor;
            if (contenu.isDataFlavorSupported(flavor)) {
               try {
                  String texte = (String) contenu.getTransferData(flavor);
                  éditeur.replaceSelection(texte);
               } 
               catch (Exception ex) { setTitle("Problème avec le Presse-papiers"); }
            }
         }
      });     
      setSize(300, 250);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setVisible(true);
   }

   public static void main(String[] args) { new PressePapiers(); }
}
Il s'agit ici d'un cas d'école. En effet, la classe JTextArea est très sophistiquée puisqu'elle dispose déjà du mécanisme de gestion du Presse-papiers système au travers de ses méthodes copy(), cut() et paste().

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

public class PressePapiers extends JFrame {
   private JTextArea éditeur = new JTextArea();
   private JToolBar boutons = new JToolBar();
   
   public PressePapiers() {
      super("Editeur simple");
      éditeur.setBackground(Color.ORANGE);
      add(new JScrollPane(éditeur));
      add(boutons, BorderLayout.NORTH);
      boutons.add(new AbstractAction("Copier") {
         @Override
         public void actionPerformed(ActionEvent e) {
            éditeur.copy();
         }
      });
      boutons.add(new AbstractAction("Coller") {
         @Override
         public void actionPerformed(ActionEvent e) {
            éditeur.paste();
         }
      });     
      setSize(300, 250);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setVisible(true);
   }

   public static void main(String[] args) { new PressePapiers(); }
}

 

Choix du chapitre L'interface Transferable et les types de données

Le Presse-papiers contient un objet qui implémente l'interface Transferable. Vous trouverez tous les types de données supportés par un objet Transferable au moyen de la méthode getTransferDataFlavors() :

DataFlavor[] flavors = transférable.getTransferDataFlavors();

Le type MIME d'un DataFlavor

Un DataFlavor se définit par deux caractéristiques :

  1. Un nom de type MIME (comme "image/jpeg") ;
  2. Une classe de représentation pour accéder aux données (comme java.awt.Image).

De plus, tous les types de données possèdent un nom intelligible (comme "Image JPEG").
§

La classe de représentation peut être spécifiée avec un paramètre class dans le type MIME, comme ceci :

image/jpeg;class=java.awt.Image

Si aucun paramètre class n'est fourni, la classe de représentation est alors InputStream.

Sun MicroSystems a défini un type MIME pour transférer des objets Java :

  1. application/x-java-jvm-local-objectref : objets locaux.
  2. application/x-java-serialized-object : objets sérialisés.
  3. application/x-java-remote-object : objets distants.

Le préfixe x indique qu'il s'agit d'un nom expérimental, qui n'est pas ratifié par l'INIA, l'organisation qui dépose les noms de types standard MIME.
§

Par exemple, le type de données standard stringFlavor est décrit par le type MIME :

application/x-java-serialized-object;class=java.lang.String

Création d'un DataFlavor avec le type MIME souhaité

La classe DataFlavor possède des constructeurs pour permettre la création du type de données à transférer adaptés à la situation. Par ailleurs, il peut être intéressant de retrouver ultérieurement les différents types choisis au travers de méthodes qui nous procurent ce type de renseignement.

La classe DataFlavor
DataFlavor(String type, String explication)
Crée un type de données qui décrit les données d'un flot dans un format correspondant à un type MIME.
- type : une chaîne de type MIME.
- explication : une version plus visible du nom.
DataFlavor(Class type, String explication)
Crée un type de données qui décrit une classe de la plate-forme Java. Son type MIME est

application/x-java-serialized-object;class=type

- type : la classe qui est retrouvée à partir du Transferable.
- explication : une version plus visible du nom.
String getMimeType()
Renvoie une chaîne de type MIME pour ce type de données.
boolean isMimeTypeEqual(String type)
Vérifie que ce type de données correspond au type MIME spéifié.
String getHumanPresentableName()
Renvoie un nom intelligible pour le format de données de ce type de données.
Class getRepresentationClass()
Renvoie un objet Class qui représente la classe de l'objet qu'un Transferable renverra lorsqu'il sera appelé avec ce type de données. Il s'agit soit d'un paramètre class du type MIME, soit de InputStream.
String getPrimaryType()
Renvoie uniquement la toute première partie du type MIME, celle qui se trouve avant le symbole "/", comme image, application, etc.
String getSubType()
Renvoie également une partie du type MIME, celle qui se situe entre le symbole "/" et le symbole ";", avant le terme class, comme jpeg, application/x-java-serialized-object, etc.

Constantes prédéfinies

La classe DataFlavor possède des attributs constants prédéfinis qui permettent de répondre à des schémas classiques de transfert de données, comme la constante stringFlavor pour les chaînes de caractères :

  1. imageFlavor : représente un transfert d'image.
  2. javaFileListFlavor : représente un transfert de liste de fichiers (très utile pour le déplacer et lâcher que nous utiliserons lors de la prochaine étude).
  3. stringFlavor : représente un transfert de texte.

 

Choix du chapitre Construire une image transférable

Les objets que vous souhaitez transférer par le Presse-papiers doivent implémenter l'interface Transferable. La classe StringSelection est actuellement la seule classe publique de la bibliothèque standard Java qui implémente l'interface Transferable. Nous allons voir, dans ce chapitre, comment transférer des images en passant par le Presse-papiers. Etant donné que le SDK ne fournit pas de classe pour le tranfert d'image, nous devrons l'implémenter nous-même.

Comme nous l'avons vu dans le chapitre précédent, le SDK fournit la constante DataFlavor.imageFlavor et effectue le lourd travail permettant de convertir des images Java et des images natives du Presse-papiers. Toutefois, curieusement, il ne fournit pas de classe enveloppe nécessaire pour placer les images dans le Presse-papiers.

Lorsque vous implémentez l'interface Transferable, vous devez alors redéfinir les méthodes suivantes : getTransferData(), getTransferDataFlavors() et isDataFlavorSupported().

Interface Transferable
Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException
Renvoie la donnée formatée selon le type spécifié. Déclenche une UnsupportedFlavorException si le type spécifié n'est pas supporté.
DataFlavor[] getTransferDataFlavors()
Renvoie un tableau des types supportés.
boolean isDataFlavorSupported(DataFlavor flavor)
Renvoie true si le type spécifié fait partie des types de données supportés et false sinon.

Exemple de mise en oeuvre

A titre d'exemple, je vous propose de mettre en place une toute petite application à l'intérieure de laquelle il sera possible de faire du copier-coller sur des images. L'ossature globale du programme est d'ailleurs très similaire à l'éditeur précédent.

codage correspondant
import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.event.ActionEvent;
import java.io.IOException;
import javax.swing.*;

public class PressePapiers extends JFrame {
   private JToolBar boutons = new JToolBar();
   private PanneauImage panneauImage = new PanneauImage();
   
   public PressePapiers() {
      super("Editeur de photo");
      add(new JScrollPane(panneauImage));
      add(boutons, BorderLayout.NORTH);
      boutons.add(new AbstractAction("Copier") {
         @Override
         public void actionPerformed(ActionEvent e) {
            Clipboard pressePapiers = Toolkit.getDefaultToolkit().getSystemClipboard();
            pressePapiers.setContents(panneauImage, null);            
         }
      });
      boutons.add(new AbstractAction("Coller") {
         @Override
         public void actionPerformed(ActionEvent e) {
            Clipboard pressePapiers = Toolkit.getDefaultToolkit().getSystemClipboard();
            Transferable contenu = pressePapiers.getContents(null);
            if (contenu==null) return;
            DataFlavor flavor = DataFlavor.imageFlavor;
            if (contenu.isDataFlavorSupported(flavor)) {
               try {
                  Image image = (Image) contenu.getTransferData(flavor);
                  panneauImage.setIcon(new ImageIcon(image));
               } 
               catch (Exception ex) { setTitle("Problème avec le Presse-papiers"); }
            }
         }
      });     
      setSize(300, 250);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setVisible(true);
   }

   public static void main(String[] args) { new PressePapiers(); }
   
   private class PanneauImage extends JLabel implements Transferable {
      @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 Utiliser un Presse-papiers local pour transférer des objets

Supposons que vous souhaitiez copier-coller un type de données qui ne soit pas l'un des types de données supporté par le Presse-papiers système. Dans un programme de dessin, par exemple, vous pourriez vouloir permettre à vos utilisateurs de copier-coller des formes arbitraires.

Pour transférer un objet Java arbitraire dans la même JVM, vous utilisez le type MIME :

application/x-java-jvm-local-objectref;class=NomDeLaClasse

Toutefois, le SDK n'inclut pas d'enveloppe Transferable pour ce type. Une référence d'objet n'est significative que dans une seule machine virtuelle. Pour cette raison, il est impossible de copier l'objet dans le Presse-papiers système. Au lieu de cela, il sera nécessaire que le programme utilise un Presse-papiers local :

Clipboard pressePapiers = new Clipboard("local");

Le paramètre de construction est le nom du Presse-papiers que vous pouvez choisir à votre convenance.
§

Toutefois, l'utilisation d'un Presse-papiers local présente un inconvénient majeur. Vous devez synchroniser les Presse-papiers local et système, afin que les utilisateurs ne confondent pas les deux. Actuellement, la plate-forme Java n'effectue pas de synchronisation pour vous.

Exemple de mise en oeuvre

Je vous propose de voir un exemple d'école qui ne présente peu d'utilité si ce n'est de comprendre le mécanisme du Presse-papiers local. Il s'agit d'une application qui permet de calculer une somme avec les taxes.

Classe principale de l'application pressepapiers.PressePapiers
package pressepapiers;

import java.awt.FlowLayout;
import java.awt.datatransfer.*;
import java.awt.event.*;
import java.text.NumberFormat;
import javax.swing.*;

public class PressePapiers extends JFrame{
   private JFormattedTextField euro = new JFormattedTextField(NumberFormat.getCurrencyInstance());
   private JFormattedTextField tva = new JFormattedTextField(19.6);
   private JFormattedTextField résultat = new JFormattedTextField(NumberFormat.getCurrencyInstance());
   private JButton copier = new JButton("Copier");
   private JButton coller = new JButton("Coller");
   private Clipboard pressePapiers = new Clipboard("local");
   
   public PressePapiers() {
      super("Copier");
      setLayout(new FlowLayout());
      euro.setValue(0);
      euro.setColumns(15);
      add(euro);
      add(new JLabel("HT"));
      tva.setColumns(5);
      add(tva);
      copier.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            Number valeurEuro = (Number) euro.getValue();
            Number valeurTVA = (Number) tva.getValue();
            Calcul calcul = new Calcul(valeurEuro.doubleValue(), valeurTVA.doubleValue());
            SélectionLocale locale = new SélectionLocale(calcul);
            pressePapiers.setContents(locale, null);
         }
      });
      add(copier);
      coller.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            Transferable contenu = pressePapiers.getContents(null);
            if (contenu==null) {
               setTitle("Presse-papiers vide");
               return;
            }
            String typeMIME = "application/x-java-jvm-local-objectref;class="+Calcul.class.getName();
            try {
               DataFlavor flavor = new DataFlavor(typeMIME);
               if (contenu.isDataFlavorSupported(flavor)) {
                  Calcul calcul = (Calcul) contenu.getTransferData(flavor);
                  résultat.setValue(calcul.getRésultat());
               }           
            } 
            catch (Exception ex) { setTitle("Problème de transfert"); }
         }
      });
      add(coller);
      résultat.setValue(0.0);
      résultat.setEditable(false);
      résultat.setColumns(15);      
      add(résultat);
      add(new JLabel("TTC"));
      pack();
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setVisible(true);
   }

   public static void main(String[] args) { new PressePapiers(); }
}
Classe qui implémente l'interface Transferable : pressepapiers.SélectionLocale
package pressepapiers;

import java.awt.datatransfer.*;
import java.io.IOException;
/**
 * Cette classe est une enveloppe pour le transfert de données des
 * références d'objet transférées dans la même machine virtuelle 
 */
public class SélectionLocale implements Transferable {
   private Object sélection;
   /**
    * Construit la sélection
    * @param sélection : n'importe quel type d'objet
    */
   public SélectionLocale(Object sélection) {
      this.sélection = sélection;
   }
   
   public DataFlavor[] getTransferDataFlavors() {
      DataFlavor[] flavors = new DataFlavor[1];
      Class type = sélection.getClass();
      String typeMIME = "application/x-java-jvm-local-objectref;class="+type.getName();
      try {
         flavors[0] = new DataFlavor(typeMIME);
         return flavors;
      } 
      catch (ClassNotFoundException ex) {
         return new DataFlavor[0];
      }      
   }

   public boolean isDataFlavorSupported(DataFlavor flavor) {
      return "application".equals(flavor.getPrimaryType())
        && "x-java-jvm-local-objectref".equals(flavor.getSubType())
        && flavor.getRepresentationClass().isAssignableFrom(sélection.getClass());
   }

   public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
      if (!isDataFlavorSupported(flavor)) throw new UnsupportedFlavorException(flavor);
      return sélection;
   }
}
Type d'objet à transférer : pressepapiers.Calcul
package pressepapiers;

public class Calcul {
   private double euro;
   private double tva;
   
   public Calcul(double euro, double tva) {
      this.euro = euro;
      this.tva = tva;
   }
   
   public double getRésultat() { return euro*(1.0+tva/100.0); }
}

 

Choix du chapitre Transférer des objets Java avec le Presse-papiers système

Supposons maintenant que vous souhaitiez copier et coller des objets d'une application Java (processus) vers une autre application (autre processus). Vous ne pouvez plus alors utiliser les Presse-papiers locaux. Fort heureusement, vous pouvez placer des objets Java sérialisés dans le Presse-papiers système.

Pour activer le transfert de données, la plate-forme Java place des données binaires dans le Presse-papier système qui contient l'objet sérialisé. Un autre programme Java, qui n'est pas nécessairement du même type que celui qui a généré les données du Presse-papiers, est capable de récupérer les données du Presse-papiers et de désérialiser l'objet.

Bien entendu, une application non-Java ne saura que faire des données du Presse-papiers. Pour l'essentiel, aucune programmation supplémentaire n'est exigée pour transférer un objet sérialisable. Vous devez juste utiliser le type MIME :

application/x-java-serialized-object;class=NomDeLaClasse

Comme auparavant, vous devez construire votre propre enveloppe de transfert.

Exemple de mise en oeuvre

Nous allons reprendre l'ossature générale de l'application précédente. Cette fois-ci toutefois, nous allons fabriquer deux applications indépendantes qui permettront d'établir une communication inter-processus au travers du Presse-papier système. La première application s'occupe de récupérer la valeur HT ainsi que la TAXE souhaitée d'une somme quelconque, la deuxième récupère ces valeurs et réalise le traitement. Cette fois-ci également, la classe Transferable et la classe à sérialiser sont une seule et même classe.

Classe principale de l'application pressepapiers.PressePapiersCopier
package pressepapiers;

import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.event.*;
import java.text.NumberFormat;
import javax.swing.*;

public class PressePapiersCopier extends JFrame implements ActionListener {
   private JFormattedTextField euro = new JFormattedTextField(NumberFormat.getCurrencyInstance());
   private JFormattedTextField tva = new JFormattedTextField(19.6);
   private JButton copier = new JButton("Copier");
   
   public PressePapiersCopier() {
      super("Copier");
      setLayout(new FlowLayout());
      euro.setValue(0);
      euro.setColumns(15);
      add(euro);
      add(new JLabel("HT"));
      tva.setColumns(5);
      add(tva);
      copier.addActionListener(this);
      add(copier);
      pack();
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setVisible(true);
   }

   public static void main(String[] args) { new PressePapiersCopier(); }

   public void actionPerformed(ActionEvent e) {
      Number valeurEuro = (Number) euro.getValue();
      Number valeurTVA = (Number) tva.getValue();
      TransfertObjet objet = new TransfertObjet(valeurEuro.doubleValue(), valeurTVA.doubleValue());
      Clipboard pressePapiers = Toolkit.getDefaultToolkit().getSystemClipboard();
      pressePapiers.setContents(objet, null);
   }
}
Classe qui implémente l'interface Transferable : pressepapiers.TransfertObjet
package pressepapiers;

import java.awt.datatransfer.*;
import java.io.*;

public class TransfertObjet implements Transferable, Serializable {
   private double euro;
   private double tva;
   
   public TransfertObjet(double euro, double tva) {
      this.euro = euro;
      this.tva = tva;
   }

   public double getRésultat() { return euro*(1.0+tva/100.0); }
      
   public DataFlavor[] getTransferDataFlavors() {
      DataFlavor[] flavors = new DataFlavor[1];
      String typeMIME = "application/x-java-serialized-object;class="+this.getClass().getName();
      try {
         flavors[0] = new DataFlavor(typeMIME);
         return flavors;
      } 
      catch (ClassNotFoundException ex) {
         return new DataFlavor[0];
      }      
   }

   public boolean isDataFlavorSupported(DataFlavor flavor) {
      return "application".equals(flavor.getPrimaryType())
        && "x-java-serialized-object".equals(flavor.getSubType())
        && flavor.getRepresentationClass().isAssignableFrom(this.getClass());
   }

   public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
      if (!isDataFlavorSupported(flavor)) throw new UnsupportedFlavorException(flavor);
      return this;
   }
}
Type d'objet à transférer : pressepapiers.PressePapiersColler
package pressepapiers;

import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.event.*;
import java.text.NumberFormat;
import javax.swing.*;

public class PressePapiersColler extends JFrame implements ActionListener {
   private JFormattedTextField résultat = new JFormattedTextField(NumberFormat.getCurrencyInstance());
   private JButton coller = new JButton("Coller");
   
   public PressePapiersColler() {
      super("Coller");
      setLayout(new FlowLayout());
      coller.addActionListener(this);
      add(coller);
      résultat.setValue(0.0);
      résultat.setEditable(false);
      résultat.setColumns(15);
      add(résultat);
      add(new JLabel("TTC"));
      pack();
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setVisible(true);
   }

   public static void main(String[] args) { new PressePapiersColler(); }

   public void actionPerformed(ActionEvent e) {
      Clipboard pressePapiers = Toolkit.getDefaultToolkit().getSystemClipboard();
      Transferable contenu = pressePapiers.getContents(null);
      if (contenu==null) {
         setTitle("Presse-papiers vide");
         return;
      }
      String typeMIME = "application/x-java-serialized-object;class="+TransfertObjet.class.getName();
      try {
         DataFlavor flavor = new DataFlavor(typeMIME);
         if (contenu.isDataFlavorSupported(flavor)) {
            TransfertObjet objet = (TransfertObjet) contenu.getTransferData(flavor);
            résultat.setValue(objet.getRésultat());
         }           
      } 
      catch (Exception ex) { setTitle("Problème de transfert"); }
   }
}