java 剪贴板内容改变时调用方法
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/14226064/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
calling a method when content of clipboard is changed
提问by user1907859
I'm trying to make a little desktop app that should show the contents of the clipboard (if it is a string). I have done a constructor that does that and it works well, now I just want to make a call to a similar method whenever a text is copied into the clipboard in the OS. I'm quite new to this so any help would be appreciated! Something tells me I should use interrupts in some way...
我正在尝试制作一个小桌面应用程序,它应该显示剪贴板的内容(如果它是一个字符串)。我已经完成了一个构造函数,它运行良好,现在我只想在将文本复制到操作系统的剪贴板时调用类似的方法。我对此很陌生,因此任何帮助将不胜感激!有人告诉我我应该以某种方式使用中断......
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);
}
回答by daredesm
I use this. The whole class.
我用这个。全班。
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) {
}
}
}
When other program takes ownership of the clipboard it waits 250 ms and takes back clipboard's ownership with updated content.
当其他程序获得剪贴板的所有权时,它会等待 250 毫秒,并用更新的内容收回剪贴板的所有权。
回答by Reimeus
You can call Clipboard.addFlavorListenerto listen for clipboard updates from the OS:
您可以调用Clipboard.addFlavorListener来监听来自操作系统的剪贴板更新:
Toolkit.getDefaultToolkit().getSystemClipboard().addFlavorListener(new FlavorListener() {
@Override
public void flavorsChanged(FlavorEvent e) {
System.out.println("ClipBoard UPDATED: " + e.getSource() + " " + e.toString());
}
});
Some Side Notes:
一些旁注:
- For launching your application, consider using initial threads.
- Call
JFrame.pack
to set the frame size. - Key Bindingsare preferred over
KeyListeners
for mappingKeyEvents
in Swing.
回答by mike rodent
Below is an SSCCE... you can run it and select text and go Ctrl-C, multiple times ... the text selected gets printed out.
下面是一个 SSCCE……您可以运行它并选择文本并按 Ctrl-C,多次……选择的文本被打印出来。
As you can see it is a little more involved than Reimius' answer. You do in fact have to clear the clipboard (which is tricky!) for the flavour listener to respond each time you copy some new text.
正如您所看到的,它比 Reimius 的回答要复杂一些。实际上,每次复制一些新文本时,您都必须清除剪贴板(这很棘手!)以使风味侦听器做出响应。
Furthermore you probably need to suppress output caused by the "flavour change" when you clear the clipboard... though there may be a cleverer solution than mine.
此外,您可能需要在清除剪贴板时抑制由“风味变化”引起的输出……尽管可能有比我更聪明的解决方案。
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++;
}
}
}
回答by Matthias Hinz
I came up with another solution to this problem: The following listener continuously reads the clipboard content using a loop. If a text is detected, it will be compared to the previous content of the clipboard, which is cached. When the clipboard contains a new text that was not cached previously, it may perform some action like 'notify observers', as in this example, which may prompt a GUI to update itself.
我想出了另一个解决此问题的方法:以下侦听器使用循环不断读取剪贴板内容。如果检测到文本,则会将其与缓存的剪贴板的先前内容进行比较。当剪贴板包含之前未缓存的新文本时,它可能会执行一些诸如“通知观察者”之类的操作,如本示例中所示,这可能会提示 GUI 更新自身。
In this example, content changes, where the clipboard contains something other than a string, are ignored.
在此示例中,内容更改(剪贴板包含字符串以外的内容)将被忽略。
Other than just detecting content type changes (i.e. by using the FlavorListerner), this solution detects changes by continuous string comparison. By just read-accessing the clipboard, I'd expect this code to cause less interference with other applications than e.g. by taking ownership of the clipboard.
除了仅检测内容类型更改(即通过使用 FlavorListerner)之外,该解决方案还通过连续字符串比较来检测更改。通过仅读取剪贴板,我希望此代码对其他应用程序的干扰比例如通过获取剪贴板的所有权更少。
Suggestions are welcome.
欢迎提出建议。
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();
}
}
回答by Nikita Karatun
I'm not completely sure if it is correct and optimal approach, but at least it worked for me just fine. Below is the sample of the clipboard handler which implements clipboard owner interface and reads clipboard buffer each time it loses the ownership. Then it gets the ownership back to be able to lose it next time when new entry comes to the clipboard to read it again. It also allows to set clipboard contents pragmatically (in following example providing new stdin line).
我不完全确定它是否是正确和最佳的方法,但至少它对我来说很好。下面是剪贴板处理程序的示例,它实现了剪贴板所有者接口并在每次失去所有权时读取剪贴板缓冲区。然后,当新条目进入剪贴板以再次读取时,它会重新获得所有权,以便下次能够失去它。它还允许以务实的方式设置剪贴板内容(在以下示例中提供新的标准输入行)。
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);
}
}
}
回答by Adam Gawne-Cain
FlavorListener doesn't work in MacOS (JRE8) so polling is the way to go. Matthias Hinz gave a polling solution without Swing. Here is a my solution which uses Swing to show live clipboard contents in a JTextPane:
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;
}
回答by Daniel Goudie
Reimeus recommended using Clipboard.AddFlavorListener. I'd like to expand on his answer slightly. The way I've been doing this in my program is like this:
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);
}
This remedies the problem of the listener only being fired when a new app copies contents to the clipboard, by essentially making the program itself become the app that copies the text to the clipboard.
这通过本质上使程序本身成为将文本复制到剪贴板的应用程序,解决了仅在新应用程序将内容复制到剪贴板时才触发侦听器的问题。
The caveat of this being the listener will get called twice for each copy event, but there are ways to deal with that, of course. At least, in this situation, the clipboard event will be called properly every time something is copied to the clipboard.
需要注意的是,对于每个复制事件,侦听器都会被调用两次,但是当然有办法解决这个问题。至少,在这种情况下,每次将某些内容复制到剪贴板时,都会正确调用剪贴板事件。