I am trying to implement copy-paste of objects between different instances of same application. Currently it works only in one application (I mean, copy and paste in the same instance of application), but does not work between different instances.
Copying code:
// MyObject is a class of objects I want to copy/paste;
// MyObjectSelection is a class that impements Transferable and ClipboardOwner interfaces
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
MyObject data = new MyObject(selectedItems);
MyObjectSelection dataSelection = new MyObjectSelection(data);
clipboard.setContents(dataSelection, this);
After that, I can check contents of the clipboard, like that:
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
Transferable clipboardContent = clipboard.getContents(this);
DataFlavor[] flavors = clipboardContent.getTransferDataFlavors();
System.out.println("flavors.length=" + flavors.length);
for (int i = 0; i < flavors.length; i++){
System.out.println("flavor=" + flavors[i]);
}
If I do this from the same application instance that I copied object, it works: flavors.length
is 1
, mimetype is application/x-java-serialized-object
, and, well, it works.
But if I open application, perform copy, then open the same application again (first one isn't closed, i.e. both instances are running simultaneously), and try to check clipboard contents from there, then flavors.length
is 0
.
I examined the docs and these examples: one, two, but I still can't find what is wrong in my implementation.
Did I miss something?
UPD: I added sscce: clipboard_test.zip.
This is test application (I use Eclipse to build it) with 3 source files:
ClipboardTest.java
- main app classMyObject.java
- class for objects to copy/paste (this class contains just array of String
)MyObjectSelection.java
- class that implements Transerable
and ClipboardOwner
interfaces
There are two buttons: "copy", "test".
When you press "copy" button, new instance of MyObject is created and put to the clipboard.
When you press "test" button, clipboard contents are checked
and echoed to the console (count of supported DataFlavor
's, and each DataFlavor
)
So, replicate these steps:
Start application
"object copied"
message in the logPress "test" button: you will see clipboard's contents:
flavors.length = 1
flavor[0] = java.awt.datatransfer.DataFlavor[mimetype=application/x-java-serialized-object;representationclass=MyObject]
Start another instance of the application (don't close the first one)
Press "test" button: you will see that clipboard is empty:
flavors.length = 0
Why is that?
UPD2: sscce posted directly here:
import java.awt.BorderLayout;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.ClipboardOwner;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.Toolkit;
import javax.swing.JButton;
import javax.swing.SwingUtilities;
import javax.swing.JFrame;
public final class ClipboardTest implements Runnable, ClipboardOwner {
public static void main(String[] args) {
SwingUtilities.invokeLater (new ClipboardTest());
}
public void run() {
JFrame f = new JFrame ("Clipboard test");
f.setDefaultCloseOperation (JFrame.DISPOSE_ON_CLOSE);
//-- Create "copy" button.
// When you click it, new object "test_items" becomes created
// and put to the clipboard.
{
JButton button_copy = new JButton("copy");
button_copy.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
String[] test_items = {
"one", "two", "three"
};
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
MyObject data = new MyObject(test_items);
MyObjectSelection dataSelection = new MyObjectSelection(data);
clipboard.setContents(dataSelection, ClipboardTest.this);
System.out.println("object copied");
}
});
f.add(button_copy, BorderLayout.NORTH);
}
//-- Create "test" button.
// When you click it, clipboard contents are checked
// and echoed to the console (count of supported DataFlavor's, and each DataFlavor)
{
JButton button_test = new JButton("test");
button_test.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
Transferable clipboardContent = clipboard.getContents(this);
DataFlavor[] flavors = clipboardContent.getTransferDataFlavors();
System.out.println("flavors.length = " + flavors.length);
for (int i = 0; i < flavors.length; i++){
System.out.println("flavor[" + i + "] = " + flavors[i]);
}
}
});
f.add(button_test, BorderLayout.SOUTH);
}
f.pack();
f.setVisible(true);
}
// ClipboardOwner implementation
@Override
public void lostOwnership(Clipboard clipboard, Transferable transferable){
System.out.println("ClipboardTest: Lost ownership");
}
/* *****************************************************************************************
* Object that I want to copy/paste
* ****************************************************************************************/
public static class MyObject {
private String[] items;
public MyObject(String[] items){
this.setItems(items);
}
public String[] getItems(){
return this.items;
}
public void setItems(String[] items){
this.items = items;
}
}
public static class MyObjectSelection implements Transferable, ClipboardOwner {
private static DataFlavor dmselFlavor = new DataFlavor(MyObject.class, "Test data flavor");
private MyObject selection;
public MyObjectSelection(MyObject selection){
this.selection = selection;
}
// Transferable implementation
@Override
public DataFlavor[] getTransferDataFlavors(){
System.out.println("getTransferDataFlavors");
DataFlavor[] ret = {dmselFlavor};
return ret;
}
@Override
public boolean isDataFlavorSupported(DataFlavor flavor){
return dmselFlavor.equals(flavor);
}
@Override
public synchronized Object getTransferData (DataFlavor flavor)
throws UnsupportedFlavorException
{
if (isDataFlavorSupported(flavor)){
return this.selection;
} else {
throw new UnsupportedFlavorException(dmselFlavor);
}
}
// ClipboardOwner implementation
@Override
public void lostOwnership(Clipboard clipboard, Transferable transferable){
System.out.println("MyObjectSelection: Lost ownership");
}
}
}
The setContents() method is used to set contents to Clipboard, like cut-copy operations, and the getContents() method is used to retrieve the content from Clipboard, like the paste operation. A data package to be set in the Clipboard must be wrapped in a Transferable object.
A class that implements a mechanism to transfer data using cut/copy/paste operations. FlavorListener s may be registered on an instance of the Clipboard class to be notified about changes to the set of DataFlavor s available on this clipboard (see addFlavorListener(java. awt.
To access the general system clipboard, use the following code: Clipboard clipboard = Clipboard. getSystemClipboard();
Quoting this tutorial:
Transferring the data using this mechanism uses
Object
serialization, so the class you use to transfer the data must implement theSerializable
interface, as must anything that is serialized with it. If everything is not serializable, you will see aNotSerializableException
during drop or copy to the clipboard.
Your MyObject
is not Serializable
, so it cannot work. The framework apparently detects this fact (as opposed to a situation with an unserializable subclass of a serializable parent class, or similar scenarios where non-serializability is detected only in the process), so it won't even offer that flavor to other processes.
In general, two instances of the same java application won't have the same address space, so they can't simply access one another's memory. Therefore everything you transfer has to be serialized.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With