在剪贴板内容发生变化时调用方法

时间:2013-01-08 23:48:43

标签: java clipboard

我正在尝试创建一个应该显示剪贴板内容的桌面应用程序(如果它是一个字符串)。我已经完成了一个构造函数,它运行良好,现在我只想在文本被复制到操作系统中的剪贴板时调用类似的方法。我是新手,所以任何帮助将不胜感激!有些东西告诉我,我应该以某种方式使用中断......

package pasty;

import java.awt.FlowLayout;
import java.awt.Toolkit;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;

public class PastyFrame implements KeyListener {

    String currentClipboardString;
    JLabel clipboardLabel = new JLabel();

    public PastyFrame() {
        JFrame frame = new JFrame();
        frame.setVisible(true);

        try {
            currentClipboardString = (String) Toolkit.getDefaultToolkit().getSystemClipboard().getData(DataFlavor.stringFlavor);
        } catch (UnsupportedFlavorException | IOException ex) {
            Logger.getLogger(PastyFrame.class.getName()).log(Level.SEVERE, null, ex);

            currentClipboardString = "";
        }
        if (currentClipboardString.isEmpty()) {
            currentClipboardString = "The clipboard is empty";
        }
        frame.setSize(400, 100);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setResizable(false);
        frame.setLayout(new FlowLayout());


        clipboardLabel.setText(currentClipboardString);
        frame.add(clipboardLabel);
}

7 个答案:

答案 0 :(得分:10)

我用这个。全班。

public class ClipBoardListener extends Thread implements ClipboardOwner{
Clipboard sysClip = Toolkit.getDefaultToolkit().getSystemClipboard();  


    @Override
  public void run() {
    Transferable trans = sysClip.getContents(this);  
    TakeOwnership(trans);       

  }  

    @Override
  public void lostOwnership(Clipboard c, Transferable t) {  

  try {  
    ClipBoardListener.sleep(250);  //waiting e.g for loading huge elements like word's etc.
  } catch(Exception e) {  
    System.out.println("Exception: " + e);  
  }  
  Transferable contents = sysClip.getContents(this);  
    try {
        process_clipboard(contents, c);
    } catch (Exception ex) {
        Logger.getLogger(ClipBoardListener.class.getName()).log(Level.SEVERE, null, ex);
    }
  TakeOwnership(contents);


}  

  void TakeOwnership(Transferable t) {  
    sysClip.setContents(t, this);  
  }  

public void process_clipboard(Transferable t, Clipboard c) { //your implementation
    String tempText;
    Transferable trans = t;

    try {
        if (trans != null?trans.isDataFlavorSupported(DataFlavor.stringFlavor):false) {
            tempText = (String) trans.getTransferData(DataFlavor.stringFlavor);
            System.out.println(tempText);  
        }

    } catch (Exception e) {
    }
}

}

当其他程序获得剪贴板的所有权时,它会等待250毫秒,并通过更新的内容收回剪贴板的所有权。

答案 1 :(得分:7)

您可以致电Clipboard.addFlavorListener来侦听来自操作系统的剪贴板更新:

Toolkit.getDefaultToolkit().getSystemClipboard().addFlavorListener(new FlavorListener() { 
   @Override 
   public void flavorsChanged(FlavorEvent e) {

      System.out.println("ClipBoard UPDATED: " + e.getSource() + " " + e.toString());
   } 
}); 

一些附注:

  • 要启动您的应用程序,请考虑使用initial threads
  • 调用JFrame.pack设置帧大小。
  • 对于在Swing中映射KeyListeners
  • Key Bindings优先于KeyEvents

答案 2 :(得分:1)

下面是一个SSCCE ...您可以运行它并选择文本并多次转到Ctrl-C ...所选文本将被打印出来。

正如你所看到的那样,它比Reimius'回答。实际上你必须清除剪贴板(这很棘手!),每次复制一些新文本时,味道监听器都会响应。

此外,您可能需要抑制由"风味变化引起的输出"当你清理剪贴板时...虽然可能有一个比我更聪明的解决方案。

public class ClipboardListenerTest {

