In the last issue we mentioned the "deprecation" of some AWT methods
as of the 1.1 release. Some of these methods are isolated, but the
basic event handling model of the AWT has itself changed in 1.1, and
it's worth illustrating the new model.
The old model was based on inheritance, with subclassing of AWT
components and overriding of the action() and handleEvent() methods.
An overriding method either would handle events and pass back "true",
or else pass back "false" and propagate the event up to the next
component in the hierarchy.
This model is somewhat unwieldy, with a requirement for lots of
subclasses, complex logic to process events, and lack of separation
between application and interface.
As of 1.1, a new "delegation" model has been implemented. In the new
model, a "listener" object expresses interest in events from one or
more "source" objects. When an event occurs, it is passed from the
source to all registered listeners. Since the listener object
implements a standard interface (like ActionListener), the source can
invoke a known method on the listener object, to pass the details of
the event.
To illustrate this further, consider the following example:
import java.awt.*; import java.awt.event.*; class Dispatch implements ActionListener { static final int COMMAND1 = 1; static final int COMMAND2 = 2; static final int EXIT = 3; int id; Delegate d; public Dispatch(int id, Delegate d) { this.id = id; this.d = d; } public void actionPerformed(ActionEvent e) { switch (id) { case COMMAND1: d.command1(); break; case COMMAND2: d.command2(); break; case EXIT: d.exit(); break; } } } class Interface { public Interface(Delegate d) { Frame f = new Frame(); f.setLayout(new FlowLayout()); Dispatch cmd1 = new Dispatch(Dispatch.COMMAND1, d); Dispatch cmd2 = new Dispatch(Dispatch.COMMAND2, d); Dispatch exit = new Dispatch(Dispatch.EXIT, d); Button b = null; b = new Button("Command #1"); f.add(b); b.addActionListener(cmd1); b = new Button("Command #2"); f.add(b); b.addActionListener(cmd2); b = new Button("Exit"); f.add(b); b.addActionListener(exit); f.pack(); f.show(); } } public class Delegate { public void command1() { System.out.println("command #1 invoked"); } public void command2() { System.out.println("command #2 invoked"); } public void exit() { System.exit(0); } public static void main(String args[]) { Delegate d = new Delegate(); Interface i = new Interface(d); } } |
In this example, Delegate is our application, and Interface the
interface for it. Dispatch is a helper class that implements the
ActionListener interface. We tie particular object instances of
Dispatch to individual buttons. When a button is selected, an event
is passed to the listener for the button, and it in turn invokes the
appropriate application method.
There is a lot more that can be said about this approach. A paper
that describes it in some detail can be found at:
http://www.javasoft.com/products/jdk/1.1/docs/guide/awt/designspec/events.html
We will be using the delegation model in future AWT examples.
We saw a simple example above of printing text. Another approach to
printing uses the AWT:
import java.awt.*; public class print { public static void main(String args[]) { Frame f = new Frame("test"); PrintJob pj = f.getToolkit().getPrintJob(null, "print1", null); Graphics g = pj.getGraphics(); g.drawLine(0, 0, 150, 150); pj.end(); } } |
This example creates a frame, and retrieves its toolkit. The toolkit
is the interface between the AWT proper and an actual GUI/window
implementation.
From the toolkit we get a PrintJob instance. This causes a print job
screen to be popped up. It asks about how many copies are desired and
similar types of questions.
Given a PrintJob instance, we can get a Graphics object, and draw a
line to it, and then end the print job.
This feature is in 1.1 and 1.1.1, but without any documentation in the
source code, so this presentation is a bit sketchy. There are
additional method calls for getting page dimensions and resolution.
As compared to the method illustrated above, using device files, the
AWT approach to printing offers platform independence (you don't have
to worry about pathnames like "/dev/lp"). This approach also offers
graphics and Unicode support.
We will likely have more to say about printing in the future.
In previous issues we've discussed some of the aspects of applet
programming, and also mentioned a new technique for serializing
objects, that is, turning an object into a stream of bytes that can be
saved to a file or sent across a network.
We can combine these techniques in order to serialize an applet, that
is, to capture it in some current state, and then restore that state
later. This technique has various uses, for example to perform
one-time initialization.
Consider first an applet:
import java.awt.*; public class applet extends java.applet.Applet { private int cnt = 1; public void paint(Graphics g) { String s = "Hello world #" + cnt++; g.drawString(s, 25, 25); } } |
and some HTML that drives it:
<html> <head> <title>Applet Example</title> </head> <body> <applet code="applet.class" width=150 height=150></applet> </body> </html> |
If we run this HTML with the JDK 1.1.2 appletviewer program (1.1 gives
an exception for what we're going to do), we can Stop and Start the
applet a few times, using the appletviewer menu. Each start results
in the displayed count being bumped up by one.
Then, if we select Save, with a name like "applet.ser", the applet
object is serialized and written to the indicated file. We can start
up the appletviewer again with HTML code of:
<html> <head> <title>Applet Example</title> </head> <body> <applet object="applet.ser" width=150 height=150></applet> </body> </html> |
and the applet will take up where it left off.
This technique is quite powerful and useful in a variety of
applications. But if you are considering using this approach, you
should investigate some of the issues around serialization. For
example, not all objects can be serialized. One case of this would be
machine-specific resources like file descriptors.
In this issue we will use a longish example of a calculator program to
illustrate how one would go about designing a custom AWT component
using JDK 1.1.
For a calculator program, where there are several buttons representing
digits and operators like "+", one approach would be use the Button
component in the AWT. But we have chosen to design our own component
for this purpose, which consists of a reddish oval with a number or
operator inside. This class is called my_Button in the code below.
When we design our own component, there are several important points
to note. One is that the component should have a constructor, to
create new instances.
Another is that the component needs a paint() method, to draw itself
on the screen.
The component should define getPreferredSize() and getMinimumSize(),
to declare its size to the AWT. This is important in setting up the
bounding box for the component and for doing layout.
A custom component may be a subclass of an AWT class like Canvas, or
in 1.1, it may extend java.awt.Component directly. This latter
approach results in what is called a "lightweight" component, one
without a native peer in the local windowing system. Lightweight
components can be more efficient, because of less overhead. my_Button
in the example below is lightweight.
Finally, we should mention how event handling is done in this
example. Each of the calculator buttons has a single instance of
"docalc" registered as a listener for it, so that when the button is
selected, docalc.actionPerformed() is called. This is the method that
actually does all the calculating work.
How do we know when a button is selected, given that we are doing our
own buttons as custom components? For this, we have another level of
event handling, where we register a mouse listener for each instance
of my_Button. When the mouse is clicked, we detect that, and dispatch
in turn to the listener for the selected button. We could have the
mouse clicks dispatch straight through to the docalc instance, but
such an architecture is not clean. It's desirable that my_Button act
like a regular button, handling its selection of itself within itself
and dispatching to a registered listener.
import java.awt.*; import java.awt.event.*; class my_Button extends Component implements MouseListener { private static final Font f = new Font("Monospaced", 12, Font.PLAIN); private static final FontMetrics fm = Toolkit.getDefaultToolkit().getFontMetrics(f); private static final int asc = fm.getLeading() + fm.getMaxAscent(); private static final int h = asc + fm.getMaxDescent(); private static final int PAD = 3; private String str = null; private int w = 0; private ActionListener listen = null; public my_Button(String s, ActionListener al) { str = s; w = fm.stringWidth(s); listen = al; addMouseListener(this); } public void mouseEntered(MouseEvent e) {} public void mouseExited(MouseEvent e) {} public void mousePressed(MouseEvent e) {} public void mouseReleased(MouseEvent e) {} public void mouseClicked(MouseEvent e) { listen.actionPerformed(new ActionEvent(this, 0, str)); } public Dimension getPreferredSize() { return new Dimension(w + PAD * 2, h + PAD * 2); } public Dimension getMinimumSize() { return getPreferredSize(); } public void paint(Graphics g) { Color c = g.getColor(); g.setFont(f); g.setColor(Color.red); g.fillOval(0, 0, w + PAD * 2 - 1, h + PAD * 2 - 1); g.setColor(Color.white); g.drawString(str, PAD, asc + PAD); g.setColor(c); } } class docalc implements ActionListener { private String str_accum = ""; private String str_num = ""; private double accum = 0.0; private double num = 0.0; private String oper = null; private void setText(String s) { calc.tf.setText(s); calc.tf.setCaretPosition(s.length()); } public void actionPerformed(ActionEvent e) { String str = e.getActionCommand(); if (str.equals("Exit")) System.exit(0); if (Character.isDigit(str.charAt(0))) { str_num += str; str_accum += str; setText(str_accum); } else { if (str_num.length() >= 1) { num = Long.parseLong(str_num); str_num = ""; if (oper != null) { switch (oper.charAt(0)) { case '+': accum += num; break; case '-': accum -= num; break; case '*': accum *= num; break; case '/': accum /= num; break; } } else { accum = num; } str_accum += str; setText(str_accum); oper = str; } if (str.equals("=")) { String s = Double.toString(accum); setText(s); str_accum = ""; accum = 0.0; oper = null; } } } } public class calc { public static TextField tf = null; private static my_Button b(String s, docalc dc) { return new my_Button(s, dc); } private static void setup(Frame f, docalc dc) { tf = new TextField(25); tf.setEditable(false); tf.setBackground(Color.blue); tf.setForeground(Color.white); GridBagConstraints gbc = new GridBagConstraints(); f.setLayout(new GridBagLayout()); gbc.insets = new Insets(2, 2, 2, 2); gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 10; gbc.anchor = GridBagConstraints.WEST; f.add(tf, gbc); gbc.gridy = 1; gbc.gridwidth = 1; gbc.anchor = GridBagConstraints.CENTER; gbc.gridx = 0; f.add(b("0", dc), gbc); gbc.gridx = 1; f.add(b("1", dc), gbc); gbc.gridx = 2; f.add(b("2", dc), gbc); gbc.gridx = 3; f.add(b("3", dc), gbc); gbc.gridx = 4; f.add(b("4", dc), gbc); gbc.gridy = 2; gbc.gridx = 0; f.add(b("5", dc), gbc); gbc.gridx = 1; f.add(b("6", dc), gbc); gbc.gridx = 2; f.add(b("7", dc), gbc); gbc.gridx = 3; f.add(b("8", dc), gbc); gbc.gridx = 4; f.add(b("9", dc), gbc); gbc.gridy = 3; gbc.gridx = 0; f.add(b("+", dc), gbc); gbc.gridx = 1; f.add(b("-", dc), gbc); gbc.gridx = 2; f.add(b("*", dc), gbc); gbc.gridx = 3; f.add(b("/", dc), gbc); gbc.gridx = 4; f.add(b("=", dc), gbc); gbc.gridy = 2; gbc.gridx = 6; gbc.weightx = 1; f.add(b("Exit", dc), gbc); } public static void main(String args[]) { Frame f = new Frame("calculator"); setup(f, new docalc()); f.pack(); f.show(); } } |
In previous issues we've seen some examples of the use of scrollbars in
GUI applications. With Java 1.1, there is an additional technique for
performing scrolling within an application. This approach uses the
ScrollPane class in the AWT. ScrollPane does "automatic" scrolling of a
specified AWT component, with some user customization possible.
For example, with this code:
import java.awt.*; class CirclePanel extends Component { public Dimension getPreferredSize() { return new Dimension(150, 150); } public Dimension getMinimumSize() { return getPreferredSize(); } public void paint(Graphics g) { g.fillOval(5, 5, 125, 125); } } public class scroll { public static void main(String args[]) { Frame f = new Frame("testing"); ScrollPane sp = new ScrollPane(); sp.setSize(100, 100); sp.add(new CirclePanel()); f.add(sp); f.pack(); f.setVisible(true); sp.setScrollPosition(65, 65); Adjustable a = sp.getVAdjustable(); a.setUnitIncrement(5); } } |
we create our our own component (CirclePanel) with a filled-in circle
inside of it, of size 150 x 150. We create a 100 x 100 ScrollPane
object and add this component to it. Without any further settings, this
will create a scroll pane with scrollbars, and unit increments for the
horizontal and vertical scrollbars.
In this particular example, we have modified the default settings in two
ways, one to set the current scroll position to (65,65), and the other to
set the vertical scroll increment to 5.
There are several other available features for controlling ScrollPane
objects.
A new feature in JDK 1.1 is popup menus, where a menu is popped up over
a GUI component in response to a user mouse action. A simple example of
this looks like:
import java.awt.*; import java.awt.event.*; class MyPopup extends Component implements ActionListener { PopupMenu pum = null; String labels[] = {"Command 1", "Command 2", "Command 3"}; String cmds[] = {"command1", "command2", "command3"}; public void actionPerformed(ActionEvent e) { String cmd = e.getActionCommand(); System.out.println("command was: " + cmd); } public MyPopup() { pum = new PopupMenu(); for (int i = 0; i < labels.length; i++) { MenuItem mi = new MenuItem(labels[i]); mi.setActionCommand(cmds[i]); mi.addActionListener(this); pum.add(mi); } add(pum); enableEvents(AWTEvent.MOUSE_EVENT_MASK); } public void processMouseEvent(MouseEvent e) { if (e.isPopupTrigger()) pum.show(this, e.getX(), e.getY()); else super.processMouseEvent(e); } public Dimension getPreferredSize() { return new Dimension(300, 300); } } public class popup { public static void main(String args[]) { Frame f = new Frame("testing"); Panel p = new Panel(); p.add("Center", new MyPopup()); f.add(p); f.pack(); f.setVisible(true); } } |
In this example we create a lightweight component of size 300 x 300, and
add a popup menu to it. The menu is created by adding MenuItems to it,
with separation of menu labels and commands (a rudimentary step toward
internationalization).
We also set up to handle mouse events, which is required to catch the
event that triggers the popup. The actual event handling is found in
processMouseEvent(), where popup events are handled directly, and others
are passed on. When actionPerformed() is called, we can retrieve the
action command set earlier.
SystemColor is a new 1.1 class that supports access to system colors,
that is, colors configured by a user for such things as text, window
borders, scrollbars, and so on.
For example, this simple program draws a graphical object that is the
same color as a window title bar:
import java.awt.*; class MyCanvas extends Canvas { public Dimension getPreferredSize() { return new Dimension(200, 200); } public void paint(Graphics g) { g.setColor(SystemColor.activeCaption); g.fillOval(50, 50, 100, 100); } } public class color { public static void main(String args[]) { Frame f = new Frame("testing"); Panel p = new Panel(); p.add(new MyCanvas()); f.add(p); f.pack(); f.setVisible(true); } } |
The actual RGB values of a system color may change over time, because a
user can customize such colors (for example, using Control Panel with
Windows NT). The method getRGB() can be applied to a color to obtain
its current color settings.
We talked earlier in this issue about focus traversal, a new AWT feature
in 1.1. Another new 1.1 feature is custom cursors. With 1.0, one could
only set a customized cursor at the Frame level, but with 1.1, a cursor
may be set for any component. For example, in this code:
import java.awt.*; import java.awt.event.*; public class testcursor implements ActionListener { public void actionPerformed(ActionEvent e) { System.exit(0); } public static void main(String args[]) { Frame f = new Frame("testing"); f.setLayout(new BorderLayout()); Button b = new Button("OK"); b.setCursor(new Cursor(Cursor.CROSSHAIR_CURSOR)); b.addActionListener(new testcursor()); f.add("North", b); f.setSize(200, 200); f.setVisible(true); } } |
we set a crosshair cursor that is effective only within the area of the
Button object that we create.
We've been spending some time discussing some new features of Java 1.1.
Another of these is keyboard focus traversal, which is a fancy way of
saying that you can use Tab to move between AWT components, giving each
the focus in turn.
For example, with this simple program:
import java.awt.*; public class focus { public static void main(String args[]) { Frame f = new Frame("testing"); f.setLayout(new FlowLayout()); Button b1 = new Button("Button #1"); Button b2 = new Button("Button #2"); f.add(b1); f.add(b2); f.pack(); f.show(); } } |
you can move between the two buttons by pressing Tab, if you are using
Java 1.1. Tab is ignored for this same program using 1.0.
Components are traversed in the order added, so button #1 will be
visited before button #2. No other knowledge or setup is required to
use this feature.
public void testChoice() { } |
public void testChoice() { Button bu_ok = new Button( " OK " ); Choice ch_one = new Choice(); Choice ch_two = new Choice(); dlg = new DialogBase( form.frame, "testChoice", true ); dlg.setSize( 400, 300 ); dlg.setLayout( new FlowLayout() ); // add listeners // bu_ok.addActionListener( this ); ch_one.addItemListener( this ); ch_two.addItemListener( this ); ch_one.setName( "ONE" ); // set name of Choice ch_one.add( "1" ); // add items ch_one.add( "2" ); ch_one.add( "3" ); ch_two.setName( "TWO" ); // set name of Choice ch_two.add( "A" ); // add items ch_two.add( "B" ); ch_two.add( "C" ); // add objects to the Dialog dlg.add( ch_one ); dlg.add( ch_two ); dlg.add( bu_ok ); dlg.setVisible( true ); } // manage Choices public void itemStateChanged( ItemEvent e ) { if( e.getSource() instanceof Choice ) { String obj = ((Component)e.getSource()).getName(); String item = (String) e.getItem(); if( obj.equals( "ONE" ) ) { if( item.equals( "1" ) ) System.out.println( "(UNO:1)" ); if( item.equals( "2" ) ) System.out.println( "(UNO:2)" ); if( item.equals( "3" ) ) System.out.println( "(UNO:3)" ); } else if( obj.equals( "TWO" ) ) { if( item.equals( "A" ) ) System.out.println( "(UNO:A)" ); if( item.equals( "B" ) ) System.out.println( "(UNO:B)" ); if( item.equals( "C" ) ) System.out.println( "(UNO:C)" ); } } } |
Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); awindow.move( ( screen.width - awindow.size().width ) / 2, ( screen.height - awindow.size().height ) / 2 ); |
In previous issues we've discussed various aspects of applet
programming. Starting with this issue, we're going to branch out a
bit and consider the more general topic of constructing Graphical User
Interfaces (GUIs) using the AWT (Abstract Windowing Toolkit). The
examples we present will be actual Java standalone programs rather
than applets. That is, they'll have their own main() method.
The AWT can be viewed as a high-level abstract means of describing
what a window or windows should look like. The AWT is actually
implemented differently on different platforms, using windowing
primitives appropriate to each platform. You can view GUI programming
using the AWT as construction of a complex data structure describing a
window, which is interpreted at program execution time.
Let's start out by looking at a simple example. This program takes a
single argument, which is the name of a text file, and displays that
file in a scrollable window. This is kind of like the "more" program
available in UNIX and DOS:
import java.awt.*; import java.io.*; public class More extends Frame { private static Button b1 = new Button( "New File" ); private static Button b2 = new Button( "Exit" ); private static TextArea ta = new TextArea( 24, 80 ); // handle mouse events public boolean action(Event e, Object arg) { if (e.target instanceof Button) { if (e.target == b1) { new_file(); return true; } if (e.target == b2) { System.exit(0); return true; } return false; } else { return false; } } // select a new file to view private void new_file() { FileDialog fd = new FileDialog(this, "Open File", FileDialog.LOAD); fd.show(); if (fd.getDirectory() == null || fd.getDirectory().equals("")) return; if (fd.getFile() == null || fd.getFile().equals("")) return; if (fd.getFile().equals("*.*")) return; String fn = fd.getDirectory() + File.separator + fd.getFile(); load(fn); } // load a file private void load(String fn) { RandomAccessFile raf = null; StringBuffer sb = new StringBuffer(); try { String s = null; raf = new RandomAccessFile(fn, "r"); while ((s = raf.readLine()) != null) { sb.append(s); sb.append('\n'); } raf.close(); } catch (Throwable e) { System.err.println("file I/O error"); System.exit(1); } ta.setText(sb.toString()); } // constructor public More(String title, String fn) { super(title); resize(600, 450); Panel p1 = new Panel(); p1.setLayout(new FlowLayout( FlowLayout.CENTER, 100, 0) ); p1.add(b1); p1.add(b2); Panel p2 = new Panel(); ta.setEditable(false); p2.add(ta); setLayout(new FlowLayout()); add(p1); add(p2); load(fn); show(); } public static void main(String args[]) { Frame f = new More( "More", args[0] ); } } |
We start by constructing a Frame, an AWT type suitable for
representing a top-level application window. We resize the frame, and
then add a couple of panels to it. What are panels? A panel is an
AWT entity useful for holding or representing a logical chunk of a
larger interface. In this example, one of the panels holds a couple
of buttons used for switching files and for exiting, and the other
holds the text area that actually displays the file.
Given two buttons within a panel, or two panels in a frame, how does
the AWT know how to order or arrange these items? We tell the AWT
about this via a layout manager, in this case by establishing a
FlowLayout object and attaching it to the frame. This type of layout
arranges objects in rows left to right, and goes to the next row when
it runs out of space. So the two buttons are arranged left to right
within panel p1. Panel p2 is laid out on the next "row" after p1,
because you can't fit a large text area right next to a set of buttons.
Actual file loading is straightforward. We read the lines in from the
file, append them together, and when done call setText() to set the
text for the TextArea object. A TextArea object already has scroll
bars with it and we don't need to worry about those.
If we want to switch files, we can use a FileDialog entity, which
handles most of the work of file selection. This brings up a menu of
files that looks similar to what you see on a PC running Windows.
Finally, we define an action() method for handling events in the
frame, in this case file switching or exiting.
This program has a couple of deficiencies, most notably that it reads
a whole file in at one time. With fairly slow I/O this can be a
problem for very large files. But the program does serve to
illustrate some of the basics of AWT use. We'll be looking at some of
these areas in more detail in future issues.
In the last issue we started a series on AWT (Abstract Window Toolkit)
programming. We presented an example of writing a "more" program, one
that can be used to page through text from a file. In this issue
we'll show another more complicated and powerful way of achieving the
same end.
Before diving into this, a few general comments are in order. This
code is written against JDK 1.1. It gives some "deprecation"
warnings, about methods which have changed in the JDK. As such, the
code needs to be updated, but doing so would make it not work with
earlier versions. This is simply something that we have to put up
with in an evolving language.
Also, this code reveals a bug in the JDK 1.1 implementation for
Windows NT. If you compile this program and run it, you will notice
that scroll bars are flaky. This is a known bug that is supposed to
be fixed in a bug fix release of 1.1.
Finally, we are getting into an area that is perhaps not as stable as
some other parts of Java, and still evolving (as is my understanding
of it). Some aspects of the example below, notably layout manager
usage, are a bit tricky.
As you will recall, the previous version of the "more" program had one
big deficiency, namely, that it read in a whole file before displaying
it. This is not acceptable for a very large file, especially with
relatively slow I/O. We've fixed that problem, and to do so, set up
our own text windowing scheme with scroll bars which we manage.
Here is the actual code. If you scan down the left side, you can see
interspersed comments starting with "//" in the left margin.
import java.awt.*; import java.io.*; // a text area in the window // This is the class used to manage a text area, with scroll bars. // It is passed a RandomAccessFile object to read lines from, and // reads them on demand only. // A Canvas is an AWT object suitable for displaying text on. class Text extends Canvas { private static final int LISTSIZ = 16; private String list[] = null; private int scnt = 0; private int currpos = 0; private RandomAccessFile raf = null; private boolean ateof = false; // initialize void init(RandomAccessFile r) { if (raf != null) { try { raf.close(); } catch (Throwable e) { System.err.println("close err"); System.exit(1); } } raf = r; ateof = false; currpos = 0; scnt = 0; list = new String[LISTSIZ]; } // We need to detab Strings that are displayed, because tabs // are handled in a funny way by Graphics.drawString(). private static String detab(String s) { StringBuffer sb = new StringBuffer(); int len = s.length(); int pos = 0; for (int i = 0; i < len; i++) { char c = s.charAt(i); if (c == '\r' || c == '\n') { } else if (c == '\t') { do { sb.append(' '); pos++; } while (pos % 8 != 0); } else { sb.append(c); pos++; } } return sb.toString(); } // This is the internal list used to store lines to be displayed. // We could also use java.util.Vector to manage this list, and // System.arraycopy() to copy the list. // add a String to the list void add(String s) { if (scnt == list.length) { String x[] = new String[list.length * 3 / 2]; for (int i = 0; i < scnt; i++) x[i] = list[i]; list = x; } list[scnt++] = detab(s); } // Called by the handleEvent() method below. // scroll up or down a line void scroll(int dir) { currpos += dir; if (currpos < 0) currpos = 0; repaint(); } // This is the paint() method, used to draw lines in the text window. // We use FontMetrics to determine how tall a character is, and // display as many lines as will fit. public void paint(Graphics g) { Dimension d = size(); int startpos = More.HEIGHT - d.height; int pos = startpos; int sp = g.getFontMetrics().getHeight(); int i = currpos; while (pos + sp < d.height) { while (i >= scnt && !ateof) { String s = null; try { s = raf.readLine(); } catch (Throwable e) { System.err.println("I/O err"); System.exit(1); } if (s == null) ateof = true; else add(s); } if (i >= scnt) break; g.drawString(list[i], 5, pos); i++; pos += sp; } } } // We use a Panel to contain the text area, along with scroll bars. // A Panel is an AWT object that can contain other objects. // a Panel for the text area, with scroll bars class Panel_ta extends Panel { private Scrollbar vbar = null; private Text t = null; // constructor Panel_ta(Text ta) { vbar = new Scrollbar(Scrollbar.VERTICAL); // Put the text area in the center, and the scroll bar on the "East" side. setLayout(new BorderLayout(0, 0)); add("Center", ta); add("East", vbar); t = ta; } // handleEvent() is called for actual scrolling. public boolean handleEvent(Event e) { if (e.target == vbar) { switch (e.id) { case Event.SCROLL_LINE_UP: t.scroll(-1); break; case Event.SCROLL_LINE_DOWN: t.scroll(1); break; } return true; } else { return super.handleEvent(e); } } } // The actual "More" class. public class More extends Frame { private Button b1 = new Button("New File"); private Button b2 = new Button("Exit"); private Text ta = new Text(); static final int WIDTH = 600; static final int HEIGHT = 450; // A dialog invoked when the user selects "New File". // open a new file private void new_file() { FileDialog fd = new FileDialog(this, "Open File", FileDialog.LOAD); fd.show(); if (fd.getDirectory() == null || fd.getDirectory().equals("")) return; if (fd.getFile() == null || fd.getFile().equals("")) return; if (fd.getFile().equals("*.*")) return; String fn = fd.getDirectory() + File.separator + fd.getFile(); load(fn); } // Called when buttons are selected. // handle an action public boolean action(Event e, Object arg) { if (e.target instanceof Button) { if (e.target == b1) { new_file(); return true; } if (e.target == b2) { dispose(); System.exit(0); return true; } return false; } else { return false; } } // Load a new file in. private void load(String fn) { try { RandomAccessFile raf = new RandomAccessFile(fn, "r"); ta.init(raf); } catch (Throwable e) { System.err.println("open err"); System.exit(1); } ta.repaint(); } // Constructor for More. public More(String title, String fn) { super(title); resize(WIDTH, HEIGHT); // Set a layout manager for the buttons, flowing across the screen. Panel p1 = new Panel(); p1.setLayout(new FlowLayout(FlowLayout.CENTER,50,0)); p1.add(b1); p1.add(b2); // Set a fixed-width font, size 12. ta.setFont(new Font("Courier", Font.PLAIN, 12)); Panel p2 = new Panel_ta(ta); // Set a layout manager for the buttons at the top, and the text area // at the bottom. GridBagLayout is the most complicated and powerful // of the layout managers. You can specify cell positions, cell // weights, and so on. Each object to be laid out has its constraints // set for it. // In this case, we give the top row of buttons a weight of 1, and the // text area a weight of 7, so the text area will take 7/8 of the // total window. GridBagLayout gbl = new GridBagLayout(); GridBagConstraints gbc = new GridBagConstraints(); setLayout(gbl); gbc.gridx = 0; gbc.gridy = 0; gbc.weightx = 1; gbc.weighty = 1; gbc.fill = GridBagConstraints.BOTH; gbc.anchor = GridBagConstraints.CENTER; gbl.setConstraints(p1, gbc); gbc.gridy = 1; gbc.weighty = 7; gbl.setConstraints(p2, gbc); add(p1); add(p2); load(fn); show(); } // Driver main() method. public static void main(String args[]) { Frame f = new More("More", args[0]); } } |
This example is fairly complicated, but illustrates several important
points about the AWT. There are other ways to approach this problem.
Another feature we could add would be one to search through the file
for a specified string, and display the line it's found on.