Reputation: 11
I'm creating some chatting functionality in Java, using WindowBuilder. My GUI class creates a thread, from within which I want to be able to update the superclass's TextArea. The reason I create a thread is that I want to be able to interrupt the code that is inside the subclass.
My problem is that I can't append to the superclass's TextArea from within the subclass's thread.
I tried cutting down my code to the bare essentials:
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JMenu;
import javax.swing.GroupLayout;
import javax.swing.GroupLayout.Alignment;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class SSCCE {
private JFrame frmRoom;
private final static String newline = "\r\n";
private JTextField textField;
private JScrollPane scrollPane;
private JTextArea textArea;
/**
* Launch the application.
*/
public static void main(String[] args) {
try {
} catch (Throwable e) {
e.printStackTrace();
}
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
SSCCE window = new SSCCE();
window.frmRoom.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the application.
*/
public SSCCE() {
initialize();
}
/**
* Initialize the contents of the frame.
*/
private void initialize() {
frmRoom = new JFrame();
frmRoom.setTitle("Test");
frmRoom.setBounds(100, 100, 450, 300);
frmRoom.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JMenuBar menuBar = new JMenuBar();
frmRoom.setJMenuBar(menuBar);
JMenu mnButton1 = new JMenu("Button 1");
menuBar.add(mnButton1);
JMenuItem mntmButton2 = new JMenuItem("Button 2");
mntmButton2.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
Thread t = new Thread(new SSCCESub());
SwingUtilities.invokeLater(t);
//t.start();
//Neither of the above work.
}
});
mnButton1.add(mntmButton2);
textField = new JTextField();
scrollPane = new JScrollPane();
textArea = new JTextArea();
textArea.setEditable(false);
scrollPane.setViewportView(textArea);
GroupLayout groupLayout = new GroupLayout(frmRoom.getContentPane());
groupLayout.setHorizontalGroup(
groupLayout.createParallelGroup(Alignment.LEADING)
.addComponent(scrollPane, GroupLayout.DEFAULT_SIZE, 434, Short.MAX_VALUE)
.addGroup(Alignment.TRAILING, groupLayout.createSequentialGroup()
.addComponent(textField, GroupLayout.PREFERRED_SIZE, 434, Short.MAX_VALUE)
.addGap(0))
);
groupLayout.setVerticalGroup(
groupLayout.createParallelGroup(Alignment.LEADING)
.addGroup(groupLayout.createSequentialGroup()
.addComponent(scrollPane, GroupLayout.DEFAULT_SIZE, 214, Short.MAX_VALUE)
.addComponent(textField, GroupLayout.PREFERRED_SIZE, 28, GroupLayout.PREFERRED_SIZE))
);
frmRoom.getContentPane().setLayout(groupLayout);
}
private void addLineToTextArea(String line) {
System.out.println("Tried calling a superclass method in order to append to the TextArea");
textArea.append(line + newline);
}
private static class SSCCESub extends SSCCE implements Runnable {
public void run() {
super.textArea.append("This won't be visible" + newline); //TODO This is what my question is about.
super.addLineToTextArea("This won't be visible");
System.out.println("This should be visible");
return;
}
}
}
Upvotes: 0
Views: 59
Reputation: 285405
The short answer is: don't use inheritance to try to facilitate communication between a child and parent objects. That's not what inheritance is for, and not how it works (as you're finding out). Instead use composition -- pass an instance of object1 into object2 using constructor or method parameters, and call public methods to pass information.
So your 2nd class could have a constructor that takes a SSCCE parameter, allows you to pass it in, then set an SSCCE field and this can allow you to call public methods of your current SSCCE object.
Next, once you fix this, your code runs afoul of Swing threading rules -- you should only mutate Swing components on the Swing event thread. Please read Lesson: Concurrency in Swing
More detailed answer coming....
For example, say you wanted to hook a Swing GUI with a Socket for simple chat communication. We could create a new GUI class, say called SSCCE2, something like....
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
import javax.swing.*;
@SuppressWarnings("serial")
public class SSCCE2 extends JPanel {
private static final int GAP = 4;
private Action submitAction = new SubmitAction("Submit");
private JTextField textField = new JTextField(40);
private JTextArea textArea = new JTextArea(20, 40);
private JButton submitButton = new JButton(submitAction);
private PrintWriter printWriter = null;
private ChatWorker chatWorker = null;
public SSCCE2(Socket socket) throws IOException {
printWriter = new PrintWriter(socket.getOutputStream());
chatWorker = new ChatWorker(this, socket);
chatWorker.execute();
textArea.setFocusable(false);
JScrollPane scrollPane = new JScrollPane(textArea);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
textField.setAction(submitAction);
JPanel bottomPanel = new JPanel();
bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.LINE_AXIS));
bottomPanel.add(textField);
bottomPanel.add(submitButton);
setBorder(BorderFactory.createEmptyBorder(GAP, GAP, GAP, GAP));
setLayout(new BorderLayout(GAP, GAP));
add(scrollPane);
add(bottomPanel, BorderLayout.PAGE_END);
}
// Action (acts as an ActionListener) that gets text from
// JTextField and puts it into JTextArea
// And also sends it via PrintWriter to the Socket
private class SubmitAction extends AbstractAction {
public SubmitAction(String name) {
super(name);
}
@Override
public void actionPerformed(ActionEvent e) {
String text = textField.getText();
textField.selectAll();
// send this to the socket for chatting....
if (printWriter != null) {
printWriter.println(text);
}
textArea.append(text);
textArea.append("\n");
}
}
// public method to allow outside objects to append to the JTextArea
public void append(String text) {
textArea.append(text);
textArea.append("\n");
}
}
with a public method, say public void append(String text)
that allows outside classes to append to the JTextArea, then we would pass an instance of this to where needed, here: chatWorker = new ChatWorker(this, socket);
by passing in this
. Then our ChatWorker can call the public methods:
import java.io.IOException;
import java.net.Socket;
import java.util.List;
import java.util.Scanner;
import javax.swing.SwingWorker;
//better if the threading is done with a SwingWorker
//to not run afoul of Swing threading rules
public class ChatWorker extends SwingWorker<Void, String> {
private SSCCE2 sscce2 = null;
private Scanner scanner = null;
public ChatWorker(SSCCE2 sscce2, Socket socket) throws IOException {
this.sscce2 = sscce2; // get the instance and assign to field
scanner = new Scanner(socket.getInputStream());
}
@Override
protected Void doInBackground() throws Exception {
// this is called in a background thread
while (scanner.hasNextLine()) {
publish(scanner.nextLine());
}
return null;
}
@Override
protected void process(List<String> chunks) {
// this is called on the Swing event thread
for (String text : chunks) {
sscce2.append(text); // append the texts as they come in
}
}
}
Upvotes: 1