6. Textareas

 

The BankView program has a textfield where the user can enter a deposit in a bank account or a withdrawal. Positive numbers indicate deposits and negative numbers indicate checks. It also has a button that the user clicks to see the last five transactions made. Each user input produces a remark in the large rectangular output area.

 

The BankView frame looks as shown in the accompanying figure after inputs of 100, -30, and 42, and a button click, except that the bottom part of the output area has been cut off. Scroll bars will appear when the textarea has too much material to display at one time. If you would like to see this BankView program in action, click this execution link.

 

 

 

The BankView class in Listing 6 has two variables, modelAccount and itsOutput, that contain objects that have to be accessed in several methods of the class. This means you have to define them as instance variables, outside of all methods, as shown in lines 1 and 2. The modelAccount object keeps track of checks and deposits for a bank account. The itsOutput object keeps track of a large area where information written by the program is to appear. We construct it in line 2, we add it to the frame in line 8, and we write information to it in lines 11 and 18.

 

An ETextArea object displays a large amount of textual information. The ETextArea construction in line 2 creates a rectangular area suitable for writing up to 10 rows of words, each row having room for at least 42 characters. This kind of component is a textarea. You will also have scroll bars on this area, so the user can scroll up-and-down or left-and-right to read all the material printed there. You add one line to the information displayed in an ETextArea by using someETextArea.say(messageString).

 

An ETextArea also has the same setText and getText methods that ELabels, EButtons, and EFields have. In particular, someETextArea.setText("") clears the area.

 

Line 7 of this class constructs a new UpdateField (a subclass of EField), then sets its width to be 5 characters. Since the width method returns the EField object itself, you can apply the text method to it, setting its text to be a zero. Since the text method also returns the EField object itself, you can use it as the parameter for the add method. All you need for an applet that contains the BankView program is the following:

 

public class BankViewApplet extends javax.swing.JApplet

{

   public void start()

   {  setContentPane (new BankView());

   }  //======================

}

 

The constructor lays out a button and a textfield for input and a textarea for output (lines 3-8). Then the runtime system pauses until some event takes place: When the button is clicked, a record of the last five transactions in the bank account appears in the textarea (line 11; see the figure above). When the user enters a new transaction in the textfield (line 15), it is checked to make sure it is not blank (line 16). If so, the bank account is updated (line 17) and then the current balance is printed in the textarea (lines 18-19). Line 21 erases the textfield for the user's convenience.

 

Listing 6 The BankView program (uses the BankAccount class)

 

public class BankView extends EPanel

{

   private BankAccount modelAccount = new BankAccount();       // 1

   private ETextArea itsOutput = new ETextArea (10, 42);       // 2

 

 

  /** The View:  Lay out the GUI components. */

 

   public BankView()                                           // 3

   {  add (new LastFiveButton().text                           // 4

            ("show last five transactions"));                  // 5

      add (new ELabel ("Enter deposit(+) or check(-)"),        // 6

            new UpdateField().width (5).text ("0"));           // 7

      add (itsOutput);                                         // 8

   }  //======================

 

 

  /** Controllers: React to click of button or ENTER key. */

 

   private class LastFiveButton extends EButton                // 9

   {  public void onClick()                                    //10

      {  itsOutput.say (modelAccount.lastFiveTransactions());  //11

      }                                                        //12

   }  //======================

 

 

   private class UpdateField extends EField                    //13

   {  public void onEnter()                                    //14

      {  String input = this.getText();                        //15

         if (input.length() > 0)                               //16

         {  modelAccount.makeChange (input);                   //17

            itsOutput.say (modelAccount.currentBalance() +     //18

               " = balance.");                                 //19

         }                                                     //20

         this.setText ("");  // clear the entry field          //21

      }                                                        //22

   }  //======================

}

 

 

This program follows the standard MVC (model-view-controller) design:

 

·         The constructor creates the view (what the user sees on the screen and interacts with) and then suspends the action of the program until some input event occurs.

·         The last two methods are the controllers of what goes on in the program, reacting to events.

