11.7 ScrollPane and ScrollbarsOne of the big advantages of Java 1.1 is the addition of a ScrollPane container. Previously, unless you were working with a text component, you had to manage scrolling yourself. It wasn't terribly difficult, but it was a pain: you had to create Scrollbar objects, attach them to whatever you were scrolling, and redisplay everything with new positions whenever the user made an adjustment. ScrollPane does it all for you. About the only time you absolutely need a Scrollbar is when you want to create a "volume control-type" object. For example, you might want to create a paint mixer that blended different amounts of red, blue, and green, depending on some scrollbar settings. The unifying theme behind both ScrollPane and Scrollbar is the Adjustable interface, which defines the responsibilities of scrollable objects. An object that implements Adjustable lets you modify an integer value through some fixed range. When a user changes the value, the object generates an AdjustmentEvent; as you might expect, to get an AdjustmentEvent, you must implement AdjustmentListener and register by calling addAdjustmentListener(). Scrollbars implement Adjustable, and a ScrollPane can return Adjustable objects for each of its scrollbars.[2]
In this section, we'll demonstrate both the ScrollPane and Scrollbar classes. We'll start with a ScrollPane. Working With A ScrollPaneTechnically, a ScrollPane is a Container, but it's a funny one. It has its own layout manager, which can't be changed. It can only accomodate one component at a time. This seems like a big limitation, but it isn't. If you want to put a lot of stuff in a ScrollPane, just put your components into a Panel, with whatever layout manager you like, and put that panel into the ScrollPane. When you create a ScrollPane, you can specify the conditions under which its srollbars will be displayed. This is called the scrollbar display policy; you can specify the policy by using one of the three constants below as an argument to the ScrollPane constructor.
By default, the policy is SCROLLBARS_AS_NEEDED. Here's an applet that uses a ScrollPane to display a large image. As you'll see, the applet itself is very simple; all we do is get the image, set the applet's layout manager, create a ScrollPane, put the image in our pane, and add the ScrollPane to the applet. To make the program slightly cleaner, we create an ImageComponent component to hold the image, rather than placing the image directly into the ScrollPane. Here's the applet itself:
import java.awt.*; public class ScrollPaneApplet extends java.applet.Applet { public void init() { Image image = getImage( getClass().getResource(getParameter("image")) ); setLayout( new BorderLayout() ); ScrollPane scrollPane = new ScrollPane(); scrollPane.add( new ImageComponent(image) ); add( "Center", scrollPane ); } } And here's the ImageComponent. It waits for the image to load, using a MediaTracker, and sets its size to the size of the image. It also provides a paint() method to draw the image. This takes a single call to drawImage(). The first argument is the image itself; the next two are the coordinates of the image relative to the ImageComponent; and the last is a reference to the ImageComponent itself (this), which serves as an image observer. (We'll discuss image observers in Chapter 14, Working With Images; for the time being, take this on faith.) We also supply an update() method that calls paint(). As we'll see later, the default version of update() automatically clears the screen, which wastes time if we already know that our image will cover the entire screen. Therefore, we override update() so that it doesn't bother clearing the screen first. Finally, ImageComponent provides a getPreferredSize() method, overriding the method it inherits from Component. This method simply returns the image's size, which is a Dimension object. When you're using a ScrollPane, it's important for the object you're scrolling to provide a reliable indication of its size, particularly if the object is a lightweight component.
import java.awt.*; class ImageComponent extends Component { Image image; Dimension size; ImageComponent ( Image image ) { this.image = image; MediaTracker mt = new MediaTracker(this); mt.addImage( image, 0 ); try { mt.waitForAll(); } catch (InterruptedException e) { /* error */ }; size = new Dimension ( image.getWidth(null), image.getHeight(null) ); setSize( size ); } public void update( Graphics g ) { paint(g); } public void paint( Graphics g ) { g.drawImage( image, 0, 0, this ); } public Dimension getPreferredSize() { return size; } } Using Scrollbars
Our next example is basically the same as the previous, except that it doesn't use the ScrollPane; it implements its own scroller using scrollbars. With Java 1.1, you'd never write code like this, but it does show how much work the ScrollPane saves, and also demonstrates how to use scrollbars in other situations. Our applet is called ComponentScrollerApplet; it uses a homegrown scroll pane called ComponentScroller. The component that we scroll is the ImageComponent we developed in the previous example. Now let's dive into the code for ComponentScrollerApplet:
import java.awt.*; import java.awt.event.*; public class ComponentScrollerApplet extends java.applet.Applet { public void init() { Image image = getImage( getClass().getResource(getParameter("image")) ); ImageComponent canvas = new ImageComponent( image ); setLayout( new BorderLayout() ); add( "Center", new ComponentScroller(canvas) ); } } class ComponentScroller extends Container { Scrollbar horizontal, vertical; Panel scrollarea = new Panel(); Component component; int orgX, orgY; ComponentScroller( Component comp ) { scrollarea.setLayout( null ); // We'll handle the layout scrollarea.add( component = comp ); horizontal = new Scrollbar( Scrollbar.HORIZONTAL ); horizontal.setMaximum( component.getSize().width ); horizontal.addAdjustmentListener( new AdjustmentListener() { public void adjustmentValueChanged(AdjustmentEvent e) { component.setLocation( orgX = -e.getValue(), orgY ); } } ); vertical = new Scrollbar( Scrollbar.VERTICAL ); vertical.setMaximum( component.getSize().height); vertical.addAdjustmentListener( new AdjustmentListener() { public void adjustmentValueChanged(AdjustmentEvent e) { component.setLocation( orgX, orgY = -e.getValue() ); } } ); setLayout( new BorderLayout() ); add( "Center", scrollarea ); add( "South", horizontal ); add( "East", vertical ); } public void doLayout() { super.doLayout(); horizontal.setVisibleAmount( scrollarea.getSize().width ); vertical.setVisibleAmount( scrollarea.getSize().height ); } } So, what do our new components do? Let's start at the top and work our way down. The applet itself is very simple; it does all of its work in init(). First it sets its layout manager to BorderLayout. Then it acquires an Image object with a call to getImage(). Finally, the applet creates an ImageComponent to hold our image, creates a ComponentScroller to hold the ImageComponent, and adds the scroller to the "Center" region of the layout. I chose BorderLayout because it resizes its central component to fill the entire area available. Next comes the ComponentScroller itself. ComponentScroller takes a reference to our ImageComponent in its constructor. It adds the component it will be scrolling to a Panel with no layout manager. It then creates horizontal and vertical Scrollbar objects (HORIZONTAL and VERTICAL are constants of the Scrollbar class, used to specify a scrollbar's direction), sets their maximum values using the height and width of the Panel, and registers an AdjustmentListener for each scrollbar. The AdjustmentListener is an anonymous inner class that implements the adjustmentValueChanged() method. This method is called whenever the user moves the scrollbar. It extracts the new scrollbar setting from an AdjustmentEvent and uses this to move the component we're scrolling to its new location. We have a separate listener for each scrollbar, so we don't have to figure out which scrollbar generated the event. The listener for the horizontal scrollbar adjusts the component's x coordinate (orgX) and leaves its y coordinate unchanged; likewise, the listener for the vertical scrollbar adjusts the component's y coordinate. By adjusting the location of the ImageComponent, we control how much of the image is displayed; anything that falls outside of the scroller's Panel (scrollarea) isn't displayed. The ComponentScroller overrides the doLayout() method of the Container class. This gives us an opportunity to change the size of the scrollbar "handles" whenever the scroller is resized. To do so, we call super.doLayout() first, to make sure that the container gets arranged properly; although we're overriding this method, we need to make sure that it does its work. Then we call the setVisibleAmount() method of each scrollbar with the new width and height of the scrolling area. So in review: we call setMaximum() to set the vertical scrollbar's maximum value to the image's height; we call setVisibleAmount() to tell the vertical scrollbar how much area we have available; and it sets the size of its "handle" accordingly. For example, if the image is 200 pixels high, and the visible amount is 100 pixels, the scrollbar sets its handle to be roughly half its length. We do similar things to the horizontal scrollbar. As a result, the handles grow or shrink as we change the size of the viewing area and indicate how much of the image is visible. The setMaximum() and setVisibleAmount() are both part of the Adjustable interface, which scrollbars implement. Other methods of this interface are:
It's worth asking why we put our image in a Canvas, which we then put into a Panel, which we put into another Panel, which we put into the applet. Surely there's a more efficient way. Yes there is, but we wanted to make as many reusable components as possible. With this design, you can use ImageComponent wherever you need to display an image and check that it is loaded first; you can use ComponentScroller wherever you need to scroll any kind of component, not just an image or a Canvas. Making resuable components is one of the big advantages of object oriented design; it's something you should always keep in mind. | ||||||||||||||||
|