-1

I'm having a bit of trouble trying to add animation to a chat program. It works fine until the JScrollPane needs to start scrolling, at which point the ScrollPane doesn't update until the "runnable" goes through its next loop (The animation works, and no, Thread.sleep isn't the issue here). Anyone have any advice? I've tried things such as invoking revalidate but to no difference. Line ~188 with the comment "//Need jsp (JScrollPane) to instantly update" is where I'd need the JScrollPane to update it's view. Thanks! package com.AI;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.IOException;
import java.sql.Date;
import java.util.Calendar;
import java.util.Random;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.LineBorder;
import javax.swing.text.DefaultCaret;

public class MainGUI {

    public String appName = "Chat Assistant v1.3.3";
    public MainGUI mainGUI;
    public JPanel mainPanel;
    public JScrollPane jsp;
    public JFrame newFrame = new JFrame(appName);
    public JButton sendMessage;
    public JTextField messageBox = new JTextField(30);
    public JTextArea chatBox;
    String username = "Evan";
    public Random rand = new Random();
    public Calendar cal= Calendar.getInstance();

    //public MainEngine me = new MainEngine();

    public String temp = "";
    public String tempL = "";

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (Exception e) {
                    e.printStackTrace();
                }
                MainGUI mainGUI = new MainGUI();
                mainGUI.display();
            }
        });
    }

    public void display() {
        mainPanel = new JPanel();
        mainPanel.setLayout(new BorderLayout());

        JPanel southPanel = new JPanel();
        southPanel.setBackground(Color.BLUE);
        southPanel.setLayout(new GridBagLayout());

        messageBox.requestFocusInWindow();

        sendMessage = new JButton("Send Message");
        sendMessage.addActionListener(new sendMessageButtonListener());
        chatBox = new JTextArea();
        chatBox.setEditable(false);
        chatBox.setFont(new Font("Arial", Font.PLAIN, 18));
        chatBox.setLineWrap(true);
        jsp = new JScrollPane(chatBox);
        jsp.setBorder(new LineBorder(Color.white, 7));

        mainPanel.add(jsp, BorderLayout.CENTER);

        GridBagConstraints left = new GridBagConstraints();
        left.anchor = GridBagConstraints.LINE_START;
        left.fill = GridBagConstraints.HORIZONTAL;
        left.weightx = 512.0D;
        left.weighty = 1.0D;

        GridBagConstraints right = new GridBagConstraints();
        right.insets = new Insets(0, 10, 0, 0);
        right.anchor = GridBagConstraints.LINE_END;
        right.fill = GridBagConstraints.NONE;
        right.weightx = 1.0D;
        right.weighty = 1.0D;

        southPanel.add(messageBox, left);
        southPanel.add(sendMessage, right);

        mainPanel.add(BorderLayout.SOUTH, southPanel);

        newFrame.add(mainPanel);
        newFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        newFrame.setSize(720, 480);
        newFrame.setVisible(true);
        newFrame.setResizable(false);
        newFrame.setLocationRelativeTo(null);
        messageBox.requestFocusInWindow();
        messageBox.addKeyListener(new KeyListener());

        startup();
    }

    public void startup() {
        int h = cal.get(Calendar.HOUR_OF_DAY);
        int n = rand.nextInt(2) + 1;
        String message = "";
        chatBox.append("AIBot:  ");
        if (n == 1)
            message = "Welcome back sir!";
        else if ( n == 2) {
            if ((h > 4) && (h < 11)) 
                message = "Good Morning sir, I hope you have a great day.";
            else if ((h >= 11) && (h < 17))
                message = "Good Afternoon sir";
            else if ((h >= 17) && (h < 25))
                message = "Good Evening sir, how was your day?";
            else
                message = "It's quite late, you should get some rest sir";
        }
        try {
            Runtime.getRuntime().exec( new String[] { "say" , "" + message }) ;
        } catch (IOException e) {
            e.printStackTrace();
        }
        messageBox.paintImmediately(messageBox.getBounds());
        sendMessage.paintImmediately(sendMessage.getBounds());
        messageBox.requestFocusInWindow();
        for (int i = 0; i < message.length(); i++) { //Appends 1 letter at a time, "animation", voice is already executed
            try {Thread.sleep(35);} catch (InterruptedException e) {e.printStackTrace();}
            chatBox.append(message.substring(i, i+1));
            chatBox.setCaretPosition(chatBox.getDocument().getLength());
            chatBox.paintImmediately(chatBox.getBounds ());
        }
        chatBox.append("\n\n");
        messageBox.setText("");
    }

    public class KeyListener extends KeyAdapter {
        @Override
            public void keyPressed(KeyEvent e) {
            if (e.getKeyCode() == KeyEvent.VK_ENTER) {
                sendMessage.doClick();
            }
        }
    }

    public class sendMessageButtonListener implements ActionListener {
        public void actionPerformed(ActionEvent event) {
            if (messageBox.getText().length() < 1) {
                // do nothing
            } else if (messageBox.getText().equals(".clear")) {
                chatBox.setText("Cleared all messages\n"); 
                messageBox.setText("");
            } else {
                chatBox.append("" + username + ":  ");
                chatBox.append(messageBox.getText() + "\n\n");
                temp = messageBox.getText();
                tempL = temp.toLowerCase();
                messageBox.setText("");
                chatBox.setCaretPosition(chatBox.getDocument().getLength());
                chatBox.paintImmediately(chatBox.getBounds ());
            }
            messageBox.requestFocusInWindow();
            chatBox.append("AIBot:  ");
            //String message = me.disperse(tempL) + " ";
            String message = "TEST................";
            if (message.contains("username")) {
                String[] t = message.split("username");
                message = t[0] + username + t[1];
            }
            chatBox.setCaretPosition(chatBox.getDocument().getLength());
            chatBox.paintImmediately(chatBox.getBounds());
            //Need jsp (JScrollPane) to instantly update
            message += "";
            try {
                Runtime.getRuntime().exec( new String[] { "say" , "" + message }) ;
            } catch (IOException e) {
                e.printStackTrace();
            }
            for (int i = 0; i < message.length(); i++) { //Appends 1 letter at a time, "animation", voice is already executed
                try {Thread.sleep(35);} catch (InterruptedException e) {e.printStackTrace();}
                chatBox.append(message.substring(i, i+1));
                chatBox.paintImmediately(chatBox.getBounds());
                chatBox.setCaretPosition(chatBox.getDocument().getLength());
            }
            chatBox.append("\n\n");

            chatBox.setCaretPosition(chatBox.getDocument().getLength());
        }
    }
}
  • As with most "Swing GUI is frozen" type questions, you're stomping on the Swing event thread, here by calling `Thread.sleep(...)` within this thread. Solution: don't do this. If you need a delay, use a Swing Timer (Google the tutorial), or if you need to run long-running code, use a Swing Worker (again Google the tutorial). – Hovercraft Full Of Eels Oct 07 '15 at 15:26
  • Hovercraft, that's not the issue. The delay works fine and the animation works. The only thing I need help with is the code necessary to instantly update the JScrollPane, regardless of any Timer/Sleep – EcstaticException Oct 08 '15 at 16:23
  • ...........................hello? Please see code changes to my answer that pretty much proves that it is a Thread.sleep issue. – Hovercraft Full Of Eels Oct 09 '15 at 22:15

