Layouts and more GUI Examples

More on Layout Managers:
 
Last time we have introduced layout managers. Let's  experiment with them a little further and take a look at our "summation program" again. It looks, approximately, like the code below.
public class TestMain
{
   public static void main(String[] args)
   {               
      TestFrame frame = new TestFrame("Testing Program");
   }
}
import java.awt.*;

public class TestFrame extends Frame
{
   private Button    add      = new Button("Add"),  
                     reset    = new Button("Reset"),
                     quit     = new Button("Quit");
   private TextField output   = new TextField(6),   
                     input    = new TextField(6);
   private Label     outLabel = new Label("Sum:"),  
                     inLabel  = new Label("Add:");
   private int       sum = 0;

   public TestFrame(String t)
   {
      super(t); setup();
      pack();   show();
   }

   public boolean action(Event e, Object o)
   {
      if (e.target == quit)
         System.exit(0);
      else if (e.target == add)
      {
         sum += Integer.parseInt(input.getText());
         output.setText(String.valueOf(sum));
      }
      else if (e.target == reset)
      {
         sum = 0;
         output.setText("");
      }     
      return false;
   }
  
   private void setup()
   {
      setLayout(new FlowLayout());
      add(inLabel);   
      add(input);
      add(outLabel);  
      add(output);
      add(add);       
      add(reset); 
      add(quit);
   }
}
Note that the actual setup, or layout, of the components has been moved to a private method called setup of our TestFrame object. This method is not inherited by extending the Frame class. We simply use this method internally for our class only, and since it is private, nobody else will even know that this method exists. Such private methods are frequently used in classes to perform class-specific work, i.e. things that are useful only for this particular class. In our example, the setup method is responsible for the particular layout of our frame:
 
 
Let's change the layout to a Gridlayout with 4 rows and 2 columns. All we have to change is the setup() method, and there only the setLayout call. That's the advantage of using private methods with specific tasks - it makes it easy to adjust one particular aspect of a program without having to search for and modify multiple pieces of the code at various location. Here's the modified setup method, using a GridLayout instead of a FlowLayout.
Note: To instantiate a grid layout, you use two integer parameters: the first denotes the number of rows, the second one the number of columns.
   private void setup()
   {
      setLayout(new GridLayout(4,3));
      add(inLabel);   
      add(input);
      add(outLabel);  
      add(output);
      add(add);       
      add(reset); 
      add(quit);
   }

Adding components when a GridLayout is specified will add them by first filling up the columns in the first row, then in the second row, and so on. In our case, it will result in the following layout:

Notice that all cells in the table have the same dimensions. That is one of the properties of a GridLayout.

Question: What will happen when adding more components than available cells?  Just change the code above to find out for yourself.

Next, let's change to a BorderLayout. Here we can add components to the East, West, North, South, or Center, and we have to indicate these area when adding a component. Here's the code:

Note: To Instantiate a BorderLayout, no input parameters are necessary. But when components are added, the strings "East", "West", "North", "South", and/or "Center" must be used.

   private void setup()
   {
      setLayout(new BorderLayout());
      add("North",inLabel);
      add("North",input);
      add("South",outLabel);
      add("South",output);
      add("East",add);
      add("West",reset);
      add("Center",quit);
   }

Common Error: It is a common error to chose a BorderLayout, and then add a component by simply saying: add(quit) - NOTHING WILL SHOW UP ! You must use, for example, add("North", quit) instead !
Here is the frame as created by the above layout. Actually, the window has been enlarged to demonstrate the fact that in a BorderLayout the center component gets all remaining space after the other components are placed using as much space as they need.
 
 
Note that a border layout provides five different slots, but we added seven components.
Question: What happens when more than five components are added to a BorderLayout ? Just check the above example carefully to see the answer.
There are two more pre-defined layout, CardLayout and GridbagLayout. We will discuss both of them at a later time, if there is time. However, we should not yet be satisfied with our layout. None of the three chosen looks particularly appealing. Fortunatelly, however, one can 'combine' different layouts by introducing Panels.
Panels: Panels are used to group components together. Each panel can have its own layout, and then different panel can be arranged with various layouts to produce the final look of a program. To instantiate a Panel, no argument is necessary. To set the layout manager for a panel, use the 'setLayout' method of the panel. To add components to a panel, use the 'add' method of the panel. Finally, use the setLayout and add methods of the frame to arrange the panels. It is important to differentiate between the setLayout and add methods of the Panel, and those of the Frame.
For example, to create a reasonably appealing layout for our summation program, we can do the following:
   private void setup()
   {
      Panel info    = new Panel();   // panel for the number and labels
      Panel buttons = new Panel();   // panel for the buttons

      info.setLayout(new GridLayout(2,2));  // methods of the info panel
      info.add(inLabel);   
      info.add(input);
      info.add(outLabel);  
      info.add(output);

      buttons.setLayout(new FlowLayout());  // methods of the buttons panel
      buttons.add(add);
      buttons.add(reset);
      buttons.add(quit);

      setLayout(new BorderLayout());       // methods of the frame
      add("South",buttons);       
      add("Center",info);
   }
