Вызов метода при изменении содержимого буфера обмена

Я пытаюсь сделать небольшое настольное приложение, которое должно показывать содержимое буфера обмена (если это строка). Я сделал конструктор, который делает это, и он работает хорошо, теперь я просто хочу вызвать аналогичный метод всякий раз, когда текст копируется в буфер обмена в ОС. Я новичок в этом, поэтому любая помощь будет оценена! Что-то говорит мне, что я должен каким-то образом использовать прерывания...

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

5 ответов

Вы можете вызвать Clipboard.addFlavorListener для прослушивания обновлений буфера обмена из ОС:

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

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

Некоторые примечания:

  • Для запуска вашего приложения рассмотрите возможность использования начальных потоков.
  • Вызов JFrame.pack установить размер кадра.
  • Ключевые привязки предпочтительнее, чем KeyListeners для картирования KeyEvents в качелях.

Я использую это. Весь класс.

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 мс и возвращает владение буфером обмена с обновленным содержимым.

Я нашел другое решение этой проблемы: следующий слушатель постоянно читает содержимое буфера обмена с помощью цикла. Если текст обнаружен, он будет сравниваться с предыдущим содержимым буфера обмена, который кэшируется. Когда буфер обмена содержит новый текст, который ранее не кэшировался, он может выполнять некоторые действия, такие как "уведомлять наблюдателей", как в этом примере, что может побудить графический интерфейс обновиться.

В этом примере изменения содержимого, когда буфер обмена содержит что-то отличное от строки, игнорируются.

Помимо определения изменений типа контента (т. Е. С помощью 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();
    }
}

FlavorListener не работает в MacOS (JRE8), поэтому лучше всего использовать опрос. Матиас Хинц дал решение опроса без 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;
}

Ниже SSCCE... вы можете запустить его, выбрать текст и нажать Ctrl-C, несколько раз... выбранный текст будет распечатан.

Как видите, это немного сложнее, чем ответ Реймиуса. Вы действительно должны очистить буфер обмена (что сложно!), Чтобы слушатель аромата отвечал каждый раз, когда вы копируете новый текст.

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

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++;
        }

    }

}

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

Это устраняет проблему, связанную с тем, что слушатель запускается только тогда, когда новое приложение копирует содержимое в буфер обмена, благодаря чему сама программа становится приложением, которое копирует текст в буфер обмена.

Предупреждение о том, что слушатель будет вызываться дважды для каждого события копирования, но, конечно, есть способы с этим справиться. По крайней мере, в этой ситуации событие буфера обмена будет вызываться правильно каждый раз, когда что-то копируется в буфер обмена.

Я не совсем уверен, что это правильный и оптимальный подход, но, по крайней мере, он работал для меня просто отлично. Ниже приведен пример обработчика буфера обмена, который реализует интерфейс владельца буфера обмена и считывает буфер буфера обмена каждый раз, когда он теряет владельца. Затем он получает право собственности, чтобы иметь возможность потерять его в следующий раз, когда новая запись попадает в буфер обмена, чтобы прочитать ее снова. Это также позволяет прагматично устанавливать содержимое буфера обмена (в следующем примере предоставлена ​​новая строка 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);
        }
    }

}
Другие вопросы по тегам