    public static void main(String[] args) throws InvocationTargetException, InterruptedException {

        SwingUtilities.invokeAndWait(new Runnable() {
            public void run() {
                final Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
                clipboard.addFlavorListener(new FlavorListener() {
                    // this is needed to prevent output when you clear the clipboard
                    boolean suppressOutput = false;

                    // this is a specially devised Transferable - sole purpose to clear the clipboard
                    Transferable clearingTransferable = new Transferable() {
                        public DataFlavor[] getTransferDataFlavors() {
                            return new DataFlavor[0];
                        }
                        public boolean isDataFlavorSupported(DataFlavor flavor) {
                            return false;
                        }
                        public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException {
                            throw new UnsupportedFlavorException(flavor);
                        }
                    };

                    @Override
                    public void flavorsChanged(FlavorEvent e) {
                        Transferable contentsTransferable = clipboard.getContents(null);
                        // NB the Transferable returned from getContents is NEVER the same as the 
                        // clearing Transferable!

                        if (!suppressOutput) {
                            System.out.println(String.format("# clipboard UPDATED, src %s, string %s, clearingT? %b", e.getSource(), e.toString(),
                                    contentsTransferable == clearingTransferable));
                            try {
                                String stringData = (String)clipboard.getData(DataFlavor.stringFlavor);
                                System.out.println(String.format("# string data |%s|", stringData ));
                            } catch (UnsupportedFlavorException | IOException e1) {
                                // TODO Auto-generated catch block
                                e1.printStackTrace();
                            } 
                        }

                        else {
                            // my experiments seem to show that you have to spawn a new Runnable if you want 
                            // to leave suppressOutput long enough for it to prevent the "CLEAR" operation 
                            // producing output...
                            SwingUtilities.invokeLater(new Runnable() {
                                @Override
                                public void run() {
                                    suppressOutput = false;
                                }
                            });

                        }
                        suppressOutput = true;
                        clipboard.setContents(clearingTransferable, null);
                    }
                });

            }

        });

        int i = 0;
        while (i < 100) {
            Thread.sleep(500L);
            System.out.println("# pibble");
            i++;
        }

    }

}

答案 3 :(得分:1)

我想出了另一个解决此问题的方法:以下侦听器使用循环不断读取剪贴板内容。如果检测到文本,它将与剪贴板中的先前内容进行比较,该内容将被缓存。当剪贴板包含以前未缓存的新文本时,它可能会执行“通知观察者”之类的操作,如本例所示,这可能会提示GUI更新自身。

在此示例中,内容更改(剪贴板中包含字符串以外的内容)将被忽略。

除了仅检测内容类型更改(即通过使用FlavorListerner)之外,此解决方案还可以通过连续的字符串比较来检测更改。通过仅读取剪贴板,我希望此代码对其他应用程序的干扰比例如通过拥有剪贴板的所有权。

欢迎提出建议。

package gui;

import java.awt.HeadlessException;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Observable;

/**
 * @author Matthias Hinz
 */
class ClipboardTextListener extends Observable implements Runnable {

    Clipboard sysClip = Toolkit.getDefaultToolkit().getSystemClipboard();

    private volatile boolean running = true;

    public void terminate() {
        running = false;
    }