This will create the following look, which does qualify as reasonable appealing:
 
 
Any number of panels can be combined, and each panel could have a different layout. In fact, each panel could contain other panels in turn, each having their own layout. Using combinations of the three basic layout managers it is often possible to create an appealing look.
 
Before creating two more reasonably useful programs, we need to introduce two more GUI components that are frequently used: the List and the TextArea classes. Here is what the Java API has to say about those classes:


List:  The List component presents the user with a scrolling list of String items. The list can be set up either so that the user can pick one item or to pick multiple items.
public class List extends Component
{   // Constructors
    public List();
    public List(int  rows, boolean  multipleSelections);
    // Methods
    public void addItem(String  item);
    public void clear();
    public int countItems();
    public void delItem(int  position);
    public String getItem(int  index);
    public int getSelectedIndex();
    public String getSelectedItem();
    public boolean isSelected(int  index);
    public void replaceItem(String  newValue, int  index);
    public void select(int  index);
}

Here is a code segment to setup a list with 4 rows, only one row can be selected at any time, and the names of some planets are added to the list:

List planets = new List(4, false);
planets.addItem("Mercury");
planets.addItem("Earth");
planets.addItem("Mars");
planets.addItem("Jupiter");
planets.addItem("Uranus");
planets.addItem("Pluto");

That list can then be added to a frame or a panel using the appropriate layout manager and the add method.


TextArea: A text area is a multiline area for displaying text. It can be set to allow editing or to be read-only.

public class TextArea extends TextComponent
{   // Constructors
    public TextArea();
    public TextArea(int  rows, int  cols);
    // Methods
    public void appendText(String  str);
    public int getColumns();
    public int getRows();
    public void insertText(String  str, int  pos);
    public void replaceText(String  str, int  start, int  end);
}

For example, here is some code to instantiate a TextArea and add two lines of test to it.

TextArea output = new TextArea(4, 25);
output.appendText("This is my name:\n");
output.appendText("Bert Wachsmuth");

That text area can then be added, as usual, to a Frame or a panel. Note that the "escape sequence" \n means to add a carriage return (new line), so that the first line will read "This is my name" and the second line will be: "Bert Wachsmuth", and the "\n" indicates to switch to a new line.

Summary of GUI components:

So far, we have seen the following GUI components:

Button: a button to click on. User action can be checked in the action method
TextField: an area to enter a single line of text
Label: a one-line, non-editable text string
List: a scrollable list of text items
TextArea: a multi-line area for displaying text
Panel: a class to group components together using various layout managers

It is essential that you understand these classes together with their most basic methods.


Now let's put our knowledge together in two reasonably long examples:

Example 1: Create a 'to-do' list for your self. You want to be able to add items to the list, delete items from the list, you need to see all items in the list, and you want to quit the program. Actually, you really also want to save your list, but we will not cover that right now.

First, let's identify the GUI components we need:

Buttons: an Add button, a Delete button, and a Quit button
TextField: a single-line area to enter a new task. The new task is added to the list when the user clicks on the Add button
List: a scrollable list containing all tasks that have been added. A task can be deleted by clicking on it, then clicking on Delete.
Perhaps a label saying 'Task:' in front of the text field

Next, let's think about a layout we want:

The three buttons should go in one row at the bottom of the frame
The TextField should go on top, together with the label in front of it
The List should occupy the rest of the space, i.e. the center

We've already done the hard part, thinking about exactly what we want to do, and how things should look. The rest is easy. Let's create our program in stages.

First, let's define the fields we need:

Button addButton = new Button("Add");
Button delButton = new Button("Delete");
Button quitButton = new Button("Quit");
TextField newTask = new TextField(25);
Label taskLabel = new Label("Task:");
List tasks = new List(5, false);

Alright, that was easy. Now let's produce the code for the layout we have chosen. We clearly need a FlowLayout for the buttons, another FlowLayout for the label and the text field, and a BorderLayout to put everything together, including the task list. Here we go:

Panel buttons = new Panel();
buttons.setLayout(new FlowLayout());
buttons.add(addButton);
buttons.add(delButton);
buttons.add(quitButton);
Panel input = new Panel();
input.setLayout(new FlowLayout());
input.add(taskLabel);
input.add(newTask);
setLayout(new BorderLayout());
add("South", buttons);
add("North", input);
add("Center", tasks);

