Section 6.6
Introduction to Layouts and Components


IN PRECEDING SECTIONS, YOU'VE SEEN how to use a graphics context to draw on the screen and how to handle mouse events and keyboard events. In one sense, that's all there is to GUI programming. If you're willing to program all the drawing and handle all the mouse and keyboard events, you have nothing more to learn. However, you would either be doing a lot more work than you need to do, or you would be limiting yourself to very simple user interfaces. A typical user interface uses standard GUI components such as buttons, scroll bars, text-input boxes, and menus. These components have already been written for you, so you don't have to duplicate the work involved in developing them. They know how to draw themselves, and they can handle the details of processing the mouse and keyboard events that concern them.

Consider one of the simplest user interface components, a push button. The button has a border, and it displays some text. This text can be changed. Sometimes the button is disabled, so that clicking on it doesn't have any effect. When it is disabled, its appearance changes. When the user clicks on the push button, the button changes appearance while the mouse button is pressed and changes back when the mouse button is released. In fact, it's more complicated than that. If the user moves the mouse outside the push button before releasing the mouse button, the button changes to its regular appearance. To implement this, it is necessary to respond to mouse exit or mouse drag events. Furthermore, on many platforms, a button can receive the input focus. The button changes appearance when it has the focus. If the button has the focus and the user presses the space bar, the button is triggered. This means that the button must respond to keyboard and focus events as well.

Fortunately, you don't have to program any of this, provided you use an object belonging to the standard class javax.swing.JButton. A JButton object draws itself and processes mouse, keyboard, and focus events on its own. You only hear from the Button when the user triggers it by clicking on it or pressing the space bar while the button has the input focus. When this happens, the JButton object creates an event object belonging to the class java.awt.event.ActionEvent. The event object is sent to any registered listeners to tell them that the button has been pushed. Your program gets only the information it needs -- the fact that a button was pushed.


Another aspect of GUI programming is laying out components on the screen, that is, deciding where they are drawn and how big they are. You have probably noticed that computing coordinates can be a difficult problem, especially if you don't assume a fixed size for the applet. Java has a solution for this, as well.

Components are the visible objects that make up a GUI. Some components are containers, which can hold other components. An applet's content pane is an example of a container. The standard class JPanel, which we have only used as a drawing surface up till now, is another example of a container. Because a JPanel object is a container, it can hold other components. So JPanels are dual purpose: You can draw on them, and you can add other components to them. Because a JPanel is itself a component, you can add a JPanel to an applet's content pane or even to another JPanel. This makes complex nesting of components possible. JPanels can be used to organize complicated user interfaces.

The components in a container must be "laid out," which means setting their sizes and positions. It's possible to program the layout yourself, but ordinarily layout is done by a layout manager. A layout manager is an object associated with a container that implements some policy for laying out the components in that container. Different types of layout manager implement different policies.

In this section, we'll look at a few examples of using components and layout managers, leaving the details until Section 7.2 and Section 7.3. The applets that we look at in this section have a large drawing area with a row of controls below it.

Our first example is rather simple. It's another "Hello World" applet, in which the color of the message can be changed by clicking one of the buttons at the bottom of the applet:

(Applet "HelloWorldJApplet" would be displayed here
if Java were available.)

In the previous JApplets that we've looked at, the entire applet was filled with a JPanel that served as a drawing surface. In this example, there are two JPanels: the large black area at the top that displays the message and the smaller area at the bottom that holds the three buttons.

Let's first consider the panel that contains the buttons. This panel is created in the applet's init() method as a variable named buttonBar, of type JPanel:

            JPanel buttonBar = new JPanel();

When a panel is to be used as a drawing surface, it is necessary to create a subclass of the JPanel class and include a paintComponent() method to do the drawing. However, when a JPanel is just being used as a container, there is no need to create a subclass. A standard JPanel is already capable of holding components of any type.

