Search code examples
javaswingcopy-pastejtextareanonblocking

JTextArea freezes UI on large pastes


I have a program which allows the user to input data into a JTextArea. The data is then parsed and processed for further usage.

While I'm well aware of the possibility to use non-blocking file-drops instead, and I'm already offering it, some users might paste large chunks of data as well. When pasting roughly 100MB of text, the entire GUI hangs for approximately 20-30 seconds.

What would be the best approach to accept such a huge chunk of data without blocking the GUI? Keeping the JTextArea isn't a requirement.

If blocking the GUI can't be avoided: is there a way to catch and delay the paste event to update the GUI with some message saying: "processing your paste command" and to proceed afterwards?

Code example:

import java.awt.EventQueue;

import javax.swing.JFrame;
import javax.swing.JTextArea;
import java.awt.BorderLayout;


public class JTextAreaExample {

    private JFrame frame;

    /**
     * Launch the application.
     */
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    JTextAreaExample window = new JTextAreaExample();
                    window.frame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Create the application.
     */
    public JTextAreaExample() {
        initialize();
    }

    /**
     * Initialize the contents of the frame.
     */
    private void initialize() {
        frame = new JFrame();
        frame.setBounds(100, 100, 450, 300);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JTextArea textArea = new JTextArea();
        frame.getContentPane().add(textArea, BorderLayout.CENTER);
    }

}

Solution

  • I wanted to share a stripped down example my "solution" (Thanks to Andrew Thompson for his hints). Even if this maybe doesn't really do anything one should get the idea.

    Keeping the GUI responsive is working as expected for the most part. Hanging of GUI when actually updating the text of the JTextArea can't be avoided without further measures (one would have to use sliding windows for that), but that's out of scope for this example. The GUI isn't blocked while processing the copy-buffer, and that's what the question was about.

    import java.awt.EventQueue;
    
    import javax.swing.JComponent;
    import javax.swing.JFrame;
    import javax.swing.JTextArea;
    import javax.swing.KeyStroke;
    
    import java.awt.BorderLayout;
    import java.awt.event.InputEvent;
    import java.awt.event.KeyEvent;
    
    
    public class JTextAreaExample {
    
        private JFrame frame;
        private static JTextArea textArea;
    
        /**
         * Launch the application.
         */
        public static void main(String[] args) {
            EventQueue.invokeLater(new Runnable() {
                public void run() {
                    try {
                        JTextAreaExample window = new JTextAreaExample();
                        window.frame.setVisible(true);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    
        /**
         * Create the application.
         */
        public JTextAreaExample() {
            JTextAreaExample.textArea = new JTextArea();
            initialize();
        }
    
        /**
         * Initialize the contents of the frame.
         */
        private void initialize() {
            frame = new JFrame();
            frame.setBounds(100, 100, 450, 300);
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
            frame.getContentPane().add(textArea, BorderLayout.CENTER);
            textArea.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_MASK), "ctrlvpressed");
            textArea.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, 0), "ctrlvpressed");
            CustomPasteAction cpa = new CustomPasteAction("Custom Paste Action", null, "A custom paste action", KeyEvent.VK_V);
            textArea.getActionMap().put("ctrlvpressed", cpa);
        }
    
        public static final JTextArea getTextArea() {
            return textArea;
        }
    
    }
    
    
    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.datatransfer.UnsupportedFlavorException;
    import java.awt.event.ActionEvent;
    import java.awt.event.KeyEvent;
    import java.io.IOException;
    
    import javax.swing.AbstractAction;
    import javax.swing.ImageIcon;
    
    public class CustomPasteAction extends AbstractAction implements ClipboardOwner {
    
        /** */
        private static final long serialVersionUID = 1L;
    
        public CustomPasteAction(String text, ImageIcon icon,
                                                 String desc, Integer mnemonic) {
            super(text, icon);
            putValue(SHORT_DESCRIPTION, desc);
            putValue(MNEMONIC_KEY, mnemonic);
        }
    
        public CustomPasteAction() {
            super("Custom Paste Action", null);
            putValue(SHORT_DESCRIPTION, "My custom paste action");
            putValue(MNEMONIC_KEY, KeyEvent.VK_V);
        }
    
        @Override
        public void actionPerformed(ActionEvent e) {
            new Thread(new Runnable() {
            public void run() {
    
                String temp = new CustomPasteAction().getClipboardContents();
                System.out.println("Processing: " + temp);
                JTextAreaExample.getTextArea().setText(temp);
    
            }
        }).start();
        }
    
        @Override
        public void lostOwnership(Clipboard clipboard, Transferable contents) {
            // do nothing
        }
    
        /**
          * Get the String residing on the clipboard.
          *
          * @return any text found on the Clipboard; if none found, return an
          * empty String.
          */
          public String getClipboardContents() {
            String result = "";
            Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
            //odd: the Object param of getContents is not currently used
            Transferable contents = null;
            try {
                contents = clipboard.getContents(null);
            } catch(IllegalStateException ise) {
                ise.printStackTrace();
            }
            boolean hasTransferableText =
              (contents != null) &&
              contents.isDataFlavorSupported(DataFlavor.stringFlavor)
            ;
            if ( hasTransferableText ) {
              try {
                result = (String)contents.getTransferData(DataFlavor.stringFlavor);
              }
              catch (UnsupportedFlavorException ex){
                //highly unlikely since we are using a standard DataFlavor
                System.out.println(ex);
                ex.printStackTrace();
              }
              catch (IOException ex) {
                System.out.println(ex);
                ex.printStackTrace();
              }
            }
            return result;
          }
    
          /**
           * Place a String on the clipboard, and make this class the
           * owner of the Clipboard's contents.
           */
           public void setClipboardContents( String aString ){
             StringSelection stringSelection = new StringSelection( aString );
             Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
             clipboard.setContents( stringSelection, this );
           }
    
    }