    public void run() {
        System.out.println("Listening to clipboard...");
        // the first output will be when a non-empty text is detected
        String recentContent = "";
        // continuously perform read from clipboard
        while (running) {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                // request what kind of data-flavor is supported
                List<DataFlavor> flavors = Arrays.asList(sysClip.getAvailableDataFlavors());
                // this implementation only supports string-flavor
                if (flavors.contains(DataFlavor.stringFlavor)) {
                    String data = (String) sysClip.getData(DataFlavor.stringFlavor);
                    if (!data.equals(recentContent)) {
                        recentContent = data;
                        // Do whatever you want to do when a clipboard change was detected, e.g.:
                        System.out.println("New clipboard text detected: " + data);
                        setChanged();
                        notifyObservers(data);
                    }
                }

            } catch (HeadlessException e1) {
                e1.printStackTrace();
            } catch (UnsupportedFlavorException e1) {
                e1.printStackTrace();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        ClipboardTextListener b = new ClipboardTextListener();
        Thread thread = new Thread(b);
        thread.start();
    }
}

答案 4 :(得分:0)

Reimeus建议使用Clipboard.AddFlavorListener。我想稍微扩展他的回答。我在我的程序中这样做的方式是这样的:

final Clipboard SYSTEM_CLIPBOARD = Toolkit.getDefaultToolkit().getSystemClipboard();

SYSTEM_CLIPBOARD.addFlavorListener(listener -> {

    string clipboardText = (String) SYSTEM_CLIPBOARD.getData(DataFlavor.stringFlavor);

    SYSTEM_CLIPBOARD.setContents(new StringSelection(clipboardText), null);

    System.out.println("The clipboard contains: " + clipboardText);
}

这解决了当新应用程序将内容复制到剪贴板时仅触发侦听器的问题,实质上是使程序本身成为将文本复制到剪贴板的应用程序。

对于每个复制事件,这个监听器的警告会被调用两次,但当然有办法解决这个问题。至少,在这种情况下,每次将某些内容复制到剪贴板时都会正确调用剪贴板事件。

答案 5 :(得分:0)

我不确定这是否正确和最佳方法,但至少对我来说很好。 下面是剪贴板处理程序的示例,该示例实现剪贴板所有者接口,并在每次失去所有权时读取剪贴板缓冲区。然后,当新条目进入剪贴板以再次读取它时,它将重新获得所有权,以备下次丢失。它还允许实用地设置剪贴板内容(在下面的示例中提供新的stdin行)。

public static class ClipboardHandler implements ClipboardOwner, Runnable {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    private final Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
    private final Consumer<String> bufferConsumer;

    public ClipboardHandler(Consumer<String> bufferConsumer) {
        this.bufferConsumer = bufferConsumer;
    }

    @Override
    public void lostOwnership(Clipboard clipboard, Transferable notUsed) {
        Transferable contents = clipboard.getContents(this);
        if (contents.isDataFlavorSupported(DataFlavor.stringFlavor)) {
            try {
                String string = (String) contents.getTransferData(DataFlavor.stringFlavor);
                bufferConsumer.accept(string);
            } catch (Exception e) {
                logger.error("Unable to read clipboard buffer.", e);
            }
        }
        getOwnership(contents);
    }

    @Override
    public void run() {
        Transferable transferable = clipboard.getContents(this);
        getOwnership(transferable);
    }

    public void setBuffer(String buffer) {
        getOwnership(new StringSelection(buffer));
    }

    private void getOwnership(Transferable transferable) {
        clipboard.setContents(transferable, this);
    }

}

public static void main(String[] args) {
    ClipboardHandler clipboardHandler = new ClipboardHandler(System.out::println);
    EventQueue.invokeLater(clipboardHandler);

    Scanner scanner = new Scanner(System.in);
    while (scanner.hasNextLine()) {
        String buffer = scanner.nextLine();
        if (!buffer.trim().isEmpty()) {
            clipboardHandler.setBuffer(buffer);
        }
    }

}

答案 6 :(得分:0)

FlavorListener在MacOS(JRE8)中不起作用,因此轮询是必须的。马蒂亚斯·辛兹(Matthias Hinz)提出了一种不使用Swing的投票解决方案。这是我的解决方案,它使用Swing在JTextPane中显示实时剪贴板内容:

import java.awt.*;
import java.awt.datatransfer.*;
import javax.swing.*;

public class ClipboardWatcher {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            JFrame f = new JFrame(ClipboardWatcher.class.getSimpleName());
            f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            JTextPane tp = new JTextPane();
            tp.setPreferredSize(new Dimension(384, 256));
            f.getContentPane().add(new JScrollPane(tp));
            f.pack();
            f.setVisible(true);

            new Timer(200, e -> {
                Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
                DataFlavor df = DataFlavor.stringFlavor;
                if (c.isDataFlavorAvailable(df)) {
                    try {
                        String data = c.getData(df).toString();
                        if (!data.equals(_lastData))
                            tp.setText(_lastData = data);
                    } catch (Exception ex) {
                        System.err.println(ex);
                    }
                }
            }).start();
        });
    }

    private static String _lastData;
}