Once the panel has been created, the three buttons are created and are added to the panel. A button is just an object belonging to the class javax.swing.JButton. When a button is created, the text that will be shown on the button is provided as a parameter to the constructor. The first button in the panel is created with the command:

            JButton redButton = new JButton("Red");

This button is added to the buttonBar panel with the command:

            buttonBar.add(redButton);

Every JPanel comes automatically with a layout manager. This default layout manager will simply line up the components that are added to it in a row. That's exactly the behavior we want here, so there is nothing more to do. If we wanted a different kind of layout, it's possible to change the panel's layout manager.

One more step is required to make the button useful: an object must be registered with the button to listen for ActionEvents. The button will generate an ActionEvent when the user clicks on it. ActionEvents are similar to MouseEvents or KeyEvents. To use them, a class should import java.awt.event.*. The object that is to do the listening must implement an interface named ActionListener. This interface requires a definition for the method "public void actionPerformed(ActionEvent evt);". Finally, the listener must be registered with the button by calling the button's addActionListener() method. In this case, the applet itself will act as listener, and the registration is done with the command:

            redButton.addActionListener(this);

After doing the same three commands for each of the other two buttons -- and setting the background color for the sake of aesthetics -- the buttonBar panel is ready to use. It just has to be added to the applet.

As we have seen, components are not added directly to an applet. Instead, they are added to the applet's content pane, which is itself a container. The content pane comes with a default layout manager that is capable of displaying up to five components. Four of these components are placed along the edges of the applet, in the so-called "North", "South", "East", and "West" positions. A component in the "Center" position fills in all the remaining space. This type of layout is called a BorderLayout. In our example, the button bar occupies the "South" position and the drawing area fills the "Center" position. When you add a component to a BorderLayout, you have to specify its position using a constant such as BorderLayout.SOUTH or BorderLayout.CENTER. In this example, buttonBar is added to the applet with the command:

            getContentPane().add(buttonBar, BorderLayout.SOUTH);

The display area of the applet is a drawing surface like those we have seen in other examples. A nested class named Display is created as a subclass of JPanel, and the display area is created as an object belonging to that class. The applet class has an instance variable named display of type Display to represent the drawing surface. The display object is simply created and added to the applet with the commands:

            display = new Display();
            getContentPane().add(display, BorderLayout.CENTER);

Putting this all together, the complete init() method for the applet becomes:

   public void init() {

       display = new Display();
             // The component that displays "Hello World".

       getContentPane().add(display, BorderLayout.CENTER);
             // Adds the display panel to the CENTER position of the
             // JApplet's content pane.

       JPanel buttonBar = new JPanel();
             // This panel will hold three buttons and will appear
             // at the bottom of the applet.
             
       buttonBar.setBackground(Color.gray);
             // Change the background color of the button panel 
             // so that the buttons will stand out better.
       
       JButton redButton = new JButton("Red");
             // Create a new button.  "Red" is the text
             // displayed on the button.

       redButton.addActionListener(this);  
             // Set up the button to send an "action event" to this applet
             // when the user clicks the button.  The parameter, this,
             // is a name for the applet object that we are creating,
             // so action events from the button will be handled by
             // calling the actionPerformed() method in this class.
             
       buttonBar.add(redButton);
             // Add the button to the buttonBar panel.
             
       JButton greenButton = new JButton("Green"); // the second button
       greenButton.addActionListener(this);
       buttonBar.add(greenButton);

       JButton blueButton = new JButton("Blue"); // the third button
       blueButton.addActionListener(this);
       buttonBar.add(blueButton);

       getContentPane().add(buttonBar, BorderLayout.SOUTH); 
             // Add button panel to the bottom of the content pane.

   }  // end init()

