Javanomicon01 - Case Study 1 - An Event Driven Calculator

From Monkeys @ Keyboards
Jump to: navigation, search

Case Study 1 - An Event Driven Calculator

Previous TOC Next
Javanomicon01 - Event Driven Programming 2 The Javanomicon Javanomicon01 - Hooray for Arrays


Introduction

Now that we've spent some time looking at the concepts that go into developing an event driven applet in Java, let's go through an example of how they work together in practise. For this example, we are going to write the code for a simple calculator applet.

Calculators are very common consumer appliances, and they all work in much the same way - you have a display that shows the answers, a set of buttons representing each of the numbers, a set of buttons for each of the calculations, and an equals button that places the result of your input into the display.

Our calculator is going to work in this same fashion, except we're going to code it ourselves using the Java concepts we've already seen. We'll fashion the buttons with our very own hands, and then lovingly position them on the screen until we create a masterpiece of mathematical perfection.

Storyboard

The first step in designing a Java applet is to draw out the interface so we know exactly what we need to include.

For this applet, we are going to need something for displaying the answer - we know that using a calculator involves entering our numbers through the provided buttons, so there will be no need for us to allow the user to enter anything into the display section. We have a component that allows us to display text with no facility for the user to alter it - a JLabel control. However, we may also want to be able to test the numeric input of our calculator without going through the predefined interface. To give us a simple display and the facility for debugging, we'll use a JtextField. We can make sure that the user can't type into later.

We also have three particular families of buttons that will need created and placed:

  1. Ten buttons for numbers
  2. Four buttons for the simple mathematical functions (addition, subtraction, division and multiplication)
  3. Two utility buttons - one for equals and one for clearing the display.

This gives us a total of sixteen buttons to be placed on our applet. Of course, knowing what we need gives us an idea of how to lay out the applet, but we still need to have some idea as to where they are placed using the setBounds method.

Working out exactly what numbers correspond to what co-ordinates takes practice, and so it's sometimes helpful to spend some time working out how these relate. This is particularly important for the length and height parameters - the other two are largely dependant on other components. Experiment with drawing various sized components onto the applet until you find a size you like. For this application, 50 by 50 seems to be a good size for each of the buttons, so that's what we'll use.

Here's where we have to make a design decision about how the applet is going to look - exactly where the buttons go is a presentation exercise that is individual to the developer. For this example, we're going to set out the applet much like a convention calculator - the display running the length of the top, with the buttons arranged into two clumps - one containing the numbers and the utility buttons, and the other containing the mathematical operations.

The overall size of our applet (in terms of what are valid co-ordinates) is held in the HTML file for our project. The standard dimensions are 500 by 300. We want the display component to run the length of the top of the applet, which would give it a length of 500. We want it to start a reasonable distance from the edges of the applet, so we'll start drawing it at position 10, 10. This means we need a length of 490 to run the whole length of the applet. We don't want it to draw quite to the edge - we want it to be cantered in the applet. It starts 10 spaces from the left edge, so it must also end ten spaces from the right edge. This gives us an overall length of 480. We want it to be tall enough to hold numbers, but not much taller. Experimenting gives an acceptable value of 30 for the height of our display.

Now that we know how big each component will be and where the display label is located , we can design our storyboard for the applet. The location of each button will be a function of the buttons beside it. For example, we draw button 7 at the top left part of the block of numbers. If we draw it at position 10, 60, then we know that the 7 button underneath must be 50 units further on in the Y axis (because the height of each button is 50), and that the 8 button to the right of it must be 50 along in the X axis (because the length of each button is 50):

6.1: Partial Button placement

All of the number buttons in our calculator application can be placed in this fashion:

6.2: Number Button placement

The second block of buttons are not directly related to the other family, but they should be placed at an equal distance from the display component in the Y axis (which would place the first one at position 60 in Y), and some distance from the right hand side of the applet (position 500). Since the setBounds is from the top left corner, the start point of the component will be 450 if we want it to draw right at the very edge. Let's place the first position at 430, 60, and base the rest of them on this starting position in the same way as we did for the first block of buttons (in other words, each one is 50 further down in the Y axis):

6.3: Side button placement

Now that we have all the numbers we're looking for, we can put them together into the storyboard for this application:

6.4: Full storboard

And that's our storyboard!

Although working out the relationship of components against other components can be time consuming, it is much quicker to plan this out before you begin placing the elements of your user interface - all you have to do at this point is translate these numbers into a setBounds call in your applet.

Setting up the Interface