So, here is the complete class, as we have it so far:

import java.awt.*;

public class Task extends Frame
{  // fields
   private Button addButton = new Button("Add");
   private Button delButton = new Button("Delete");
   private Button quitButton = new Button("Quit");
   private TextField newTask = new TextField(25);
   private Label taskLabel = new Label("Task:");
   private List tasks = new List(5, false);
   // constructor
   public Task()
   {
      super("Task List");
      setup();
      validate();
      pack();
      show();
   }
   private void setup()
   {
      Panel buttons = new Panel();
      buttons.setLayout(new FlowLayout());
      buttons.add(addButton);
      buttons.add(delButton);
      buttons.add(quitButton);

      Panel input = new Panel();
      input.setLayout(new FlowLayout());
      input.add(taskLabel);
      input.add(newTask);

      setLayout(new BorderLayout());
      add("South", buttons);
      add("North", input);
      add("Center", tasks);
   }
}

This will compile correctly, but it will not run correctly. First, there is no standard main method, so obviously it will not execute. Moreover, there is no 'action' method to actually act according to the buttons that the user presses, so nothing can happen anyhow.

So, let's think about the action method:

if a user clicks the quit button, the program should, naturally, quit. We can use the System.exit(0) method for that, so that's okay
if a user clicks on the add button, the program should take whatever is in the newTask text field and add that to the task list. That's easy: the method to read from a text field is 'getText', and the method to add a String to a list is 'addItem'
if a user clicks on the delete button, we should first check if there's an item in the task list selected. If so, remove that item from the task list.

So, here goes the code for the action method:

public boolean action(Event e, Object o)
{
   if (e.target == quitButton)
      System.exit(0);
   else if (e.target == addButton)
      tasks.addItem(newTask.getText());
   else if (e.target == delButton)
   {
      if (tasks.getSelectedIndex() >= 0)
         tasks.delItem(tasks.getSelectedIndex());
   }
   return true;
}
In particular, take a look at the line tasks.addItem(newTask.getText()); what happens is that first we retrieve whatever is entered in the newTask TextField via the method newTask.getText(). That method returns a String, which we right away use as input to the addItem of the List object named tasks.
I leave it up to you to create a 'TaskTest' class with the standard main method, to put everything for the Task class together, and to see if it really works. Also, see the portfolio suggestions for possible improvements of this class.

Example 2: Ask the user to enter, say, 20 integer numbers. Your program should keep track of those numbers, compute the average of them, and show the answers to the user when the 20 numbers are added.
Again, let's identify the GUI components we need for this program:
a TextField to enter a new number
a Label explaining the text field
a Button to add the number from the text field to the existing numbers
a display area to inform the user about the numbers already entered, and to display the answers. Since that will contain multiple lines, we will use a TextArea for this
a Button to quit the program
Thus, we need the following fields in our class:
Label label = new Label("Number:");
TextField input = new TextField(10);
Button addButton = new Button("Add");
Button quitButton = new Button("Quit");
TextArea output = new TextArea();
Note that the TextArea is of undetermined size. We will let the layout manager figure out the best size for that component automatically.
We could add the numbers to a List instead of a TextArea, but then we would not have 'room' to display the average. So, we will add the numbers to an array (so that we can compute the average), and at the same time display them in a TextArea.
Next, let's think about the layout we want to use:
we arrange the label, the text field, and the add button in a row
we add that row at the top of the frame, and the text field in the center
we add the quit button at the bottom of the frame
Here's the code for this layout, using the above fields:
Panel topRow = new Panel();
topRow .setLayout(new FlowLayout());
topRow .add(label);
topRow .add(input);
topRow .add(addButton);

setLayout(new BorderLayout());
add("North", topRow);
add("Center", output);
add("South", quitButton);
So far, our complete code - without the action button - will look like this:
import java.awt.*;