Notice that the variables buttonBar, redButton, greenButton, and blueButton are local to the init() method. This is because once the buttons and panel have been added to the applet, the variables are no longer needed. The objects continue to exist, since they have been added to the applet. But they will take care of themselves, and there is no need to manipulate them elsewhere in the applet. The display variable, on the other hand, is an instance variable that can be used throughout the applet. This is because we are not finished with the display object after adding it to the applet. When the user clicks a button, we have to change the color of the display. We need a way to keep the variable around so that we can refer to it in the actionPerformed() method. In general, you don't need an instance variable for every component in an applet -- just for the components that will be referred to outside the init() method.


The drawing surface in our example is defined by a nested class named Display which is a subclass of JPanel. The class contains a paintComponent() method that is responsible for drawing the message "Hello World" on a black background. The Display class also contains a variable that it uses to remember the current color of the message and a method that can be called to change the color. This class is more self-contained than than most of the drawing surface classes that we have looked at, and in fact it could have been defined as an independent class instead of as a nested class. Here is the definition of the nested class, Display:

      class Display extends JPanel {
           // This nested class defines a component that displays
           // the string "Hello World".  The color and font for
           // the string are recorded in the variables colorNum
           // and textFont.

         int colorNum;     // Keeps track of which color is displayed;
                           //     1 for red, 2 for green, 3 for blue.

         Font textFont;    // The font in which the message is displayed.
                           // A font object represents a certain size and
                           // style of text drawn on the screen.

         Display() {
               // Constructor for the Display class.  Set the background
               // color and assign initial values to the instance
               // variables, colorNum and textFont.
            setBackground(Color.black);
            colorNum = 1;   // The color of the message is set to red.
            textFont = new Font("Serif",Font.BOLD,36);
                // Create a font object representing a big, bold font.
         }

         void setColor(int code) {
               // This method is provided to be called by the
               // main class when it wants to set the color of the
               // message.  The parameter value should be 1, 2, or 3
               // to indicate the desired color.
            colorNum = code;
            repaint(); // Tell the system to repaint this component.
         }

         public void paintComponent(Graphics g){
               // This routine is called by the system whenever this
               // panel needs to be drawn or redrawn.  It first calls 
               // super.paintComponent() to fill the panel with the
               // background color.  It then displays the message
               // "Hello World" in the proper color and font.
            super.paintComponent(g);
            switch (colorNum) {         // Set the color.
               case 1:
                  g.setColor(Color.red);
                  break;
               case 2:
                  g.setColor(Color.green);
                  break;
               case 3:
                  g.setColor(Color.blue);
                  break;
            }
            g.setFont(textFont);       // Set the font.
            g.drawString("Hello World!", 25,50);    // Draw the message.
         } // end paintComponent

      } // end nested class Display

The main class has an instance variable named display of type Display. When the user clicks one of the buttons in the applet, this variable is used to call the setColor() method in the drawing surface object. This is done in the applet's actionPerformed() method. This method is called when the user clicks any one of the three buttons, so it needs some way to tell which button was pressed. This information is provided in the parameter to the actionPerformed() method. This parameter contains an "action command," which in the case of a button is just the string that is displayed on the button:

   public void actionPerformed(ActionEvent evt) {
         // This routine is called by the system when the user clicks
         // on one of the buttons.  The response is to set the display's
         // color accordingly.  

      String command = evt.getActionCommand();
               // The "action command" associated with the event 
               // is the text on the button that was clicked.
               
      if (command.equals("Red"))       // Set the color.
         display.setColor(1);
      else if (command.equals("Green"))
         display.setColor(2);
      else if (command.equals("Blue"))
         display.setColor(3);

   }  // end actionPerformed()
   

We have now looked at all the pieces of the sample applet. You can find the entire source code in the file HelloWorldJApplet.java.


For a second example, let's look at something a little more interesting. Here's a simple card game in which you look at a playing card and try to predict whether the next card will be higher or lower in value. (Aces have the lowest value in this game.) You've seen a text-oriented version of the same game in Section 5.3. That section also defined Deck, Hand, and Card classes that are used in this applet. In this GUI version of the game, you click on a button to make your prediction. If you predict wrong, you lose. If you make three correct predictions, you win. After completing one game, you can click the "New Game" button to start a new game. Try it! See what happens if you click on one of the buttons at a time when it doesn't make sense to do so.

