Java – Paquete java.awt.datatransfer – Manejando Portapapeles

Normalmente cuando hablamos de el portapapeles (en inglés: Clipboard), estamos hablando de una herramienta del Sistema Operativo que permite almacenar temporalmente cualquier tipo de datos. Este puede admitir simple texto o el contenido de cualquier tipo de archivo, los cuales son usualmente agregados con las funciones “Copiar”, “Cortar” y accedidos con las funciones “Pegar”, “Mover”. Normalmente solo puede haber un tipo de dato o archivo a la vez, ya que el contenido es constantemente reemplazado por uno nuevo.

En Java desde la versión 1.1, en su API estándar, tenemos a nuestra disposición el paquete java.awt.datatransfer el cual contiene un grupo de clases que nos permite trabajar con portapapeles. Estas clases son:

Nombre Tipo Descripción
Clipboard Clase Objeto que representa un portapapeles.
ClipboardOwner Interface Se usa para recibir notificación de que el contenido del portapapeles ah sido reemplazado.
Transferable Interface Representa los objetos que se quiera introducir o extraer del portapapeles. Todos estos objetos deben de implementar esta interface
DataFlavor Clase Representa los tipos de datos soportados por un objeto Transferable. Cada DataFlavor tiene un tipo MIME asociado.
StringSelection Clase Un objeto Transferable para trabajar con Strings.
UnsupportedFlavorException Clase Excepción lanzada por un objeto Transferable en caso de un DataFlavor no soportado

Básicamente existen dos tipos de portapapeles:

  • Sistema (Global): es el portapapeles propio del Sistema Operativo el cual ya fue anteriormente mencionado, su contenido es accesible para todas las aplicaciones.
  • Local: es un portapapeles propio de nuestra aplicación, su contenido es accesible solo dentro de la JVM donde fue creado. Una ventaja aquí es que podemos crear múltiples portapapeles.

A continuación veremos como controlar el portapapeles del sistema y también como crear uno propio para nuestra aplicación.

Portapapeles del Sistema


Para acceder tenemos que utilizar el método Toolkit.getDefaultToolkit().getSystemClipboard() el cual nos devuelve un objeto de tipo Clibpoard representando el portapapeles del sistema. A través de este objeto podemos introducir o extraer datos de algún tipo determinado. Una parte de nuestro código contendrá lo siguiente:

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

Introducir datos

Para introducir datos en el portapapeles utilizamos el método void setContents(Transferable contents, ClipboardOwner owner) donde el argumento contents es el objeto dato que queremos introducir y debe de implementar la interface Transferable la cual define el tipo de contenido con el que se esta trabajando, el argumento owner es el objeto dueño del contenido introducido en el portapapeles el cual debe implementar la interface ClipboardOwner si quiere recibir notificación de cuando el contenido sea reemplazado.

Veremos los casos mas típicos, strings e imágenes. Para trabajar con strings ya disponemos en el paquete java.awt.datatransfer de la clase StringSelection la cual ya implementa Transferable y ClipboardOwner por lo tanto será el caso mas sencillo. Para trabajar con imágenes no disponemos de alguna implementación especifica así que tendremos que crearla lo cual nos llevara algo mas de tiempo y código. Véase algunos ejemplos a continuación:

  • Como introducir un string:
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;

class ClipDemo1 {

    public static void main(String args[]) {
        //este es nuestro objeto Transferable para trabajar con strings
        StringSelection ss = new StringSelection("Hola Mundo");

        //accedemos al portapapeles del sistema
        Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard();

        //añadimos nuestro dato Transferable y como no usaremos ningún clipboardOwner le pasamos null como argumento
        cb.setContents(ss, null);
    }
}

Al ejecutar este código se introducirá el texto “Hola Mundo” en el portapapeles reemplazando cualquier cosa que hubiera anteriormente, para comprobarlo simplemente utiliza la función “Pegar” en cualquier editor de texto.

  • Como introducir una imagen:
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;

class ClipDemo2 {

    public static void main(String args[]) {
        //esta es la ruta hacia el archivo de imagen (en mi pc)
        String ruta = "c:\\imagen.jpg";

        Toolkit tk = Toolkit.getDefaultToolkit();
        Image imagen = tk.getImage(ruta);

        //este es nuestro objeto Transferable para trabajar con imágenes
        ImageSelection is = new ImageSelection(imagen);

        //accedemos al portapapeles del sistema
        Clipboard cb = tk.getSystemClipboard();

        //añadimos nuestro dato Transferable y como no usaremos ningún ClipboardOwner le pasamos null como argumento
        cb.setContents(is, null);
    }