1 Answers1

0

Hovercraft, that's not the issue. The delay works fine and the animation works. The only thing I need help with is the code necessary to instantly update the JScrollPane, regardless of any Timer/Sleep

Yes your use of Thread.sleep(...) is the issue. For instance if you use a Swing Timer as recommended above for the delayed printing, the scrollpane updates immediately:

import java.awt.BorderLayout;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;

@SuppressWarnings("serial")
public class MainGui2 extends JPanel {
    private static final Font TEXT_AREA_FONT = new Font("Arial", Font.PLAIN, 18);
    private static final String TEST_MESSAGE = "TEST................";
    private static final String TIMER = "Timer";
    private static final String THREAD_SLEEP = "Thread.sleep";
    private static final String[] RADIOS = { TIMER, THREAD_SLEEP };
    public static final int TIMER_DELAY = 80;
    private JTextArea textArea = new JTextArea(20, 40);
    private JTextField textField = new JTextField(10);
    private Action sendMsgAxn = new SendMessageAction("Send Message");
    private JButton sendMsgButton = new JButton(sendMsgAxn);
    private ButtonGroup buttonGroup = new ButtonGroup();

    public MainGui2() {
        textArea.setFont(TEXT_AREA_FONT);
        textArea.setWrapStyleWord(true);
        textArea.setLineWrap(true);
        textArea.setFocusable(false);
        JScrollPane scrollPane = new JScrollPane(textArea);
        scrollPane
                .setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);

