Lucky38i
Lucky38i

Reputation: 135

Using JFrame from another class

Hi I'm small a little issue trying to append some text to a JTextArea from another class within the same package. Below is the main class that pertains to the JFrame:

  public class Client extends JFrame{

        //Static variables
        private static final String host = "localhost";
        private static final int portNumber = 4444;

        //Variables
        private String userName;

        //JFrame Variables
        private JPanel contentPanel;
        private JTabbedPane panel_Social;
        private JPanel jpanel_Social;
        private JPanel jpanel_Chat;
        private JTextArea textArea_Receive;
        private JTextField textField_Send;
        private JTextArea textArea_ClientList;
        private JButton btn_Enter;


        public JTextArea getTextArea_Receive(){
            return this.textArea_Receive;
        }

        //Constructor
        private Client(String userName, String host, int portNumber){
            this.userName = userName;
            this.serverHost = host;
            this.serverPort = portNumber;
        }

        public void main(String args[]){
            //Requests user to enter name
            String readName = null;
            Scanner scan = new Scanner(System.in);
            System.out.println("Please enter username");
            readName = scan.nextLine();

            //Start client
            Client client = new Client(readName, host, portNumber);
            client.startClient(scan);
        }

        private void startClient(Scanner scan){
            try{
                //Create new socket and wait for network communication
                Socket socket = new Socket(serverHost, serverPort);
                Thread.sleep(1000);

                //Create thread and start it
                serverThread = new ServerThread(socket, userName);
                Thread serverAccessThread = new Thread(serverThread);
                serverAccessThread.start();
            }
        }
    }


Below is the serverThread class

    public class ServerThread implements Runnable{

        private Socket socket;
        private String userName;
        private boolean isAlived;
        private final LinkedList<String> messagesToSend;
        private boolean hasMessages = false;

        //Constructor
        ServerThread(Socket socket,  String userName){
            this.socket = socket;
            this.userName = userName;
            messagesToSend = new LinkedList<String>();
        }

        public void run(){   
            try{
                Client test1 = new Client();
                JTextArea test2 = test1.getTextArea_Receive();
                String test3 = "Hello World";
                test2.append(test3);
            } catch (IOException e){
}
    }

I included test variables just to easily recreate the issue but whenever the append function is run nothing appears in the text area of the jFrame. In my scenario I'm having the client receive text from a server then append it to the text box.

BTW I'm using the IntelliJ GUI designer for the JFrame. I've only included code needed to recreate the problem. I'm still trying to create MCVE questions so feel free to let me know mistakes I made.

Upvotes: 0

Views: 122

Answers (2)

MadProgrammer
MadProgrammer

Reputation: 347332

the append function is run nothing appears in the text area of the jFrame

There's not enough information available in you question to ascertain why this might be happening, how ever, there are a number of important pieces of information you need to take into account.

  1. Swing is single threaded and not thread safe
  2. You want to, as much as possible, decouple of the code

Basically, the first point means that you shouldn't be running any long running or blocking processes within the Event Dispatching Thread AND you should not be modifying the UI (or any state the UI relies on) from outside the context of the Event Dispatching Thread

Start by taking a look at Concurrency in Swing for more details.

The second point means that, for even given part of your code, you want to be asking, "how hard would it be to replace it with some other implementation?" - If the amount of work scares you, then your code is probably to tightly coupled.

To that end, I started with...

public interface Client {
    public void append(String message);
}

This is really basic, but it means that some component can send a message to some other component and neither should care about each other beyond this capability.

Next, I looked at ServerThread. Basically this class becomes responsible for the management of the Socket and delivery of the messages to the Client. Because of the requirements of Swing, I've used a SwingWorker. This allows me to run the Socket code on a background thread, but ensure that the messages are delivered to the Client within the context of the Event Dispatching Thread

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.util.List;
import javax.swing.SwingWorker;

public class ServerThread extends SwingWorker<Void, String> {

    private String host;
    private int port;
    private Client client;

    //Constructor
    ServerThread(String host, int port, Client client) {
        this.host = host;
        this.port = port;
        this.client = client;
    }

    @Override
    protected void process(List<String> chunks) {
        for (String message : chunks) {
            client.append(message);
        }
    }

    @Override
    protected Void doInBackground() throws Exception {
        try (Socket socket = new Socket(host, port)) {
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
                String text = null;
                while ((text = reader.readLine()) != null) {
                    System.out.println(text);
                    if (text.equals("<stop>")) {
                        break;
                    }
                    publish(text);
                }
            }
        } catch (IOException exp) {
            exp.printStackTrace();
            publish("Failed to establish connection to " + host + ":" + port + " - " + exp.getMessage());
        }
        return null;
    }
}