    /**
     * Esta clase interna es mi implementación de Transferable para imágenes
     */
    static class ImageSelection implements Transferable {
        //esta será la imagen almacenada
        private Image image;

        public ImageSelection(Image image) {
            this.image = image;
        }

        //retorna los tipos de datos soportados
        public DataFlavor[] getTransferDataFlavors() {
            return new DataFlavor[] {
                        DataFlavor.imageFlavor
                    };
        }

        //retorna true si un determinado tipo de dato es soportado
        public boolean isDataFlavorSupported(DataFlavor flavor) {
            return DataFlavor.imageFlavor.equals(flavor);
        }

        //retorna la imagen almacenada
        public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException,
                                                                IOException {
            if (DataFlavor.imageFlavor.equals(flavor) == true) {
                return image;
            } else {
                throw new UnsupportedFlavorException(flavor);
            }
        }
    }
}

En este código la diferencia principal con el caso anterior del string esta en que tuve que crear mi propia implementación de la interface Transferable específicamente para imágenes, la cual nombré ImageSelection y la declaré como clase interna y estática. Al implementar Transferable tuve que implementar tres métodos:

  • DataFlavor[] getTransferDataFlavors() – Retornamos un array de DataFlavor, estos representan los tipos de datos soportados en un Transferable. En mi implementación el único que retorne como valido fue el de imagen: DataFlavor.imageFlavor
  • boolean isDataFlavorSupported(DataFlavor flavor) – Retornamos true solo en el caso de que el DataFlavor pasado como argumento sea soportado por nuestra implementación de Transferable, por lo tanto debe de ser DataFlavor.imageFlavor.
  • Object getTransferData (DataFlavor flavor) – Retornamos nuestro objeto imagen si el DataFlavor pasado como argumento es un DataFlavor.imageFlavor.

Al ejecutar el código anterior se introducirá la imagen “c:\imagen.jpg” (en mi caso)  en el portapapeles reemplazando cualquier cosa que hubiera anteriormente, para comprobarlo simplemente utiliza la función “Pegar” en cualquier editor de imágenes.

Puede ser que hasta aquí algunas cosas parezcan innecesarias, pero tendrán mas sentido como veremos a continuación.

Extraer datos

Para obtener datos desde el portapapeles utilizamos el método Transferable getContents(Object requestor) el cual nos retorna un objeto Transferable representando el contenido del portapapeles, el argumento requestor no tiene utilidad alguna en Java (al menos por ahora), por lo que podemos pasarle null o this.

Como vimos anteriormente al introducir algún tipo de datos en el portapapeles tenemos que hacerlo mediante objetos envueltos por la interface Transferable la cual define la información sobre el tipo de datos. Al obtener datos desde el portapapeles podemos saber con que tipo de datos estamos tratando si antes obtenemos los DataFlavors soportados por el objeto Transferable. De esta forma podremos estar seguros de que desde el portapapeles obtendremos el tipo de dato que pretendemos. Es una forma segura y organizada de trabajar.

Recordando nuestra implementación de Transferable sabemos que podemos usar  el método DataFlavor[] getDataTransferDataFlavors() para obtener todos los tipos de dato soportados, o el metodo boolean isDataFlavorSupported(DataFlavor flavor) para averiguar si algún tipo de dato especifico es soportado. Veamos de nuevo como trabajar con strings e imágenes, pero esta ves para extraer los datos:

  • Como extraer un String:
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;

class ClipDemo3 {

    public static void main(String args[]) throws ClassNotFoundException,
                                                  UnsupportedFlavorException,
                                                  IOException {
        //accedemos al portapapeles del sistema
        Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard();

        //obtenemos el contenido del portapapeles representado por un objeto Transferable
        Transferable t = cb.getContents(null);

        //construimos el DataFlavor que representa al String
        DataFlavor dfString = new DataFlavor("application/x-java-serialized-object; class=java.lang.String");

        //verificamos si el objeto Transferable soporta strings
        if (t.isDataFlavorSupported(dfString) == true) {
            //obtenemos el objeto almacenado, lo convertimos en string y lo imprimimos por pantalla
            String texto = (String) t.getTransferData(dfString);
            System.out.println(texto);
        } else {
            //generamos una excepción
            throw new UnsupportedFlavorException(dfString);
        }
    }
}

Para probar este código, selecciona y copia cualquier texto antes de ejecutarlo.

