Given by Tom Scavo at NPAC/ECS Java Academy on February to April 98. Foils prepared 13 July 98
Outside Index
Summary of Material
Java Fonts |
Working With Color |
Object-Oriented Geometry |
Graphical User Interfaces |
Note: This tutorial assumes you've completed Part I |
Outside Index Summary of Material
Tom Scavo |
<trscavo@npac.syr.edu> |
NPAC @ SU |
111 College Place |
Syracuse, NY 13244-4100 |
Syracuse University |
College of Engineering and Computer Science |
Northeast Parallel Architectures Center |
present |
Java Fonts |
Working With Color |
Object-Oriented Geometry |
Graphical User Interfaces |
Note: This tutorial assumes you've completed Part I |
A Java Font object has three properties: font name, font style, and font size |
Instantiate a Font object like this: Font f = new Font( "Serif", // font name Font.PLAIN, // font style 12 // font size ); |
Java guarantees the following five fonts on every platform: Dialog, SansSerif, Serif, Monospaced, and DialogInput |
Other fonts may be available on a client-by-client basis |
To get the name of the current font: Font f = g.getFont(); String name = f.getName(); |
Three font styles are supported: plain, italic, and bold |
The Font class defines three static integer constants: Font.PLAIN, Font.ITALIC, and Font.BOLD |
Note that font styles are additive (but only bold-italic makes sense): Font.BOLD + Font.ITALIC |
Font size is measured in points (72 points per inch) |
Common sizes are 10–12 points for body text and 18–36 points for headings |
The method getSize() gets the size of the current font: Font f = g.getFont(); int size = f.getSize(); |
Every font has associated with it a set of "metrics" that define the precise dimensions of the font |
Some of the metric properties of a font are ascent, descent, leading, and height, where height = ascent + descent + leading |
All font metric properties are captured in a FontMetrics object |
Given a Graphics object g, the metric properties of the current font are obtained as follows: FontMetrics fm = g.getFontMetrics(); int ascent = fm.getAscent(); int descent = fm.getDescent(); int leading = fm.getLeading(); int height = fm.getHeight(); |
The drawString(...) method of the Graphics class draws a string at (x,y): public void drawString( String s, int x, int y ); |
The anchor point (x,y) is the left-hand endpoint of the string's baseline |
Note: the top left-hand corner of the string's bounding box is at point (x, y - ascent) |
A previous example used drawString(...) to draw three strings in the applet window: g.drawString( startMsg, 25, 25 ); g.drawString( paintMsg, 25, 50 ); g.drawString( stopMsg, 25, 75 ); |
Each string is drawn at the same horizontal position, but the vertical position is arbitrary |
We want to do better than that... |
To determine the width of the string s in the current font: FontMetrics fm = g.getFontMetrics(); int stringWidth = fm.stringWidth( s ); where g is a Graphics object |
To determine the string height, simply add the ascent and descent of the current font |
Here's how to center a string s horizontally in the applet window: int appletWidth = getSize().width; FontMetrics fm = g.getFontMetrics(); int stringWidth = fm.stringWidth( s ); int x = (appletWidth - stringWidth)/2; g.drawString( s, x, y ); where y is arbitrary (to center the string vertically, we have to work a bit harder... ) |
Vertical centering is independent of s: int appletHeight = getSize().height; int ascent = fm.getAscent(); int descent = fm.getDescent(); int stringHeight = ascent + descent; int y = ascent + (appletHeight - stringHeight )/2; g.drawString( s, x, y ); where x is computed as in the previous foil |
Note: We add ascent to y since the latter is with respect to the string's baseline |
First, calculate the height of both strings: int leading = fm.getLeading(); int height = 2*stringHeight + leading; |
Now calculate the position of the first string: int y = (appletHeight - height )/2 + ascent; where x is computed as before |
The y-position of the second string is simply: y += stringHeight + leading; |
This is a straightforward generalization... |
First, calculate the height of n strings : int height = n*stringHeight + (n - 1)*leading; |
The y-position of the first string is once again: int y = (appletHeight - height )/2 + ascent; while the position of each subsequent string is: y += stringHeight + leading; |
In Java, a Color object is represented in RGB format (R = Red, G = Green, B = Blue) |
Each color component (R, G, or B) is an integer between 0 and 255, inclusive |
For example: Color red = new Color( 255, 0, 0 ); Color black = new Color( 0, 0, 0 ); Color white = new Color( 255, 255, 255 ); |
Recall that HTML colors are given as hexadecimal RGB constants: <BODY BGCOLOR="#000000" TEXT="#FF0000" LINK="#FFFFFF"> |
Java color values may also be given in hex: Color red = new Color( 0xFF0000 ); Color black = new Color( 0x000000 ); Color white = new Color( 0xFFFFFF ); |
This constructor is very useful... |
In the HTML document: <APPLET ...> <PARAM NAME="bgColor" VALUE="00ffff"> </APPLET> |
In the Java applet: String colorStr; int colorInt; colorStr = getParameter( "bgColor" ); colorInt = Integer.parseInt( colorStr, 16 ); setBackground( new Color( colorInt ) ); |
For convenience, the Color class pre-defines thirteen static Color constants |
Use a constant anywhere a Color is required: setBackground( Color.red ); setForeground( Color.black ); ... g.setColor( Color.white ); |
(Why did the Java designers neglect to capitalize the Color constants?) |
Suppose you are given an unknown color c |
To check if c is red, use: if ( c.equals( Color.red ) ) ... |
Do not use if ( c == Color.red ) ... since this comparison will always be false! |
The reason is subtle: c and Color.red are distinct objects (although their values are equal) |
Colors can be stored in an array: Color color[] = { Color.black, Color.blue, Color.cyan, Color.darkGray }; |
This array might be used in a loop: int n = color.length; for ( int i = 0; i < n; i++ ) { g.setColor( color[i] ); ... } |
Sometimes a 2-d array is useful: Color color[][] = { { Color.black, Color.blue, ... }, { Color.gray, Color.green, ... }, { Color.orange, Color.pink, ... }, ... }; |
The following, for example, is true: color[2,1].equals( Color.gray ) |
A 2-d array might be used like this: int m = color.length; for ( int i = 0; i < m; i++ ) { n = color[i].length; for ( int j = 0; j < n; j++ ) { g.setColor( color[i][j] ); ... } } |
To associate colors with their names: Hashtable colors = new Hashtable(); colors.put( "black", Color.black ); colors.put( "brown", new Color( 0xA5, 0x2A, 0x2A ) ); |
Access the hashtable as follows: c = ( Color ) colors.get( "brown" ); |
Note: The Color cast above is required since a hashtable holds objects of any type |
The SystemColor class is a subclass of the Color class |
SystemColor has many static constants: SystemColor.desktop SystemColor.menu ... |
These are the colors the system uses to build its graphical user interface (GUI) |
The previous examples involving arrays, loops, and hashtables really don't have anything to do with Color objects |
This unit was simply an opportune time to introduce these important concepts |
Note: In Java, you can construct arrays of any object (e.g., String) or primitive type (such as int) |
This section is a rewrite of a previous section in object-oriented terms |
At the end of this unit, you will understand the concepts of class and subclass, cornerstones of the object-oriented programming paradigm |
Along the way, you will learn a good deal of geometry as well |
All of the Java source files we've seen so far contain one and only one class, a subclass of the Applet class |
In fact, a source file may contain any number of classes (but only one may be public) |
The Java compiler (javac) will generate one .class file for each class in the source file |
This source file defines two classes: public class TriangleTest extends Applet { ... } class Triangle extends Polygon { ... } |
The compiler will generate two .class files |
The Triangle class class Triangle extends Polygon { ... } is a subclass of the Polygon class |
An instance of Triangle is created with new: Triangle triangle = new Triangle(...); |
The Triangle constructor on the right is defined in the Triangle class above |
A class may have multiple constructors: class Triangle extends Polygon { public Triangle() {...} public Triangle(...) {...} ... } |
Each constructor must have a unique signature, that is, a unique ordered set of arguments |
The simple constructor public Triangle() { super(); } has no arguments and calls the corresponding no-argument constructor of the superclass Polygon |
As a result, the expression new Triangle() has the same effect as new Polygon() |
Here is a more complex constructor: public Triangle( int x1, int y1, int x2, int y2, int x3, int y3 ) { super(); this.addPoint( x1, y1 ); this.addPoint( x2, y2 ); this.addPoint( x3, y3 ); } |
This constructor takes six integer arguments |
The previous constructor first calls the no-argument constructor of the superclass Polygon |
It then "constructs" a Triangle object by adding three points to the this reference |
The keyword this (which may be omitted) refers to the particular Triangle object being created |
Our Triangle class also defines a pair of instance methods: class Triangle extends Polygon { ... public void draw(...) {...} public void fill(...) {...} } |
Evidently, these methods are used to draw and fill Triangle objects |
The two methods rely on the corresponding methods of the Graphics class: public void draw( Graphics g ) { g.drawPolygon( this ); } public void fill( Graphics g ) { g.fillPolygon( this ); } |
Note that each method takes an argument g |
In summary, the Triangle class has two constructors and two instance methods: class Triangle extends Polygon { public Triangle() {...} public Triangle(...) {...} public void draw(...) {...} public void fill(...) {...} } |
Now let's see how we can use this class... |
The TriangleTest applet declares two Triangle objects: public class TriangleTest extends Applet { private Triangle t1, t2; public void init(...) {...} public void paint(...) {...} } |
It also overrides two Applet methods... |
The applet instantiates two Triangle objects: public void init() { t1 = new Triangle(...); t2 = new Triangle(...); } |
It then draws one triangle and fills the other: public void paint( Graphics g ) { t1.draw( g ); t2.fill( g ); } |
The previous example had two classes in a single file (but only one was public) |
The next example also has two classes, but in separate files (both may be public) |
The advantage of this approach is that other Java programs may use these classes (since they're defined in separate files |
In this way, a class becomes reusable |
File QuadrilateralTest.java: public class QuadrilateralTest extends Applet {...} |
File Quadrilateral.java: public class Quadrilateral extends Polygon {...} |
The latter may be used by other programs |
The Quadrilateral class is analogous to the Triangle class: class Quadrilateral extends Polygon { public Quadrilateral() {...} public Quadrilateral(...) {...} public void draw(...) {...} public void fill(...) {...} } |
Note: The above class is not public (but it could be!) |
The only difference is that one of the constructors takes eight arguments: public Quadrilateral( int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4 ) { super(); this.addPoint( x1, y1 ); this.addPoint( x2, y2 ); this.addPoint( x3, y3 ); this.addPoint( x4, y4 ); } |
The test applet for the Quadrilateral class is essentially the same as the test applet for the Triangle class |
It first instantiates two Quadrilateral objects in the init() method |
In the paint(...) method, the applet then draws one Quadrilateral and fills the other |
The next example involves octagons |
Since an octagon has eight vertices, one of the constructors of the Octagon class requires sixteen integers |
To make it easier to work with octagons, we introduce another class: the Point class |
Like Polygon, the Point class is a core class in package java.awt |
One of the constructors of the Point class takes two integer arguments: Point p = new Point( 69, 25 ); |
Eight such points define an octagon: Point p1 = new Point(...); Point p2 = new Point(...); ... Point p8 = new Point(...); |
Our new Octagon class is similar to the previous classes, except that a third constructor is defined: class Octagon extends Polygon { public Octagon() {...} public Octagon(...) {...} public Octagon(...) {...} ... } |
The new Octagon constructor takes eight Point objects: public Octagon( Point p1, Point p2, Point p3, Point p4, Point p5, Point p6, Point p7, Point p8 ) { ... } |
The body of this constructor appears later... |
The addPoint(...) method that we've been using (which takes two arguments) was inherited from the superclass Polygon |
Unfortunately, the Polygon class does not define an addPoint(...) method that takes a single Point as argument |
So we will overload the addPoint(...) method in the Octagon class |
This method appears in the Octagon class: // Overload Polygon.addPoint( int, int ): public void addPoint( Point p ) { super.addPoint( p.x, p.y ); } |
This new version of the addPoint(...) method relies on the old version (which virtually guarantees its correctness) |
So here is the complete constructor: public Octagon( Point p1, Point p2, Point p3, Point p4, Point p5, Point p6, Point p7, Point p8 ) { super(); this.addPoint( p1 ); ... this.addPoint( p8 ); } |
A better way is to let the new constructor depend on a previous constructor: public Octagon( Point p1, Point p2, Point p3, Point p4, Point p5, Point p6, Point p7, Point p8 ) { this( p1.x, p1.y, ... p8.x, p8.y ); } |
In this way, there is less chance of bugs |
The new version of addPoint(...) defined in the Octagon class belongs in all subclasses of Polygon |
It would be unwise to do so, however (why?) |
Thus, in the next example (Hexagon), we define a subclass of Polygon called MyPolygon, which itself is subclassed by the Hexagon class |
The Hexagon class is a subclass of MyPolygon, which itself is a subclass of Polygon: class Hexagon extends MyPolygon { ... } class MyPolygon extends Polygon { ... } |
The MyPolygon class contains those methods common to all polygons: class MyPolygon extends Polygon { public void draw(...) {...} public void fill(...) {...} public void addPoint(...) {...} } |
Any subclass of MyPolygon inherits all of these methods |
We will learn how to build graphical user interfaces (GUIs) in Java |
GUI building blocks are called components, which are part of Java's abstract windowing toolkit (AWT) |
AWT components include: labels, buttons, text fields, text areas, checkboxes, radio buttons, drop-down lists (or "choices"), scrollbars, and scrollable lists |
The simplest component is a Label object |
Adding a label to a GUI is like drawing a string--the only difference is that labels generate events (which we'll have more to say about later) |
To add a label to an applet, type: add( new Label( "This is a label!" ) ); |
It's that easy! |
Adding a button to an applet is just as easy: add( new Button( "Red" ) ); |
This statement adds a button with label "Red" |
The problem is that nothing happens when you press the button! First you must write a "handler" that handles button events |
A button event is a type of event called an ActionEvent |
ActionEvent is a class in the core package java.awt.event |
The most important method in this class is: public String getActionCommand(); |
We will use this method to process an ActionEvent generated by a button, but first we need to understand what a Java interface is... |
An interface is a special type of class in which all the methods have null bodies |
A class is said to implement an interface if it agrees to provide bodies for all the methods of the interface |
Any class may implement a given interface; all such classes have identical APIs, since the interface defines the API of each method |
ActionListener is an interface in the core package java.awt.event |
ActionListener declares one method: public interface ActionListener { public void actionPerformed( ActionEvent e ); } |
Any class that implements this interface must implement the actionPerformed(...) method |
Pressing a button generates an ActionEvent |
An ActionEvent is "handled" by the actionPerformed(...) method of the ActionListener interface |
A Java event is like the proverbial tree in the woods: nobody will hear it unless someone is listening! So our button applet must "register" itself as a listener of button events... |
Any class that intends to "listen" for an ActionEvent must implement the ActionListener interface |
To register the applet as a listener of button events, type the following: Button button = new Button(...); button.addActionListener( this ); |
The keyword this refers to the current applet |
Here is an outline of our button applet: public class ButtonTest extends Applet implements ActionListener { private Button button; public void init() { button = new Button(...); button.addActionListener( this ); ... } public void actionPerformed(...) {...} } |
Our test applet has three buttons |
The actionPerformed(...) method determines which button was pressed : public void actionPerformed(ActionEvent e) { String label = e.getActionCommand(); if ( label.equals( "Red" ) {...} else if ( label.equals( "White" ) {...} else if ( label.equals( "Blue" ) {...} ... } |
In summary, our button test applet:
|
This is called the delegation event model |
A TextField is a box in which the user enters a single line of text: TextField tf = new TextField(20); |
The integer argument to the TextField constructor above is the width of the field (in characters) |
A TextField object generates an ActionEvent when the user presses return |
Since TextField objects generate ActionEvents, what we said earlier of Button objects still applies |
The next example, however, uses a separate class to handle the events generated by a pair of TextField objects |
In this way, the handler may be reused |
As before, our handler must implement the ActionListener interface: class TextFieldHandler implements ActionListener { public TextFieldHandler(...) {...} public void actionPerformed(...) { ... } } |
In the actionPerformed(...) method, the handler extracts the text from the TextField and displays it on the status line of the applet: public void actionPerformed( ActionEvent e ) { String text = e.getActionCommand(); if ( ! text.equals( "" ) ) { applet.showStatus( "Text: " + text ); } } |
Notice the reference to the class variable applet in actionPerformed(...) |
The TextFieldHandler constructor accepts and stores an Applet object: private Applet applet; public TextFieldHandler( Applet a ) { applet = a; } |
Thus the handler works with any applet |
The test applet instantiates two text fields that share a single handler: handler = new TextFieldHandler( this ); username = new TextField( 20 ); username.addActionListener( handler ); password = new TextField( 20 ); password.addActionListener( handler ); |
Alternatively, each text field could have its own handler |
The overall structure of the TextFieldTest applet is as follows: public class TextFieldTest extends Applet { public void init() {...} } class TextFieldHandler implements ActionListener { public TextFieldHandler(...) {...} public void actionPerformed(...) {...} } |
A TextArea is a multi-line TextField (conversely, a TextField is a one-line TextArea) |
Consecutive lines of a TextArea are separated by newlines |
Unlike TextFields, however, TextAreas do not generate ActionEvents (since pressing return inserts a newline into the text) |
The constructor new TextArea( 7, 60 ) creates an empty TextArea with 7 rows and 60 columns, whereas the constructor new TextArea( text, 7, 60 ) creates a TextArea containing the value of the String variable text |
Various scrollbar options are available... |
A TextArea may have zero, one (horizontal or vertical), or two scrollbars |
The TextArea class defines four static constants for this purpose:
|
For example, the constructor int scrollbars = TextArea.SCROLLBARS_BOTH; TextArea ta = new TextArea( text, // initial text string 7, 60, // textarea dimensions scrollbars ); creates a TextArea with two scrollbars |
TextArea objects do generate events, but we delay this topic until later |
Applet TextAreaTest simply inserts a bunch of text into a TextArea |
Note that our test applet creates a non-editable TextArea (for reading purposes only): TextArea verse = new TextArea(...); verse.setEditable( false ); |
The next batch of components generates a different type of event called an ItemEvent (a class in package java.awt.event) |
The corresponding interface is called ItemListener, which has a single method called itemStateChanged(...) |
To listen for ItemEvents, a listener must invoke the addItemListener(...) method |
Classes:
|
Methods:
|
Classes:
|
Methods:
|
There's a one-to-one relationship between ActionEvents and ItemEvents |
A Checkbox component is either checked or unchecked |
Clicking a Checkbox toggles its state from checked to unchecked (or vice versa) |
In a graphical user interface, a Checkbox is used to make a yes/no decision |
The states of multiple checkboxes are independent of one another |
Checkbox objects are instantiated with the Checkbox constructor and added to the applet with the add(...) method: add( new Checkbox( "Bold" ) ); |
Initially, checkboxes are unchecked, but this may be overridden: Checkbox checkbox = new Checkbox( "Italic" ); add( checkbox, true ); |
Clicking a Checkbox generates an ItemEvent |
A class intending to handle ItemEvents must implement the ItemListener interface |
The ItemListener interface defines one method: public interface ItemListener { public void itemStateChanged(...); } |
Our checkbox applet has five checkboxes: Checkbox[] box = new Checkbox[5]; box[0] = new Checkbox( "Shoes" ); box[1] = new Checkbox( "Socks" ); box[2] = new Checkbox( "Pants" ); box[3] = new Checkbox( "Shirt" ); box[4] = new Checkbox( "Underwear", true ); |
Note that box[4] is checked by default |
A for-loop processes each checkbox: int n = box.length; for ( int i = 0; i < n; i++ ) { box[i].setForeground(...); box[i].setBackground(...); add( box[i] ); box[i].addItemListener( this ); } |
The keyword this refers to the current applet |
In the example applet CheckboxTest, the itemStateChanged(...) method builds a string depending on the state of each checkbox |
It uses methods getState() and getLabel() of the Checkbox class, and methods equals(...), lastIndexOf(...), substring(...), and toLowerCase() of the String class |
A CheckboxGroup is a group of related checkboxes (sometimes called radio buttons) |
Checking a box in a group causes all other boxes in the group to become unchecked |
Consequently, at most one box from a CheckboxGroup may be checked at a time |
In a GUI, a CheckboxGroup is used to select from among n alternatives (n > 2) |
To create a CheckboxGroup, one uses yet another form of the Checkbox constructor: int n = 6; Checkbox[] radio = new Checkbox[n]; CheckboxGroup group = new CheckboxGroup(); for ( int i = 0; i < n; i++ ) { radio[i] = new Checkbox( label[i], group, false ); ... } |
Since a CheckboxGroup is a set of checkboxes, everything we said before still applies:
|
Note: The getSelectedCheckbox() method of the CheckboxGroup class returns the currently selected checkbox |
An AWT Choice object is sometimes called a drop-down list |
In a GUI, a Choice is logically equivalent to a CheckboxGroup, but the latter usually requires more space in the applet window |
A Choice item may be selected programatically with the select(...) method of the Choice class |
Items are added individually to a Choice: Choice list = new Choice(); list.addItem( "HTML" ); list.addItem( "JavaScript" ); list.addItem( "CGI" ); list.addItem( "Java" ); list.addItem( "VRML" ); list.select(2); // select "CGI" |
As usual, items are indexed starting with 0 |
Like checkboxes and radio buttons, Choice objects generate ItemEvents |
These are handled in the usual way:
|
Certain AWT components (such as TextAreas) have optional scrollbars |
Scrollbars may exist independently of other components, however |
In a GUI, scrollbars are often used to select from a continuous range of values |
Scrollbars are created with the Scrollbar constructor of the Scrollbar class |
In its most general form, the Scrollbar constructor takes five integer arguments: public Scrollbar( int orientation, int value, int visible, int minimum, int maximum ); |
The properties of a scrollbar are as follows:
|
These properties may be changed at any time |
Scrollbars generate a different kind of event called an AdjustmentEvent |
AdjustmentEvents are handled by an AdjustmentListener, an interface in the core Java package java.awt.event |
The AdjustmentListener interface declares just one method called adjustmentValueChanged(...) |
A class that implements AdjustmentListener interface must implement the adjustmentValueChanged(...) method |
An AdjustmentListener is added to the list of listeners with the method addAdjustmentListener(...) |
So AdjustmentEvents are analogous to ActionEvents and ItemEvents |
The last AWT component we'll look at in this section is the List class |
List objects are sometimes called scrollable lists (since they often have scrollbars) |
Clicking an item in a List selects that item; multiple selections are possible (but disallowed by default) |
The most general form of the List constructor takes two arguments: public List( int rows, boolean multiple ); |
rows is the number of visible rows in the list while multiple determines whether or not multiple selections are allowed |
The List object is unique in that it can generate two kinds of events |
An ItemEvent is generated if the user clicks an item; an ActionEvent is generated if the user double-clicks an item |
A List handler must therefore implement two interfaces: ItemListener and ActionListener |