Now that we have the numbers, let's start setting up our application. The first thing we'll do is take a copy of our standard Swing applet framework, as we saw in chapter four. We'll use this as the basis for developing our applet since all we have to do then is slot in the components we wish to be displayed:

<source lang = "java">

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

public class CalculatorApplet extends JApplet {

   public void init() {
       Container c = getContentPane();
   }
   public void paint(Graphics g) {
       super.paint(g);
   }

} </source>

We should start off slowly, and we'll start by placing our text component onto the applet. The component we need for this is called JTextField, and we're going to refer to it within code by the name display. The first thing we need to do is create a class-wide variable that can hold a JTextField:

<source lang = "java">

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

public class CalculatorApplet extends JApplet {

   JTextField display;
   public void init() {
       Container c = getContentPane();
   }
   public void paint(Graphics g) {
       super.paint(g);
   }

} </source>

Once we've got this, we can set up the component in the init method:

<source lang = "java">

   public void init() {
       Container c = getContentPane();
       display = new JTextField(10);
   }

</source>

By default, the applet is still using the BorderLayout manager to place components - obviously since we've spent all this time working out co-ordinates we are going to want to switch this off in favour of our own layout:

<source lang = "java">

   public void init() {
       Container c = getContentPane();
       c.setLayout(null);
       display = new JTextField(10);
   }

</source>

Now that we've indicated to Java that we don't want to use the standard layout manager, we can add our display component to the applet:

<source lang = "java">

   public void init() {
       Container c = getContentPane();
       display = new JTextField(10);
       add(display);
   }

</source>

And finally, we set the bounds of our component according to the values we worked out for our storyboard:

<source lang = "java">

   public void init() {
       setLayout(null);
       display = new JTextField(10);
       add(display);
       display.setBounds(10, 10, 480, 30);
   }

</source>

And that's our JTextField sorted out. By default, we're not going to do anything in our applet until the buttons are pressed, so we don't add a listener object for this component.

Next, let's add the first of the number buttons. We start off by placing the button 7, since that appears at the top left corner of a calculator. We follow exactly the same procedure as before. First we create a class wide variable that will hold the button:

<source lang = "java">

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

public class CalculatorApplet extends JApplet {

   JTextField display;
   JButton num7;
   public void init() {
       setLayout(null);
       display = new JTextField(10);
       add(display);
       display.setBounds(10, 10, 480, 30);
   }
   public void paint(Graphics g) {
       super.paint(g);
   }

}

</source>

Then we put something into the variable within our init method:

<source lang = "java">

   public void init() {
       setLayout(null);
       display = new JTextField(10);
       add(display);
       display.setBounds(10, 10, 480, 30);
       num7 = new JButton("7");
   }

</source>

Next, we add it to the applet - note that we don't need to call setLayout(null) a second time. We only need do that once:

<source lang = "java">

   public void init() {
       setLayout(null);
       display = new JTextField(10);
       add(display);
       display.setBounds(10, 10, 480, 30);
       num7 = new JButton("7");
       add(num7);
   }

</source>

And then we set the bounds of the control. As before, we get the numbers for this from the storyboard we developed:

<source lang = "java">

   public void init() {
       setLayout(null);
       display = new JTextField(10);
       add(display);
       display.setBounds(10, 10, 480, 30);
       num7 = new JButton("7");
       add(num7);
       num7.setBounds(10, 60, 50, 50);
   }

</source>

Since this is a button and we want something to happen when the button is pressed, we must register an ActionListener for this component:

<source lang = "java">

   public void init() {
       setLayout(null);
       display = new JTextField(10);
       add(display);
       display.setBounds(10, 10, 480, 30);
       num7 = new JButton("7");
       add(num7);
       num7.setBounds(10, 60, 50, 50);
       num7.addActionListener(this);
   }

</source>

And that's our first button (and second component) set up. We follow exactly this same procedure for each of the buttons. For example, when it comes time for us to add in button eight, we add a class wide variable, put something into the variable, add the component to the applet, set the bounds on the component and then register an action listener.

This is a fairly cumbersome process - when we talk about arrays in the next chapter, we'll see a way by which we can make Java do most of this hard work for us. Until then, we're restricted to the tools we've already covered.

We'll see that the interface takes up by far the largest proportion of the code for this applet - as more techniques become available to us, we can begin to make our code much more compact and easy to maintain. For now though, we must suffer in silence.

<source lang = "java">

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

public class CalculatorApplet extends JApplet {