(Applet "HighLowGUI" would be displayed here
if Java were available.)

The overall form of this applet is the same as that of the previous example: It has three buttons in a panel at the bottom of the applet and a large drawing surface that displays the cards and a message. However, I've organized the code a little differently in this example. In this case, it's the drawing surface object, rather than the applet, that listens for events from the buttons, and I've put almost all the programming into the display surface class. The applet object is only responsible for creating the components and adding them to the applet. This is done in the following init() method, which has almost the same form as the init() method in the previous example:

   public void init() {
   
         // The init() method lays out the applet. A HighLowCanvas
         // occupies the CENTER position of the layout. On the
         // bottom is a panel that holds three buttons.  The
         // HighLowCanvas object listens for ActionEvents from the
         // buttons and does all the real work of the program.
         
      setBackground( new Color(130,50,40) );
      
      HighLowCanvas board = new HighLowCanvas();
      getContentPane().add(board, BorderLayout.CENTER);
      
      JPanel buttonPanel = new JPanel();
      buttonPanel.setBackground( new Color(220,200,180) );
      getContentPane().add(buttonPanel, BorderLayout.SOUTH);
      
      JButton higher = new JButton( "Higher" );
      higher.addActionListener(board);
      buttonPanel.add(higher);
      
      JButton lower = new JButton( "Lower" );
      lower.addActionListener(board);
      buttonPanel.add(lower);
      
      JButton newGame = new JButton( "New Game" );
      newGame.addActionListener(board);
      buttonPanel.add(newGame);
      
   }  // end init()

In programming the drawing surface class, HighLowCanvas, it is important to think in terms of the states that the game can be in, how the state can change, and how the response to events can depend on the state. The approach that produced the original, text-oriented game in Section 5.3 is not appropriate here. Trying to think about the game in terms of a process that goes step-by-step from beginning to end is more likely to confuse you than to help you.

The state of the game includes the cards and the message. The cards are stored in an object of type Hand. The message is a String. These values are stored in instance variables. There is also another, less obvious aspect of the state: Sometimes a game is in progress, and the user is supposed to make a prediction about the next card. Sometimes we are between games, and the user is supposed to click the "New Game" button. It's a good idea to keep track of this basic difference in state. The canvas class uses a boolean variable named gameInProgress for this purpose.

The state of the applet can change whenever the user clicks on a button. The HighLowCanvas class implements the ActionListener interface and defines an actionPerformed() method to respond to the user's clicks. This method simply calls one of three other methods, doHigher(), doLower(), or newGame(), depending on which button was pressed. It's in these three event-handling methods that the action of the game takes place.

We don't want to let the user start a new game if a game is currently in progress. That would be cheating. So, the response in the newGame() method is different depending on whether the state variable gameInProgress is true or false. If a game is in progress, the message instance variable should be set to show an error message. If a game is not in progress, then all the state variables should be set to appropriate values for the beginning of a new game. In any case, the board must be repainted so that the user can see that the state has changed. The complete newGame() method is as follows:

     void doNewGame() {
            // Called by the constructor, and called by actionPerformed()
            // when the user clicks the "New Game" button.  Start a new game.
        if (gameInProgress) {
                // If the current game is not over, it is an error to try
                // to start a new game.
           message = "You still have to finish this game!";
           repaint();
           return;
        }
        deck = new Deck();  // Create a deck and hand to use for this game.
        hand = new Hand();
        deck.shuffle();
        hand.addCard( deck.dealCard() );    // Deal the first card.
        message = "Is the next card higher or lower?";
        gameInProgress = true;  // State changes! A game has started.
        repaint();
     }