And then the actual UI client itself...

import java.awt.BorderLayout;
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

public class Main {

    public static void main(String[] args) {
        new Main();
    }

    //Constructor
    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                    ClientUI clientUI = new ClientUI();

                    ServerThread thread = new ServerThread("localhost", 4321, clientUI);
                    thread.execute();

                    JFrame frame = new JFrame("Client");
                    frame.add(clientUI);
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
            }
        });
    }

    public class ClientUI extends JPanel implements Client {

        private JTextArea ta;

        public ClientUI() {
            setLayout(new BorderLayout());
            ta = new JTextArea(10, 20);
            add(new JScrollPane(ta));
        }

        @Override
        public void append(String message) {
            ta.append(message + "\n");
        }

    }
}

Not much going on here

Finally, I wrote a simple Server test the code, which simply sends the current date/time as a String to the connected client.

When I say simple, I mean simple. This is intended, by design, to tell with a single client connection and is meant only for testing the above code

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.DateFormat;
import java.util.Date;

public class Server {

    public static void main(String[] args) {
        DateFormat format = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG);
        try (ServerSocket server = new ServerSocket(4321)) {
            Socket socket = server.accept();
            System.out.println("Connected");
            try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))) {
                while (true) {
                    System.out.println("Write >> ");
                    writer.write(format.format(new Date()));
                    writer.newLine();
                    writer.flush();
                    Thread.sleep(1000);
                }
            }
        } catch (IOException | InterruptedException exp) {
            exp.printStackTrace();
        }
    }
}

To test it all, start the Server and then run the Main class

Upvotes: 1

jseashell
jseashell

Reputation: 755

You should pass Client into ServerThread via the constructor. The Client you are instantiating within run() is not the same reference to the Client you created in main(). So your ServerThread class would be something like

ServerThread(Client client, Socket socket,  String userName) {
    this.client = client;
    this.socket = socket;
    this.userName = userName;
    messagesToSend = new LinkedList<String>();
}

public void run() {
    try
    {
        JTextArea test2 = this.client.getTextArea_Receive();
        String test3 = "Hello World";
        test2.append(test3);
    } 
    catch (IOException e)
    {}
}

Your startClient() method would be updated to something like this

private void startClient(Client client, Scanner scan)
{
    try
    {
        //Create new socket and wait for network communication
        Socket socket = new Socket(serverHost, serverPort);
        Thread.sleep(1000);

        //Create thread and start it
        ServerThread serverThread = new ServerThread(client, socket, userName);
        serverAccessThread.run();
    }
}

All that being said,

I would recommend moving your main() out of Client and into a class that isn't so coupled to the Swing UI code. Something like this:

public class MySwingApplication {

    private static final String host = "localhost";
    private static final int portNumber = 4444;

    public static void main(String[] args) {
        // Requests user to enter name
        // Start client
    }
}

Your Client is then built more like an instance object

public class Client extends JFrame {
    public JTextArea getTextArea_Receive(){
        // Return the text area
    }

    // Constructor -- public to allow instantiation from main()
    public Client(String userName, String host, int portNumber) {
        // Do stuff
    }

    private void startClient(Scanner scan) {
        // Show the JFrame on screen
        // Spawn Server
    }
}

Upvotes: 1

Related Questions