public class Average extends Frame
{  // fields
   private Label label = new Label("Number:");
   private TextField input = new TextField(10);
   private Button addButton = new Button("Add");
   private Button quitButton = new Button("Quit");
   private TextArea output = new TextArea();
   // Constructor
   public Average()
   {
      super("Finding Average");
      setup();
      validate();
      pack();
      show();
   }
   private void setup()
   {
      Panel topRow = new Panel();
      topRow .setLayout(new FlowLayout());
      topRow .add(label);
      topRow .add(input);
      topRow .add(addButton);

      setLayout(new BorderLayout());
      add("North", topRow);
      add("Center", output);
      add("South", quitButton);
   }
}
Actually, if you look at this layout - by creating a simple, standard AverageTest class with the standard main method - you will see that it does not look quite right: the 'Quit' button is way too larger. But, for now, let's try to add the code that will actually make the program work, and worry about refining the layout later.
To make the program work, we clearly have to override the usual action method. But, to 'add' a number, we first need something to be added to. Therefore, we need to add several more fields to our program:
an array of integers to contain the 20 numbers
a counter to tell us how many numbers have been added so far
a constant maxNumber that's set to 20, so that we could easily modify our program to handle more than 20 numbers
Finally, one program before we can start is that we can get thetext from a TextField using the 'getText' method, but it will return a String instead of an integer. Therefore, we need to convert the string to an integer. The method
int Integer.parseInt(String s)
does exactly that, and with that we should be ready to go:
public boolean action(Event e, Object o)
{
   if (e.target == quitButton)
      System.exit(0);
   else if (e.target == addButton)
   {
      theNumbers[counter] = Integer.parseInt(input.getText());
      output.appendText("Number added: " + theNumbers[counter] + "\n");
      counter++;
   }
   return true;
}
So, let's put it all together, and it will  - almost - work. Here's the code so far:
import java.awt.*;

public class Average extends Frame
{  // fields
   private int MAXNUMBERS = 20;
   private int theNumbers[];
   private int counter = 0;

   private Label label = new Label("Number:");
   private TextField input = new TextField(10);
   private Button addButton = new Button("Add");
   private Button quitButton = new Button("Quit");
   private TextArea output = new TextArea();
   // Constructor
   public Average()
   {
      super("Finding Average");
      setup();
      validate();
      pack();
      show();
   }
   public boolean action(Event e, Object o)
   {
      if (e.target == quitButton)
         System.exit(0);
      else if (e.target == addButton)
      {
         theNumbers[counter] = Integer.parseInt(input.getText());
         output.appendText("Number added: " + theNumbers[counter] + "\n");
         counter++;
      }
      return true;
   }  
   private void setup()
   {
      Panel topRow = new Panel();
      topRow.setLayout(new FlowLayout());
      topRow.add(label);
      topRow.add(input);
      topRow.add(addButton);

      setLayout(new BorderLayout());
      add("North", topRow);
      add("Center", output);
      add("South", quitButton);
   }
}
Note that we have added the additional fields, and the action method.  However, the program does not work at this time, for two reasons:
we forgot to allocate memory for the array. Remember, we need to declare the array (which we did, as a field), we need to allocate memory (with 'new', which we did not do), and we need to populate the array (which we do in the 'action' method.
we forgot to compute and display the average once the numbers have been entered
Finally, we might want to display a 'nice' intro string in our output area. Here's the final, working program, with the new code in bold:
import java.awt.*;

public class Average extends Frame
{  // fields
   private int MAXNUMBERS = 20;
   private int theNumbers[];
   private int counter = 0;

   private Label label = new Label("Number:");
   private TextField input = new TextField(10);
   private Button addButton = new Button("Add");
   private Button quitButton = new Button("Quit");
   private TextArea output = new TextArea();
   // Constructor
   public Average()
   {
      super("Finding Average");
      theNumbers = new int[MAXNUMBERS];
      setup();
      validate();
      pack();
      show();
   }
   public boolean action(Event e, Object o)
   {
      if (e.target == quitButton)
         System.exit(0);
      else if (e.target == addButton)
      {
         theNumbers[counter] = Integer.parseInt(input.getText());
         output.appendText("Number added: " + theNumbers[counter] + "\n");
         if (counter == MAXNUMBERS - 1)
           computeAndShowAverage();
         else
            counter++;
      }
      return true;
   }  
   private void computeAndShowAverage()
   {
      double sum = 0;
      for (int i = 0; i < MAXNUMBERS; i++)
         sum += theNumbers[i];
      output.appendText("\nAverage is: " + sum / MAXNUMBERS + "\n");
   }
   private void setup()
   {
      Panel topRow = new Panel();
      topRow.setLayout(new FlowLayout());
      topRow.add(label);
      topRow.add(input);
      topRow.add(addButton);

      setLayout(new BorderLayout());
      add("North", topRow);
      add("Center", output);
      add("South", quitButton);

      output.appendText("Welcome to the Averager\n\n");
   }
}
Note: we have delegated the computing and displaying of the average to a new private method. That's good programming style: everything that is a seperate, distinct task should be handled in a seperate method, and no method, if possible, should manage too many tasks. In our case, the 'action' method handles what it says: the 'overall action' or the class. The actual computation is delegated to another method, who handles exactly that: computing and displaying the average. That method need not worry about anything else, it's task is clearly defined, and easy to accomplish.

(bgw)