Java / Swing: владение системным буфером обмена - PullRequest
2 голосов
/ 27 августа 2011

Я пишу небольшую 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 голосов
/ 28 августа 2011

Ключ к пониманию проблемы утраченного владения находится в этой строке

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

Второй передаваемый вами параметр - ClipboardOwner.JavaDocs для clipboard.setContents говорит:

Если существует существующий владелец, отличный от владельца аргумента, этот владелец получает уведомление о том, что он больше не владеет содержимым буфера обмена через вызов 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 голосов
/ 28 августа 2011

Одно предположение: вы выполняете всю свою обработку (вызывая другой процесс) в потоке диспетчеризации событий AWT (например, непосредственно из ActionListener или аналогичного).

Сообщения об изменениях буфера обмена также будут обрабатываться виртуальной машиной на EDT ... но только после того, как вы нажмете кнопку.

Мораль: не делайте долгоиграющих вещей (и вещей, которые имеют эффекты, которые должны быть поставлены в очередь в очереди событий) на EDT - вместо этого создайте новый поток для этого.

...