Nota: podemos referirnos al DataFlavor de string simplemente utilizando DataFlavor.stringFlavor sin necesidad de construirlo. Por ejemplo:

...
if (t.isDataFlavorSupported(DataFlavor.stringFlavor) == true) {
...

  • Como extraer una imagen:
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;

class ClipDemo4 {
    public static void main(String args[]) throws ClassNotFoundException,
                                                  UnsupportedFlavorException,
                                                  IOException {
        //accedemos al portapapeles del sistema
        Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard();

        //obtenemos el contenido del portapapeles representado por un objeto Transferable
        Transferable t = cb.getContents(null);

        //construimos el DataFlavor que representa una imagen
        DataFlavor dfImagen = new DataFlavor("image/x-java-image; class=java.awt.Image");

        //verificamos si el objeto Transferable soporta imagenes
        if (t.isDataFlavorSupported(dfImagen) == true) {
            //obtenemos el objeto almacenado y lo convertimos en un BufferedImage
            BufferedImage imagen = (BufferedImage) t.getTransferData(dfImage);

            //creamos un archivo de extension JPG en el directorio home del usuario actual en el sistema
            File f = new File(System.getProperty("user.home") + File.separator + "imagen_del_portapapeles.jpg");

            //guardamos el contenido del portapapeles en el archivo JPG
            ImageIO.write(imagen, "jpg", f);
        } else {
            //generamos una excepción
            throw new UnsupportedFlavorException(dfImagen);
        }
    }
}

Para probar este código, selecciona y copia alguna imagen desde un editor antes de ejecutarlo. El contenido del portapapeles será guardado en un nuevo archivo llamado “imagen_del_portapapeles.jpg” en el directorio home del usuario actual en el sistema.

Nota: podemos referirnos al DataFlavor de imagen simplemente utilizando DataFlavor.imageFlavor sin necesidad de construirlo. Por ejemplo:

...
if (t.isDataFlavorSupported(DataFlavor.imageFlavor) == true) {
...

Es importante decir que copiar un archivo desde el sistema de archivos (por ejemplo hacer click derecho sobre el icono y seleccionar “Copiar”), aunque sea de texto o imagen, no es lo mismo que copiar un texto o una imagen. En ese caso el DataFlavor soportado sería siempre: “application/x-java-file-list; class=java.util.List”.

En los ejemplos anteriores se utilizo el método boolean isDataFlavorSupported(DataFlavor flavor) porque fue suficiente para el caso, pero si lo necesitamos podemos ver todos los tipos de dato soportados por el contenido actual con un simple trozo de código como el siguiente:

...
//accedemos al portapapeles del sistema
Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard();

//obtenemos el contenido del portapapeles representado por un objeto Transferable
Transferable t = cb.getContents(null);

//obtenemos la lista de DataFlavor soportados
DataFlavor[] dfs = t.getTransferDataFlavors();

//iteramos en un ciclo for each mostrando los tipos MIME asociados a cada DataFlavor
for (DataFlavor df : dfs) {
    System.out.println(df.getMimeType());
}
...

El código anterior imprimiría por pantalla algo como lo siguiente en caso de que hubiera texto plano en el portapapeles:

application/x-java-url; class=java.net.URL
application/x-java-text-encoding; class=”[B”
text/uri-list; class=java.io.Reader; charset=Unicode
text/uri-list; class=java.lang.String; charset=Unicode
text/uri-list; class=java.nio.CharBuffer; charset=Unicode
text/uri-list; class=”[C”; charset=Unicode
text/uri-list; class=java.io.InputStream; charset=UTF-16
text/uri-list; class=java.nio.ByteBuffer; charset=UTF-16
text/uri-list; class=”[B”; charset=UTF-16
text/uri-list; class=java.io.InputStream; charset=UTF-8
text/uri-list; class=java.nio.ByteBuffer; charset=UTF-8
text/uri-list; class=”[B”; charset=UTF-8
text/uri-list; class=java.io.InputStream; charset=UTF-16BE
text/uri-list; class=java.nio.ByteBuffer; charset=UTF-16BE
text/uri-list; class=”[B”; charset=UTF-16BE
text/uri-list; class=java.io.InputStream; charset=UTF-16LE
text/uri-list; class=java.nio.ByteBuffer; charset=UTF-16LE
text/uri-list; class=”[B”; charset=UTF-16LE
text/uri-list; class=java.io.InputStream; charset=ISO-8859-1
text/uri-list; class=java.nio.ByteBuffer; charset=ISO-8859-1
text/uri-list; class=”[B”; charset=ISO-8859-1
text/uri-list; class=java.io.InputStream; charset=US-ASCII
text/uri-list; class=java.nio.ByteBuffer; charset=US-ASCII
text/uri-list; class=”[B”; charset=US-ASCII
application/x-java-serialized-object; class=java.lang.String
text/plain; class=java.io.Reader; charset=Unicode
text/plain; class=java.lang.String; charset=Unicode
text/plain; class=java.nio.CharBuffer; charset=Unicode
text/plain; class=”[C”; charset=Unicode
text/plain; class=java.io.InputStream; charset=unicode
text/plain; class=java.nio.ByteBuffer; charset=UTF-16
text/plain; class=”[B”; charset=UTF-16
text/plain; class=java.io.InputStream; charset=UTF-8
text/plain; class=java.nio.ByteBuffer; charset=UTF-8
text/plain; class=”[B”; charset=UTF-8
text/plain; class=java.io.InputStream; charset=UTF-16BE
text/plain; class=java.nio.ByteBuffer; charset=UTF-16BE
text/plain; class=”[B”; charset=UTF-16BE
text/plain; class=java.io.InputStream; charset=UTF-16LE
text/plain; class=java.nio.ByteBuffer; charset=UTF-16LE
text/plain; class=”[B”; charset=UTF-16LE
text/plain; class=java.io.InputStream; charset=ISO-8859-1
text/plain; class=java.nio.ByteBuffer; charset=ISO-8859-1
text/plain; class=”[B”; charset=ISO-8859-1
text/plain; class=java.io.InputStream; charset=US-ASCII
text/plain; class=java.nio.ByteBuffer; charset=US-ASCII
text/plain; class=”[B”; charset=US-ASCII

Ó lo siguiente en caso de que hubiera una imagen en el portapapeles:

image/x-java-image; class=java.awt.Image

Implementando la interface ClipboardOwner

Desde el momento que introducimos algún tipo de dato en el portapapeles también podemos especificar quien es su dueño para que este sea notificado cuando el contenido en el portapapeles sea reemplazado. Para esto en el método void setContents(Transferable contents, ClipboardOwner owner) el argumento owner debe de ser un objeto que implemente la interface ClipboardOwner. Este es un simple ejemplo:

import java.awt.Image;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.ClipboardOwner;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;

class ClipDemo5 extends Thread {

    public static void main(String args[]) throws UnsupportedFlavorException,
                                                  ClassNotFoundException,
                                                  IOException {
        final ClipDemo5 p = new ClipDemo5();

        //este es nuestro objeto Transferable para trabajar con strings
        StringSelection ss = new StringSelection("Hola Mundo");

        //accedemos al portapapeles del sistema
        Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard();

        //añadimos nuestro dato Transferable e implementamos en una clase anónima la interface CliboardOwner
        cb.setContents(ss, new ClipboardOwner() {

            //este método es invocado cuando el contenido es reemplazado
            @Override
            public void lostOwnership (Clipboard clipboard, Transferable contents) {
                //imprimimos un mensaje en pantalla
                System.out.println("Mi contenido en el portapapeles fue reemplazado!");
                //interrumpimos el hilo de espera para finalizar la aplicación
                p.interrupt();
            }
        });

        p.start();    //iniciamos un hilo de espera
    }

    /* este hilo simplemente dormirá un minuto (60000 ms) para que la aplicación se mantenga
    en espera y no finalice inmediatamente luego de ser ejecutada */
    @Override
    public void run() {
            try {
                  Thread.sleep(60000);
            } catch (InterruptedException ie) {
            }
    }
}

Al ejecutar este código se introduce el texto “Hola Mundo” en el portapapeles y se espera por un minuto a que el contenido sea reemplazado. Luego de ejecutarlo puedes probar copiar algo y entonces se debería de ver en la consola un mensaje notificando el reemplazo del contenido en el portapapeles, en ese momento la aplicación terminará.

Portapapeles Local


Para crear nuestro propio portapapeles solo tenemos que construir nuestro propio objeto Clipboard pasándole como argumento su nombre:

...
Clipboard lcb = new Clipboard("Mi propio portapapeles");
...

El manejo de un portapapeles local es igual al del sistema, por lo tanto lo aprendido anteriormente se aplicará igualmente.


Ejemplos:
* Java – Clase java.awt.datatransfer.StringSelection

Documentación:
* Java™ Platform, Standard Edition 6 API Specification


, , , , , ,

  1. #1 por limewire el febrero 24, 2011 - 5:41 pm

    fantastic

  2. #2 por Gabriel el mayo 23, 2011 - 10:54 pm

    Hola, me ha sido de utilidad,

    Tengo una pregunta, ¿que pasa cuando quiero extraer datos que han sido copiados de una hoja de cálculos como excel u openoffice?

    ¿me conviene extraerlos como texto con new DataFlavor(“application/x-java-serialized-object; class=java.lang.String”); o hay una mejor solución?

    lo que obtengo es “a -> b\n1 -> 2\n” separado por tabs y enters.

    • #3 por Dark[byte] el mayo 24, 2011 - 9:39 pm

      Hola Gabriel.

      No hay otra solución porque no existe un DataFlavor especifico para lo que te refieres (Excel o Calc de OpenOffice). Tendrás que lidiar con los saltos de línea (\n) y las tabulaciones (\t) procesándolas de la forma que te parezca mas conveniente según tus necesidades.

      Por ejemplo si tienes una tabla en Calc como esta:
      —————–
      a b c
      d e f
      g h i
      —————–

      Le haces un “Copy”, y la podrás procesar desde el portapapeles de esta forma:

      ...
      String clipString = (String) Toolkit.getDefaultToolkit().getSystemClipboard().getContents(null).getTransferData(DataFlavor.stringFlavor);
      String[] filas = clipString.split("\n");
      String[] celdas;
      String fila;                
      String celda;
      for (int i = 0 ; i < filas.length ; i++) {                        
              fila = filas[i];
              System.out.println("Fila: " + (i + 1));
              celdas = fila.split("\t");                        
              for (int j = 0 ; j < celdas.length ; j++)  {                                
                      celda = celdas[j];
                      System.out.println("Celda " + (j + 1) + " : " + celda);
              }                        
      }
      ...
      

      Y el resultado seria:
      —————————-
      Fila: 1 …
      Celda 1: a
      Celda 2: b
      Celda 3: c
      Fila: 2 …
      Celda 1: d
      Celda 2: e
      Celda 3: f
      Fila: 3 …
      Celda 1: g
      Celda 2: h
      Celda 3: i
      —————————-

      Por cierto puedes escribir DataFlavor.stringFlavor en cambio de new DataFlavor(“application/x-java-serialized-object; class=java.lang.String”);

      Espero haberte ayudado. Y si no te conformas, sigue investigando y quizás encuentres algo mas práctico.

      Salu2!

      • #4 por Gabriel el mayo 24, 2011 - 10:41 pm

        Hola, Gracias por responder
        No me he conformado y sigo investigando, pero por ahora lo he echo como vos decis, o mas o menos.
        Como puede venir los dato de cualquier lado pensé en una tabla que no te copie las celdas null ( no lo conozco pero nunca se sabe ) entonces puede venir “tablero 1\t2\ntablero 2\t4\t5\ntablero 3”. por lo que no se sabe cuantas celdas tiene cada fila pero lo que quiero es una matriz regular:
        [ tablero 1 ][ 2 ][ ]
        [ tablero 2 ][ 4 ][ 5 ]
        [ tablero 3 ][ ][ ]

        Lo resolví de la siguiente manera

        public String[][] extraerMatriz()
        {
        String[] filas = extraerString().split("\n");

        int count = 0;//cuenta la m'axima cantidad de columnas
        ArrayList lista = new ArrayList();

        for(int i = 0; i count) { count = aux.length; }//pregunto por la cantidad de celdas de cada fila y guardo la mayor
        lista.add(aux);
        }
        String[][] retorno = new String[filas.length][count];//creo ahora la matriz regular

        int fila = 0;
        for (Iterator it = lista.iterator(); it.hasNext(); fila++)
        {
        String[] strings = it.next();
        for(int i = 0; i < count; i++)
        {
        retorno[fila][i] = (i < strings.length ? strings[i] : "");//es como un if-else, el ? viene de C++ o C
        }
        }
        return retorno;
        }

    • #5 por Dark[byte] el mayo 24, 2011 - 11:29 pm

      Gabriel me alegro que ya estés trabajando a tu manera para procesar el contenido.
      En cuanto a una solución mas “avanzada” para el problema, puedes intentar escribir tu duda en el foro de http://www.java-forums.org/ o incluso en el foro de oracle http://forums.oracle.com/forums/forum.jspa?forumID=922

      Salu2!

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

Conectando a %s

A %d blogueros les gusta esto: