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

发布于 2024-12-01 14:52:40 字数 3259 浏览 1 评论 0原文

我正在编写一个小型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 事件触发时才会发生。任何帮助解决这个问题的帮助都是值得赞赏的。

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) {
            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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

烟花肆意 2024-12-08 14:52:40

一种猜测:您正在 AWT 事件调度线程上进行整个处理(调用其他进程)(例如直接从 ActionListener 或类似线程)。

剪贴板更改消息也将由虚拟机在 EDT 上处理……但仅在按钮单击完成后才处理。

道德:不要在 EDT 上执行长时间运行的操作(以及具有应在事件队列中排队的效果的操作) - 相反,为此启动一个新线程。

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.

蝶舞 2024-12-08 14:52:40

理解丢失所有权问题的关键在于这一行。

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

您传入的第二个参数是 ClipboardOwner。 Clipboard.setContents 的 JavaDocs 说

如果存在与参数所有者不同的现有所有者,则通过对该所有者调用 ClipboardOwner.lostOwnership() 来通知该所有者它不再拥有剪贴板内容的所有权。 setContents() 的实现可以自由地不直接从此方法调用lostOwnership()。例如,lostOwnership() 可能稍后在不同的线程上调用。这同样适用于在此剪贴板上注册的 FlavorListener。

好吧,发生了什么事?当您传入所有者时,剪贴板现在拥有对该对象的引用。在本例中是 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();
}

您必须提供有关另一个问题的更多详细信息“就好像我的程序没有意识到剪贴板已更新,因为它仍然看到自己的字符串从第一次开始。”

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. "

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文