0

I am using BoxLayout to position GUI elements of my Server GUI. I am having a weird thing happening with my JLabels. I was wondering why when I append a string to my JTextArea, the JLabels moves from their original locations. So the first figure is the JLabels in the correct positions. The second image is the JLabels skewed

Figure 1

enter image description here

Figure 2

enter image description here

Below is the code for the Server GUI

GUI.java

package serverGUI;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;

public class GUI extends JFrame{

    public JTextArea serverInfo;
    public JButton serverRunningState;
    public JLabel serverConnectionStatus = new JLabel(GUIConfig.DEFAULT_SERVER_STATUS);
    public JLabel serverInfoLabel = new JLabel(GUIConfig.DEFAULT_SERVER_EVENT_LABEL);

    public GUI(){
        super("Yahtzee Game Server");

        //set the server online/off-line status to default color
        serverConnectionStatus.setForeground(GUIConfig.DEFAULT_SERVER_STATUS_COLOR);

        //create the server events text area. It will hold all info
        //about server events including client connections/disconnections
        serverInfo = new JTextArea();
        serverInfo.setEditable(false);
        serverInfo.setPreferredSize(new Dimension(350, 450));

        //create the connect/disconnect button. Will be connect when server 
        //is not connected and disconnect when server is connected
        serverRunningState = new JButton();
        serverRunningState.setText("Run Server");

        //Create JPanel with box layout
        JPanel guiPanel = new JPanel();
        guiPanel.setLayout(new BoxLayout(guiPanel, BoxLayout.PAGE_AXIS));

        //add components to panel
        guiPanel.add(serverInfoLabel);
        guiPanel.add(Box.createRigidArea(new Dimension(0,5)));
        guiPanel.add(serverInfo);
        guiPanel.add(Box.createRigidArea(new Dimension(0,5)));
        guiPanel.add(serverConnectionStatus);

        //create a bit of space around components so they get away from edges
        guiPanel.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));

        //add panel to frame
        add(guiPanel, BorderLayout.CENTER);

        //create new panel for buttons
        JPanel buttonPanel = new JPanel();
        buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.PAGE_AXIS));

        //add connection button
        serverRunningState.setAlignmentX(Component.CENTER_ALIGNMENT);
        buttonPanel.add(serverRunningState);
        buttonPanel.add(Box.createRigidArea(new Dimension(0,5)));
        //add panel to frame
        add(buttonPanel, BorderLayout.SOUTH);

        //set size, do not allow resize and show
        setSize(GUIConfig.DEFAULT_FRAME_WIDTH, GUIConfig.DEFUALT_FRAME_HEIGHT);
        setResizable(false);
        setVisible(true);

        //set it to terminate frame on exit
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    public static void main(String args[]) {
        GUI g = new GUI();
    }
}

ActionListener Code (ServerController.java):

package serverController;

import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import serverGUI.GUI;
import serverModel.AppServer;
import serverModel.Config;

public class ServerController {

    public AppServer server;
    public GUI serverGUI;

    public ServerController() {
        super();

        //create the view and add an action listener to know when run server
        //button is pressed
        serverGUI = new GUI();

        //add action listener on run server button
        serverGUI.serverRunningState.addActionListener(new ActionListener()
                {
                    public void actionPerformed(ActionEvent e) {
                        //start server and disable run server button. change its text to server running
                        server = new AppServer(Config.DEFAULT_PORT);

                        if(server.isRunning() == true){
                            serverGUI.serverRunningState.setEnabled(false);
                            serverGUI.serverRunningState.setText("Server Running");
                            serverGUI.serverInfo.append("Server started!\nWaiting for new clients...\n\n");
                            serverGUI.serverInfo.setLineWrap(true);

                            //change JLabel to "Server Online" from "Server Off-line"
                            serverGUI.serverConnectionStatus.setText("Server Online");
                            serverGUI.serverConnectionStatus.setForeground(Color.GREEN);
                        }
                    }
                });
    }

    public void updateServerEventLog() {}

    private void checkServerStatus(AppServer s) {

    }

