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]);
}
}
}
}
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.
Personally, I would create separate "Runnable" classes for each type of message you want to send instead of one generic
GUIUpdateArrayHandler
: likeAppendHandler
forMessageType.APPEND
,SetHandler
forMessageType.SET
, but if you think it's "less sloppy" to have them in the same place in a single handler, up to you.