开发者

Java/Swing: ownership of the system clipboard

I'm writing a small Java program that's supposed to run an external program that copies an image to the system clipboard (i.e. the Windows 7 "snipping tool"), wait for it to finish, save the image from the clipboard to disk and copy a URL (from which the image can be accessed) to clipboard. In short, it is supposed to:

  1. run external tool and wait for it
  2. copy an image from clipboard
  3. copy a string to clipboard

This, my program is perfectly able to do. However, I would like to use Swing/AWT to present a user interface. I'm using a system tray icon, but for simplicity's sake, it could just as well be a JButton in a frame. When the button is clicked, the process above should be carried out. The first time this is done, it works as it should. The image is copied, pasted to disk and the string is copied to clipboard. Then, the second time the button is clicked, it is as though my program does not realize that the clipboard has been updated, as it is still seeing its own string from the first time around. Only afterwards does my clipboard handling class lose ownership, and in effect, every second attempt at the procedure fails.

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) {
  开发者_Python百科          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);
    }
}

If you try running it, notice that on the second run, the lostOwnership method is only called AFTER the attempt at copying the image. I'm guessing this is the source of my problem, and I have no idea why it is happening, except that it only happens when triggered by a Swing event. Any help solving this is appreciated.


One guess: You are doing your whole handling (calling the other process) on the AWT event dispatch thread (e.g. directly from the ActionListener or similar).

The Clipboard change messages will also be handled by the VM on the EDT ... but only after your button-click is done.

The moral: Don't do long-running stuff (and stuff which has effects which should be enqueued in the event queue) on the EDT - instead, start a new thread for this.


The key to understanding the lost ownership issue is in this line

Toolkit.getDefaultToolkit().getSystemClipboard().setContents(strsel, this);

The second parameter you are passing in is ClipboardOwner. JavaDocs for clipboard.setContents says

If there is an existing owner different from the argument owner, that owner is notified that it no longer holds ownership of the clipboard contents via an invocation of ClipboardOwner.lostOwnership() on that owner. An implementation of setContents() is free not to invoke lostOwnership() directly from this method. For example, lostOwnership() may be invoked later on a different thread. The same applies to FlavorListeners registered on this clipboard.

Okay so what's happening? When you pass in the owner, Clipboard now has a reference to that object. In this case it's CBHandler. You then create a new one and attempt to set the contents again. Clipboard then goes back to the old owner (your original instance) and tells it "Hey, your not the owner anymore".

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

You'll have to provide more details on the other issue "it is as though my program does not realize that the clipboard has been updated, as it is still seeing its own string from the first time around. "

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