Java/Swing: 系统剪贴板的所有权

4
我在编写一个小的Java程序,该程序可以运行一个外部程序来复制图像到系统剪贴板(即Windows 7的“截图工具”),等待其完成后将剪贴板上的图像保存到磁盘,并将URL(从中可以访问该图像)复制到剪贴板。简而言之,它应该能够:
1. 运行外部工具并等待其返回结果; 2. 从剪贴板中复制一个图像; 3. 将一个字符串复制到剪贴板。
我的程序完全可以实现以上功能。然而,我想要使用Swing/AWT框架来提供用户界面。我正在使用系统托盘图标,但为了简化起见,它也可以是一个JButton在一个框架中。当用户单击按钮时,上述过程应该被执行。第一次执行时,它可以正常工作:图像被复制并粘贴到磁盘中,字符串被复制到剪贴板中。但是,第二次单击按钮时,好像我的程序没有意识到剪贴板已经更新,因为它仍然看到自己在第一次操作时复制的字符串。只有在我的剪贴板处理类失去所有权后,每个操作才会失败。
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.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.IOException;

import javax.swing.JButton;
import javax.swing.JFrame;

public class Main {
    private static BufferedImage image; //the image from clipboard to be saved

    public static void main(String[] args) throws InterruptedException, IOException {
        new GUI();
    }

    public static void run(String filename) throws IOException, InterruptedException {
        CBHandler cbh = new CBHandler();

        //run tool, tool will copy an image to system clipboard
        Process p = Runtime.getRuntime().exec("C:\\Windows\\system32\\SnippingTool.exe");
        p.waitFor();

        //copy image from clipboard
        image = cbh.getClipboard();
        if(image == null) {
            System.out.println("No image found in clipboard.");
            return;
        }

        //save image to disk...

        //copy file link to clipboard
        String link = "http://somedomain.com/" + filename;
        cbh.setClipboard(link);
    }
}

class CBHandler implements ClipboardOwner {
    public BufferedImage getClipboard() {
        Transferable t = Toolkit.getDefaultToolkit().getSystemClipboard().getContents(null);

        try {
            if(t.isDataFlavorSupported(DataFlavor.imageFlavor))
                return (BufferedImage) t.getTransferData(DataFlavor.imageFlavor);
        }
        catch(Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public void setClipboard(String str) {
        StringSelection strsel = new StringSelection(str);
        Toolkit.getDefaultToolkit().getSystemClipboard().setContents(strsel, this);
    }

    @Override
    public void lostOwnership(Clipboard arg0, Transferable arg1) {
        System.out.println("Lost ownership!");
    }
}

class GUI extends JFrame {
    public GUI() {
        JButton button = new JButton("Run");
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent arg0) {
                try {
                    Main.run("saveFile.png");
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        add(button);
        pack();
        setVisible(true);
    }
}

如果您尝试运行它,会注意到在第二次运行时,只有在尝试复制图像之后才调用lostOwnership方法。我猜这是我的问题的根源,但我不知道为什么会发生这种情况,除非是由Swing事件触发才会发生。希望能得到解决这个问题的任何帮助。
2个回答

0
理解失去所有权问题的关键在于这一行。
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(strsel, this);

你传递的第二个参数是ClipboardOwner。clipboard.setContents的JavaDocs说:
如果存在与参数所有者不同的现有所有者,则通过在该所有者上调用ClipboardOwner.lostOwnership()来通知该所有者它不再持有剪贴板内容的所有权。 setContents()的实现可以自由地不直接从此方法调用lostOwnership()。例如,可能稍后在不同的线程上调用lostOwnership()。对于在此剪贴板上注册的FlavorListeners也是如此。
好的,那么发生了什么?当你传递所有者时,剪贴板现在具有对该对象的引用。在这种情况下,它是CBHandler。然后,你创建一个新的并尝试再次设置内容。剪贴板然后返回旧所有者(原始实例),并告诉它“嘿,你不再是所有者了”。
public synchronized void setContents(Transferable contents, ClipboardOwner owner) {
    final ClipboardOwner oldOwner = this.owner;
    final Transferable oldContents = this.contents;

    this.owner = owner;
    this.contents = contents;

    if (oldOwner != null && oldOwner != owner) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                oldOwner.lostOwnership(Clipboard.this, oldContents);
            }
        });
    }
    fireFlavorsChanged();
}

您需要提供更多关于另一个问题的细节:“似乎我的程序没有意识到剪贴板已经更新,因为它仍然看到第一次运行时的自己的字符串。”


0
一个猜测:你正在AWT事件分发线程上处理整个过程(例如直接从ActionListener或类似组件中调用其他进程)。
剪贴板更改消息也将在EDT上被VM处理...但只有在你的按钮单击完成后才会执行。
道德:不要在EDT上执行长时间运行的任务(和应该排队到事件队列中的任务),而是为此启动一个新线程。

能否给我一个例子(伪代码或其他方式)来说明如何在我的情况下完成这个任务? - Per

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