·         The BankAccount object stores information that is accessed at several points in the execution of the program. So it is a model of a part of the real world (a bank account in this case). The controllers act as intermediaries between the model and the view.

 

The BankAccount makeChange method, called in line 17, accepts any string of one or more characters and makes the change for a check (if less than zero) or a deposit. The methods in this class have nothing to do with GUIs, so we do not discuss them here. You should study the discussion of the BankAccount class at this language link if you are not fully familiar with how to make this kind of class.

Exercise 6.1 Change BankView so that it says "The last 5 are " before it lists the five.

Exercise 6.2 Change BankView as follows, on the assumption that BankAccount has another instance method, numChecks(), to tell the number of checks so far:  Clicking a button causes the number of checks to appear in the textarea.

Exercise 6.3* Revise BankView to have two textfields, one for checks only and one for deposits only. Both will be entered as positive numbers. Delete any leading '$' character from input sent to the makeChange method. Hint: Use the substring method.

Exercise 6.4** Write a program that has one textfield and one textarea. The user is to enter numbers in the textfield. After each number is entered, write a line that says "INPUT: " followed by the user's input, then a line that gives the average of all numbers entered so far. Hint: The statement n = Integer.parseInt(this.getText()) produces the numeric equivalent of the string of characters that the user enters. Have a variable that tracks the total entered and another variable that counts how many entries are made.

 

 

7. Timers

 

A subclass of ETimer is generally defined inside the EPanel subclass. It must have an onBeep() instance method. You use the instance method delay(m) to tell the timer to wait for a period of m milliseconds before executing its onBeep method. 1000 milliseconds is 1 second.  The delay method returns the ETimer object itself, so it can be used in e.g. add (new XTimer().delay(1200)). You can stop the beeping with the ETimer stop() method. ETimer is a subclass of ELabel, since an ETimer is often used to display a picture that changes frequently, once each time onBeep is called. So you have available the setPicture, getPicture, setText, and getText methods.

 

Example: You could add the following three statements in the constructor of any of the example classes we have had. They will display a picture of Rome for just 1.2 seconds, then replace the picture of Rome by the picture of Ireland, if you have defined the RomeTimer class shown:

 

        ETimer flash = new RomeTimer();
        flash.setPicture ("Rome.jpg");
        add (flash);
 

   private class RomeTimer extends ETimer

   {  public void onBeep()

      {  this.setPicture ("Ireland.jpg");

      }

   }

 

Each EButton and EField has a setEnabled method: x.setEnabled(false) keeps the user from clicking the button or typing in the textfield. x.setEnabled(true) re-activates the component. When a component is first created, it is of course already enabled.

 

The GuessNumberGame is a subclass of EPanel you can put in an EFrame (for an application) or a JApplet (for an applet in a webpage). The design is to have a button the user clicks to start a new game. When that happens, the program chooses a secret number from 1 to 100 and invites the user to guess it. Each time the user types a guess into the textfield, the response is "Too high" or "Too low", whichever is appropriate. When the user gets it right, the response is "Right. Congratulations!" and the game stops; the user clicks the button to play again. The GuessNumberGame frame looks as shown in the accompanying figure, after a guess of 40 which was too high. If you would like to see this GuessNumberGame program in action, click this execution link.

 

 

 

The GuessNumberGame class in Listing 7 has four instance variables, since they have to be accessed in several methods of the class:

 

·         The textfield named itsInput is declared in line 1 to allow room for 3 or more characters. It is added to the panel in line 8. It is disabled initially in lines 10, and again disabled at the end of each game in line 35. It is enabled at the start of a game in line 19, when the user clicks the button that says "Click to play the game".

·         The label named itsAnswer is declared in line 2 to initially have no text (indicated by the empty string "").  It displays the program's responses in lines 27, 30, and 33.

·         The integer value named itsSecret is the secret number the program chooses. The program checks it against the user's guess in lines 26 and 29.

·         The timer named itsTimer is declared in line 4. Its purpose is to choose a secret number. It does this by adding 13 to itsSecret number 50 times a second (i.e., a delay of 20 milliseconds between additions) until the user clicks the button to start the game. But when it goes over 100, it subtracts 100 to keep it in the range of 1 through 100 (lines 13-15). In general x += k changes x to be k more than it was, and x -= k changes x to be k less than it was.

 