    public static void main(String args[]) {
        ServerController sController = new ServerController();
    }

}
soccerman stan
  • 121
  • 1
  • 11
  • How do the labels get swapped/modified? It would probably be a good idea to include the button `ActionListener`. What you've provided gives us the initial state, but doesn't say anything about the state after 'Run Server' is clicked, which is where you're seeing the problem. – nihilon Jan 25 '15 at 22:21
  • @nihilon included action listener code. Did not change the position of the labels, just appended the text to the JTextArea, and change text of the "Server Offline" label to read "Server Online" – soccerman stan Jan 25 '15 at 22:29

2 Answers2

2

The reason for this behaviour is that once you add something to the JTextArea, the whole JPanel has to be redrawn. And because you did not explicitly set the alignment, Swing will align it to the center, messing your UI.

You need to explicitly specify the alignment for all components inside the JPanel using BoxLayout:

    //add components to panel
    serverInfoLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
    guiPanel.add(serverInfoLabel);
    guiPanel.add(Box.createRigidArea(new Dimension(0,5)));
    serverInfo.setAlignmentX(Component.LEFT_ALIGNMENT);
    guiPanel.add(serverInfo);
    guiPanel.add(Box.createRigidArea(new Dimension(0,5)));
    serverConnectionStatus.setAlignmentX(Component.LEFT_ALIGNMENT);
    guiPanel.add(serverConnectionStatus);

After adding it should work.

Crazyjavahacking
  • 9,343
  • 2
  • 31
  • 40
1

So, while Crazyjavahacking's answer gets straight to the point, I wanted to add some things. But before that, you may also want to take a look at the "official" tutorial for using BoxLayout, and maybe even the BoxLayout JavaDoc. Remember that the JavaDocs, Google, and searching SO can be your best friend in situations like this :).

UPDATE: Also, on running Swing applications: Swing Single Threading Rule

That is an important thing that I can't believe I missed, mostly because I have been putzing around with a Swing interface for the last few weeks. Here is the link to an answer that explains it better than I ever could: EDT: Event Dispatching Thread.

On to my additions...

First, there are a number of problems with the way you are using your GUI class and your ServerController class. First, declaring all your fields public is a terrible idea. Not only is it frowned upon, but it breaks a very fundamental principle on which Java, and Object Oriented programming in general, builds called Encapsulation. I've provided a link to a short article on the topic, and there are many, many posts here on SO, and all over the internet that cover it, so I am not going to cover it here. The other thing I felt was an issue was that you were extend JFrame in your GUI class. I know there are a lot of tutorials and examples all over the place that show this being done, but for future reference, in general, doing so isn't a good idea. Also, I feel you may want to reconsider choosing your field names. As an example, calling the server start button serverRunningState isn't the best name choice. In the code I've provided below, it will cause confusion later down the road. Consider giving your components names like, in this case, serverStartButton. That name both tells me what the component does and what it is, so later on there is a smaller chance of becoming confused, or having to go back and find the declaration to know what I am dealing with.

I took the time to do a quick rewrite of your classes taking those two things into mind. Read the following code until you understand it. I am going to venture to guess that your application is mostly written in the fashion these two classes, so chances are you won't simply be able to drop these into place and have them work. But take the time to understand what is going on here and why, it will help make your future work even better.

It's worth noting that these rewrites are not thorough in any sense. I primarily fixed the problem with the utilization of public fields. The rest will be up to you to correct and improve on:

GUI Class:

import javax.swing.*;
import java.awt.*;

