Java:使用剪贴板在同一应用程序的不同实例之间复制和粘贴Java对象

9
我正在尝试在同一应用程序的不同实例之间实现对象的复制和粘贴。目前它只在一个应用程序中工作(我的意思是,在应用程序的同一实例中复制和粘贴),但在不同的实例之间无法工作。
复制代码:
// 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);

在此之后,我可以检查剪贴板的内容,如下所示:
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]);
}

如果我从同一应用程序实例中复制对象进行此操作,则可以正常运行:flavors.length1,MIME类型为application/x-java-serialized-object

但是,如果我打开应用程序并执行复制,然后再次打开相同的应用程序(第一个应用程序未关闭,即同时运行两个实例),并尝试从其中检查剪贴板内容,则flavors.length0

我查看了文档和这些示例:one, two,但仍然找不到我的实现有什么问题。

我有遗漏吗?


更新: 我添加了一个最小化可复现代码示例:clipboard_test.zip

这是一个测试应用程序(我使用 Eclipse 构建),包含 3 个源文件:

  • ClipboardTest.java - main app class
  • MyObject.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

  • Press "copy" button: you will see "object copied" message in the log
  • Press "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
    
为什么会这样?

更新2: 在此直接发布了SSCCE:

  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");
        }

     }

  }

3
为了更快地获得帮助,请发布一个SSCCE。请提供简洁明了的代码示例,以便更好地理解并解决您的问题。 - Andrew Thompson
操作系统和JVM的启动参数/配置对访问全局剪贴板也有很大影响。 - tucuxi
你使用的操作系统/窗口环境是什么? - tucuxi
1
如果 SSCCE 足够短小,可以直接发布到论坛上,请这样做。我不会下载 Zip 文件来帮助你。 - Andrew Thompson
1
一般来说,我认为在一个SSCCE中应该包括导入语句。SSCCE也应该是一个源文件(即使这个源文件内有多个类)。 - Andrew Thompson
显示剩余4条评论
1个回答

7
引用这篇教程
传输数据时,使用的机制是Object序列化,因此你用于传输数据的类必须实现Serializable接口,并且与之序列化的任何内容也必须如此。如果不是所有内容都可序列化,您会在拖放或复制到剪贴板时看到NotSerializableException
您的MyObject没有实现Serializable,因此无法工作。框架显然检测到了这一点(而不是检测到仅在进程中检测到不可序列化的父类的非可序列化子类或类似情况),因此它甚至不会向其他进程提供那种格式。
通常,两个相同的Java应用程序实例不具有相同的地址空间,因此它们不能简单地访问彼此的内存。因此,您传输的所有内容都必须被序列化。

非常感谢。不幸的是,我无法从官方的DataFlavor文档中找到此教程的链接:http://docs.oracle.com/javase/7/docs/api/java/awt/datatransfer/DataFlavor.html - Dmitry Frank
@DmitryFrank:那个链接已经失效了,我刚刚为此提交了一个错误报告。不过通过标题,很容易找到我所引用的版本。 - MvG

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接