The doHigher() and doLower() methods are almost identical to each other (and could probably have been combined into one method with a parameter, if I were more clever). Let's look at the doHigher() routine. This is called when the user clicks the "Higher" button. This only makes sense if a game is in progress, so the first thing doHigher() should do is check the value of the state variable gameInProgress. If the value is false, then doHigher() should just set up an error message. If a game is in progress, a new card should be added to the hand and the user's prediction should be tested. The user might win or lose at this time. If so, the value of the state variable gameInProgress must be set to false because the game is over. In any case, the board is repainted to show the new state. Here is the doHigher() method:

     void doHigher() {
              // Called by actionPerformed() when user clicks "Higher".
              // Check the user's prediction.  Game ends if user guessed
              // wrong or if the user has made three correct predictions.
        if (gameInProgress == false) {
              // If the game has ended, it was an error to click "Higher",
              // so set up an error message and abort processing.
           message = "Click \"New Game\" to start a new game!";
           repaint();
           return;
        }
        hand.addCard( deck.dealCard() );     // Deal a card to the hand.
        int cardCt = hand.getCardCount();    // How many cards in the hand?
        Card thisCard = hand.getCard( cardCt - 1 );  // Card just dealt.
        Card prevCard = hand.getCard( cardCt - 2 );  // The previous card.
        if ( thisCard.getValue() < prevCard.getValue() ) {
           gameInProgress = false;
           message = "Too bad! You lose.";
        }
        else if ( thisCard.getValue() == prevCard.getValue() ) {
           gameInProgress = false;
           message = "Too bad!  You lose on ties.";
        }
        else if ( cardCt == 4) {
           gameInProgress = false;
           message = "You win!  You made three correct guesses.";
        }
        else {
           message = "Got it right!  Try for " + cardCt + ".";
        }
        repaint();
     }

The paintComponent() method of the HighLowCanvas class uses the values in the state variables to decide what to show. It displays the string stored in the message variable. It draws each of the cards in the hand. There is one little tricky bit: If a game is in progress, it draws an extra face-down card, which is not in the hand, to represent the next card in the deck. Drawing the cards requires some care and computation. I wrote a method, "void drawCard(Graphics g, Card card, int x, int y)", which draws a card with its upper left corner at the point (x,y). The paintComponent() routine decides where to draw each card and calls this routine to do the drawing. You can check out all the details in the source code, HighLowGUI.java.


As a final example, let's look quickly at an improved paint program, similar to the one from Section 4. The user can draw on the large white area. In this version, the user selects the drawing color from the pop-up menu at the bottom-left of the applet. If the user hits the "Clear" button, the drawing area is filled with the background color. I've added one feature: If the user hits the "Set Background" button, the background color of the drawing area is set to the color currently selected in the pop-up menu, and the drawing area is cleared. This lets you draw in cyan on a magenta background if you have a mind to.

(Applet "SimplePaint2" would be displayed here
if Java were available.)

The drawing area in this applet is a component, belonging to the nested class SimplePaintCanvas. I wrote this class, as usual, as a sub-class of JPanel and programmed it to listen for mouse events and to respond by drawing a curve. As in the HighLowGUI applet, all the action takes place in the nested class. The main applet class just does the set up. One new feature of interest is the pop-up menu. This component is an object belonging to the standard class, JComboBox. We'll cover this component class in Chapter 7.

What you should note about this version of the paint applet is that in many ways, it was easier to write than the original. There are no computations about where to draw things and how to decode user mouse clicks. We don't have to worry about the user drawing outside the drawing area. The graphics context that is used for drawing on the canvas can only draw on the canvas. If the user tries to extend a curve outside the canvas, the part that lies outside the canvas is automatically ignored. We don't have to worry about giving the user visual feedback about which color is selected. That is handled by the text displayed on the pop-up menu.

You'll find the source code for this example in the file SimplePaint2.java. After struggling through this chapter, you should be equipped to understand it almost in its entirety!



End of Chapter 6

[ Next Chapter | Previous Section | Chapter Index | Main Index