public class GUI implements Runnable{

public GUI(){
    private JFrame frame = new JFrame("Yahtzee Game Server");

    //set the server online/off-line status to default color
    private JLabel serverConnectionStatus = new JLabel(GUIConfig.DEFAULT_SERVER_STATUS);
    serverConnectionStatus.setForeground(GUIConfig.DEFAULT_SERVER_STATUS_COLOR);

    //create the server events text area. It will hold all info
    //about server events including client connections/disconnections
    private JTextArea serverInfo = new JTextArea();
    serverInfo.setEditable(false);
    serverInfo.setPreferredSize(new Dimension(350, 450));

    //create the connect/disconnect button. Will be connect when server
    //is not connected and disconnect when server is connected
    private JButton serverRunningState = new JButton();
    serverRunningState.setText("Run Server");

    //Create JPanel with box layout
    private JPanel guiPanel = new JPanel();
    guiPanel.setLayout(new BoxLayout(guiPanel, BoxLayout.PAGE_AXIS));

    //add components to panel
    private JLabel serverInfoLabel = new JLabel(GUIConfig.DEFAULT_SERVER_EVENT_LABEL);
    guiPanel.add(serverInfoLabel);
    guiPanel.add(Box.createRigidArea(new Dimension(0,5)));
    serverInfo.setAlignmentX(Component.LEFT_ALIGNMENT); // Fixed the actual problem
    guiPanel.add(serverInfo);
    guiPanel.add(Box.createRigidArea(new Dimension(0,5)));
    serverConnectionStatus.setAlignmentX(Component.LEFT_ALIGNMENT); // Fixed the actual problem
    guiPanel.add(serverConnectionStatus);

    //create a bit of space around components so they get away from edges
    guiPanel.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));

    //add panel to frame
    frame.add(guiPanel, BorderLayout.CENTER);

    //create new panel for buttons
    JPanel buttonPanel = new JPanel();
    buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.PAGE_AXIS));

    //add connection button
    serverRunningState.setAlignmentX(Component.CENTER_ALIGNMENT);
    buttonPanel.add(serverRunningState);
    buttonPanel.add(Box.createRigidArea(new Dimension(0,5)));
    //add panel to frame
    frame.add(buttonPanel, BorderLayout.SOUTH);

    // Add ActionListener 
    serverRunningState.addActionListener(new ServerController(
        serverRunningState, serverInfo, serverInfoLabel, serverConnectionStatus
    ));

    //set size, do not allow resize and show
    frame.setSize(GUIConfig.DEFAULT_FRAME_WIDTH, GUIConfig.DEFUALT_FRAME_HEIGHT);
    frame.setResizable(false);
    frame.setVisible(true);

    //set it to terminate frame on exit
    frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}

@Override
public void run() {
    // It seems to me folks like to include the code that sets the size,
    // close option, frame visibility, etc, here. But I don't believe it
    // necessary. However, any Swing gui you create needs to implement
    // Runnable and be instantiated in the manner shown in main().
    // See: https://bitguru.wordpress.com/2007/03/21/will-the-real-swing-single-threading-rule-please-stand-up/
}
public static void main(String args[]) {
    //SwingUtilities.invokeLater() is another method aside from EventQueue.invokeLater()
    SwingUtilities.invokeLater(new GUI());
}
}

ActionListener

import javax.swing.*;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class ServerController implements ActionListener {
private JTextArea serverInfo;
private JLabel serverRunningState;
private JLabel serverConnectionStatus;
private JButton serverStartButton;

public ServerController(JButton serverStartButton,
                        JTextArea serverInfo,
                        JLabel serverRunningState,
                        JLabel serverConnectionStatus) {
    this.serverInfo = serverInfo;
    this.serverRunningState = serverRunningState;
    this.serverConnectionStatus = serverConnectionStatus;
    this.serverStartButton = serverStartButton;
}

@Override
public void actionPerformed(ActionEvent e) {
    server = new AppServer(Config.DEFAULT_PORT);

    if (server.isRunning()) { /* If this method is a boolean, putting
                                 (server.isRunning() == true) is pointless.
                                 It will either return true or not. */
        serverStartButton.setEnabled(false);
        serverStartButton.setText("Server Running");
        serverInfo.append(
            "Server started!\nWaiting for new clients...\n\n");
        serverInfo.setLineWrap(true);

        //change JLabel to "Server Online" from "Server Off-line"
        serverConnectionStatus.setText("Server Online");
        serverConnectionStatus.setForeground(Color.GREEN);
    }
}
}
Community
  • 1
  • 1
nihilon
  • 834
  • 2
  • 8
  • 23
  • No worries. I also just added some info regarding the running of swing applications, and updated to example to reflect that. It's an important piece that would be a good idea to look into. – nihilon Jan 27 '15 at 20:00