        textField.setAction(sendMsgAxn);

        JPanel radioPanel = new JPanel(new GridLayout(1, 0));
        for (String radioText : RADIOS) {
            JRadioButton rBtn = new JRadioButton(radioText);
            rBtn.setActionCommand(radioText);
            buttonGroup.add(rBtn);
            radioPanel.add(rBtn);
        }

        JPanel southPanel = new JPanel();
        southPanel.setLayout(new BoxLayout(southPanel, BoxLayout.LINE_AXIS));
        southPanel.add(textField);
        southPanel.add(radioPanel);
        southPanel.add(sendMsgButton);

        setLayout(new BorderLayout());
        add(scrollPane, BorderLayout.CENTER);
        add(southPanel, BorderLayout.PAGE_END);
    }

    class SendMessageAction extends AbstractAction {
        private Timer timer;

        public SendMessageAction(String name) {
            super(name);
            int mnemnoic = (int) name.charAt(0);
            putValue(MNEMONIC_KEY, mnemnoic);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            if (timer != null && timer.isRunning()) {
                return;
            }

            ButtonModel model = buttonGroup.getSelection();
            if (model == null) {
                return;
            }

            String text = textField.getText();
            textField.selectAll(); // so text can easily be changed
            textArea.append("\nUser > " + text + "\n");
            // TODO: in a background thread -- send text to outside process

            String command = model.getActionCommand();
            if (command.equals(TIMER)) {
                timerAction(TEST_MESSAGE);
            } else if (command.equals(THREAD_SLEEP)) {
                threadSleepAction(TEST_MESSAGE);
            }

        }

        private void threadSleepAction(String text) {
            for (int i = 0; i < text.length(); i++) {
                textArea.append("" + text.charAt(i));
                textArea.setCaretPosition(textArea.getDocument().getLength());
                textArea.paintImmediately(textArea.getBounds());
                try {
                    Thread.sleep(TIMER_DELAY);
                } catch (InterruptedException e) {
                }
            }
        }

        private void timerAction(String text) {
            timer = new Timer(TIMER_DELAY, new TimerListener(text));
            timer.start();
        }
    }

    private class TimerListener implements ActionListener {
        private String message;
        private int index = 0;

        public TimerListener(String message) {
            this.message = message;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            if (message.length() == index) {
                ((Timer) e.getSource()).stop(); // stop the timer
            } else {
                textArea.append("" + message.charAt(index));
                index++;
            }
        }
    }

    private static void createAndShowGui() {
        JFrame frame = new JFrame("MainGui2");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(new MainGui2());
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGui();
            }
        });
    }
}

And don't use a KeyListener with a JTextField ever. Instead here you want to use an ActionListener or an Action, and in fact can use the same Action for your JButton as for your JTextField.

Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373