Java AWT

Previous Chapter 11
Scrolling
Next
 

11.2 Scrolling An Image

Example 11.1 is a Java application that displays any image in the current directory in a viewing area. The viewing area scrolls to accommodate larger images; the user can use the scrollbars or keypad keys to scroll the image. In Java 1.1, it is trivial to implement this example with a ScrollPane; however, if you're using 1.0, you don't have this luxury. Even if you're using 1.1, this example shows a lot about how to use scrollbars.

Our application uses a Dialog to select which file to display; a FilenameFilter limits the list to image files. We use a menu to let the user request a file list or exit the program. After the user picks a file, the application loads it into the display area. Figure 11.3 shows the main scrolling window.

Figure 11.3: Scrolling an image

[Graphic: Figure 11-3]

The code for the scrolling image consists of a ScrollingImage class, plus several helper classes. It places everything into the file ScrollingImage.java for compilation.

Example 11.1: Source Code for Scrolling an Image

import java.awt.*;
import java.io.FilenameFilter;
import java.io.File;

The first class contains the FilenameFilter used to select relevant filenames: that is, files that are likely to contain GIF, JPEG, or XBM images. If the filename has an appropriate ending, the accept() method returns true; otherwise, it returns false.

// True for files ending in jpeg/jpg/gif/xbm
class ImageFileFilter implements FilenameFilter {
    public boolean accept (File dir, String name) {
        String tempname = name.toLowerCase();
        return (tempname.endsWith ("jpg") || tempname.endsWith ("jpeg") ||
                tempname.endsWith ("gif") || tempname.endsWith ("xbm"));
    }
}

The ImageListDialog class displays a list of files from which the user can select. Instead of using FileDialog, we created a customized List to prevent the user from roaming around the entire hard drive; choices are limited to appropriate files in the current directory. When the user selects an entry (by double-clicking), the image is then displayed in the scrolling area.

class ImageListDialog extends Dialog {
    private String name = null;
    private String entries[];
    private List list;
    ImageListDialog (Frame f) {
        super (f, "Image List", true);
        File dir = new File (System.getProperty("user.dir"));
        entries = dir.list (new ImageFileFilter());
        list = new List (10, false);
        for (int i=0;i<entries.length;i++) {
            list.addItem (entries[i]);
        }
        add ("Center", list);
        pack();
    }
    public String getName () {
        return name;
    }
    public boolean action (Event e, Object o) {
        name = (String)e.arg;
        ((ScrollingImage)getParent()).processImage();
        dispose();
        return true;
    }
}

The code in this class is straightforward. The constructor reads the current directory from the system property list, uses the list() method of the File class to create a list of files that match our filename filter, and then creates a List object that lists these files. getName() returns the name of the selected file. action() is called when the user selects a file; it sets the name of the selected file from the arg field of the Event and then calls the processImage() method of its parent container. The parent container of our ImageListDialog is the ScrollingImage class we are defining; its processImage() method displays a scrollable image.

The next class, ImageCanvas, is the Canvas that the image is drawn onto. We use a separate Canvas rather than drawing directly onto the Frame so that the scrollbars do not overlap the edges of the image. You will notice that the paint() method uses negative x and y values. This starts the drawing outside the Canvas area; as a result, the Canvas displays only part of the image. This is how we do the actual scrolling. (xPos, yPos) are the values given to us from the scrollbars; by positioning the image at (-xPos, -yPos), we ensure that the point (xPos, yPos) appears in the upper left corner of the canvas.

class ImageCanvas extends Canvas {
    Image image;
    int xPos, yPos;
    public void redraw (int xPos, int yPos, Image image) {
        this.xPos = xPos;
        this.yPos = yPos;
        this.image = image;
        repaint();
    }
    public void paint (Graphics g) {
        if (image != null)
            g.drawImage (image, -xPos, -yPos, this);
    }
}

ScrollingImage provides the framework for the rest of the program. It provides a menu to bring up the Dialog to choose the file, the scrollbars to scroll the scrolling canvas, and the image canvas area. This class also implements event handling methods to capture the scrollbar events, paging keys, and menu events.

public class ScrollingImage extends Frame {
    static Scrollbar horizontal, vertical;
    ImageCanvas center;
    int xPos, yPos;
    Image image;
    ImageListDialog ild;
    ScrollingImage () {
        super ("Image Viewer");
        add ("Center", center = new ImageCanvas ());
        add ("South",  horizontal = new Scrollbar (Scrollbar.HORIZONTAL));
        add ("East", vertical = new Scrollbar (Scrollbar.VERTICAL));
        Menu m = new Menu ("File", true);
        m.add ("Open");
        m.add ("Close");
        m.add ("-");
        m.add ("Quit");
        MenuBar mb = new MenuBar();
        mb.add (m);
        setMenuBar (mb);
        resize (400, 300);
    }
    public static void main (String args[]) {
        ScrollingImage si = new ScrollingImage ();
        si.show();
    }
    public boolean handleEvent (Event e) {
         if (e.id == Event.WINDOW_DESTROY) {
            System.exit(0);
        } else if (e.target instanceof Scrollbar) {
            if (e.target == horizontal) {
                xPos = ((Integer)e.arg).intValue();
            } else if (e.target == vertical) {
                yPos = ((Integer)e.arg).intValue();
            }
            center.redraw (xPos, yPos, image);
        }
        return super.handleEvent (e);
    }

This handleEvent() method kills the program if the user used the windowing system to exit from it (WINDOW_DESTROY). More to the point, if a Scrollbar generated the event, handleEvent() figures out if it was the horizontal or vertical scrollbar, saves its value as the x or y position, and redraws the image in the new location. Finally, it calls super.handleEvent(); as we will see in the following code, other events that we care about (key events and menu events) we don't want to handle here--we would rather handle them normally, in action() and keyDown() methods.

