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.
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. |
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.
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();
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);
pressePapiers.setContents(sélection, null);
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) { ... } }
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.
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(); } }
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(); } }
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();
Un DataFlavor se définit par deux caractéristiques :
De plus, tous les types de données possèdent un nom intelligible (comme "Image JPEG").
§
image/jpeg;class=java.awt.Image
Sun MicroSystems a défini un type MIME pour transférer des objets Java :
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.
§
application/x-java-serialized-object;class=java.lang.String
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.
application/x-java-serialized-object;class=type
- type : la classe qui est retrouvée à partir du Transferable.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 :
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().
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.
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); } } } }
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
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.
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.
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(); } }
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; } }
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); } }
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.
application/x-java-serialized-object;class=NomDeLaClasse
Comme auparavant, vous devez construire votre propre enveloppe de transfert.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.
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); } }
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; } }
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"); } } }