GUI II

From CompSciWiki
Jump to: navigation, search

GUI Part II

In this continuation of the previous lab on GUIs in Java, will look at more complicated issues of layout, controlling buttons and showing images, as well as look at a final example using google maps.

You can use the GUI program that you made in the last lab as a basis, or you can download this file to start. We're going to begin with the ButtonAdder program (linked to in the previous sentence) as an example and build upon the example throughout the lab.

Step 1: Controlling layouts

In the last lab, we added components to a GUI, but our only control over them was to add them in a certain order, so they appeared in that same order in our final window. We can have more control over the layout than that, however. The two simplest methods for controlling layout are the FlowLayout and BorderLayout managers.

FlowLayout

The FlowLayout is the default layout for JPanel. It follows the rule we've seen already: if we add more than one component to the panel, then the first component added will appear on the left, and later additions (in the code) will appear further right. The layout management will also adjust the position of the components when the window is resized.

There is one aspect of the FlowLayout that we can adjust: the alignment. By default, all components are centered in the row. But we may have situations where we want the elements in a panel to be aligned on the right or left of the panel (instead of being centered). To do this, we must use the setLayout of the panel (or frame) we are dealing with. If myPan is a JPanel, then we can adjust its layout like this:

 myPan.setLayout(new FlowLayout(FlowLayout.LEFT));

The parameters for FlowLayout are FlowLayout.LEFT, FlowLayout.RIGHT and FlowLayout.CENTER. After setting this, everything else is the same (including the calls to the method add to add components to this panel), and the layout will be automatically adjusted.

BorderLayout

The FlowLayout is nice, but doesn't really allow control over the position of components on the page, which may be useful when you have lots of buttons, text fields and images to display. Another simple layout manager is the BorderLayout. It divides a panel or frame into five areas: north, south, east, west and center. You can only place one component at a time in each region! (If you want more than five components in one window, you can nest components, which we will learn about in the next section.)

There are two steps to using the BorderLayout:

  1. you must set the layout of the current panel (like when we used FlowLayout in the last section).
  2. when we add new components to that panel, we have to specify which of the five regions (north, south, east, west, center) the component should go in.

So imagine you have three buttons (button1,button2,button3) that you want to put in the Panel called myPanel. After declaring all of the buttons and myPanel, you would add this code:

myPanel.setLayout (new BorderLayout()); // change the layout to border layout style. 
myPanel.add( button1 , BorderLayout.NORTH);
myPanel.add( button2,  BorderLayout.CENTER);
myPanel.add( button3, BorderLayout.SOUTH);

Notice that before when we added a component to a panel, we only specified the component to add. But when we are using the BorderLayout layout, we need to call add like this:

myPanel.add ( [COMPONENT TO ADD], BorderLayout.X );

where X is one of the five regions (NORTH,SOUTH,EAST,WEST or CENTER).

Nesting Components

The BorderLayout is great and everything, but what if we want to add more than five items to our screen? One solution is to add Frames to the positions of the BorderLayout, rather than components themselves. That way you could, for example, group three buttons together in a panel and then add that panel to the NORTH section of another panel. Or if you have a text label and a text field which should be next to each other, they could be placed in a panel, then you could nest that panel in one of the five locations of another panel. This can get tedious, but you can nicely control the layout of your window this way. Here's an example:

                 mapButton = new JButton("Map it!");
		 mapButton.addActionListener(new ButtonListener());
		 
		 lat = new JTextField(10);
		 latLab = new JLabel("Latitude");
		 latPan = new JPanel ();
		 latPan.setLayout(new BorderLayout());
		 latPan.add(latLab,BorderLayout.NORTH);
		 latPan.add(lat,BorderLayout.SOUTH);
		 
		 longi = new JTextField(10);
		 longLab = new JLabel("Longitude");
		 longPan = new JPanel ();
		 longPan.setLayout(new BorderLayout());
		 longPan.add(longLab,BorderLayout.NORTH);
		 longPan.add(longi,BorderLayout.SOUTH);
	
	         // define the zoomPan and typePan, too.	 
                 // (..omitted..)
		 
		 allPan = new JPanel ();
		 allPan.setLayout(new FlowLayout(FlowLayout.CENTER));
		 allPan.add(latPan);
                 allPan.add(longPan);
                 allPan.add(zoomPan);
                 allPan.add(typePan);
                 allPan.add(mapButton);