    public void processImage () {
        image = getToolkit().getImage (ild.getName());
        MediaTracker tracker = new MediaTracker (this);
        tracker.addImage (image, 0);
        try {
            tracker.waitForAll();
        } catch (InterruptedException ie) {
        }
        xPos = 0;
        yPos = 0;
        int imageHeight = image.getHeight (this);
        int imageWidth = image.getWidth (this);
        vertical.setValues (0, 5, 0, imageHeight);
        horizontal.setValues (0, 5, 0, imageWidth);
        center.redraw (xPos, yPos, image);
    }

processImage() reads the image's filename, calls getImage(), and sets up a MediaTracker to wait for the image to load. Once the image has loaded, it reads the height and width, and uses these to set the maximum values for the vertical and horizontal scrollbars by calling setValues(). The scrollbars' minimum and initial values are both 0. The size of the scrollbar "handle" is set to 5, rather than trying to indicate the visible portion of the image.

    public boolean action (Event e, Object o) {
        if (e.target instanceof MenuItem) {
            if ("Open".equals (o)) {
                // If showing already, do not show again
                if ((ild == null) || (!ild.isShowing())) {
                    ild = new ImageListDialog (this);
                    ild.show();
                }
            } else if ("Close".equals(o)) {
                image = null;
                center.redraw (xPos, yPos, image);
            } else if ("Quit".equals(o)) {
                System.exit(0);
            }
            return true;
        }
        return false;
    }

action() handles menu events. If the user selected Open, it displays the dialog box that selects a file. Close redraws the canvas with a null image; Quit exits the program. If any of these events occurred, action() returns true, indicating that the event was fully handled. If any other event occurred, action() returns false, so that the system will deliver the event to any other methods that might be interested in it.

    public boolean keyDown (Event e, int key) {
        if (e.id == Event.KEY_ACTION) {
            Scrollbar target = null;
            switch (key) {
                case Event.HOME:
                    target = vertical;
                    vertical.setValue(vertical.getMinimum());
                    break;
                case Event.END:
                    target = vertical;
                    vertical.setValue(vertical.getMaximum());
                    break;
                case Event.PGUP:
                    target = vertical;
                    vertical.setValue(vertical.getValue()
                             - vertical.getPageIncrement());
                    break;
                case Event.PGDN:
                    target = vertical;
                    vertical.setValue(vertical.getValue()
                             + vertical.getPageIncrement());
                    break;
                case Event.UP:
                    target = vertical;
                    vertical.setValue(vertical.getValue()
                             - vertical.getLineIncrement());
                    break;
                case Event.DOWN:
                    target = vertical;
                    vertical.setValue(vertical.getValue()
                             + vertical.getLineIncrement());
                    break;
                case Event.LEFT:
                    target = horizontal;
                    if (e.controlDown())
                        horizontal.setValue(horizontal.getValue() -
                            horizontal.getPageIncrement());
                    else
                        horizontal.setValue(horizontal.getValue() -
                            horizontal.getLineIncrement());
                    break;
                case Event.RIGHT:
                    target = horizontal;
                    if (e.controlDown())
                        horizontal.setValue(horizontal.getValue() +
                            horizontal.getPageIncrement());
                    else
                        horizontal.setValue(horizontal.getValue() +
                            horizontal.getLineIncrement());
                    break;
                default:
                    return false;
            }
            Integer value = new Integer (target.getValue());
            postEvent (new Event ((Object)target,
                       Event.SCROLL_ABSOLUTE, (Object)value));
            return true;
        }
        return false;
    }
}

keyDown() isn't really necessary, but it adds a nice extension to our scrollbars: in addition to using the mouse, the user can scroll with the arrow keys. Pressing an arrow key generates a KEY_ACTION event. If we have one of these events, we check what kind of key it was, then compute a new scrollbar value, then call setValue() to set the appropriate scrollbar to this value. For example, if the user presses the page up key, we read the page increment, add it to the current value of the vertical scrollbar, and then set the vertical scrollbar accordingly. (Note that this works even though nondefault page and line increments aren't implemented correctly.) The one trick here is that we have to get the rest of the program to realize that the scrollbar values have changed. To do so, we create a new SCROLL_ABSOLUTE event, and call postEvent() to deliver it.


Previous Home Next
Scrollbar Book Index The Adjustable Interface

Java in a Nutshell Java Language Reference Java AWT Java Fundamental Classes Exploring Java