   JTextField display;
   JButton num7;
   JButton num8;
   public void init() {
       setLayout(null);
       display = new JTextField(10);
       add(display);
       display.setBounds(10, 10, 480, 30);
       num7 = new JButton("7");
       add(num7);
       num7.setBounds(10, 60, 50, 50);
       num7.addActionListener(this);
       num8 = new JButton("8");
       add(num8);
       num8.setBounds(60, 60, 50, 50);
       num8.addActionListener(this);
   }
   public void paint(Graphics g) {
       super.paint(g);
   }

}

</source>

The procedure of declaring a class-wide variable, instantiating it and setting its bounds should be repeated until all the buttons are placed on the applet.

Once we've done this, we'll want to take a look at our applet and make sure everything is positioned as it should be... but when we compile, it gives an error. This is because each of our buttons has an action listener registered, but we haven't implemented the ActionListener class or included the actionPerformed method in our applet. We must add these before our applet will compile:

<source lang = "java">

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

public class CalculatorApplet extends JApplet implements ActionListener {

   JTextField display;
   JButton num7;
   JButton num8;

// Lots and lots more.

   public void init() {

// Lots and lots of components being set up.

   }
   public void paint(Graphics g) {
       super.paint(g);
   }
   public void actionPerformed(ActionEvent e) {
   }

} </source>

Now when we compile and execute the applet, we get to see our storyboard brought to life:

6.5: Screenshot

That's a lot of space between the two families of buttons, but we are implementing only a simple calculator. If desired, we could later add buttons for square roots, logarithms and other more complex functionality, so this empty space can later be put to good use.

But we've completed the hardest part of this application - setting up the interface. Unlike the simple examples we discussed in chapters four and five, this is more than a basic proof of concept - despite their rather simple function, calculators have a relatively complex interface that requires a degree of precision in setup.

Now that we have our interface, we need to do something with it.

The Functionality

The functionality for this particular calculator is simple. Whenever a number is typed, it should be appended to the text component. If we press an operation button (like addition or subtraction) it should store the number that is currently in the text field and then clear the display, ready for another number to be entered. If we press equals, it should take the number from the text component and perform the last selected operation on that and the number we previously stored.

The first thing we're going to need is a temporary variable to hold the last number typed. We're only implementing a simple calculator here, so we'll stick purely to whole numbers. We add a class wide int variable called lastNumber to our applet.

We also need a variable to hold what the last operation selected was. Let's also use an integer for this. We will avoid the use of magic numbers by making variables to hold which integer corresponds to which operation:

<source lang = "java">

public class CalculatorApplet extends JApplet implements

       ActionListener {
   JTextField display;
   JButton num7;
   JButton num8;

// Lots and lots more.

   int lastNumber;
   String lastOperation;
   int ADDITION_OPERATION;
   public void init() {
       ADDITION_OPERATION = 1;

// Lots and lots of components being set up

   }
   public void paint(Graphics g) {
       super.paint(g);
   }
   public void actionPerformed(ActionEvent e) {
   }

}

</source>

Every time an operation button is pressed, we're going to take the current contents of the text component and place them into our lastNumber variable, overwriting any previous contents. We also want to store information on what the operation button pressed actually was (for when we press equals).

Since we want this to happen when a button is pressed, we place the code for this in the event handler method for the event type (in this case, actionPerformed). Let's start by adding this functionality for the addition button.

The only difficulty in this is that the text field stores its contents as a string, and not as a number - if we try and assign the return value of the getText method to an integer we will get an error. We can turn a string containing an integer (for example, "10") into the actual integer value using the following method call:

<source lang = "java"> int myNumber = Integer.parseInt (stringContainingInteger); </source>

So, for our addition button:

<source lang = "java">

   public void actionPerformed(ActionEvent e) {
       String temp;
       if (e.getSource() == addition) {
           temp = display.getText();
           lastNumber = Integer.parseInt(temp);
           lastOperation = ADDITION_OPERATION;
           display.setText("0");
       }
   }

</source>

When we press the addition button, we store the current contents of the display text component in a temporary string variable called temp. We pass this temporary variable to the Integer.parseInt code to turn it into an integer variable, and place the result in out lastNumber class variable.

We then make our lastOperation variable equal the value of the ADDITION_OPERATION variable (we set this to 1 in the init method above). Finally, we call setText with an empty string on the display text component to empty it.

And that's the functionality we need for all of our operation buttons, we just need to create a variable for each kind of operation for storing in lastOperation:

<source lang = "java">

public class MyFirstApplet extends JApplet implements ActionListener {

   JTextField display;
   JButton num7;
   JButton num8;

// Lots and lots more.