Here, we have one big panel (allPan) which contains five components using the flow layout. Four of those components are themselves panels (e.g., latPan) which contain a label in the north part of the layout and a text field in the south part of the layout. (We'll use this code in our full example on Google Maps at the end of this lab.)

Another alternative to nesting and using BorderLayout would be to use the Grid Layout. We won't cover it, but here's a tutorial on the Grid Layout.

Step 2: Multiple buttons

In the previous lab, we learned how to add a button to a GUI and write code to react to button clicks. Remember the process is:

  1. declare a button variable, and instantiate it (urButton = new JButton("button label");)
  2. declare a new (nested) class in your main class which implements the listener.
  3. add the action listener to your button so that when it is clicked, the code in the listener is executed.

If this is fuzzy for you, you might want to re-read the previous lab.

What happens if you have several buttons? We could rewrite the listener class so that we had one class per button, but that duplicates a lot of code that is pretty much the same. Instead of repeating the code, we can write one listener class that has the ability to distinguish which button was pressed using the getSource method. Here's a hypothetical example. Suppose you have three buttons:

private JButton myButton1;
private JButton myButton2;
private JButton myButton3;

And suppose you bound all of them to the ButtonListener class using the addActionListener method. Then that ButtonListener class could look like:

 private class ButtonListener implements ActionListener {
                public void actionPerformed (ActionEvent e) {

                        if (e.getSource() == myButton1) {
                                 // Code that's executed when myButton1 is pushed.
                        } else if (e.getSource() == myButton2) {
                                // Code that's executed when myButton2 is pushed.

                        } else if (e.getSource() == myButton3) {
                                // Code that's executed when myButton3 is pushed.

                        } 
                }
        }

Note that e.getSource() will always be the same: e is the parameter that's passed in that gives a summary of "what's happened" when an event happens, so the source (e.getSource()) will be the button that's clicked. We can refer to these by the variable name of the button that you are interested in.

Step 3: Images

Images can be drawn on a JPanel without too much work. First, make sure to have these import statements:

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.net.*;
import javax.imageio.*;
import java.awt.image.*;

These will provide the necessary methods for drawing pictures (as well as the rest of the GUI).

Defining a picture in Java is fairly easy. We can specify the location of the image (as a URL) and then read the image into a variable of type BufferedImage:

URL imageURL = new URL ("http://www.where.the/picture_is.jpg");
BufferedImage img = ImageIO.read(imageURL);

The ImageIO.read method can also be given a File location rather than a URL, but we haven't dealt with the File type, so we'll focus on loading images from the web for this example.

How do we display the image? There is a drawImage method, but it is a little convoluted to use. One easy way to do this is to make an "image panel" which will be an extended JPanel and then give your new "image panel" instructions on how to "draw" itself by overriding some built-in methods.

In particular, suppose we have a class like this:

public class imagePanel extends JPanel {
 // stuff for loading img, constructors go here. 

 public void paintComponent(Graphics g) {
        super.paintComponent(g);

        g.drawImage(img, 10, 20, null);

  }
}

In this class, notice that we extend the JPanel class, so that our imagePanel will be able to do everything that a JPanel can do (like be put in a JFrame and displayed. But we rewrite (or override) the paintComponent method to tell it to draw the image img whenever it is painted (displayed on the screen.) This method supposes that you've already defined the value of the variable img, probably in the constructor for the imagePanel class.

The drawImage method takes four parameters: the image (img), the (x,y) coordinates of the upper-left-hand corner of where the picture should be drawn, and a final parameter that can always be given as null.

For a full example of drawing images, see the source code here. This example is described in detail in the next section.

Example: Google Maps in Java

Using the elements of GUI development we've described in the last two labs, we can build a working Google Maps app in Java. The app will

  1. ask the user for a latitude,longitude pair, as well as a map type (terrain, street, satellite) and a zoom level.
  2. the app will then ask Google Maps to send a map of the area, using the Google Maps Static API.
  3. the image will then be displayed using the image drawing techniques of the previous example.

The Google Maps Static API allows us to access JPG images from Google Maps by supplying a well-formed URL of the place we want. In particular, the requests will look like

http://maps.google.com/maps/api/staticmap?center=[LAT],[LONG]&zoom=[ZOOM_LEVEL]&size=400x400&format=jpg&sensor=false&mapType=[MAP_TYPE]

We will let the user supply the portions in the square brackets, and then pass the URL to Google and wait for the answer in the form of an image. We will get the information from the user with our GUI. The final layout looks like this:


Googlemaps.jpg

The entire source code for the google maps app is available here.

Here are some notes on the source code:

  1. The main work is done (as usual) in the ButtonListener class. This code is executed when the user clicks the "Map it!" button, and just reads all the information from the text fields and combo boxes (and does a little bit of processing) and then passes them to the newMapPosition method.
  2. the newMapPosition method constructs the correct URL and gets the corresponding map from Google Maps (as a JPG). The variable img is read, and then the line this.repaint() is called, which repaints the panel containing the image. This updates the map to the user's choice of location.