2

What design pattern is best for updating the GUI when dealing with other threads in Java (Swing)?

For example, imagine an Object (like a custom JPanel) that has a JList that has a DefaultListModel supporting it. A threading listening on a Socket can receive data and then wants to update the JList from the information that came in on the socket.

I understand the SwingUtilities.invokeLater, but that seems like sloopy code, because in reality I have many different functions that can be called (from non EDT threads) that manipulate different GUI components.

The idea that I thought of is creating some kind of messaging system with an ArrayBlockingQueue. Basically I implement Runnable and in the SwingUtilities.invokeLater method call I pass in this. Then the method gets executed, but it doesn't really know what to do, but that is where I pop the "messages" from the thread safe ArrayBlockingQueue.

Is there a better design pattern than this? My base JPanel Class

public class JPanelGUIThread extends JPanel implements Runnable
{
    protected ArrayBlockingQueue<Object> guiUpdateMessages;
    
    public JPanelGUIThread()
    {
        guiUpdateMessages = new ArrayBlockingQueue<Object>(10);
    }
    
    @Override
    public void run()
    {
        while(guiUpdateMessages.size() > 0)
        {
            try
            {
                Object data = guiUpdateMessages.take();
                
                if(data instanceof Object[])
                {
                    handleGUIUpdateArray((Object[])data);
                }
                else
                {
                    handleGUIUpdateObject(data);
                }
                
            } 
            catch (InterruptedException e)
            {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            
        }
    }
    
    public void handleGUIUpdateArray(Object[] objectArray)
    {
        
    }
    public void handleGUIUpdateObject(Object object)
    {
        
    }
}

My main JPanel



    
    public JLabel getChatLabel()
    {
        return chatLabel;
    }

    public JTextArea getChatArea()
    {
        return chatArea;
    }

    public JScrollPane getChatScrollPane()
    {
        return chatScrollPane;
    }

    public JTextField getMychat()
    {
        return mychat;
    }

    public JButton getSendButton()
    {
        return sendButton;
    }

    //This method is called from the EDT, so no need to perform adding messages
    @Override
    public void actionPerformed(ActionEvent e)
    {
        if(e.getSource() == sendButton)
        {
            client.sendChatInformation(mychat.getText());
            mychat.setText("");
        }
    }

    public void clearOldChat()
    {
        Object[] data = new Object[3];
        data[0] = chatArea;
        data[1] = MessageType.SET;
        data[2] = "";
        guiUpdateMessages.add(data);
        SwingUtilities.invokeLater(this);
    }


    @Override
    public void handleGUIUpdateArray(Object[] objectArray)
    {
        if(objectArray[0] == chatArea)
        {
            if(objectArray[1] == MessageType.APPEND)
            {
                chatArea.append((String) objectArray[2]);
            }
            else if(objectArray[1] == MessageType.SET)
            {
                chatArea.setText((String) objectArray[2]);
            }
            
        }
    }
}
Matthew
  • 3,886
  • 7
  • 47
  • 84
  • 1
    By the way, the [Records](https://openjdk.java.net/jeps/384) feature arriving in Java 16, and previewed in Java 14 & 15, could be used to replace your use of `Object` array as a tuple. – Basil Bourque Jul 16 '20 at 15:55

1 Answers1

3

You are reinventing the "event queue" that makes a graphical user interface work in the first place. There already is a queue where you can add new messages, implemented in the java.awt.EventQueue class.

The convenient way to add a message to the event queue is with SwingUtilities.invokeLater(Runnable). The Runnable instance that you pass in should contain all the information necessary to process the event. Even better: since it's a Runnable, it can encapsulate the code you need to run to process the event.

For example: here is how you can encapsulate your general "object array" message within a Runnable, and add it to the event queue.

        public void clearOldChat() {
            Object[] data = new Object[3];
            data[0] = chatArea;
            data[1] = MessageType.SET;
            data[2] = "";
            SwingUtilities.invokeLater(new GUIUpdateArrayHandler(data));
        }

        class GUIUpdateArrayHandler implements Runnable {

            Object[] objectArray;

            public GUIUpdateArray(Object[] objectArray) {
                this.objectArray = objectArray;
            }

            public void run() {
                if (objectArray[0] == chatArea) {
                    if (objectArray[1] == MessageType.APPEND) {
                        chatArea.append((String) objectArray[2]);
                    } else if (objectArray[1] == MessageType.SET) {
                        chatArea.setText((String) objectArray[2]);
                    }

                }
            }
        }

Personally, I would create separate "Runnable" classes for each type of message you want to send instead of one generic GUIUpdateArrayHandler: like AppendHandler for MessageType.APPEND, SetHandler for MessageType.SET, but if you think it's "less sloppy" to have them in the same place in a single handler, up to you.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
Joni
  • 108,737
  • 14
  • 143
  • 193