   int lastNumber;
   String lastOperation;
   int ADDITION_OPERATION;
   int SUBTRACTION_OPERATION;
   int DIVISION_OPERATION;
   int MULTIPLICATION_OPERATION;
   public void init() {
       ADDITION_OPERATION = 1;
       SUBTRACTION_OPERATION = 2;
       DIVISION_OPERATION = 3;
       MULTIPLICATION_OPERATION = 4;

// Lots and lots of components being set up.

   }
   public void paint(Graphics g) {
       super.paint(g);
   }
   public void actionPerformed(ActionEvent e) {
       String temp;
       if (e.getSource() == addition) {
           temp = display.getText();
           lastNumber = Integer.parseInt(temp);
           lastOperation = ADDITION_OPERATION;
           display.setText("");
       }
   }

}

</source>

We can expand our actionPerformed method to cope with all our operations using the same structure:

<source lang = "java">

   public void actionPerformed(ActionEvent e) {
       String temp;
       if (e.getSource() == addition) {
           temp = display.getText();
           lastNumber = Integer.parseInt(temp);
           lastOperation = ADDITION_OPERATION;
           display.setText("");
       }
       if (e.getSource() == subtraction) {
           temp = display.getText();
           lastNumber = Integer.parseInt(temp);
           lastOperation = SUBTRACTION_OPERATION;
           display.setText("");
       }
       if (e.getSource() == multiplication) {
           temp = display.getText();
           lastNumber = Integer.parseInt(temp);
           lastOperation = MULTIPLICATION_OPERATION;
           display.setText("");
       }
       if (e.getSource() == division) {
           temp = display.getText();
           lastNumber = Integer.parseInt(temp);
           lastOperation = DIVISION_OPERATION;
           display.setText("");
       }
   }

</source>

Now we have the operation buttons working correctly, we need to write the code for dealing with the number buttons - since we have a JTextField for display we can actually type numbers directly into the display. This is very useful for testing purposes, but it doesn't properly model the way a calculator behaves.

Dealing with the number buttons is easy - all they are going to do is add a number to the end of the display. We know that the JTextField class has a setText method, and we know it has a getText method. First, we'll call getText to get the current contents, and then call setText with our new number added to the end. This is the system we'll use to add each of the numbers. Within our actionPerformed, we need some code. The following code is for the number 1 button:

<source lang = "java">

       if (e.getSource() == num1) {
           temp = display.getText();
           temp = temp + "1";
           display.setText(temp);
       }

</source>

We repeat this code for each button:

<source lang = "java">

       if (e.getSource() == num2) {
           temp = display.getText();
           temp = temp + "2";
           display.setText(temp);
       }

</source>

And when we compile and execute our code, we can run our applet and enter all the numbers via the buttons. Neato!

Again, this is a lot of repetition - it would be much more effective if there was a way we could simplify the setup and make the code more concise. There is, but it's outside what we've discussed at the moment.

We still need to add the functionality for the clear button - this is simple. All we do is call setText("") on the display:

<source lang = "java">

       if (e.getSource() == clear) {
           display.setText("");
       }

</source>

Finally, we need to write the code for dealing with the equals button. All this is going to do is check to see which was the last operation selected, and then perform the selected operation on the value stored in the variable lastNumber and the current contents of the JTextField, and display the result in display. We'll use an if-else structure to determine the proper course of action.

We're going to need a temporary integer variable to hold the answer before it is displayed on the display text component. We will need another to store the result of the Integer.parseInt call. We'll set them at the top of the actionPerformed method along with the String temp declaration.

<source lang = "java">

       if (e.getSource() == equals) {
           temp = display.getText();
           tempVal = Integer.parseInt(temp);
           if (lastOperation == ADDITION_OPERATION) {
               answer = lastNumber + tempVal;
           } else if (lastOperation == DIVISION_OPERATION) {
               answer = lastNumber / tempVal;
           } else if (lastOperation == MULTIPLICATION_OPERATION) {
               answer = lastNumber * tempVal;
           } else {
               answer = lastNumber - tempVal;
           }
           display.setText("" + answer);
       }

</source>

And that's it, we've created ourselves a calculator applet!

Conclusion

Obviously there is more work to be done - it doesn't meet many of the hallmarks of good code discussed in the third chapter. There are many gains to be made in efficiency, readability, maintainability and robustness. These are left as an exercise for the reader. In the process of this chapter we have seen all of the aspects required for building an event driven program. We have designed a storyboard and then implemented a substantial interface based on our plan.

Calculators are interesting from a event-drive perspective. They are functionally simple (mostly), in that the code required to implement the calculator logic is not syntactically complex. However, the interface, as intuitive as it is, requires a large amount of planning to implement and lay out correctly.