This is a workable way to generate a number sort of at random in the range from 1 to 100. By adding 13 each time, which is a prime number, this method cycles through all 100 possibilities once each two seconds. Since the user will probably take one or more seconds from the time "Congratulations!" is displayed to the start of the next game, this is acceptably "random". Of course, nothing is actually random about the way a computer functions.

 

Another way to produce a random number is have an instance variable java.util.Random randy = new java.util.Random(); then each call of randy.nextInt(n) produces a "random" non-negative number less than n. Exercise 7.3 illustrates its use.  Note that the ETimer is not added to the display, since there is nothing to show about this particular ETimer; but usually there is.

 

Line 25 has to obtain a number from the string of characters that the user typed in the textfield.  It uses the standard Integer.parseInt(someString) method to derive the number.

 

 

Listing 7 The GuessNumberGame program

 

public class GuessNumberGame extends EPanel

{

   private EField itsInput = new PlayOneGame().width (3);      // 1

   private ELabel itsAnswer = new ELabel ("");                 // 2

   private int itsSecret = 50;                                 // 3

   private ETimer itsTimer = new SecretTimer().delay (20);     // 4

 

 

  /** The View:  Lay out the GUI components. */

 

   public GuessNumberGame()                                    // 5

   {  add (new StartGame().text ("Click to play the game"));   // 6

      add (new ELabel ("Guess 1 to 100"));                     // 7

      add (itsInput);                                          // 8

      add (itsAnswer);                                         // 9

      itsInput.setEnabled (false);                             //10

   }  //======================

 

 

  /** Controllers: React to various events. */

 

   private class SecretTimer extends ETimer                    //11

   {  public void onBeep()                                     //12

      {  itsSecret += 13;                                      //13

         if (itsSecret > 100)                                  //14

            itsSecret -= 100;                                  //15

         this.delay (20);                                      //16

      }                                                        //17

   }  //======================

 

 

   private class StartGame extends EButton                     //18

   {  public void onClick()                                    //19

      {  itsTimer.stop();                                      //20

         itsInput.setEnabled (true);                           //21

      }                                                        //22

   }  //======================

 

 

   private class PlayOneGame extends EField                    //23

   {  public void onEnter()                                    //24

      {  int guess = Integer.parseInt (this.getText());        //25

         if (guess > itsSecret)                                //26

         {  itsAnswer.setText ("Too high");                    //27

         }                                                     //28

         else if (guess < itsSecret)                           //29

         {  itsAnswer.setText ("Too low");                     //30

         }                                                     //31

         else  // don't continue, this game is over            //32

         {  itsAnswer.setText ("Right. Congratulations!");     //33

            itsTimer.delay (20);                               //34

            itsInput.setEnabled (false);                       //35

         }                                                     //36

      }                                                        //37

   }  //======================

}

 

 

Exercise 7.1 What is the first secret number the program chooses, if the user waits exactly 1.00 seconds before clicking the button? What is the second secret number, if the user waits exactly 4.04 seconds before clicking the button again?

Exercise 7.2 Change GuessNumberGame to have another instance variable itsLimit, which is initially 100. The secret number is 1 up through itsLimit. Provide another button the user can click to change itsLimit to 15 (to make the game easier for small children) or back again to 100. How many guesses would it take you to get it right if itsLimit is 15?

Exercise 7.3 Change GuessNumberGame to use a java.util.Random object instead of a timer.

Exercise 7.4* You can get along without the setEnabled method if you have to. To do this, eliminate all references to it and then declare an instance variable isEnabled, set to true or false appropriately. Test isEnabled in the onEnter method to see if the textfield should react in any way.

Exercise 7.5** Write a program that asks three questions of your choosing. Supply a textfield for each answer, but have the onEnter method do nothing for each textfield. At the end of 30 seconds, the program "calls time" and tells how many answers were right.

Exercise 7.6** After you write the program for the preceding exercise, add a label that counts down to zero starting from 30, changing once each second.

 

Link to next section (Part D)