|
|||||
At
compile-time, this is
enforcedonly by your
ownself-imposed
rules,but
at
run-time the cast
ensuresit.
Nowpolymorphism
takes over andthe
exact method that'scalled
for the
Shapeis
determined by whether
thereference is for a
Circle,
Square,
or
Triangle.
And in general, this is how
it should be; you
wantthe bulk of
yourcode
to know as little as possible
about specifictypes
of objects, and
to
just deal with
thegeneral representation of a
family of objects (in
this
case,Shape).
As a result, your code will
be easier to write,
read,and
maintain,and
your designs will be easier
to implement,
understand,and
change.
So polymorphism is the
generalgoal in
object-oriented
programming.
Butwhat
if you have a
specialprogramming problem
that'seasiest to
solve
if you know the
exacttype of a generic
reference?For
example,
supposeyou
want to allow yourusers to
highlight all theshapes of
any
particulartype
by turning them purple.This
way, they canfind
all the
triangles
on the screen by
highlightingthem. This is
whatRTTI
accomplishes:you
can ask a Shapereferencethe
exact type
thatit's
referringto.
The
Class
object
To
understand how RTTI works in
Java, you must
firstknow how
type
information
is represented at run-time. This is
accomplished through a
specialkind
of object called
theClassobject,
whichcontains
information
aboutthe
class. (This is sometimes
called a meta-class.)
In fact, the Class
object
is used to create all of
the"regular" objects of
yourclass.
There's
a Classobjectfor
each class that is part of
your program.
Thatis,
eachtime
you write andcompile a
new class, a single
Classobject
is also
created(and
stored, appropriatelyenough, in an
identically named.class
file).
At run-time, when you want
to make an object of
thatclass, the
Java
VirtualMachine
(JVM) that'sexecuting your
program firstchecks to see
if
theClassobjectfor
that type is loaded. If not,
the JVM loads it by
finding
the.classfilewith
that name. Thus, a Java
program
isn'tcompletely
loadedbefore
it begins, which is different
from manytraditional
languages.
662
Thinking
in Java
Oncethe
Classobjectfor
that type is in memory, it is
used to createall
objects
of that type.
If
this seems shadowy or if
youdon't really believe
it,here's a
demonstrationprogram
to prove it:
//:
c12:SweetShop.java
//
Examination of the way the class loader works.
class
Candy {
static
{
System.out.println("Loading
Candy");
}
}
class
Gum {
static
{
System.out.println("Loading
Gum");
}
}
class
Cookie {
static
{
System.out.println("Loading
Cookie");
}
}
public
class SweetShop {
public
static void main(String[] args) {
System.out.println("inside
main");
new
Candy();
System.out.println("After
creating Candy");
try
{
Class.forName("Gum");
}
catch(ClassNotFoundException e) {
e.printStackTrace(System.err);
}
System.out.println(
"After
Class.forName(\"Gum\")");
new
Cookie();
System.out.println("After
creating Cookie");
Chapter12:
Run time
TypeIdentification
663
}
}
///:~
Each
of the classes Candy,
Gum,
and Cookiehave
a staticclausethat
is
executed
as the class is loaded
forthe first time.
Informationwill be
printed
to tell you when
loadingoccurs for that
class. In main(
),
the
objectcreations
are spread outbetween
print statements to help
detect
thetime
of loading.
A
particularly interesting
lineis:
Class.forName("Gum");
Thismethod
is a staticmember
of Class(towhich
all Classobjects
belong).
A Classobject
is like any other
objectand so you can
getand
manipulate
a reference to it.
(That'swhat the loader
does.)One of the
ways
to get a reference to
theClassobject
is forName(
),
which takes a
Stringcontainingthe
textual name (watchthe
spelling and
capitalization!)
of the particular class
youwant a reference for. It
returns
a
Classreference.
Theoutput
of this program forone
JVM is:
inside
main
Loading
Candy
After
creating Candy
Loading
Gum
After
Class.forName("Gum")
Loading
Cookie
After
creating Cookie
Youcan
see that eachClassobject
is loaded only when
it'sneeded, and
thestaticinitialization
is performed upon
classloading.
Classliterals
Javaprovides
a second way to produce the
reference to theClassobject,
using
a classliteral.
In theabove program this
wouldlook like:
Gum.class;
which
is not only simpler,
butalso safer since
it'schecked at
compile-
time.Because
it eliminates themethod
call, it's alsomore
efficient.
664
Thinking
in Java
Classliterals
work with regularclasses as
well as interfaces,arrays,
and
primitivetypes.
In addition, there's a standard
field calledTYPE
that
existsfor
each of the primitivewrapper
classes. TheTYPE
fieldproduces
a
reference to the Classobjectfor
the associated
primitivetype, such
that:
...
is equivalent to ...
boolean.class
Boolean.TYPE
char.class
Character.TYPE
byte.class
Byte.TYPE
short.class
Short.TYPE
int.class
Integer.TYPE
long.class
Long.TYPE
float.class
Float.TYPE
double.class
Double.TYPE
void.class
Void.TYPE
My
preference is to use the
".class"
versions if you can,
sincethey're more
consistentwith
regular classes.
Checkingbefore
a cast
So
far, you've seen
RTTIforms including:
1.
Theclassic
cast; e.g., "(Shape),"
which uses RTTI to
makesure the
cast
is correct and throws a
ClassCastExceptionif
you've
performed
a bad cast.
2.
TheClassobjectrepresenting
the type of your object.
The Class
objectcan
be queried for
usefulrun-time
information.
In
C++, the classic cast
"(Shape)"
does not
performRTTI.
It simply tells
thecompiler
to treat the object as the
new type. In Java,which
does
performthe
type check, thiscast is
often called a "typesafe
downcast."
Chapter12:
Run time
TypeIdentification
665
Thereason
for the term"downcast" is
the historicalarrangement of
the
classhierarchy
diagram. If casting a Circleto
a Shapeis
an upcast, then
casting
a Shapeto
a Circleis
a downcast. However,
youknow a Circle
is
also a Shape,
and the compiler
freelyallows an upcast
assignment,but
youdon'tknowthat
a Shapeis
necessarily a Circle,
so the compiler
doesn'tallow
you to perform a downcast
assignment withoutusing
an
explicitcast.
There's
a third form of RTTI in
Java. This is the
keywordinstanceofthat
tellsyou
if an object is an instance of a
particular type. It returns
a
booleanso
you use it in the form of a
question, like this:
if(x
instanceof Dog)
((Dog)x).bark();
Theabove
if statementchecks
to see if the
objectx
belongs
to the class
Dog
beforecastingx to
a Dog.
It's important to
useinstanceofbefore
a
downcastwhen
you don't haveother
information that tellsyou
the type of
theobject;
otherwise you'll end up
with a ClassCastException.
Ordinarily,you
might be hunting forone
type (triangles to
turnpurple,
forexample),
but you caneasily
tally all
of
the objects using instanceof.
Supposeyou
have a family of Petclasses:
//:
c12:Pets.java
class
Pet {}
class
Dog extends Pet {}
class
Pug extends Dog {}
class
Cat extends Pet {}
class
Rodent extends Pet {}
class
Gerbil extends Rodent {}
class
Hamster extends Rodent {}
class
Counter { int i; } ///:~
TheCounterclass
is used to keep track of
thenumber of
anyparticular
type
of Pet.
You could think of it as an
Integerthatcan
be modified.
Usinginstanceof,
all the pets can be
counted:
//:
c12:PetCount.java
//
Using instanceof.
666
Thinking
in Java
import
java.util.*;
public
class PetCount {
static
String[] typenames = {
"Pet",
"Dog", "Pug", "Cat",
"Rodent",
"Gerbil", "Hamster",
};
//
Exceptions thrown out to console:
public
static void main(String[] args)
throws
Exception {
ArrayList
pets = new ArrayList();
try
{
Class[]
petTypes = {
Class.forName("Dog"),
Class.forName("Pug"),
Class.forName("Cat"),
Class.forName("Rodent"),
Class.forName("Gerbil"),
Class.forName("Hamster"),
};
for(int
i = 0; i < 15; i++)
pets.add(
petTypes[
(int)(Math.random()*petTypes.length)]
.newInstance());
}
catch(InstantiationException e) {
System.err.println("Cannotinstantiate");
throw
e;
}
catch(IllegalAccessException e) {
System.err.println("Cannot
access");
throw
e;
}
catch(ClassNotFoundException e) {
System.err.println("Cannot
find class");
throw
e;
}
HashMap
h = new HashMap();
for(int
i = 0; i < typenames.length; i++)
h.put(typenames[i],
new Counter());
for(int
i = 0; i < pets.size(); i++) {
Object
o = pets.get(i);
if(o
instanceof Pet)
Chapter12:
Run time
TypeIdentification
667
((Counter)h.get("Pet")).i++;
if(o
instanceof Dog)
((Counter)h.get("Dog")).i++;
if(o
instanceof Pug)
((Counter)h.get("Pug")).i++;
if(o
instanceof Cat)
((Counter)h.get("Cat")).i++;
if(o
instanceof Rodent)
((Counter)h.get("Rodent")).i++;
if(o
instanceof Gerbil)
((Counter)h.get("Gerbil")).i++;
if(o
instanceof Hamster)
((Counter)h.get("Hamster")).i++;
}
for(int
i = 0; i < pets.size(); i++)
System.out.println(pets.get(i).getClass());
for(int
i = 0; i < typenames.length; i++)
System.out.println(
typenames[i]
+ " quantity: " +
((Counter)h.get(typenames[i])).i);
}
}
///:~
There's
a rather narrow restriction on
instanceof:
you can compare it to
a
named type only,
andnot to a Classobject.
In the example
aboveyou
mightfeel
that it's tedious to write
out all of thoseinstanceof
expressions,and
you're right. Butthere is no
way to
cleverlyautomate
instanceofby
creating an ArrayList
of
Classobjectsand
comparing it
to
those instead
(staytuned--you'll see an
alternative).This isn't as
great
a
restriction as you
mightthink, because
you'lleventually
understandthat
yourdesign
is probably flawed if you
end up writing a lot of
instanceof
expressions.
Of
course this example is
contrived--you'd probably put a
staticdata
member
in each type and increment
it in the constructor to
keeptrack of
thecounts.
You would do something like
thatif
youhad
control of the
sourcecode
for the classand
could change it.Since
this is not
alwaysthe
case,RTTI
can come in handy.
668
Thinking
in Java
Usingclass
literals
It'sinteresting
to see how thePetCount.javaexamplecan
be rewritten
usingclass
literals. The result is
cleaner in manyways:
//:
c12:PetCount2.java
//
Using class literals.
import
java.util.*;
public
class PetCount2 {
public
static void main(String[] args)
throws
Exception {
ArrayList
pets = new ArrayList();
Class[]
petTypes = {
//
Class literals:
Pet.class,
Dog.class,
Pug.class,
Cat.class,
Rodent.class,
Gerbil.class,
Hamster.class,
};
try
{
for(int
i = 0; i < 15; i++) {
//
Offset by one to eliminate Pet.class:
int
rnd = 1 + (int)(
Math.random()
* (petTypes.length - 1));
pets.add(
petTypes[rnd].newInstance());
}
}
catch(InstantiationException e) {
System.err.println("Cannotinstantiate");
throw
e;
}
catch(IllegalAccessException e) {
System.err.println("Cannot
access");
throw
e;
}
HashMap
h = new HashMap();
for(int
i = 0; i < petTypes.length; i++)
h.put(petTypes[i].toString(),
Chapter12:
Run time
TypeIdentification
669
new
Counter());
for(int
i = 0; i < pets.size(); i++) {
Object
o = pets.get(i);
if(o
instanceof Pet)
((Counter)h.get("classPet")).i++;
if(o
instanceof Dog)
((Counter)h.get("classDog")).i++;
if(o
instanceof Pug)
((Counter)h.get("classPug")).i++;
if(o
instanceof Cat)
((Counter)h.get("classCat")).i++;
if(o
instanceof Rodent)
((Counter)h.get("classRodent")).i++;
if(o
instanceof Gerbil)
((Counter)h.get("classGerbil")).i++;
if(o
instanceof Hamster)
((Counter)h.get("classHamster")).i++;
}
for(int
i = 0; i < pets.size(); i++)
System.out.println(pets.get(i).getClass());
Iterator
keys = h.keySet().iterator();
while(keys.hasNext())
{
String
nm = (String)keys.next();
Counter
cnt = (Counter)h.get(nm);
System.out.println(
nm.substring(nm.lastIndexOf('.')
+ 1) +
"
quantity: " + cnt.i);
}
}
}
///:~
Here,the
typenamesarrayhas
been removed in favor of
getting the type
namestrings
from the Classobject.Notice
that the
systemcan
distinguishbetween
classes
andinterfaces.
Youcan
also see thatthe
creation of petTypesdoesnot
need to be
surrounded
by a try
blocksince
it's evaluated at compile-time
and thus
won'tthrow
any exceptions,
unlikeClass.forName(
).
Whenthe
Petobjectsare
dynamically created, youcan
see thatthe
randomnumber
is restricted so it is betweenone
and petTypes.length
670
Thinking
in Java
anddoes
not include zero.That's
because zero refers to
Pet.class,
and
presumably
a generic Petobject
is not interesting.
However,since
Pet.classis
part of petTypestheresult
is that all of thepets
get counted.
A
dynamic instanceof
TheClassisInstance
methodprovides
a way to dynamicallycall
the
instanceofoperator.Thus,
all those tediousinstanceofstatementscan
be
removed in the PetCountexample:
//:
c12:PetCount3.java
//
Using isInstance().
import
java.util.*;
public
class PetCount3 {
public
static void main(String[] args)
throws
Exception {
ArrayList
pets = new ArrayList();
Class[]
petTypes = {
Pet.class,
Dog.class,
Pug.class,
Cat.class,
Rodent.class,
Gerbil.class,
Hamster.class,
};
try
{
for(int
i = 0; i < 15; i++) {
//
Offset by one to eliminate Pet.class:
int
rnd = 1 + (int)(
Math.random()
* (petTypes.length - 1));
pets.add(
petTypes[rnd].newInstance());
}
}
catch(InstantiationException e) {
System.err.println("Cannotinstantiate");
throw
e;
}
catch(IllegalAccessException e) {
System.err.println("Cannot
access");
throw
e;
Chapter12:
Run time
TypeIdentification
671
}
HashMap
h = new HashMap();
for(int
i = 0; i < petTypes.length; i++)
h.put(petTypes[i].toString(),
new
Counter());
for(int
i = 0; i < pets.size(); i++) {
Object
o = pets.get(i);
//
Using isInstance to eliminate individual
//
instanceof expressions:
for
(int j = 0; j < petTypes.length; ++j)
if
(petTypes[j].isInstance(o)) {
String
key = petTypes[j].toString();
((Counter)h.get(key)).i++;
}
}
for(int
i = 0; i < pets.size(); i++)
System.out.println(pets.get(i).getClass());
Iterator
keys = h.keySet().iterator();
while(keys.hasNext())
{
String
nm = (String)keys.next();
Counter
cnt = (Counter)h.get(nm);
System.out.println(
nm.substring(nm.lastIndexOf('.')
+ 1) +
"
quantity: " + cnt.i);
}
}
}
///:~
Youcan
see that theisInstance(
) methodhas
eliminated the
needfor
theinstanceofexpressions.
In addition, this means
thatyou can
add
newtypes
of pets simply by changing
the petTypesarray;the
rest of the
programdoes
not need modification(as it
did when usingthe
instanceof
expressions).
instanceof
vs.Class equivalence
Whenquerying
for typeinformation, there's
an importantdifference
betweeneither
form of instanceof(thatis,
instanceofor
isInstance(
),
which produce
equivalentresults) and
thedirect
comparison
of the Classobjects.Here's
an example
thatdemonstrates
thedifference:
672
Thinking
in Java
//:
c12:FamilyVsExactType.java
//
The difference between instanceof and class
class
Base {}
class
Derived extends Base {}
public
class FamilyVsExactType {
static
void test(Object x) {
System.out.println("Testing
x of type " +
x.getClass());
System.out.println("x
instanceof Base " +
(x
instanceof Base));
System.out.println("x
instanceof Derived " +
(x
instanceof Derived));
System.out.println("Base.isInstance(x)
" +
Base.class.isInstance(x));
System.out.println("Derived.isInstance(x)
" +
Derived.class.isInstance(x));
System.out.println(
"x.getClass()
== Base.class " +
(x.getClass()
== Base.class));
System.out.println(
"x.getClass()
== Derived.class " +
(x.getClass()
== Derived.class));
System.out.println(
"x.getClass().equals(Base.class))
" +
(x.getClass().equals(Base.class)));
System.out.println(
"x.getClass().equals(Derived.class))
" +
(x.getClass().equals(Derived.class)));
}
public
static void main(String[] args) {
test(new
Base());
test(new
Derived());
}
}
///:~
Thetest( )
methodperforms
type checking withits
argument usingboth
forms
of instanceof.
It then gets the Classreferenceand
uses ==
and
equals(
) to
test for equality of
theClassobjects.Here
is the output:
Testing
x of type class Base
Chapter12:
Run time
TypeIdentification
673
x
instanceof Base true
x
instanceof Derived false
Base.isInstance(x)
true
Derived.isInstance(x)
false
x.getClass()
== Base.class true
x.getClass()
== Derived.class false
x.getClass().equals(Base.class))
true
x.getClass().equals(Derived.class))
false
Testing
x of type class Derived
x
instanceof Base true
x
instanceof Derived true
Base.isInstance(x)
true
Derived.isInstance(x)
true
x.getClass()
== Base.class false
x.getClass()
== Derived.class true
x.getClass().equals(Base.class))
false
x.getClass().equals(Derived.class))
true
Reassuringly,instanceofandisInstance(
) produceexactly
the same
results,
as do equals(
) and==.
But the tests
themselvesdraw
different
conclusions.
In keeping with the concept
of type, instanceofsays"are
youthis
class, or a classderived
from this class?" On the
other hand, if
youcompare
the actual Classobjectsusing
==,
there is no
concernwith
inheritance--it'seither
the exact type or it
isn't.
RTTI
syntax
Javaperforms
its RTTI usingthe
Classobject,even
if you're doing
somethinglike
a cast. The classClassalsohas
a number of otherways
youcan
use RTTI.
First,you
must get a reference to the
appropriate Classobject.One
way
to
do this, as shown in
theprevious example, is to
use a string and
the
Class.forName(
) method.This
is convenient becauseyou
don't need an
object
of that type in order to
getthe Classreference.However,
if you do
alreadyhave
an object of the typeyou're
interested in, youcan
fetch the
Classreference
by calling a method
that'spart of the Objectrootclass:
getClass(
).
This returns the Classreferencerepresenting
theactual
type
of the object. Classhasmany
interesting methods,demonstrated
in
thefollowing
example:
674
Thinking
in Java
//:
c12:ToyTest.java
//
Testing class Class.
interface
HasBatteries {}
interface
Waterproof {}
interface
ShootsThings {}
class
Toy {
//
Comment out the following default
//
constructor to see
//
NoSuchMethodError from (*1*)
Toy()
{}
Toy(int
i) {}
}
class
FancyToy extends Toy
implements
HasBatteries,
Waterproof,ShootsThings
{
FancyToy()
{ super(1); }
}
public
class ToyTest {
public
static void main(String[] args)
throws
Exception {
Class
c = null;
try
{
c
= Class.forName("FancyToy");
}
catch(ClassNotFoundException e) {
System.err.println("Can't
find FancyToy");
throw
e;
}
printInfo(c);
Class[]
faces = c.getInterfaces();
for(int
i = 0; i < faces.length; i++)
printInfo(faces[i]);
Class
cy = c.getSuperclass();
Object
o = null;
try
{
//
Requires default constructor:
o
= cy.newInstance(); // (*1*)
}
catch(InstantiationException e) {
System.err.println("Cannotinstantiate");
Chapter12:
Run time
TypeIdentification
675
throw
e;
}
catch(IllegalAccessException e) {
System.err.println("Cannot
access");
throw
e;
}
printInfo(o.getClass());
}
static
void printInfo(Class cc) {
System.out.println(
"Class
name: " + cc.getName() +
"
is interface? [" +
cc.isInterface()
+ "]");
}
}
///:~
Youcan
see that class
FancyToy is
quitecomplicated, since it
inherits
fromToy andimplements
theinterfaces
of HasBatteries,
Waterproof,
and ShootsThings.
In main(
),
a Classreference
is
createdand
initialized to theFancyToy
Class usingforName( )
inside
an
appropriate try
block.
TheClass.getInterfaces(
) methodreturns
an array of Classobjects
representingthe
interfaces that arecontained
in the Classobject
of
interest.
If
you have a Classobjectyou
can also ask it for
its direct baseclass
using
getSuperclass(
).
This, of course, returns a
Classreferencethat
you can
furtherquery.
This means that, at
run-time, you can discover
an object's
entireclass
hierarchy.
ThenewInstance(
) method
of Classcan,
at first, seem like
justanother
way
to clone(
) an
object. However, you
cancreate a new
objectwith
newInstance(
) without
an
existing object, as
seenhere, because
there
is
no Toy
object--onlycy,
which is a reference to y's
Classobject.This
is
a
way to implement a
"virtualconstructor," which
allowsyou to say "I
don'tknow
exactly what typeyou
are, but createyourself
properly
anyway."
In the example
above,cy
is
just a Classreferencewith
no
furthertype
information known at compile-time.
And when youcreate
a
newinstance,
you get back an Objectreference.
Butthat reference is
pointing
to a Toy
object.
Of course, before you
cansend any
messages
otherthan
those accepted by Object,
you have to investigate it a
bit and
676
Thinking
in Java
do
some casting. In
addition,the class that's
beingcreated with
newInstance(
) musthave
a default constructor. In the
next section,
you'llsee
how to dynamicallycreate
objects of classesusing
any
constructor,with
the Java reflectionAPI.
Thefinal
method in the listing is
printInfo( ),
whichtakes
a Class
referenceand
gets its namewith
getName( ),
andfinds
out whetherit's
an
interface with isInterface(
).
Theoutput
from this
programis:
Class
name:
FancyToy
is interface? [false]
Class
name:
HasBatteries
is interface? [true]
Class
name:
Waterproof
is interface? [true]
Class
name:
ShootsThings
is interface? [true]
Class
name:
Toy
is interface? [false]
Thus,with
the Classobjectyou
can find outjust
about everythingyou
want
to know about an
object.
Reflection:
run-time
class
information
If
you don't know
theprecise type of an
object,RTTI will
tellyou.
However,there's
a limitation: thetype must
be known at compile-time in
orderfor
you to be able to detect it
using RTTI and do something
useful
withthe
information. Put anotherway,
the compiler mustknow
about all
theclasses
you're working withfor
RTTI.
Thisdoesn't
seem like thatmuch of a
limitation at first,but
suppose
you'regiven
a reference to an objectthat's
not in your programspace.
In
fact,the
class of the objectisn't
even available to
yourprogram at
compile-time.For
example, suppose youget a
bunch of bytes from a
disk
file
or from a network
connectionand you're told
thatthose bytes
represent
a class. Since the
compilercan't know about
theclass while
it's
compilingthe
code, how canyou
possibly use such a
class?
In
a traditional programming
environmentthis seems like a
far-fetched
scenario.But
as we move into a
largerprogramming world
thereare
Chapter12:
Run time
TypeIdentification
677
importantcases
in which this happens.The
first is component-based
programming,
in which you build
projectsusing RapidApplication
Development(RAD)
in an application builder
tool.This is a visual
approach
to creating a program
(whichyou see on the
screen as a "form")
by
moving icons that
representcomponents onto the
form.These
componentsare
then configured by setting
some of their values
at
programtime.
This design-timeconfiguration
requires thatany
component
be instantiable, that it
exposesparts of itself, and
that it allows
itsvalues
to be read and set. In
addition, components
thathandle GUI
eventsmust
expose informationabout
appropriate methods so that
the
RADenvironment
can assist theprogrammer in
overriding
theseevent-
handlingmethods.
Reflection providesthe
mechanism to
detectthe
availablemethods
and produce themethod
names. Java provides
a
structurefor
component-based programmingthrough
JavaBeans
(described
in Chapter 13).
Anothercompelling
motivation fordiscovering
class information at
run-
time
is to provide the ability to
create and execute objects
on remote
platformsacross
a network. This is called
RemoteMethod
Invocation
(RMI)and
it allows a Java program to
have objects
distributedacross
manymachines.
This distributioncan happen
for a number of
reasons:
forexample,
perhaps you'redoing a
computation-intensive taskand
you
want
to break it up and
putpieces on machines that
areidle in order to
speedthings
up. In somesituations you
might want to place code
that
handlesparticular
types of tasks(e.g.,
"Business Rules" in a
multitier
client/serverarchitecture)
on a particular machine, so that
machine
becomes
a common repository
describingthose actions and
it can be
easilychanged
to affect everyone in the
system. (This is an
interesting
development,since
the machine existssolely to
make softwarechanges
easy!)Along
these lines,distributed
computing alsosupports
specialized
hardwarethat
might be good at a particular
task--matrix
inversions,for
example--butinappropriate
or too expensivefor general
purpose
programming.
Theclass
Class(describedpreviously
in this chapter)supports
the
concept
of reflection,
and there's an
additionallibrary,
java.lang.reflect,withclasses
Field,
Method,
and Constructor
(each
of which implement
theMember
interface). Objects of
these
typesare
created by the JVM at
run-time to represent
thecorresponding
678
Thinking
in Java
member
in the unknown class.
Youcan then use
theConstructors
to
createnew
objects, the get(
) andset( )
methods
to read and
modifythe
fieldsassociated
with Fieldobjects,and
the invoke(
) method
to call a
methodassociated
with a Method
object.
In addition, you can
callthe
conveniencemethods
getFields(
),
getMethods(
),
getConstructors(
),
etc., to return arrays of
the objects
representingthe
fields,methods,
and constructors.(You can
find outmore by looking
up
theclass
Classin
your online
documentation.)Thus, the
class
informationfor
anonymous objects can be
completely determined at
run-
time,and
nothing need be known at
compile-time.
It'simportant
to realize thatthere's
nothing magic
aboutreflection. When
you'reusing
reflection to interactwith an
object of an unknowntype,
the
JVMwill
simply look at theobject
and see that it belongs to a
particular
class(just
like ordinary RTTI)but
then, before it can do
anything else, the
Classobjectmust
be loaded. Thus,
the.classfilefor
that particulartype
muststill
be available to theJVM,
either on the localmachine
or across
thenetwork.
So the truedifference
between RTTI andreflection
is that
withRTTI,
the compiler opensand
examines the .classfile
at compile-
time.Put
another way, youcan
call all themethods of an
object in the
"normal"way.
With reflection,
the.classfile
is unavailable at compile-
time;
it is opened and examined by
the
run-timeenvironment.
A
class method extractor
You'llrarely
need to use thereflection
tools directly;they're in
the
language
to support other
Javafeatures, such as
objectserialization
(Chapter11),
JavaBeans (Chapter 13),and
RMI (Chapter
15).However,
thereare
times when it'squite
useful to be able to dynamically
extract
informationabout
a class. One extremelyuseful
tool is a classmethod
extractor.
As mentioned before, looking at a
class definition
sourcecode
or
online documentation
showsonly the methods
thatare defined or
overriddenwithin
that class definition.
But there could be
dozensmore
available
to you that have
comefrom base classes. To
locatethese is both
tediousand
time consuming1.
Fortunately, reflection provides a
way to
1
Especially in the
past.However, Sun has
greatlyimproved its HTML
Javadocumentation
so
that it's easier to
seebase-class
methods.
Chapter12:
Run time
TypeIdentification
679
write
a simple tool that
willautomatically show you
theentire interface.
Here'sthe
way it works:
//:
c12:ShowMethods.java
//
Using reflection to show all the methods of
//
a class, even if the methods are defined in
//
the base class.
import
java.lang.reflect.*;
public
class ShowMethods {
static
final String usage =
"usage:
\n" +
"ShowMethodsqualified.class.name\n"
+
"To
show all methods in class or: \n" +
"ShowMethodsqualified.class.name
word\n" +
"To
search for methods involving 'word'";
public
static void main(String[] args) {
if(args.length
< 1) {
System.out.println(usage);
System.exit(0);
}
try
{
Class
c = Class.forName(args[0]);
Method[]
m = c.getMethods();
Constructor[]
ctor = c.getConstructors();
if(args.length
== 1) {
for
(int i = 0; i < m.length; i++)
System.out.println(m[i]);
for
(int i = 0; i < ctor.length; i++)
System.out.println(ctor[i]);
}
else {
for
(int i = 0; i < m.length; i++)
if(m[i].toString()
.indexOf(args[1])!=
-1)
System.out.println(m[i]);
for
(int i = 0; i < ctor.length; i++)
if(ctor[i].toString()
.indexOf(args[1])!=
-1)
System.out.println(ctor[i]);
}
}
catch(ClassNotFoundException e) {
680
Thinking
in Java
System.err.println("No
such class: " + e);
}
}
}
///:~
TheClassmethodsgetMethods(
) andgetConstructors(
) return
an
array
of Method
andConstructor,
respectively. Each of
theseclasses
hasfurther
methods to dissectthe names,
arguments, andreturn
values
of
the methods they
represent.But you can
alsojust use toString(
),
as is
donehere,
to produce a Stringwiththe
entire method signature.The
rest
of
the code is just
forextracting command
lineinformation,
determining
if
a particular signature
matcheswith your target
string(using
indexOf(
)),
and printing
theresults.
Thisshows
reflection in action,since
the result produced
by
Class.forName(
) cannot
be known at compile-time,
andtherefore all
themethod
signature information is being
extracted at run-time. If
you
investigateyour
online documentation on reflection,
you'll see
thatthere
is
enough support to
actuallyset up and make a
methodcall on an
object
that'stotally
unknown at compile-time(there
will be examples of
this
later
in this book). Again, this
is something you may
neverneed to do
yourself--thesupport
is there for RMIand so a
programming
environmentcan
manipulate JavaBeans--butit's
interesting.
An
interesting experiment is to
run
java
ShowMethods ShowMethods
Thisproduces
a listing thatincludes a
publicdefaultconstructor,
even
thoughyou
can see fromthe
code that no constructorwas
defined. The
constructoryou
see is the onethat's
automatically synthesized by
the
compiler.
If you then make ShowMethodsa
non-publicclass(that
is,
friendly),the
synthesized defaultconstructor no
longer shows up in
the
output.The
synthesized defaultconstructor is
automatically
giventhe
sameaccess
as the class.
Theoutput
for ShowMethodsis
still a little tedious.
Forexample, here's
a
portion of the
outputproduced by invoking
javaShowMethods
java.lang.String:
public
boolean
Chapter12:
Run time
TypeIdentification
681
java.lang.String.startsWith(java.lang.String,int)
public
boolean
java.lang.String.startsWith(java.lang.String)
public
boolean
java.lang.String.endsWith(java.lang.String)
It
would be even nicer if
thequalifiers like java.langcould
be stripped
off.The
StreamTokenizerclassintroduced
in the previouschapter
can
helpcreate
a tool to solve
thisproblem:
//:
com:bruceeckel:util:StripQualifiers.java
package
com.bruceeckel.util;
import
java.io.*;
public
class StripQualifiers {
private
StreamTokenizer st;
public
StripQualifiers(String qualified) {
st
= new StreamTokenizer(
new
StringReader(qualified));
st.ordinaryChar('
'); // Keep the spaces
}
public
String getNext() {
String
s = null;
try
{
int
token = st.nextToken();
if(token
!= StreamTokenizer.TT_EOF) {
switch(st.ttype)
{
case
StreamTokenizer.TT_EOL:
s
= null;
break;
case
StreamTokenizer.TT_NUMBER:
s
= Double.toString(st.nval);
break;
case
StreamTokenizer.TT_WORD:
s
= new String(st.sval);
break;
default:
// single character in ttype
s
= String.valueOf((char)st.ttype);
}
}
}
catch(IOException e) {
682
Thinking
in Java
System.err.println("Error
fetching token");
}
return
s;
}
public
static String strip(String qualified) {
StripQualifiers
sq =
new
StripQualifiers(qualified);
String
s = "", si;
while((si
= sq.getNext()) != null) {
int
lastDot = si.lastIndexOf('.');
if(lastDot
!= -1)
si
= si.substring(lastDot + 1);
s
+= si;
}
return
s;
}
}
///:~
To
facilitate reuse, this class
is placed in com.bruceeckel.util.
As you
cansee,
this uses theStreamTokenizerandStringmanipulation
to do
itswork.
Thenew
version of the programuses
the above class to clean up
the
output:
//:
c12:ShowMethodsClean.java
//
ShowMethods with the qualifiers stripped
//
to make the results easier to read.
import
java.lang.reflect.*;
import
com.bruceeckel.util.*;
public
class ShowMethodsClean {
static
final String usage =
"usage:
\n" +
"ShowMethodsCleanqualified.class.name\n"
+
"To
show all methods in class or: \n" +
"ShowMethodsCleanqualif.class.name
word\n" +
"To
search for methods involving 'word'";
public
static void main(String[] args) {
if(args.length
< 1) {
System.out.println(usage);
System.exit(0);
Chapter12:
Run time
TypeIdentification
683
}
try
{
Class
c = Class.forName(args[0]);
Method[]
m = c.getMethods();
Constructor[]
ctor = c.getConstructors();
//
Convert to an array of cleaned Strings:
String[]
n =
new
String[m.length + ctor.length];
for(int
i = 0; i < m.length; i++) {
String
s = m[i].toString();
n[i]
= StripQualifiers.strip(s);
}
for(int
i = 0; i < ctor.length; i++) {
String
s = ctor[i].toString();
n[i
+ m.length] =
StripQualifiers.strip(s);
}
if(args.length
== 1)
for
(int i = 0; i < n.length; i++)
System.out.println(n[i]);
else
for
(int i = 0; i < n.length; i++)
if(n[i].indexOf(args[1])!=
-1)
System.out.println(n[i]);
}
catch(ClassNotFoundException e) {
System.err.println("No
such class: " + e);
}
}
}
///:~
Theclass
ShowMethodsCleanis
quite similar to
theprevious
ShowMethods,
except that it takes
thearrays of Method
and
Constructorandconverts
them into a singlearray of
String.
Each of
theseStringobjects
is then passed
throughStripQualifiers.Strip(
) to
removeall
the
methodqualification.
Thistool
can be a realtime-saver
while you'reprogramming,
when you
can'tremember
if a class has a particular
method and youdon't
want to
go
walking through the
classhierarchy in the
onlinedocumentation, or if
youdon't
know whether thatclass
can do anything with,for
example,
Colorobjects.
684
Thinking
in Java
Chapter
13 contains a GUI version of
thisprogram (customized to
extract
informationfor
Swing components) so youcan
leave it runningwhile
you'rewriting
code, to allow
quicklookups.
Summary
RTTIallows
you to discover
typeinformation from an
anonymousbase-
classreference.
Thus, it's ripefor
misuse by the novicesince it
might
makesense
before polymorphicmethod
calls do. Formany
people coming
from
a procedural background,
it'sdifficult not to
organizetheir
programs
intosets
of switchstatements.They
could accomplish thiswith
RTTI and
thuslose
the important value of
polymorphism in code
developmentand
maintenance.The
intent of Java is thatyou
use polymorphic
methodcalls
throughoutyour
code, and youuse
RTTI only whenyou
must.
However,using
polymorphic methodcalls as
they are
intendedrequires
thatyou
have control of
thebase-class definition
because at some point
in
theextension
of your programyou might
discover thatthe base
class
doesn'tinclude
the method youneed. If
the base classcomes
from a
library
or is otherwise controlled by
someoneelse, a solution to
the
problem
is RTTI: You can inherit a
new type and
addyour extra
method.
Elsewhere
in the code you
candetect your particular
typeand call
that
specialmethod.
This doesn't destroythe
polymorphism
andextensibility
of
the program because adding a
new type will
notrequire you to
huntfor
switchstatements
in your program.However,
when you addnew
code in
yourmain
body that requiresyour
new feature, youmust
use RTTI to
detectyour
particular type.
Putting
a feature in a base
classmight mean that,
forthe benefit of
one
particularclass,
all of the otherclasses
derived from thatbase
require
somemeaningless
stub of a method.This makes
the interfaceless
clear
andannoys
those who mustoverride
abstract methodswhen they
derive
fromthat
base class. Forexample,
consider a classhierarchy
representing
musicalinstruments.
Suppose youwanted to clear
the spitvalves of all
the
appropriateinstruments
in your orchestra.One option
is to put a
clearSpitValve(
) method
in the base class Instrument,
but this is
confusingbecause
it implies thatPercussionandElectronic
instrumentsalso
have spit valves.RTTI
provides a much
morereasonable
Chapter12:
Run time
TypeIdentification
685
solution
in this case because
youcan place the
method in the specific
class
(Wind in
this case), where
it'sappropriate. However, a
moreappropriate
solution
is to put a prepareInstrument(
) method
in the base
class,but
youmight
not see thiswhen
you're first solvingthe
problem andcould
mistakenlyassume
that you mustuse
RTTI.
Finally,RTTI
will sometimes
solveefficiency problems. If
yourcode nicely
usespolymorphism,
but it turnsout that
one of yourobjects reacts to
this
generalpurpose
code in a horriblyinefficient
way, you canpick
out that
typeusing
RTTI and writecase-specific
code to improvethe
efficiency. Be
wary,however,
of programming forefficiency
too soon. It's a
seductive
trap.It's
best to get theprogram
working first,
then decide if
it'srunning
fastenough,
and only thenshould
you attack
efficiencyissues--with a
profiler.
Exercises
Solutions
to selected exercises can be
found in the
electronicdocument TheThinking
in Java
AnnotatedSolution
Guide,
availablefor a small fee
from.
1.
AddRhomboid
to
Shapes.java.
Create a Rhomboid,
upcast it
to
a Shape,
then downcast it back to a
Rhomboid.
Try
downcasting
to a Circleandsee
what happens.
2.
ModifyExercise
1 so that it usesinstanceofto
check the type
beforeperforming
thedowncast.
3.
ModifyShapes.javaso
that it can "highlight" (set
a flag) in all
shapes
of a particular type.
ThetoString(
) methodfor
each
derivedShapeshouldindicate
whether thatShapeis
"highlighted."
4.
ModifySweetShop.javaso
that each type of
objectcreation is
controlled
by a command-line argument.
Thatis, if your
command
line
is "javaSweetShop
Candy,"
thenonly the Candy
object
is
created.Notice
how you cancontrol
which Classobjectsare
loadedvia
the
command-lineargument.
5.
Add
a new type of Petto
PetCount3.java.
Verify that it is
createdand
counted correctly in main(
).
686
Thinking
in Java
6.
Write
a method that takes an
object and recursivelyprints
all the
classes
in that
object'shierarchy.
7.
ModifyExercise
6 so that it usesClass.getDeclaredFields(
) to
alsodisplay
information aboutthe fields
in a class.
8.
In
ToyTest.java,
comment out Toy's
default
constructorand
explainwhat
happens.
9.
Incorporate
a new kind of interfaceintoToyTest.javaand
verifythat
it is detected anddisplayed
properly.
10.
Create
a new type of container
thatuses a private
ArrayList to
holdthe
objects. Capture thetype of
the first objectyou
put in it,
andthen
allow the user to insert
objects of only thattype
from
thenon.
11.
Write
a program to determine whether an
array of char
is
a
primitivetype
or a trueobject.
12.
ImplementclearSpitValve(
) as
described in
thesummary.
13.
Implementthe
rotate(Shape)methoddescribed
in thischapter,
suchthat
it checks to see if it is rotating a
Circle(and,
if so,
doesn'tperform
theoperation).
14.
ModifyExercise
6 so that it usesreflection
instead of RTTI.
15.
ModifyExercise
7 so that it usesreflection
instead of RTTI.
16.
In
ToyTest.java,
use reflection to create a
Toy objectusing
the
nondefaultconstructor.
17.
Look
up the interface
forjava.lang.Classin
the HTML Java
documentationfrom
java.sun.com.
Write a program that
takesthe
name
of a class as a command-line
argument,then uses the
Class
methods
to dump all the
informationavailable for
that class.Test
yourprogram
with a standardlibrary class
and a classyou
create.
Chapter12:
Run time
TypeIdentification
687
13:
Creating
Windows
&
Applets
A
fundamental design guideline is
"make simple things
easy,and
difficult thingspossible."
1
Theoriginal
design goal of thegraphical
user interface(GUI) library
in
Java1.0
was to allow theprogrammer
to build a GUI thatlooks
good on
allplatforms.
That goal wasnot
achieved. Instead, theJava
1.0 Abstract
Window
Toolkit (AWT)produces
a GUI that looksequally
mediocre on all
systems.
In addition, it's
restrictive:you can use
onlyfour fonts
andyou
cannotaccess
any of the moresophisticated
GUI elements thatexist
in
youroperating
system. The Java1.0
AWT programming model is
also
awkwardand
non-object-oriented. A student in one of
my seminars(who
hadbeen
at Sun during thecreation of
Java) explainedwhy: the
original
AWThad
been conceptualized,designed,
and implemented in a
month.
Certainly
a marvel of productivity,
andalso an object lesson in
whydesign
is
important.
Thesituation
improved with theJava
1.1 AWT eventmodel,
which takes a
muchclearer,
object-oriented approach,along
with the addition of
JavaBeans,
a component programming
modelthat is oriented
towardthe
easycreation
of visual programmingenvironments.
Java 2 finishesthe
transformationaway
from the oldJava
1.0 AWT by
essentiallyreplacing
everythingwith
the JavaFoundation
Classes (JFC),the
GUI portion of
which
is called "Swing." These
are a rich set of
easy-to-use,easy-to-
1
A variation on
this is called "theprinciple
of least astonishment," which essentially
says:
"don'tsurprise
the user."
689
understandJavaBeans
that can be dragged and
dropped (aswell as
hand
programmed)
to create a GUI that you
can(finally) be satisfied
with.The
"revision
3" rule of the
softwareindustry (a product
isn'tgood until
revision
3) seems to hold true
withprogramming languages as
well.
Thischapter
does not coveranything
but the modern,Java 2
Swing
library,and
makes the
reasonableassumption that
Swing is thefinal
destination
GUI library for Java. If
forsome reason you
need to use the
original"old"
AWT (because
you'resupporting old code or
youhave
browserlimitations),
you can findthat
introduction in thefirst
edition of
thisbook,
downloadable at (alsoincluded
on the
CD
ROM bound with
thisbook).
Early
in this chapter, you'll
seehow things are
differentwhen you want
to
create
an applet vs. a
regularapplication using
Swing, andhow to
create
programsthat
are both appletsand
applications so they can be
run either
inside
a browser or from
thecommand line. Almost
allthe GUI examples
in
this book will be executable
as either applets or
applications.
Please
be aware that this is not a
comprehensive glossary of
eitherall the
Swingcomponents,
or all themethods for
the describedclasses. What
you
seehere
is intended to be simple.The
Swing library is vast,and
the goal of
thischapter
is only to get youstarted
with the essentialsand
comfortable
withthe
concepts. If you need to do
more, then Swing
canprobably give
youwhat
you want if you'rewilling to
do theresearch.
I
assume here that
youhave downloaded
andinstalled the
(free)Java
librarydocuments
in HTML formatfrom java.sun.comandwill
browse
thejavax.swingclasses
in that documentation to
seethe full
detailsand
methods
of the Swing
library.Because of the
simplicity of the
Swing
design,this
will often be
enoughinformation to solve
yourproblem. There
arenumerous
(rather thick)books
dedicated solely to Swing
and you'll
want
to go to those if you
needmore depth, or if you
want to modify the
defaultSwing
behavior.
As
you learn about
Swingyou'll discover:
1.
Swing
is a much better
programmingmodel than
you'veprobably
seen
in other languages
anddevelopment
environments.
690
Thinking
in Java
JavaBeans(which
will be introducedtoward the
end of this
chapter)
is the framework for
thatlibrary.
2.
"GUIbuilders"
(visual programmingenvironments)
are a de
rigueuraspect
of a complete Java
developmentenvironment.
JavaBeansand
Swing allow the GUI builder
to write code foryou
as
youplace
components onto formsusing
graphical tools.
Thisnot
onlyrapidly
speeds developmentduring GUI
building, but it
allows
forgreater
experimentation andthus the
ability to tryout
more
designsand
presumably come up with a
better one.
3.
Thesimplicity
and well-designednature of
Swing means
thateven
if
you do use a GUI
builderrather than coding by
hand,the
resultingcode
will still be comprehensible--this
solves a big
problemwith
GUI builders from thepast,
which couldeasily
generateunreadable
code.
Swingcontains
all the componentsthat
you expect to see in a
modern UI,
everythingfrom
buttons that containpictures
to trees andtables. It's a
big
library,but
it's designed to
haveappropriate complexity
forthe task at
hand--ifsomething
is simple, youdon't have to
write muchcode but
as
youtry
to do more complexthings,
your code
becomesproportionally
morecomplex.
This means an easyentry
point, but you'vegot
the power if
youneed
it.
Much
of what you'll like
aboutSwing could be
called"orthogonality of
use."That
is, once youpick up
the general ideasabout
the library
youcan
applythem
everywhere. Primarilybecause of
the standardnaming
conventions,much
of the time that I was
writing these examples I
could
guess
at the method names
andget it right the
firsttime, without
looking
anythingup.
This is certainly
thehallmark of a good
librarydesign. In
addition,you
can generally plugcomponents
into othercomponents
and
thingswill
work correctly.
Forspeed,
all the componentsare
"lightweight," and Swing is
written
entirely
in Java
forportability.
Keyboardnavigation
is automatic--you canrun a
Swingapplication
withoutusing
the mouse, andthis
doesn't require
anyextra
programming.Scrolling
support is effortless--yousimply
wrap your
Chapter13:
Creating Windows &
Applets
691
component
in a JScrollPaneas
you add it to your
form.Features such as
tooltips
typically require a single
line of code to use.
Swingalso
supports a ratherradical
feature called"pluggable
look and
feel,"which
means that theappearance of
the UI can be
dynamically
changed
to suit the expectations of
users working
underdifferent
platformsand
operating systems. It'seven
possible (albeitdifficult)
to
inventyour
own look
andfeel.
The
basic applet
One
of Java's design goals is to
create applets,
which are
littleprograms
thatrun
inside a Web browser.Because
they must be safe,applets
are
limited
in what they can
accomplish.However, applets
are a powerful tool
thatsupport
client-side programming, a major
issue for
theWeb.
Appletrestrictions
Programmingwithin
an applet is so restrictivethat
it's often referred to
as
being"inside
the sandbox," sinceyou
always havesomeone--that is,
the
Javarun-time
security system--watchingover
you.
However,you
can also stepoutside
the sandbox andwrite
regular
applicationsrather
than applets, in which case
you canaccess the
other
features
of your OS. We've
beenwriting regular
applicationsall along
in
thisbook,
but they've beenconsoleapplications
withoutany
graphical
components.Swing
can also be used to build
GUI interfaces
forregular
applications.
Youcan
generally answer thequestion
of what an applet is able to do
by
looking
at what it is supposedto
do: extend the functionality
of a Web
page
in a browser. Since, as a
Netsurfer, you never
reallyknow if a Web
page
is from a friendly place or
not, you want
anycode that it runs to
be
safe.
So the biggest
restrictionsyou'll notice
areprobably:
1.
An
applet can't touch
thelocal disk.
Thismeans writing or reading,
sinceyou
wouldn't want an applet to
read and
transmitprivate
informationover
the Internet withoutyour
permission. Writing is
prevented,
of course, since that would
be an open invitation to a
virus.Java
offers digitalsigning
forapplets.
Many applet
692
Thinking
in Java
restrictionsare
relaxed when youchoose to
allow trustedapplets
(thosesigned
by a trusted source) to have
access to
yourmachine.
2.
Appletscan
take longer to display,sinceyou
must downloadthe
wholething
every time, including a
separate server hit
foreach
differentclass.
Your browser cancache
the applet, butthere
are no
guarantees.Because
of this, you shouldalways
package your
applets
in a JAR (Java ARchive)
filethat combines all
theapplet
components(including
other .classfiles
as well as images
and
sounds)together
into a singlecompressed file
that can be
downloaded
in a single server
transaction."Digital signing"
is
availablefor
each individual entry in the
JAR file.
Appletadvantages
If
you can live
withinthe restrictions,
appletshave
definiteadvantages,
especiallywhen
building client/server or other
networkedapplications:
1.
There
is no installation issue. An
applethas true
platform
independence(including
the ability to easily play
audio files,etc.)
so
you don't need to
makeany changes in your
codefor different
platformsnor
does anyone have to perform
any "tweaking" on
installation.
In fact, installation is
automaticevery time
theuser
loadsthe
Web page thatcontains
applets, so
updateshappen
silentlyand
automatically. In
traditionalclient/server
systems,
buildingand
installing a new version of
the client software is
often
a
nightmare.
2.
Youdon't
have to worry about bad code
causing damage to
someone'ssystem,
because
of the security built
intothe core
Java
languageand
applet structure. This,along
with the
previouspoint,
makesJava
popular for
so-calledintranetclient/server
applicationsthat
live only within a company
or restricted arena of
operationwhere
the user environment(Web
browser andadd-ins)
can
be specified
and/orcontrolled.
Becauseapplets
are automaticallyintegrated
with HTML, youhave
a
built-inplatform-independent
documentation system to support
the
applet.It's
an interesting twist,since
we're used to
havingthe
documentationpart
of the program ratherthan
vice versa.
Chapter13:
Creating Windows &
Applets
693
Applicationframeworks
Librariesare
often grouped according to
their
functionality.Some
libraries,for
example, are used as is,
off the shelf.The
standard Java
libraryStringandArrayList
classesare
examples of
these.Other
librariesare
designed specifically as building
blocks to createother
classes.
A certain category of library is
the applicationframework,
whose
goal
is to help you
buildapplications by providing a
class or set of
classes
thatproduces
the basic behaviorthat
you need in everyapplication
of a
particulartype.
Then, to customize
thebehavior to your own
needs,you
inheritfrom
the application classand
override the methods of
interest.
Theapplication
framework's defaultcontrol
mechanism will
callyour
overriddenmethods
at the appropriatetime. An
application framework is
a
good example of
"separatingthe things that
changefrom the
thingsthat
staythe
same," since it attempts to
localize all the
uniqueparts of a
program
in the
overriddenmethods2.
Appletsare
built using an application
framework. Youinherit from
class
JApplet
andoverride
the appropriatemethods.
There are a
fewmethods
thatcontrol
the creation andexecution of
an applet on a
Webpage:
Method
Operation
init(
)
Automaticallycalled
to perform
first-timeinitialization
of
the applet,
includingcomponent layout.
You'llalways
overridethis
method.
start(
)
Calledevery
time the appletmoves
into sight on the
Webbrowser
to allow the applet to
start up itsnormal
operations(especially
those that areshut
off by
stop(
)).
Also called after init(
).
stop(
)
Calledevery
time the appletmoves
out of sight on the
Webbrowser
to allow the applet to shut
off expensive
operations.Also
called right beforedestroy(
).
destroy(
)
Calledwhen
the applet is beingunloaded
from thepage
to
perform final release of
resources when the applet
is
no
longer used
2
This is an
example of the design pattern called the
templatemethod.
694
Thinking
in Java
Withthis
information you areready to
create a
simpleapplet:
//:
c13:Applet1.java
//
Very simple applet.
import
javax.swing.*;
import
java.awt.*;
public
class Applet1 extends JApplet {
public
void init() {
getContentPane().add(newJLabel("Applet!"));
}
}
///:~
Notethat
applets are notrequired to
have a main(
).
That's all
wiredinto
theapplication
framework; youput any
startup code in init(
).
In
this program, the
onlyactivity is putting a
textlabel on the
applet,via
theJLabelclass(the
old AWT appropriatedthe
name Labelas
well as
othernames
of components, so youwill
often see a leading
"J"
used with
Swingcomponents).
The constructorfor this
class takes a Stringand
uses
it to create the label. In
the above program
thislabel is placed on
the
form.
Theinit( )
method
is responsible for
puttingall the components on
the
formusing
the add(
) method.You
might think thatyou
ought to be able
to
simply call add(
) by
itself, and in fact
that'sthe way it used to be
in the
oldAWT.
However, Swing requiresyou
to add all components to
the
"contentpane"
of a form, and so youmust
call getContentPane(
) as
part
of the add(
) process.
Running
applets inside a Web
browser
To
run this program
youmust place it inside a
Webpage and
viewthat
pageinside
your Java-enabled
Webbrowser. To place an
appletinside a
Chapter13:
Creating Windows &
Applets
695
Webpage
you put a specialtag
inside the HTMLsource
for thatWeb
page3 to
tell the page how to
load and run
theapplet.
Thisprocess
used to be verysimple, when
Java itselfwas simple
and
everyonewas
on the same bandwagonand
incorporated the
sameJava
supportinside
their Web browsers.Then
you might havebeen
able to get
awaywith
a very simple bit of HTML
inside your Webpage,
like this:
<applet
code=Applet1 width=100 height=50>
</applet>
Thenalong
came the browserand
language wars, and we
(programmers
andend
users alike) lost.After
awhile, JavaSoftrealized
that we could no
longerexpect
browsers to supportthe
correct flavor of Java,and
the only
solutionwas
to provide some kind of
add-on that would conform to
a
browser'sextension
mechanism. By usingthe
extensionmechanism
(which
a browser vendor
cannotdisable--in an attempt to
gain
competitiveadvantage--without
breaking allthe
third-partyextensions),
JavaSoftguarantees
that Java cannot be shut
out of the Webbrowser
by
an
antagonistic vendor.
WithInternet
Explorer, theextension
mechanism is theActiveX
control,
andwith
Netscape it is theplug-in. In
your HTML code,you
must provide
tags
to support both. Here's
whatthe simplest resulting
HTMLpage looks
likefor
Applet1:4
//:!
c13:Applet1.html
<html><head><title>Applet1</title></head><hr>
<OBJECT
classid="clsid:8AD9C840-044E-11D1-B3E9-00805F499D93"
width="100"height="50"
align="baseline"
codebase="http://java.sun.com/products/plugin/1.2.2/ji
nstall-1_2_2-win.cab#Version=1,2,2,0">
<PARAM
NAME="code"VALUE="Applet1.class">
3
It is
assumedthat the reader is
familiarwith the basics of
HTML.It's not too hard
to
figure
out, and there are lots of
booksand resources.
4
This
page--inparticular, the `clsid'
portion--seemed to work fine
with both JDK1.2.2
and
JDK1.3
rc-1. However, you
mayfind that you
have to change the tag sometime in
the
future.Details
can be found at java.sun.com.
696
Thinking
in Java
<PARAM
NAME="codebase" VALUE=".">
<PARAM
NAME="type"VALUE="application/x-java-
applet;version=1.2.2">
<COMMENT>
<EMBED
type=
"application/x-java-applet;version=1.2.2"
width="200"height="200"
align="baseline"
code="Applet1.class"codebase="."
pluginspage="http://java.sun.com/products/plugin/1.2/p
lugin-install.html">
<NOEMBED>
</COMMENT>
No
Java 2 support for APPLET!!
</NOEMBED>
</EMBED>
</OBJECT>
<hr></body></html>
///:~
Some
of these lines were
toolong and had to be
wrapped to fit on
the
page.The
code in this book'ssource
code (on thebook's CD
ROM, and
downloadablefrom
)
will work without having
to
worryabout
correcting linewraps.
Thecodevaluegives
the name of the.classfilewhere
the appletresides.
Thewidth andheightspecifythe
initial size of theapplet
(in pixels, as
before).There
are other itemsyou
can place withinthe
applet tag: a place
to
find other .classfiles
on the Internet (codebase),
alignment
information
(align),
a special identifier
thatmakes it possible
forapplets
to
communicate with each other
(name),
and applet parameters
to
provideinformation
that the appletcan
retrieve. Parameters are in
the
form:
<param
name="identifier" value =
"information">
andthere
can be as many as
youwant.
Thesource
code package forthis
book provides an HTMLpage
for each of
theapplets
in this book, andthus
many examples of theapplet
tag. You
canfind
a full and
currentdescription of the
details of placing applets
in
Webpages
at java.sun.com.
Chapter13:
Creating Windows &
Applets
697
Using
Appletviewer
Sun'sJDK
(freely downloadablefrom
java.sun.com)
contains a toolcalled
theAppletviewerthatpicks
the <applet>
tagsout
of the HTML
fileand
runsthe
applets withoutdisplaying
the surroundingHTML text.
Because
theAppletviewer
ignores everythingbut APPLET
tags, youcan put
those
tags
in the Java source file as
comments:
//
< applet code=MyApplet width=200
height=100>
//
< /applet>
Thisway,
you can run "appletviewerMyApplet.java"
andyou don't
need
to create tiny HTML files
to run tests. For
example,you can
addthe
commentedHTML
tags to Applet1.java:
//:
c13:Applet1b.java
//
Embedding the applet tag for Appletviewer.
//
< applet code=Applet1b width=100
height=50>
//
< /applet>
import
javax.swing.*;
import
java.awt.*;
public
class Applet1b extends JApplet {
public
void init() {
getContentPane().add(newJLabel("Applet!"));
}
}
///:~
Nowyou
can invoke theapplet
with thecommand
appletviewerApplet1b.java
In
this book, this
formwill be used for
easytesting of
applets.Shortly,
you'llsee
another coding approachwhich
will allow you to execute
applets
fromthe
command line withoutthe
Appletviewer.
Testingapplets
Youcan
perform a simple testwithout
any networkconnection by
starting
up
your Web browser
andopening the HTML
filecontaining the
applet
tag.
As the HTML file is
loaded,the browser will
discoverthe applet
tag
and
go hunt for the .classfilespecified
by the codevalue.
Of course, it
698
Thinking
in Java
looks
at the CLASSPATH to find
outwhere to hunt, and if
your.classfile
isn't
in the CLASSPATH then it
will give an error message
on the status
line
of the browser to the
effectthat it couldn't find
that.classfile.
Whenyou
want to try thisout on
your Web sitethings
are a littlemore
complicated.First
of all, you musthavea
Web site, which
formost people
means
a third-party Internet
ServiceProvider (ISP) at a
remotelocation.
Sincethe
applet is just a file or set
of files, the ISPdoes
not have to
provideany
special support forJava.
You must alsohave a
way to move
theHTML
files and the.classfilesfrom
your site to thecorrect
directory
on
the ISP machine. This is
typically done with a
FileTransfer Protocol
(FTP)program,
of which there aremany
different typesavailable for
free
or
as shareware. So it would
seemthat all you
need to do is move the
files
to
the ISP machine
withFTP, then connect to
thesite and
HTMLfile
usingyour
browser; if the appletcomes
up and works,
theneverything
checksout,
right?
Here'swhere
you can getfooled. If
the browser on the client
machine
cannotlocate
the .classfile
on the server, it will
huntthrough the
CLASSPATH
on your localmachine.Thus,
the applet mightnot
be
loadingproperly
from the server,but to
you it looks fineduring
your
testingprocess
because the browserfinds it
on your machine.When
someoneelse
connects, however, his or
her browser can't
findit. So when
you'retesting,
make sure youerase
the relevant .classfiles(or
.jar file)
on
your local machine to
verifythat they exist in
theproper location on
theserver.
One
of the most insidious
placeswhere this happened to
me is when I
innocentlyplaced
an applet inside a package.
After uploading
theHTML
fileand
applet, it turned outthat
the server path to the
applet was
confusedbecause
of the package name.However,
my browser found it in
thelocal
CLASSPATH. So I wasthe only
one whocould properly
loadthe
applet.
It took some time to
discover that the packagestatementwas
the
culprit.
In general, you'll want to
leave the packagestatementout
of an
applet.
Chapter13:
Creating Windows &
Applets
699
Running
applets from the
command
line
Thereare
times when you'dlike to
make a windowedprogram
do
somethingelse
other than sit on a Web
page. Perhaps you'dalso
like it to
do
some of the things a
"regular" application can do
but still
havethe
vauntedinstant
portability provided by Java. In
previous chapters in
this
bookwe've
made command-lineapplications,
but in someoperating
environments(the
Macintosh, for example)there
isn't a commandline.
So
for any number of
reasonsyou'd like to build a
windowed, non-applet
programusing
Java. This is certainly a
reasonabledesire.
TheSwing
library allows you to make
an application thatpreserves
the
lookand
feel of the
underlyingoperating environment. If
youwant to
buildwindowed
applications, it makessense to do
so5 only if you
canuse
thelatest
version of Java
andassociated tools so you
candeliver
applicationsthat
won't confound yourusers. If
for some
reasonyou're
forced
to use an older version of
Java, think hard
beforecommitting to
building
a significant
windowedapplication.
Oftenyou'll
want to be able to create a
class that can be invoked as
either
a
window or an applet. This is
especially convenient
whenyou're testing
theapplets,
since it'stypically much
faster andeasier to run
theresulting
applet-applicationfrom
the command linethan it is
to start up a Web
browser
or the Appletviewer.
To
create an applet that can be
run from the
consolecommand line,
you
simplyadd
a main(
) to
your applet that builds an
instance of theapplet
inside
a JFrame.6
As a
simpleexample, let's look at
Applet1b.java
modified
to work as both an
applicationand an
applet:
//:
c13:Applet1c.java
5
In my
opinion.And after you learn
about Swing, you won't want
to wasteyour time on
the
earlier stuff.
6
As
describedearlier, "Frame"
was already taken by the AWT, so Swing
uses JFrame.
700
Thinking
in Java
//
An application and an applet.
//
< applet code=Applet1c width=100
height=50>
//
< /applet>
import
javax.swing.*;
import
java.awt.*;
import
com.bruceeckel.swing.*;
public
class Applet1c extends JApplet {
public
void init() {
getContentPane().add(newJLabel("Applet!"));
}
//
A main() for the application:
public
static void main(String[] args) {
JApplet
applet = new Applet1c();
JFrame
frame = new JFrame("Applet1c");
//
To close the application:
Console.setupClosing(frame);
frame.getContentPane().add(applet);
frame.setSize(100,50);
applet.init();
applet.start();
frame.setVisible(true);
}
}
///:~
main(
) is
the only element added to
the applet, and
therest of the
applet
is
untouched. The applet is
created and added to a
JFrame
so
that it can
be
displayed.
Theline:
Console.setupClosing(frame);
Causesthe
window to be properlyclosed.
Consolecomesfrom
com.bruceeckel.swingandwill
be explained a
littlelater.
Youcan
see that in main(
),
the applet is
explicitlyinitialized
andstarted
since
in this case the
browserisn't available to do it
foryou. Of course,
thisdoesn't
provide the fullbehavior of
the browser,which also
calls
Chapter13:
Creating Windows &
Applets
701
stop(
) anddestroy(
),
but for most
situationsit's acceptable. If
it's a
problem,you
can force thecalls
yourself.7
Noticethe
last line:
frame.setVisible(true);
Withoutthis,
you won't seeanything on
thescreen.
A
display framework
Althoughthe
code that turnsprograms
into both appletsand
applications
producesvaluable
results, if usedeverywhere it
becomes
distractingand
wastespaper.
Instead, thefollowing
display frameworkwill be
used for
theSwing
examples in the rest of
this book:
//:
com:bruceeckel:swing:Console.java
//
Tool for running Swing demos from the
//
console, both applets and JFrames.
package
com.bruceeckel.swing;
import
javax.swing.*;
import
java.awt.event.*;
public
class Console {
//
Create a title string from the class name:
public
static String title(Object o) {
String
t = o.getClass().toString();
//
Remove the word "class":
if(t.indexOf("class")
!= -1)
t
= t.substring(6);
return
t;
}
public
static void setupClosing(JFrame frame) {
//
The JDK 1.2 Solution as an
//
anonymous inner class:
frame.addWindowListener(newWindowAdapter()
{
7
This
willmake sense after you've
readfurther in this chapter.
First,make the
reference
JAppleta
staticmember
of the class (instead of a
localvariable of main(
)),
and then
callapplet.stop(
) andapplet.destroy(
) inside
WindowAdapter.windowClosing(
) beforeyou
call System.exit(
).
702
Thinking
in Java
public
void windowClosing(WindowEvent e) {
System.exit(0);
}
});
//
The improved solution in JDK 1.3:
//
frame.setDefaultCloseOperation(
//
EXIT_ON_CLOSE);
}
public
static void
run(JFrame
frame, int width, int height) {
setupClosing(frame);
frame.setSize(width,
height);
frame.setVisible(true);
}
public
static void
run(JApplet
applet, int width, int height) {
JFrame
frame = new JFrame(title(applet));
setupClosing(frame);
frame.getContentPane().add(applet);
frame.setSize(width,
height);
applet.init();
applet.start();
frame.setVisible(true);
}
public
static void
run(JPanel
panel, int width, int height) {
JFrame
frame = new JFrame(title(panel));
setupClosing(frame);
frame.getContentPane().add(panel);
frame.setSize(width,
height);
frame.setVisible(true);
}
}
///:~
This
is a tool you may want to
use yourself, so it's placed
in the library
com.bruceeckel.swing.
The Consoleclassconsists
entirely of static
methods.The
first is used to extractthe
class name (usingRTTI)
from any
objectand
to remove the word"class,"
which is typicallyprepended
by
getClass(
).
This uses the StringmethodsindexOf(
) to
determine
whetherthe
word "class" is there,and
substring(
) to
produce the new
Chapter13:
Creating Windows &
Applets
703
stringwithout
"class" or thetrailing
space. This name is used to
label the
windowthat
is displayed by therun(
) methods.
setupClosing(
) is
used to hide the
codethat causes a JFrame
to
exit a
programwhen
that JFrame
is
closed. The default behavior
is to do
nothing,
so if you don't
callsetupClosing(
) or
write the
equivalentcode
foryour
JFrame,
the application won't
close.The reason this
code is
hiddenrather
than placing it directly in
the subsequentrun(
) methods
is
partlybecause
it allows you to usethe
method by itself whenwhat
you
want
to do is more complicated
thanwhat run(
) provides.However,
it
alsoisolates
a change factor:Java 2 has
two ways of causing certain
types
of
windows to close. In JDK1.2,
the solution is to create a
new
WindowAdapter
classand
implement windowClosing(
),
as seen
above(the
meaning of this will be
fully explained later in
this chapter).
However,during
the creation of JDK1.3
the library
designersobserved
thatyou
typically need to
closewindows whenever
you'recreating a non-
applet,and
so they added thesetDefaultCloseOperation(
) to
JFrame
andJDialog.
From the standpoint of
writing code,
thenew
method
is much nicer to use
butthis book was
writtenwhile there
was
still
no JDK 1.3 implemented on
Linux and other platforms,
so in the
interest
of cross-version portability
thechange was
isolatedinside
setupClosing(
).
Therun( )
method
is overloaded to work
withJApplets,
JPanels,
and
JFrames.
Note that only if it's a
JApplet
areinit( )
andstart( )
called.
Nowany
applet can be runfrom
the console by creating a
main(
)
containing
a line like this:
Console.run(new
MyClass(), 500, 300);
in
which the last
twoarguments are the
displaywidth and
height.Here's
Applet1c.javamodified
to use Console:
//:
c13:Applet1d.java
//
Console runs applets from the command line.
//
< applet code=Applet1d width=100
height=50>
//
< /applet>
import
javax.swing.*;
import
java.awt.*;
import
com.bruceeckel.swing.*;
704
Thinking
in Java
public
class Applet1d extends JApplet {
public
void init() {
getContentPane().add(newJLabel("Applet!"));
}
public
static void main(String[] args) {
Console.run(newApplet1d(),
100, 50);
}
}
///:~
Thisallows
the elimination of repeated
code whileproviding
thegreatest
flexibility
in running
theexamples.
Using
the Windows Explorer
If
you're using Windows,
youcan simplify the
process of running a
command-lineJava
program by configuringthe
WindowsExplorer--the
filebrowser
in Windows, not
theInternet
Explorer--so thatyou
can
simplydouble-click
on a .classfile
to execute it. There
areseveral steps in
thisprocess.
First,download
and install thePerl
programming
languagefrom
www.Perl.org.
You'll find the
instructionsand
languagedocumentation
on
that site.
Next,create
the following scriptwithout
the first andlast
lines (thisscript
is
part of this
book'ssource-code
package):
//:!
c13:RunJava.bat
@rem
= '--*-Perl-*--
@echo
off
perl
-x -S "%0" %1 %2 %3 %4 %5 %6 %7 %8 %9
goto
endofperl
@rem
';
#!perl
$file
= $ARGV[0];
$file
=~ s/(.*)\..*/\1/;
$file
=~ s/(.*\\)*(.*)/$+/;
`java
$file`;
__END__
:endofperl
///:~
Chapter13:
Creating Windows &
Applets
705
Now,open
the Windows Explorer,select
"View," "FolderOptions,"
then
click
on the "File Types"
tab.Press the "New
Type"button. For
"Description
of Type" enter "Java
classfile." For
"AssociatedExtension,"
enter"class."
Under "Actions"press the
"New" button.For "Action"
enter
"Open,"and
for "Application used to
perform action" enter a
linelike this:
"c:\aaa\Perl\RunJava.bat"
"%L"
Youmust
customize the pathbefore
"RunJava.bat" to conform to
the
locationwhere
you placed thebatch
file.
Onceyou
perform thisinstallation,
you may runany
Java program by
simplydouble-clicking
on the .classfilecontaining
a main(
).
Making
a button
Making
a button is quite
simple:you just call
theJButton
constructor
withthe
label you want on the
button. You'll seelater
that you can do
fancierthings,
like putting graphicimages
on buttons.
Usuallyyou'll
want to create a field for
the buttoninside your
class so that
youcan
refer to it later.
TheJButton
is
a component--its own
littlewindow--that
will
automaticallyget
repainted as part of an update.
This means
thatyou
don'texplicitly
paint a button or any other
kind of control;you
simply
placethem
on the form andlet
them automatically takecare
of painting
themselves.
So to place a button on a
form,you do it inside
init(
):
//:
c13:Button1.java
//
Putting buttons on an applet.
//
< applet code=Button1 width=200
height=50>
//
< /applet>
import
javax.swing.*;
import
java.awt.*;
import
com.bruceeckel.swing.*;
public
class Button1 extends JApplet {
JButton
b1
= new JButton("Button 1"),
b2
= new JButton("Button 2");
706
Thinking
in Java
public
void init() {
Container
cp = getContentPane();
cp.setLayout(newFlowLayout());
cp.add(b1);
cp.add(b2);
}
public
static void main(String[] args) {
Console.run(new
Button1(), 200, 50);
}
}
///:~
Somethingnew
has been addedhere:
before any elementsare
placed on
thecontent
pane, it is given a new
"layout manager," of
type
FlowLayout.
The layout manager is
theway that the
paneimplicitly
decideswhere
to place the control on the
form. The normalbehavior of
an
applet
is to use the BorderLayout,
but that won't
workhere because
(as
youwill
learn later in thischapter
when controlling thelayout
of a form is
examined
in more detail) it defaults to
covering each
controlentirely with
everynew
one that is added.However,
FlowLayout
causesthe
controls
to
flow evenly onto
theform, left to right
andtop to bottom.
Capturing
an event
You'llnotice
that if you compileand
run the appletabove,
nothing
happenswhen
you press thebuttons.
This is where youmust
step in and
writesome
code to determine whatwill
happen. The basis of
event-driven
programming,which
comprises a lot of what a GUI is
about, is tying
events
to code that responds to
those events.
Theway
that this is accomplished in
Swing is by cleanlyseparating
the
interface(the
graphical components)and the
implementation
(thecode
thatyou
want to run when an event
happens to a component).Each
Swing
componentcan
report all theevents
that might happen to it,
and it can
reporteach
kind of eventindividually. So if
you're notinterested in,
for
example,whether
the mouse is beingmoved
over your button,you
don't
registeryour
interest in that event.It's
a very straightforwardand
elegant
way
to handle event-driven
programming,and once you
understandthe
basicconcepts
you can easilyuse
Swing components thatyou
haven't seen
Chapter13:
Creating Windows &
Applets
707
before--infact,
this model extends to
anything that can be
classified as a
JavaBean(which
you'll learn aboutlater in
thechapter).
At
first, we will just focus on
the main event of
interestfor the
componentsbeing
used. In the case of a
JButton,
this "event of
interest"
is
that the button is pressed.
To register your interest in
when a button is
pressed,you
call the JButton's
addActionListener(
) method.This
methodexpects
an argument that is an object
that implementsthe
ActionListenerinterface,which
contains a singlemethod
called
actionPerformed(
).
So all you have to do to
attach code to a JButton
is
to implement the ActionListenerinterface
in a class, and register
an
object
of that class with
theJButton
viaaddActionListener(
).
The
methodwill
be called when thebutton is
pressed (this is normally
referred
to
as a callback).
Butwhat
should the result of
pressing that button
be?We'd like to
see
somethingchange
on the screen, so a new
Swing component will
be
introduced:the
JTextField.
This is a place where
textcan be typed, or
in
thiscase
modified by theprogram.
Although there are a number
of ways
to
create a JTextField,
the simplest is just to
tellthe constructor
how
wideyou
want that field to be.
Once the JTextFieldis
placed on the
form,you
can modify itscontents by
using thesetText(
) method(there
aremany
other methods in JTextField,
but you must
lookthese up in
theHTML
documentation for theJDK
from java.sun.com).
Here is what
it
looks like:
//:
c13:Button2.java
//
Responding to button presses.
//
< applet code=Button2 width=200
height=75>
//
< /applet>
import
javax.swing.*;
import
java.awt.event.*;
import
java.awt.*;
import
com.bruceeckel.swing.*;
public
class
Button2
extends JApplet {
JButton
b1
= new
JButton("Button
1"),
b2
= new
JButton("Button
2");
JTextField
txt
= new JTextField(10);
708
Thinking
in Java
class
BL implements ActionListener {
public
void actionPerformed(ActionEvent e){
String
name =
((JButton)e.getSource()).getText();
txt.setText(name);
}
}
BL
al = new BL();
public
void init() {
b1.addActionListener(al);
b2.addActionListener(al);
Container
cp = getContentPane();
cp.setLayout(newFlowLayout());
cp.add(b1);
cp.add(b2);
cp.add(txt);
}
public
static void main(String[] args) {
Console.run(new
Button2(), 200, 75);
}
}
///:~
Creating
a JTextFieldandplacing
it on the canvastakes the
samesteps
as
for JButtons,
or for any Swing
component.The difference in
theabove
program
is in the creation of
theaforementioned ActionListenerclass
BL.
The argument to actionPerformed(
) is
of type ActionEvent,
whichcontains
all the informationabout
the event andwhere it
came
from.
In this case, I wanted to
describe the button
thatwas pressed:
getSource(
) producesthe
object where theevent
originated, and I
assumedthat
is a JButton.
getText(
) returnsthe
text that's on the
button,and
this is placed in
theJTextFieldto
prove that the
codewas
actuallycalled
when the buttonwas
pressed.
In
init(
),
addActionListener(
) is
used to register
theBL
objectwith
boththe
buttons.
It
is often more convenient to
code the ActionListeneras
an
anonymousinner
class, especiallysince you
tend to onlyuse a
single
instance
of each listener
class.Button2.javacan
be modified to use an
anonymousinner
class as follows:
Chapter13:
Creating Windows &
Applets
709
//:
c13:Button2b.java
//
Using anonymous inner classes.
//
< applet code=Button2b width=200
height=75>
//
< /applet>
import
javax.swing.*;
import
java.awt.event.*;
import
java.awt.*;
import
com.bruceeckel.swing.*;
public
class Button2b extends JApplet {
JButton
b1
= new JButton("Button 1"),
b2
= new JButton("Button 2");
JTextField
txt = new JTextField(10);
ActionListener
al = new ActionListener() {
public
void actionPerformed(ActionEvent e){
String
name =
((JButton)e.getSource()).getText();
txt.setText(name);
}
};
public
void init() {
b1.addActionListener(al);
b2.addActionListener(al);
Container
cp = getContentPane();
cp.setLayout(newFlowLayout());
cp.add(b1);
cp.add(b2);
cp.add(txt);
}
public
static void main(String[] args) {
Console.run(newButton2b(),
200, 75);
}
}
///:~
Theapproach
of using an anonymousinner
class will be preferred
(when
possible)for
the examples in
thisbook.
710
Thinking
in Java
Text
areas
A
JTextArea
is
like a JTextFieldexceptthat
it can have
multiplelines
andhas
more functionality. A particularly
useful method is append(
);
withthis
you can easilypour
output into theJTextArea,
thus making a
Swingprogram
an improvement (sinceyou can
scroll backward)over
whathas
been accomplished thusfar
using command-lineprograms
that
print
to standard output. As an
example,the following
program fills a
JTextArea
withthe
output from thegeographygenerator
in Chapter 9:
//:
c13:TextArea.java
//
Using the JTextArea control.
//
< applet code=TextArea width=475
height=425>
//
< /applet>
import
javax.swing.*;
import
java.awt.event.*;
import
java.awt.*;
import
java.util.*;
import
com.bruceeckel.swing.*;
import
com.bruceeckel.util.*;
public
class TextArea extends JApplet {
JButton
b
= new JButton("Add Data"),
c
= new JButton("Clear Data");
JTextArea
t = new JTextArea(20, 40);
Map
m = new HashMap();
public
void init() {
//
Use up all the data:
Collections2.fill(m,
Collections2.geography,
CountryCapitals.pairs.length);
b.addActionListener(newActionListener()
{
public
void actionPerformed(ActionEvent e){
for(Iterator
it= m.entrySet().iterator();
it.hasNext();){
Map.Entry
me = (Map.Entry)(it.next());
t.append(me.getKey()
+ ": "
+
me.getValue() + "\n");
}
Chapter13:
Creating Windows &
Applets
711
}
});
c.addActionListener(newActionListener()
{
public
void actionPerformed(ActionEvent e){
t.setText("");
}
});
Container
cp = getContentPane();
cp.setLayout(newFlowLayout());
cp.add(new
JScrollPane(t));
cp.add(b);
cp.add(c);
}
public
static void main(String[] args) {
Console.run(newTextArea(),
475, 425);
}
}
///:~
In
init(
),
the Map
is
filled with all
thecountries and
theircapitals. Note
thatfor
both buttons theActionListeneris
created and
addedwithout
defining
an intermediate variable,
sinceyou never need to
refer to that
listeneragain
during the program.The
"Add Data" buttonformats
and
appendsall
the data, whilethe
"Clear Data" buttonuses
setText(
) to
removeall
the text fromthe
JTextArea.
As
the JTextArea
is
added to the applet, it is
wrapped in a
JScrollPane,
to control scrolling
whentoo much text is
placed on the
screen.That's
all you must do in order to
produce fullscrolling
capabilities.Having
tried to figure outhow to do
the equivalent in
some
other
GUI programming environments, I am
veryimpressed with
the
simplicityand
good design of components
like JScrollPane.
Controlling
layout
Theway
that you placecomponents on
a form in Java is
probably
differentfrom
any other GUI systemyou've
used. First, it'sall
code; there
are
no "resources" that
controlplacement of components.
Second,the way
componentsare
placed on a form is controlled
not by
absolutepositioning
but
by a "layout manager"
thatdecides how the
componentslie based
on
theorder
that you add(
) them.The
size, shape, andplacement
of
712
Thinking
in Java
componentswill
be remarkably differentfrom
one layout manager
to
another.
In addition, the
layoutmanagers adapt to
thedimensions of your
applet
or application window, so if
thewindow dimension is
changed,the
size,shape,
and placement of
thecomponents can change in
response.
JApplet,
JFrame
JWindow,
andJDialogcanall
produce a
ContainerwithgetContentPane(
) thatcan
contain anddisplay
Components.
In Container,there's
a method called setLayout(
) that
allowsyou
to choose a differentlayout
manager. Otherclasses, such
as
JPanel,
contain and
displaycomponents directly
and so you also
setthe
layoutmanager
directly, withoutusing the
contentpane.
In
this section we'll
explorethe various layout
managers by placing
buttons
in them (since that's
thesimplest thing to do).
Therewon't be any
capturing
of button events since
theseexamples are just
intended to show
howthe
buttons are
laidout.
BorderLayout
Theapplet
uses a default layoutscheme:
the BorderLayout(a
number
of
the previous example
havechanged the layout
manager to
FlowLayout).
Without any
otherinstruction, this
takeswhatever you
add(
) to
it and places it in
thecenter, stretching
theobject all
theway
out
to the edges.
However,there's
more to theBorderLayout.
This layout
managerhas
theconcept
of four borderregions and a
center area.When you
add
something
to a panel that's using a
BorderLayoutyoucan
use the
overloadedadd( )
methodthat
takes a constant value as
its first
argument.This
value can be any of the
following:
BorderLayout.
NORTH (top)
BorderLayout.
SOUTH (bottom)
BorderLayout.
EAST (right)
BorderLayout.
WEST (left)
BorderLayout.CENTER(fillthe
middle, up to
theother
components
or to the edges)
If
you don't specify an area to
place the object, it
defaults to CENTER.
Chapter13:
Creating Windows &
Applets
713
Here's
a simple example.
Thedefault layout is
used,since JApplet
defaults
to BorderLayout:
//:
c13:BorderLayout1.java
//
Demonstrates BorderLayout.
//
< applet code=BorderLayout1
//
width=300 height=250> </applet>
import
javax.swing.*;
import
java.awt.*;
import
com.bruceeckel.swing.*;
public
class BorderLayout1 extends JApplet {
public
void init() {
Container
cp = getContentPane();
cp.add(BorderLayout.NORTH,
new
JButton("North"));
cp.add(BorderLayout.SOUTH,
new
JButton("South"));
cp.add(BorderLayout.EAST,
new
JButton("East"));
cp.add(BorderLayout.WEST,
new
JButton("West"));
cp.add(BorderLayout.CENTER,
new
JButton("Center"));
}
public
static void main(String[] args) {
Console.run(newBorderLayout1(),
300, 250);
}
}
///:~
Forevery
placement butCENTER,
the element that
youadd is
compressed
to fit in the
smallestamount of space
along onedimension
while
it is stretched to the
maximumalong the
otherdimension.
CENTER,
however, spreads out in
bothdimensions to
occupythe
middle.
FlowLayout
Thissimply
"flows" thecomponents onto
the form,from left to
rightuntil
thetop
space is full, thenmoves
down a row andcontinues
flowing.
714
Thinking
in Java
Here's
an example that sets
thelayout manager to
FlowLayout
andthen
placesbuttons
on the form. You'llnotice
that with FlowLayout
the
componentstake
on their "natural"size. A
JButton,
for example, will be
thesize
of its string.
//:
c13:FlowLayout1.java
//
Demonstrates FlowLayout.
//
< applet code=FlowLayout1
//
width=300 height=250> </applet>
import
javax.swing.*;
import
java.awt.*;
import
com.bruceeckel.swing.*;
public
class FlowLayout1 extends JApplet {
public
void init() {
Container
cp = getContentPane();
cp.setLayout(newFlowLayout());
for(int
i = 0; i < 20; i++)
cp.add(new
JButton("Button " + i));
}
public
static void main(String[] args) {
Console.run(newFlowLayout1(),
300, 250);
}
}
///:~
Allcomponents
will be compacted to their
smallest size in a
FlowLayout,
so you might get a
littlebit of surprising
behavior.For
example,because
a JLabelwill
be the size of its
string,attempting to
right-justifyits
text yields an
unchangeddisplay when
using
FlowLayout.
GridLayout
A
GridLayout
allowsyou
to build a table of components,
and as youadd
themthey
are placedleft-to-right and
top-to-bottom in the grid. In
the
constructoryou
specify the number of rows
and columns thatyou
need
andthese
are laid out in equal
proportions.
//:
c13:GridLayout1.java
//
Demonstrates GridLayout.
//
< applet code=GridLayout1
Chapter13:
Creating Windows &
Applets
715
//
width=300 height=250> </applet>
import
javax.swing.*;
import
java.awt.*;
import
com.bruceeckel.swing.*;
public
class GridLayout1 extends JApplet {
public
void init() {
Container
cp = getContentPane();
cp.setLayout(newGridLayout(7,3));
for(int
i = 0; i < 20; i++)
cp.add(new
JButton("Button " + i));
}
public
static void main(String[] args) {
Console.run(newGridLayout1(),
300, 250);
}
}
///:~
In
this case there are 21
slots but only 20
buttons.The last slot is
left
emptybecause
no "balancing" goes on with a
GridLayout.
GridBagLayout
TheGridBagLayoutprovidesyou
with tremendous control in
deciding
exactlyhow
the regions of yourwindow
will lay themselvesout
and
reformatthemselves
when the window is resized.
However, it'salso
the
mostcomplicated
layout manager,and quite
difficult to understand. It is
intendedprimarily
for automatic codegeneration
by a GUI builder(good
GUI
builders will use GridBagLayoutinstead
of absolute placement). If
yourdesign
is so complicated thatyou
feel you need to
use
GridBagLayout,
then you should be using a
GUI builder tool to
generatethat
design. If you feelyou
must know theintricate
details,I'll
referyou
to CoreJava
2 by
Horstmann & Cornell
(Prentice-Hall,1999),
or
a dedicated Swing book, as a
starting point.
Absolutepositioning
It
is also possible to set
theabsolute position of
thegraphical
components
in
this way:
1.
Set
a null
layoutmanager
for your Container:
setLayout(null).
716
Thinking
in Java
2.
CallsetBounds(
) or
reshape(
) (depending
on the language
version)for
each component, passing a
bounding rectangle in
pixel
coordinates.You
can do this in
theconstructor, or in paint(
),
depending
on what you want to
achieve.
Some
GUI builders use
thisapproach extensively,
butthis is usually
not
thebest
way to generate code.More
useful GUI builderswill
use
GridBagLayoutinstead.
BoxLayout
Becausepeople
had so much
troubleunderstanding and
workingwith
GridBagLayout,
Swing also includes
theBoxLayout,
which gives you
many
of the benefits of GridBagLayoutwithoutthe
complexity, so you
canoften
use it when youneed to do
hand-coded layouts(again, if
your
designbecomes
too complex, use a GUI
builder thatgenerates
GridBagLayouts
for you). BoxLayoutallowsyou
to control the
placement
of components either vertically or
horizontally, and to
control
thespace
between the componentsusing
something called"struts
and
glue."First,
let's see how to use
the BoxLayoutdirectly,
in the same way
thatthe
other layout managershave
beendemonstrated:
//:
c13:BoxLayout1.java
//
Vertical and horizontal BoxLayouts.
//
< applet code=BoxLayout1
//
width=450 height=200> </applet>
import
javax.swing.*;
import
java.awt.*;
import
com.bruceeckel.swing.*;
public
class BoxLayout1 extends JApplet {
public
void init() {
JPanel
jpv = new JPanel();
jpv.setLayout(
new
BoxLayout(jpv, BoxLayout.Y_AXIS));
for(int
i = 0; i < 5; i++)
jpv.add(new
JButton("" + i));
JPanel
jph = new JPanel();
jph.setLayout(
new
BoxLayout(jph, BoxLayout.X_AXIS));
for(int
i = 0; i < 5; i++)
Chapter13:
Creating Windows &
Applets
717
jph.add(new
JButton("" + i));
Container
cp = getContentPane();
cp.add(BorderLayout.EAST,
jpv);
cp.add(BorderLayout.SOUTH,
jph);
}
public
static void main(String[] args) {
Console.run(newBoxLayout1(),
450, 200);
}
}
///:~
Theconstructor
for BoxLayoutis
a bit different than
theother layout
managers--youprovide
the Containerthat
is to be controlled by the
BoxLayoutas
the first argument,
andthe direction of the
layout as the
secondargument.
To
simplify matters, there's a
special container
calledBox
thatuses
BoxLayoutas
its native manager.
Thefollowing example
laysout
componentshorizontally
and verticallyusing
Box,
which has two static
methods
to create boxes
withvertical and
horizontalalignment:
//:
c13:Box1.java
//
Vertical and horizontal BoxLayouts.
//
< applet code=Box1
//
width=450 height=200> </applet>
import
javax.swing.*;
import
java.awt.*;
import
com.bruceeckel.swing.*;
public
class Box1 extends JApplet {
public
void init() {
Box
bv = Box.createVerticalBox();
for(int
i = 0; i < 5; i++)
bv.add(new
JButton("" + i));
Box
bh = Box.createHorizontalBox();
for(int
i = 0; i < 5; i++)
bh.add(new
JButton("" + i));
Container
cp = getContentPane();
cp.add(BorderLayout.EAST,
bv);
cp.add(BorderLayout.SOUTH,
bh);
}
public
static void main(String[] args) {
718
Thinking
in Java
Console.run(new
Box1(), 450, 200);
}
}
///:~
Onceyou
have a Box,
you pass it as a
secondargument when
adding
components
to the content pane.
Strutsadd
space betweencomponents,
measured in pixels. To use a
strut,
yousimply
add it in between
theaddition of the
componentsthat you
wantspaced
apart:
//:
c13:Box2.java
//
Adding struts.
//
< applet code=Box2
//
width=450 height=300> </applet>
import
javax.swing.*;
import
java.awt.*;
import
com.bruceeckel.swing.*;
public
class Box2 extends JApplet {
public
void init() {
Box
bv = Box.createVerticalBox();
for(int
i = 0; i < 5; i++) {
bv.add(new
JButton("" + i));
bv.add(Box.createVerticalStrut(i*10));
}
Box
bh = Box.createHorizontalBox();
for(int
i = 0; i < 5; i++) {
bh.add(new
JButton("" + i));
bh.add(Box.createHorizontalStrut(i*10));
}
Container
cp = getContentPane();
cp.add(BorderLayout.EAST,
bv);
cp.add(BorderLayout.SOUTH,
bh);
}
public
static void main(String[] args) {
Console.run(new
Box2(), 450, 300);
}
}
///:~
Strutsseparate
components by a fixedamount,
but glue is theopposite:
it
separatescomponents
by as much as possible.Thus
it's more of a
"spring"
Chapter13:
Creating Windows &
Applets
719
than"glue"
(and the design on which
this was basedwas
called "springs
andstruts"
so the choice of theterm is
a bitmysterious).
//:
c13:Box3.java
//
Using Glue.
//
< applet code=Box3
//
width=450 height=300> </applet>
import
javax.swing.*;
import
java.awt.*;
import
com.bruceeckel.swing.*;
public
class Box3 extends JApplet {
public
void init() {
Box
bv = Box.createVerticalBox();
bv.add(new
JLabel("Hello"));
bv.add(Box.createVerticalGlue());
bv.add(new
JLabel("Applet"));
bv.add(Box.createVerticalGlue());
bv.add(new
JLabel("World"));
Box
bh = Box.createHorizontalBox();
bh.add(new
JLabel("Hello"));
bh.add(Box.createHorizontalGlue());
bh.add(new
JLabel("Applet"));
bh.add(Box.createHorizontalGlue());
bh.add(new
JLabel("World"));
bv.add(Box.createVerticalGlue());
bv.add(bh);
bv.add(Box.createVerticalGlue());
getContentPane().add(bv);
}
public
static void main(String[] args) {
Console.run(new
Box3(), 450, 300);
}
}
///:~
A
strut works in onedirection,
but a rigid areafixes
the spacingbetween
components
in both directions:
//:
c13:Box4.java
//
Rigid Areas are like pairs of struts.
//
< applet code=Box4
//
width=450 height=300> </applet>
720
Thinking
in Java
import
javax.swing.*;
import
java.awt.*;
import
com.bruceeckel.swing.*;
public
class Box4 extends JApplet {
public
void init() {
Box
bv = Box.createVerticalBox();
bv.add(new
JButton("Top"));
bv.add(Box.createRigidArea(
new
Dimension(120, 90)));
bv.add(new
JButton("Bottom"));
Box
bh = Box.createHorizontalBox();
bh.add(new
JButton("Left"));
bh.add(Box.createRigidArea(
new
Dimension(160, 80)));
bh.add(new
JButton("Right"));
bv.add(bh);
getContentPane().add(bv);
}
public
static void main(String[] args) {
Console.run(new
Box4(), 450, 300);
}
}
///:~
Youshould
be aware that rigidareas
are a bitcontroversial.
Since theyuse
absolutevalues,
some people feelthat
they cause moretrouble
than they
areworth.
The
best approach?
Swing
is powerful; it can get a
lot done with a
fewlines of code.
The
examplesshown
in this book arereasonably
simple, and
forlearning
purposes
it makes sense to write
them by hand. You
canactually
accomplishquite
a bit by combiningsimple
layouts. At
somepoint,
however,
it stops making sense to
hand-code GUI forms--it
becomestoo
complicatedand
is not a good use of your
programming time.
TheJava
andSwing
designers oriented
thelanguage and libraries to
support GUI
buildingtools,
which have beencreated
for the expresspurpose of
making
yourprogramming
experience easier. As long as
you understandwhat's
going
on with layouts and how to
deal with the
events(described
next),
it'snot
particularly importantthat
you actually knowthe
details of how to
Chapter13:
Creating Windows &
Applets
721
layout
components by hand--letthe
appropriate tool do thatfor
you
(Javais,
after all, designed to
increase
programmerproductivity).
The
Swing event model
In
the Swing event model a
component can
initiate("fire") an
event.Each
type
of event is represented by a
distinctclass. When an event
is fired, it is
received
by one or more
"listeners,"which act on
that event.Thus,
the
source
of an event and the
placewhere the event is
handledcan be
separate.Since
you typically useSwing
components as theyare, but
need
to
write code that is
calledwhen the components
receive an event, this
is
an
excellent example of
theseparation of interface
andimplementation.
Eachevent
listener is an object of a class
that implements a
particular
type
of listener interface.
So as a programmer, all you do is
create a
listenerobject
and register it withthe
component that's firingthe
event.
Thisregistration
is performed by calling an addXXXListener( )
method
in
the event-firing component, in
which "XXX"
represents the type
of
eventlistened
for. You caneasily
know what types of events
can be
handled
by noticing the names of
the"addListener" methods,
and if you
try
to listen for the
wrongevents you'll discover
yourmistake at
compile-
time.You'll
see later in thechapter
that JavaBeans alsouse
the names of
the"addListener"
methods to determinewhat
events a Bean
canhandle.
All
of your event logic,
then,will go inside a
listenerclass. When
you
create
a listener class, the
solerestriction is that it
mustimplement the
appropriateinterface.
You can create a global
listener class, butthis is
a
situation
in which inner classes tend
to be quite useful, not
onlybecause
theyprovide
a logical grouping of your
listener classes insidethe
UI or
businesslogic
classes they areserving,
but because (asyou
shall seelater)
thefact
that an inner classobject
keeps a reference to its
parent object
provides
a nice way to call
acrossclass and
subsystemboundaries.
Allthe
examples so far in
thischapter have been
usingthe Swing
event
model,but
the remainder of thissection
will fill outthe
details of that
model.
722
Thinking
in Java
Event
and listener types
AllSwing
components includeaddXXXListener(
) and
removeXXXListener(
) methods
so that the
appropriatetypes of
listenerscan
be added and removedfrom
each component.
You'llnotice
thatthe
"XXX"
in each case also
representsthe argument for
themethod,
forexample:
addMyListener(MyListener
m).
The following table
includesthe
basic associated
events,listeners, and
methods,along with
thebasic
components thatsupport those
particularevents by
providing
theaddXXXListener( )
andremoveXXXListener(
) methods.You
shouldkeep
in mind that theevent
model is designed to be extensible,
so
youmay
encounter other eventsand
listener types thatare
not covered in
thistable.
Event,listener
interface and
Componentssupporting
this
add-
and remove-methods
event
ActionEvent
JButton,
JList, JTextField,
ActionListener
JMenuItem
andits
derivatives
addActionListener(
)
includingJCheckBoxMenuItem,
removeActionListener(
)
JMenu,
andJpopupMenu.
AdjustmentEvent
JScrollbar
AdjustmentListener
andanything
you createthat
addAdjustmentListener(
)
implementsthe
Adjustable
removeAdjustmentListener(
)
interface.
ComponentEvent
*Componentandits
derivatives,
ComponentListener
includingJButton,
JCanvas,
addComponentListener(
)
JCheckBox,JComboBox,
removeComponentListener(
)
Container,JPanel,
JApplet,
JScrollPane,
Window, JDialog,
JFileDialog,JFrame,
JLabel,
JList,JScrollbar,
JTextArea, and
JTextField.
ContainerEvent
Containerandits
derivatives,
ContainerListener
includingJPanel,JApplet,
addContainerListener(
)
JScrollPane,
Window, JDialog,
removeContainerListener(
)
JFileDialog,andJFrame.
FocusEvent
Componentandderivatives*.
FocusListener
addFocusListener(
)
removeFocusListener(
)
KeyEvent
Componentandderivatives*.
Chapter13:
Creating Windows &
Applets
723
Event,listener
interface and
Componentssupporting
this
add-
and remove-methods
event
KeyListener
addKeyListener(
)
removeKeyListener(
)
MouseEvent
(forboth
clicks and
Componentandderivatives*.
motion)
MouseListener
addMouseListener(
)
removeMouseListener(
)
MouseEvent8
(forboth
clicks and
Componentandderivatives*.
motion)
MouseMotionListener
addMouseMotionListener(
)
removeMouseMotionListener(
)
WindowEvent
Window
andits
derivatives,
WindowListener
includingJDialog,JFileDialog,
addWindowListener(
)
and
JFrame.
removeWindowListener(
)
ItemEvent
JCheckBox,
ItemListener
JCheckBoxMenuItem,
addItemListener(
)
JComboBox,JList,
andanything
removeItemListener(
)
thatimplements
the
ItemSelectableinterface.
TextEvent
Anythingderived
from
TextListener
JTextComponent,including
addTextListener(
)
JTextAreaandJTextField.
removeTextListener(
)
Youcan
see that eachtype of
component supportsonly
certain types of
events.
It turns out to be
ratherdifficult to look up
all theevents
supported
by each component. A
simplerapproach is to
modifythe
ShowMethodsClean.javaprogramfrom
Chapter 12 so that it
displays
allthe
event listenerssupported by
any Swingcomponent that
youenter.
Chapter
12 introduced reflectionandused
that feature to look up
methods
for
a particular class--either
theentire list of methods or
a subset of those
8
There is no
MouseMotionEventeven
though it seems like there ought to be.
Clicking
andmotion
is combined intoMouseEvent,
so this second appearance of
MouseEvent
in
the table is not an error.
724
Thinking
in Java
whosenames
match a keyword thatyou
provide. The magic of this
is that
it
can automatically show
youall
themethods
for a class
withoutforcing
you
to walk up the
inheritancehierarchy examining
the baseclasses at
eachlevel.
Thus, it provides a valuable
timesaving tool
forprogramming:
becausethe
names of most Javamethods
are made nicelyverbose
and
descriptive,you
can search forthe
method names thatcontain a
particular
word
of interest. When you
findwhat you think
you'relooking for,
check
theonline
documentation.
However,
by Chapter 12 you
hadn'tseen Swing, so the
tool in that chapter
wasdeveloped
as a command-line application.Here is
the moreuseful
GUI
version, specialized to
lookfor the
"addListener"methods in
Swing
components:
//:
c13:ShowAddListeners.java
//
Display the "addXXXListener" methods of any
//
Swing class.
//
< applet code = ShowAddListeners
//
width=500 height=400></applet>
import
javax.swing.*;
import
javax.swing.event.*;
import
java.awt.*;
import
java.awt.event.*;
import
java.lang.reflect.*;
import
java.io.*;
import
com.bruceeckel.swing.*;
import
com.bruceeckel.util.*;
public
class ShowAddListeners extends JApplet {
Class
cl;
Method[]
m;
Constructor[]
ctor;
String[]
n = new String[0];
JTextField
name = new JTextField(25);
JTextArea
results = new JTextArea(40, 65);
class
NameL implements ActionListener {
public
void actionPerformed(ActionEvent e) {
String
nm = name.getText().trim();
if(nm.length()
== 0) {
results.setText("No
match");
n
= new String[0];
Chapter13:
Creating Windows &
Applets
725
return;
}
try
{
cl
= Class.forName("javax.swing." + nm);
}
catch(ClassNotFoundException ex) {
results.setText("No
match");
return;
}
m
= cl.getMethods();
//
Convert to an array of Strings:
n
= new String[m.length];
for(int
i = 0; i < m.length; i++)
n[i]
= m[i].toString();
reDisplay();
}
}
void
reDisplay() {
//
Create the result set:
String[]
rs = new String[n.length];
int
j = 0;
for
(int i = 0; i < n.length; i++)
if(n[i].indexOf("add")
!= -1 &&
n[i].indexOf("Listener")
!= -1)
rs[j++]
=
n[i].substring(n[i].indexOf("add"));
results.setText("");
for
(int i = 0; i < j; i++)
results.append(
StripQualifiers.strip(rs[i])
+ "\n");
}
public
void init() {
name.addActionListener(new
NameL());
JPanel
top = new JPanel();
top.add(new
JLabel(
"Swing
class name (press ENTER):"));
top.add(name);
Container
cp = getContentPane();
cp.add(BorderLayout.NORTH,
top);
cp.add(new
JScrollPane(results));
}
public
static void main(String[] args) {
726
Thinking
in Java
Console.run(newShowAddListeners(),
500,400);
}
}
///:~
TheStripQualifiersclassdefined
in Chapter 12 is reusedhere
by
importingthe
com.bruceeckel.utillibrary.
The
GUI contains a JTextField
name in
which you can
enterthe Swing
classname
you want to lookup.
The results aredisplayed in
a
JTextArea.
You'llnotice
that there are no buttons or
other components by which
to
indicatethat
you want thesearch to
begin. That'sbecause the
JTextField
is
monitored by an ActionListener.
Whenever you make a
changeand
pressENTER,
the list is immediately
updated. If thetext isn't
empty, it is
usedinside
Class.forName(
) to
try to look up the class. If
the name is
incorrect,Class.forName(
) willfail,
which means that it throws
an
exception.This
is trapped and theJTextArea
is
set to "No match." But
if
youtype
in a correct name(capitalization
counts), Class.forName(
) is
successfuland
getMethods(
) willreturn
an array of Method
objects.
Each
of the objects in the array
is turned into a StringviatoString(
)
(thisproduces
the complete
methodsignature) and added
to n,
a String
array.The
array n
is
a member of classShowAddListeners
and
is used
in
updating the displaywhenever
reDisplay(
) is
called.
reDisplay(
) creates
an array of Stringcalledrs (for"result
set"). The
resultset
is conditionally copiedfrom
the Strings
in n
thatcontain
"add"
and"Listener."
indexOf(
) andsubstring(
) arethen
used to remove
thequalifiers
like public,
static,
etc. Finally, StripQualifiers.strip(
)
removesthe
extra namequalifiers.
Thisprogram
is a convenient way to investigate
the capabilities of a
Swing
component.Once
you know whichevents a
particularcomponent
supports,you
don't need to lookanything
up to react to thatevent.
You
simply:
1.
Takethe
name of the eventclass
and remove theword
"Event."
Addthe
word "Listener"
to what remains. This is
thelistener
interfaceyou
must implement in yourinner
class.
Chapter13:
Creating Windows &
Applets
727
2.
Implementthe
interface above andwrite
out the methodsfor
the
eventsyou
want to capture. Forexample,
you might be looking
for
mousemovements,
so you write codefor
the mouseMoved(
)
method
of the MouseMotionListenerinterface.(You
must
implementthe
other methods, of course,
but there's often a
shortcutfor
that which you'llsee
soon.)
3.
Create
an object of the
listenerclass in Step 2.
Register it with your
componentwith
the method produced by
prefixing "add"
to your
listenername.
For example, addMouseMotionListener(
).
Hereare
some of the
listenerinterfaces:
Listenerinterface
Methods
in interface
w/
adapter
ActionListener
actionPerformed(ActionEvent)
AdjustmentListener
adjustmentValueChanged(
AdjustmentEvent)
ComponentListener
componentHidden(ComponentEvent)
ComponentAdapter
componentShown(ComponentEvent)
componentMoved(ComponentEvent)
componentResized(ComponentEvent)
ContainerListener
componentAdded(ContainerEvent)
ContainerAdapter
componentRemoved(ContainerEvent)
FocusListener
focusGained(FocusEvent)
FocusAdapter
focusLost(FocusEvent)
KeyListener
keyPressed(KeyEvent)
KeyAdapter
keyReleased(KeyEvent)
keyTyped(KeyEvent)
MouseListener
mouseClicked(MouseEvent)
MouseAdapter
mouseEntered(MouseEvent)
mouseExited(MouseEvent)
mousePressed(MouseEvent)
mouseReleased(MouseEvent)
MouseMotionListener
mouseDragged(MouseEvent)
MouseMotionAdapter
mouseMoved(MouseEvent)
WindowListener
windowOpened(WindowEvent)
WindowAdapter
windowClosing(WindowEvent)
728
Thinking
in Java
Listenerinterface
Methods
in interface
w/
adapter
windowClosed(WindowEvent)
windowActivated(WindowEvent)
windowDeactivated(WindowEvent)
windowIconified(WindowEvent)
windowDeiconified(WindowEvent)
ItemListener
itemStateChanged(ItemEvent)
This
is not an exhaustive
listing,partly because the
eventmodel allows
you
to create your own
eventtypes and
associatedlisteners.
Thus,you'll
regularlycome
across libraries thathave
invented their ownevents,
and
theknowledge
gained in thischapter will
allow you to figure out
how to
usethese
events.
Usinglistener
adapters
forsimplicity
In
the table above,
youcan see that
somelistener interfaces
haveonly one
method.These
are trivial to implement
since you'llimplement them
only
whenyou
want to write thatparticular
method. However,the
listener
interfacesthat
have multiple methodscan be
less pleasant to
use.For
example,something
you must always do when
creating an application is
provide
a WindowListener
to
the JFrame
so
that when you
getthe
windowClosing(
) eventyou
can call System.exit(
) to
exit the
application.But
since WindowListener
is
an interface,
you must
implementall
of the other methodseven if
they don't do anything.
This
can
be annoying.
To
solve the problem,
some(but not all) of
thelistener
interfacesthat
havemore
than one methodare
provided with adapters,
the names of
whichyou
can see in thetable
above. Each adapterprovides
defaultempty
methodsfor
each of the
interfacemethods. Then all
youneed to do is
inheritfrom
the adapter andoverride
only the methodsyou
need to
change.For
example, the
typicalWindowListener
you'lluse
looks like
this(remember
that this hasbeen
wrapped inside theConsoleclass
in
com.bruceeckel.swing):
class
MyWindowListener extends WindowAdapter
{
public
void windowClosing(WindowEvent e) {
System.exit(0);
Chapter13:
Creating Windows &
Applets
729
}
}
Thewhole
point of the adapters is to
make the creation of
listener classes
easy.
There
is a downside to adapters, however, in
the form of a
pitfall.Suppose
youwrite
a WindowAdapter
likethe
one above:
class
MyWindowListener extends WindowAdapter
{
public
void WindowClosing(WindowEvent e) {
System.exit(0);
}
}
Thisdoesn't
work, but it willdrive
you crazy trying to figure
out why,
sinceeverything
will compile andrun
fine--except thatclosing
the
windowwon't
exit the program.Can
you see theproblem?
It's in thename
of
the method: WindowClosing(
) instead
of windowClosing(
).
A
simpleslip
in capitalization results in the
addition of a
completelynew
method.However,
this is not themethod
that's called whenthe
window is
closing,
so you don't get
thedesired results. Despite
theinconvenience, an
interfacewillguarantee
that the methodsare
properlyimplemented.
Trackingmultiple
events
To
prove to yourself that
theseevents are in fact
beingfired, and as an
interestingexperiment,
it's worthcreating an applet
thattracks extra
behavior
in a JButton
(otherthan
just whether it'spressed or
not). This
examplealso
shows you how to inherit
your own buttonobject
because
that'swhat
is used as the target of
all the events of interest.
To do so,you
canjust
inherit from JButton.9
TheMyButton
class
is an inner class of TrackEvent,
so MyButton
can
reachinto
the parent windowand
manipulate its textfields,
which is
what'snecessary
to be able to writethe
status information intothe
fields
of
the parent. Of course this
is a limited solution,
sincemyButton
can
be
9
In Java1.0/1.1
you couldnotusefully
inherit from the button
object.This was only
one of
numerous
fundamental design flaws.
730
Thinking
in Java
usedonly
in conjunction withTrackEvent.
This kind of code is
sometimescalled
"highlycoupled":
//:
c13:TrackEvent.java
//
Show events as they happen.
//
< applet code=TrackEvent
//
width=700 height=500></applet>
import
javax.swing.*;
import
java.awt.*;
import
java.awt.event.*;
import
java.util.*;
import
com.bruceeckel.swing.*;
public
class TrackEvent extends JApplet {
HashMap
h = new HashMap();
String[]
event = {
"focusGained","focusLost",
"keyPressed",
"keyReleased","keyTyped",
"mouseClicked",
"mouseEntered","mouseExited","mousePressed",
"mouseReleased","mouseDragged",
"mouseMoved"
};
MyButton
b1
= new MyButton(Color.blue, "test1"),
b2
= new MyButton(Color.red, "test2");
class
MyButton extends JButton {
void
report(String field, String msg) {
((JTextField)h.get(field)).setText(msg);
}
FocusListener
fl = new FocusListener() {
public
void focusGained(FocusEvent e) {
report("focusGained",e.paramString());
}
public
void focusLost(FocusEvent e) {
report("focusLost",e.paramString());
}
};
KeyListener
kl = new KeyListener() {
public
void keyPressed(KeyEvent e) {
report("keyPressed",e.paramString());
}
public
void keyReleased(KeyEvent e) {
Chapter13:
Creating Windows &
Applets
731
report("keyReleased",e.paramString());
}
public
void keyTyped(KeyEvent e) {
report("keyTyped",e.paramString());
}
};
MouseListener
ml = new MouseListener() {
public
void mouseClicked(MouseEvent e) {
report("mouseClicked",e.paramString());
}
public
void mouseEntered(MouseEvent e) {
report("mouseEntered",e.paramString());
}
public
void mouseExited(MouseEvent e) {
report("mouseExited",e.paramString());
}
public
void mousePressed(MouseEvent e) {
report("mousePressed",e.paramString());
}
public
void mouseReleased(MouseEvent e) {
report("mouseReleased",e.paramString());
}
};
MouseMotionListener
mml =
new
MouseMotionListener() {
public
void mouseDragged(MouseEvent e) {
report("mouseDragged",e.paramString());
}
public
void mouseMoved(MouseEvent e) {
report("mouseMoved",e.paramString());
}
};
public
MyButton(Color color, String label) {
super(label);
setBackground(color);
addFocusListener(fl);
addKeyListener(kl);
addMouseListener(ml);
addMouseMotionListener(mml);
}
}
732
Thinking
in Java
public
void init() {
Container
c = getContentPane();
c.setLayout(newGridLayout(event.length+1,2));
for(int
i = 0; i < event.length; i++) {
JTextField
t = new JTextField();
t.setEditable(false);
c.add(new
JLabel(event[i], JLabel.RIGHT));
c.add(t);
h.put(event[i],
t);
}
c.add(b1);
c.add(b2);
}
public
static void main(String[] args) {
Console.run(newTrackEvent(),
700, 500);
}
}
///:~
In
the MyButton
constructor,the
button's color is setwith a
call to
SetBackground(
).
The listeners are
allinstalled with
simplemethod
calls.
TheTrackEventclasscontains
a HashMap
to
hold the strings
representingthe
type of event andJTextFields
where
informationabout
thatevent
is held. Of course,these
could have beencreated
statically
ratherthan
putting them in a HashMap,
but I think you'll
agreethat it's
a
lot easier to use
andchange. In particular, if
youneed to add or
remove
a
new type of event in
TrackEvent,
you simply add or remove a
string in
theeventarray--everythingelse
happensautomatically.
Whenreport( )
is
called it is given the name
of the event
andthe
parameterstring
from the event. It uses
the HashMap
h in
the outer
class
to look up the
actualJTextFieldassociatedwith
that eventname,
andthen
places the parameterstring
into thatfield.
Thisexample
is fun to play withsince
you can reallysee
what's going on
withthe
events in
yourprogram.
Chapter13:
Creating Windows &
Applets
733
A
catalog of Swing
components
Nowthat
you understand
layoutmanagers and the
eventmodel, you're
ready
to see how Swing
componentscan be used. This
section is a
nonexhaustivetour
of the Swing componentsand
features thatyou'll
probablyuse
most of the time.Each
example is intended to be
reasonably
small
so that you can
easilylift the code
anduse it in your
ownprograms.
Youcan
easily see whateach of
these examples lookslike
while running
by
viewing the HTML pages in
the downloadable source
codefor this
chapter.
Keep
in mind:
1.
TheHTML
documentation fromjava.sun.comcontainsall
of the
Swingclasses
and methods (only a few
are shownhere).
2.
Because
of the naming
conventionused for Swing
events,it's fairly
easy
to guess how to write
andinstall a handler for a
particular type
of
event. Use the
lookupprogram ShowAddListeners.javafrom
earlier
in this chapter to aid in
your investigation of a
particular
component.
3.
Whenthings
start to getcomplicated you
shouldgraduate to a GUI
builder.
Buttons
Swingincludes
a number of differenttypes of
buttons. Allbuttons,
check
boxes,radio
buttons, and evenmenu
items are
inheritedfrom
AbstractButton(which,since
menu items areincluded,
wouldprobably
havebeen
better named"AbstractChooser" or
something
equallygeneral).
You'llsee
the use of menuitems
shortly, but thefollowing
exampleshows
thevarious
types of
buttonsavailable:
//:
c13:Buttons.java
//
Various Swing buttons.
//
< applet code=Buttons
734
Thinking
in Java
//
width=350 height=100></applet>
import
javax.swing.*;
import
java.awt.*;
import
java.awt.event.*;
import
javax.swing.plaf.basic.*;
import
javax.swing.border.*;
import
com.bruceeckel.swing.*;
public
class Buttons extends JApplet {
JButton
jb = new JButton("JButton");
BasicArrowButton
up
= new BasicArrowButton(
BasicArrowButton.NORTH),
down
= new BasicArrowButton(
BasicArrowButton.SOUTH),
right
= new BasicArrowButton(
BasicArrowButton.EAST),
left
= new BasicArrowButton(
BasicArrowButton.WEST);
public
void init() {
Container
cp = getContentPane();
cp.setLayout(newFlowLayout());
cp.add(jb);
cp.add(new
JToggleButton("JToggleButton"));
cp.add(new
JCheckBox("JCheckBox"));
cp.add(new
JRadioButton("JRadioButton"));
JPanel
jp = new JPanel();
jp.setBorder(newTitledBorder("Directions"));
jp.add(up);
jp.add(down);
jp.add(left);
jp.add(right);
cp.add(jp);
}
public
static void main(String[] args) {
Console.run(new
Buttons(), 350, 100);
}
}
///:~
Thisbegins
with the BasicArrowButtonfrom
javax.swing.plaf.basic,
then continues with
thevarious
specifictypes
Chapter13:
Creating Windows &
Applets
735
of
buttons. When you
runthe example, you'll
seethat the
togglebutton
holdsits
last position, in or out.
But the checkboxes
and radiobuttons
behaveidentically
to each other,just clicking
on or off(they are
inherited
fromJToggleButton).
Buttongroups
If
you want radio buttons to
behave in an "exclusive
or"fashion, you
must
addthem
to a "button group."But, as
the example
belowdemonstrates,
anyAbstractButtoncan
be added to a ButtonGroup.
To
avoid repeating a lot of
code, this example
usesreflection to
generate
thegroups
of different types of buttons.
This is seen in makeBPanel(
),
whichcreates
a button group and a
JPanel.
The second argument
to
makeBPanel(
) is
an array of String.
For each String,
a button of the
classrepresented
by the firstargument is
added to theJPanel:
//:
c13:ButtonGroups.java
//
Uses reflection to create groups
//
of different types of AbstractButton.
//
< applet code=ButtonGroups
//
width=500 height=300></applet>
import
javax.swing.*;
import
java.awt.*;
import
java.awt.event.*;
import
javax.swing.border.*;
import
java.lang.reflect.*;
import
com.bruceeckel.swing.*;
public
class ButtonGroups extends JApplet {
static
String[] ids = {
"June",
"Ward", "Beaver",
"Wally",
"Eddie", "Lumpy",
};
static
JPanel
makeBPanel(Class
bClass, String[] ids) {
ButtonGroup
bg = new ButtonGroup();
JPanel
jp = new JPanel();
String
title = bClass.getName();
title
= title.substring(
title.lastIndexOf('.')
+ 1);
736
Thinking
in Java
jp.setBorder(newTitledBorder(title));
for(int
i = 0; i < ids.length; i++) {
AbstractButton
ab = new JButton("failed");
try
{
//
Get the dynamic constructor method
//
that takes a String argument:
Constructor
ctor = bClass.getConstructor(
new
Class[] { String.class });
//
Create a new object:
ab
= (AbstractButton)ctor.newInstance(
new
Object[]{ids[i]});
}
catch(Exception ex) {
System.err.println("can't
create " +
bClass);
}
bg.add(ab);
jp.add(ab);
}
return
jp;
}
public
void init() {
Container
cp = getContentPane();
cp.setLayout(newFlowLayout());
cp.add(makeBPanel(JButton.class,
ids));
cp.add(makeBPanel(JToggleButton.class,
ids));
cp.add(makeBPanel(JCheckBox.class,
ids));
cp.add(makeBPanel(JRadioButton.class,
ids));
}
public
static void main(String[] args) {
Console.run(newButtonGroups(),
500, 300);
}
}
///:~
Thetitle
for the border is taken
from the name of the
class, strippingoff
allthe
path information.
TheAbstractButtonis
initialized to a
JButton
thathas
the label "Failed" so if you
ignore theexception
message,you'll
still see theproblem on
screen. ThegetConstructor(
)
methodproduces
a Constructorobjectthat
takes the array of
arguments
of
the types in the Classarraypassed
to getConstructor(
).
Then all
you
do is call newInstance(
),
passing it an array of Objectcontaining
youractual
arguments--in thiscase, just
the Stringfromthe
ids array.
Chapter13:
Creating Windows &
Applets
737
Thisadds
a little complexity to what is a
simple process. To get
"exclusive
or"behavior
with buttons, youcreate a
button group andadd
each button
forwhich
you want thatbehavior to
the group. Whenyou
run the
program,you'll
see that allthe
buttons except JButton
exhibitthis
"exclusiveor"
behavior.
Icons
Youcan
use an Icon
inside
a JLabelor
anything that
inheritsfrom
AbstractButton(includingJButton,
JCheckBox,
JRadioButton,
andthe
different kinds of JMenuItem).
Using Icons
with JLabels
is
quitestraightforward
(you'll see an example
later). Thefollowing
example
exploresall
the additional waysyou
can use Icons
with buttons
andtheir
descendants.
Youcan
use any giffilesyou
want, but theones
used in this
exampleare
part
of this book's
codedistribution, available at
.
To
open a file and bring in
the image, simply create an
ImageIcon
and
hand
it the file name.
Fromthen on, you
canuse the resulting
Icon in
yourprogram.
Notethat
path information is hard-coded
into thisexample; you
willneed
to
change the path to
correspond to the location of
the image files.
//:
c13:Faces.java
//
Icon behavior in Jbuttons.
//
< applet code=Faces
//
width=250 height=100></applet>
import
javax.swing.*;
import
java.awt.*;
import
java.awt.event.*;
import
com.bruceeckel.swing.*;
public
class Faces extends JApplet {
//
The following path information is necessary
//
to run via an applet directly from the disk:
static
String path =
"C:/aaa-TIJ2-distribution/code/c13/";
static
Icon[] faces = {
new
ImageIcon(path + "face0.gif"),
new
ImageIcon(path + "face1.gif"),
738
Thinking
in Java
new
ImageIcon(path + "face2.gif"),
new
ImageIcon(path + "face3.gif"),
new
ImageIcon(path + "face4.gif"),
};
JButton
jb
= new JButton("JButton", faces[3]),
jb2
= new JButton("Disable");
boolean
mad = false;
public
void init() {
Container
cp = getContentPane();
cp.setLayout(newFlowLayout());
jb.addActionListener(newActionListener()
{
public
void actionPerformed(ActionEvent e){
if(mad)
{
jb.setIcon(faces[3]);
mad
= false;
}
else {
jb.setIcon(faces[0]);
mad
= true;
}
jb.setVerticalAlignment(JButton.TOP);
jb.setHorizontalAlignment(JButton.LEFT);
}
});
jb.setRolloverEnabled(true);
jb.setRolloverIcon(faces[1]);
jb.setPressedIcon(faces[2]);
jb.setDisabledIcon(faces[4]);
jb.setToolTipText("Yow!");
cp.add(jb);
jb2.addActionListener(newActionListener()
{
public
void actionPerformed(ActionEvent e){
if(jb.isEnabled())
{
jb.setEnabled(false);
jb2.setText("Enable");
}
else {
jb.setEnabled(true);
jb2.setText("Disable");
}
}
});
Chapter13:
Creating Windows &
Applets
739
cp.add(jb2);
}
public
static void main(String[] args) {
Console.run(new
Faces(), 400, 200);
}
}
///:~
An
Icon can
be used in many
constructors,but you can
alsouse
setIcon(
) to
add or change an Icon.
This example also
showshow a
JButton
(orany
AbstractButton)
can set the
variousdifferent sorts
of
iconsthat
appear when thingshappen to
that button: whenit's
pressed,
disabled,
or "rolled over" (the
mousemoves over it
withoutclicking).
You'llsee
that this givesthe
button a nice
animatedfeel.
Tool
tips
Theprevious
example added a "tool tip"
to the button.Almost all of
the
classesthat
you'll be using to create
your user interfacesare
derived from
JComponent,
which contains a
methodcalled
setToolTipText(String).
So, for virtually
anythingyou place on
your
form,all
you need to do is say(for an
object jc
of
any JComponent-
derivedclass):
jc.setToolTipText("My
tip");
andwhen
the mouse staysover
that JComponent
for
a predetermined
period
of time, a tiny
boxcontaining your text
willpop up next to
the
mouse.
Textfields
Thisexample
shows the extrabehavior
that JTextFields
are capable of:
//:
c13:TextFields.java
//
Text fields and Java events.
//
< applet code=TextFields width=375
//
height=125></applet>
import
javax.swing.*;
import
javax.swing.event.*;
import
javax.swing.text.*;
import
java.awt.*;
import
java.awt.event.*;
740
Thinking
in Java
import
com.bruceeckel.swing.*;
public
class TextFields extends JApplet {
JButton
b1
= new JButton("Get Text"),
b2
= new JButton("Set Text");
JTextField
t1
= new JTextField(30),
t2
= new JTextField(30),
t3
= new JTextField(30);
String
s = new String();
UpperCaseDocument
ucd
= new UpperCaseDocument();
public
void init() {
t1.setDocument(ucd);
ucd.addDocumentListener(new
T1());
b1.addActionListener(new
B1());
b2.addActionListener(new
B2());
DocumentListener
dl = new T1();
t1.addActionListener(new
T1A());
Container
cp = getContentPane();
cp.setLayout(newFlowLayout());
cp.add(b1);
cp.add(b2);
cp.add(t1);
cp.add(t2);
cp.add(t3);
}
class
T1 implements DocumentListener {
public
void changedUpdate(DocumentEvent e){}
public
void insertUpdate(DocumentEvent e){
t2.setText(t1.getText());
t3.setText("Text:
"+ t1.getText());
}
public
void removeUpdate(DocumentEvent e){
t2.setText(t1.getText());
}
}
class
T1A implements ActionListener {
private
int count = 0;
public
void actionPerformed(ActionEvent e) {
Chapter13:
Creating Windows &
Applets
741
t3.setText("t1
Action Event " + count++);
}
}
class
B1 implements ActionListener {
public
void actionPerformed(ActionEvent e) {
if(t1.getSelectedText()
== null)
s
= t1.getText();
else
s
= t1.getSelectedText();
t1.setEditable(true);
}
}
class
B2 implements ActionListener {
public
void actionPerformed(ActionEvent e) {
ucd.setUpperCase(false);
t1.setText("Inserted
by Button 2: " + s);
ucd.setUpperCase(true);
t1.setEditable(false);
}
}
public
static void main(String[] args) {
Console.run(newTextFields(),
375, 125);
}
}
class
UpperCaseDocument extends PlainDocument
{
boolean
upperCase = true;
public
void setUpperCase(boolean flag) {
upperCase
= flag;
}
public
void insertString(int offset,
String
string, AttributeSet
attributeSet)
throws
BadLocationException {
if(upperCase)
string
= string.toUpperCase();
super.insertString(offset,
string,
attributeSet);
}
}
///:~
742
Thinking
in Java
TheJTextField
t3 is
included as a place to
reportwhen the
action
listenerfor
the JTextField
t1 is
fired. You'll see
thatthe action
listener
for
a JTextFieldis
fired only when
youpress the
"enter"key.
TheJTextField
t1 hasseveral
listeners attached to it.
The T1
listener
is a
DocumentListenerthatresponds
to any change in the
"document" (the
contents
of the JTextField,
in this case). It
automaticallycopies all
text
fromt1 intot2.
In addition, t1's
document is set to a
derivedclass of
PlainDocument,
called UpperCaseDocument,
which forces all
characters
to uppercase. It automatically
detectsbackspaces
andperforms
thedeletion,
adjusting thecaret and
handlingeverything as
youwould
expect.
Borders
JComponent
contains
a method called setBorder(
),
which allows you
to
place various
interestingborders on any
visiblecomponent. The
followingexample
demonstrates a number of the
different bordersthat
areavailable,
using a methodcalled
showBorder(
) thatcreates
a
JPanelandputs
on the border in eachcase.
Also, it uses RTTI to find
the
name
of the border that
you'reusing (stripping off
allthe path
information),then
puts that name in a
JLabelin
the middle of
thepanel:
//:
c13:Borders.java
//
Different Swing borders.
//
< applet code=Borders
//
width=500 height=300></applet>
import
javax.swing.*;
import
java.awt.*;
import
java.awt.event.*;
import
javax.swing.border.*;
import
com.bruceeckel.swing.*;
public
class Borders extends JApplet {
static
JPanel showBorder(Border b) {
JPanel
jp = new JPanel();
jp.setLayout(newBorderLayout());
String
nm = b.getClass().toString();
nm
= nm.substring(nm.lastIndexOf('.') + 1);
jp.add(new
JLabel(nm, JLabel.CENTER),
Chapter13:
Creating Windows &
Applets
743
BorderLayout.CENTER);
jp.setBorder(b);
return
jp;
}
public
void init() {
Container
cp = getContentPane();
cp.setLayout(newFlowLayout());
cp.setLayout(newGridLayout(2,4));
cp.add(showBorder(newTitledBorder("Title")));
cp.add(showBorder(newEtchedBorder()));
cp.add(showBorder(newLineBorder(Color.blue)));
cp.add(showBorder(
new
MatteBorder(5,5,30,30,Color.green)));
cp.add(showBorder(
new
BevelBorder(BevelBorder.RAISED)));
cp.add(showBorder(
new
SoftBevelBorder(BevelBorder.LOWERED)));
cp.add(showBorder(newCompoundBorder(
new
EtchedBorder(),
new
LineBorder(Color.red))));
}
public
static void main(String[] args) {
Console.run(new
Borders(), 500, 300);
}
}
///:~
Youcan
also create yourown
borders and putthem
inside
buttons,labels,
etc.--anythingderived
from JComponent.
JScrollPanes
Most
of the time you'll
justwant to let a JScrollPanedo
it's job,
butyou
canalso
control which scrollbars
are allowed--vertical,horizontal,
both,
or
neither:
//:
c13:JScrollPanes.java
//
Controlling the scrollbars in a
JScrollPane.
//
< applet code=JScrollPanes width=300
height=725>
//
< /applet>
import
javax.swing.*;
import
java.awt.*;
import
java.awt.event.*;
744
Thinking
in Java
import
javax.swing.border.*;
import
com.bruceeckel.swing.*;
public
class JScrollPanes extends JApplet {
JButton
b1
= new JButton("Text Area 1"),
b2
= new JButton("Text Area 2"),
b3
= new JButton("Replace Text"),
b4
= new JButton("Insert Text");
JTextArea
t1
= new JTextArea("t1", 1, 20),
t2
= new JTextArea("t2", 4, 20),
t3
= new JTextArea("t3", 1, 20),
t4
= new JTextArea("t4", 10, 10),
t5
= new JTextArea("t5", 4, 20),
t6
= new JTextArea("t6", 10, 10);
JScrollPane
sp3
= new JScrollPane(t3,
JScrollPane.VERTICAL_SCROLLBAR_NEVER,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER),
sp4
= new JScrollPane(t4,
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER),
sp5
= new JScrollPane(t5,
JScrollPane.VERTICAL_SCROLLBAR_NEVER,
JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS),
sp6
= new JScrollPane(t6,
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
class
B1L implements ActionListener {
public
void actionPerformed(ActionEvent e) {
t5.append(t1.getText()
+ "\n");
}
}
class
B2L implements ActionListener {
public
void actionPerformed(ActionEvent e) {
t2.setText("Inserted
by Button 2");
t2.append(":
" + t1.getText());
t5.append(t2.getText()
+ "\n");
}
}
Chapter13:
Creating Windows &
Applets
745
class
B3L implements ActionListener {
public
void actionPerformed(ActionEvent e) {
String
s = " Replacement ";
t2.replaceRange(s,
3, 3 + s.length());
}
}
class
B4L implements ActionListener {
public
void actionPerformed(ActionEvent e) {
t2.insert("
Inserted ", 10);
}
}
public
void init() {
Container
cp = getContentPane();
cp.setLayout(newFlowLayout());
//
Create Borders for components:
Border
brd = BorderFactory.createMatteBorder(
1,
1, 1, 1, Color.black);
t1.setBorder(brd);
t2.setBorder(brd);
sp3.setBorder(brd);
sp4.setBorder(brd);
sp5.setBorder(brd);
sp6.setBorder(brd);
//
Initialize listeners and add components:
b1.addActionListener(new
B1L());
cp.add(b1);
cp.add(t1);
b2.addActionListener(new
B2L());
cp.add(b2);
cp.add(t2);
b3.addActionListener(new
B3L());
cp.add(b3);
b4.addActionListener(new
B4L());
cp.add(b4);
cp.add(sp3);
cp.add(sp4);
cp.add(sp5);
cp.add(sp6);
}
public
static void main(String[] args) {
Console.run(newJScrollPanes(),
300, 725);
746
Thinking
in Java
}
}
///:~
Usingdifferent
arguments in theJScrollPaneconstructorcontrols
the
scrollbarsthat
are available. Thisexample
also dresses things up a
bit
usingborders.
A
mini-editor
TheJTextPanecontrolprovides
a great deal of support for
editing,
withoutmuch
effort. The followingexample
makes very simpleuse of
this,
ignoringthe
bulk of thefunctionality of
theclass:
//:
c13:TextPane.java
//
The JTextPane control is a little editor.
//
< applet code=TextPane width=475
height=425>
//
< /applet>
import
javax.swing.*;
import
java.awt.*;
import
java.awt.event.*;
import
com.bruceeckel.swing.*;
import
com.bruceeckel.util.*;
public
class TextPane extends JApplet {
JButton
b = new JButton("Add Text");
JTextPane
tp = new JTextPane();
static
Generator sg =
new
Arrays2.RandStringGenerator(7);
public
void init() {
b.addActionListener(newActionListener()
{
public
void actionPerformed(ActionEvent e){
for(int
i = 1; i < 10; i++)
tp.setText(tp.getText()
+
sg.next()
+ "\n");
}
});
Container
cp = getContentPane();
cp.add(new
JScrollPane(tp));
cp.add(BorderLayout.SOUTH,
b);
}
public
static void main(String[] args) {
Chapter13:
Creating Windows &
Applets
747
Console.run(newTextPane(),
475, 425);
}
}
///:~
Thebutton
just adds randomlygenerated
text. The intent of
the
JTextPaneis
to allow text to be edited in
place, so you will
seethat there
is
no append(
) method.
In this case (admittedly, a
poor use of the
capabilities
of JTextPane),
the text must be
captured,modified,
and
placedback
into the paneusing
setText(
).
As
mentioned before, thedefault
layout behavior of an applet is to
use the
BorderLayout.
If you add something to
thepane without
specifyingany
details,
it just fills the center of
the pane out to
theedges. However, if
you
specifyone
of the surroundingregions
(NORTH, SOUTH, EAST,
or
WEST)
as is done here,
thecomponent will fit
itselfinto that
region--in
thiscase,
the button willnest
down at the bottom of the
screen.
Noticethe
built-in features of JTextPane,
such as automaticline
wrapping.There
are lots of otherfeatures
that you canlook up
using the
JDKdocumentation.
Check
boxes
A
check box provides a way to
make a single on/off choice;
it consists of a
tinybox
and a label. Thebox
typically holds a little"x"
(or someother
indicationthat
it is set) or is empty,depending on
whether thatitem
was
selected.
You'llnormally
create a JCheckBoxusing
a constructor that
takesthe
label
as an argument. You can
getand set the
state,and also get
andset
thelabel
if you want to read or
change it after theJCheckBoxhasbeen
created.
Whenever
a JCheckBoxis
set or cleared, an
eventoccurs, which
youcan
capturethe
same way you do a button, by
using an ActionListener.
The
followingexample
uses a JTextArea
to
enumerate all the
checkboxes
thathave
been checked:
//:
c13:CheckBoxes.java
//
Using JCheckBoxes.
//
< applet code=CheckBoxes width=200
height=200>
748
Thinking
in Java
//
< /applet>
import
javax.swing.*;
import
java.awt.event.*;
import
java.awt.*;
import
com.bruceeckel.swing.*;
public
class CheckBoxes extends JApplet {
JTextArea
t = new JTextArea(6, 15);
JCheckBox
cb1
= new JCheckBox("Check Box 1"),
cb2
= new JCheckBox("Check Box 2"),
cb3
= new JCheckBox("Check Box 3");
public
void init() {
cb1.addActionListener(newActionListener()
{
public
void actionPerformed(ActionEvent e){
trace("1",
cb1);
}
});
cb2.addActionListener(newActionListener()
{
public
void actionPerformed(ActionEvent e){
trace("2",
cb2);
}
});
cb3.addActionListener(newActionListener()
{
public
void actionPerformed(ActionEvent e){
trace("3",
cb3);
}
});
Container
cp = getContentPane();
cp.setLayout(newFlowLayout());
cp.add(new
JScrollPane(t));
cp.add(cb1);
cp.add(cb2);
cp.add(cb3);
}
void
trace(String b, JCheckBox cb) {
if(cb.isSelected())
t.append("Box
" + b + " Set\n");
else
t.append("Box
" + b + " Cleared\n");
}
Chapter13:
Creating Windows &
Applets
749
public
static void main(String[] args) {
Console.run(newCheckBoxes(),
200, 200);
}
}
///:~
Thetrace( )
methodsends
the name of theselected
JCheckBoxandits
currentstate
to the JTextArea
usingappend(
),
so you'll see a
cumulativelist
of the checkboxes thatwere
selected and whattheir
state
is.
Radio
buttons
Theconcept
of a radio button in GUI programming
comes frompre-
electroniccar
radios with
mechanicalbuttons: when you
pushone in,
any
otherbutton
that was pressedpops
out. Thus, it allowsyou to
force a
singlechoice
among many.
Allyou
need to do to set up an associated
group of JRadioButtons
is to
addthem
to a ButtonGroup(youcan
have any number of
ButtonGroups
on a form). One of
thebuttons can optionally
haveits
startingstate
set to true
(usingthe
second argument in
theconstructor).
If
you try to set
morethan one radio
button to true
thenonly
the final
oneset
will be true.
Here's
a simple example of the use
of radio buttons. Note
thatyou capture
radiobutton
events like
allothers:
//:
c13:RadioButtons.java
//
Using JRadioButtons.
//
< applet code=RadioButtons
//
width=200 height=100> </applet>
import
javax.swing.*;
import
java.awt.event.*;
import
java.awt.*;
import
com.bruceeckel.swing.*;
public
class RadioButtons extends JApplet {
JTextField
t = new JTextField(15);
ButtonGroup
g = new ButtonGroup();
JRadioButton
rb1
= new JRadioButton("one", false),
750
Thinking
in Java
rb2
= new JRadioButton("two", false),
rb3
= new JRadioButton("three", false);
ActionListener
al = new ActionListener() {
public
void actionPerformed(ActionEvent e) {
t.setText("Radio
button " +
((JRadioButton)e.getSource()).getText());
}
};
public
void init() {
rb1.addActionListener(al);
rb2.addActionListener(al);
rb3.addActionListener(al);
g.add(rb1);g.add(rb2);
g.add(rb3);
t.setEditable(false);
Container
cp = getContentPane();
cp.setLayout(newFlowLayout());
cp.add(t);
cp.add(rb1);
cp.add(rb2);
cp.add(rb3);
}
public
static void main(String[] args) {
Console.run(newRadioButtons(),
200, 100);
}
}
///:~
To
display the state, a
textfield is used. This
field is set to
noneditable
becauseit's
used only to displaydata,
not to collect it.Thus it is
an
alternative
to using a JLabel.
Combo
boxes (drop-down lists)
Like
a group of radio buttons, a
drop-down list is a way to
force the user
to
select only one
elementfrom a group of
possibilities.However, it's
a
morecompact
way to accomplishthis, and
it's easier to change
the
elements
of the list
withoutsurprising the user.
(Youcan change
radio
buttonsdynamically,
but that tends to be
visibly jarring).
Java'sJComboBox
box
is not like the
combobox in Windows,
whichlets
youselect
from a list or
type
in your own selection. With
a JComboBox
boxyou
choose one andonly
one element fromthe
list. In thefollowing
Chapter13:
Creating Windows &
Applets
751
example,the
JComboBox
boxstarts
with a certain number of
entries
andthen
new entries areadded to
the box when a button is
pressed.
//:
c13:ComboBoxes.java
//
Using drop-down lists.
//
< applet code=ComboBoxes
//
width=200 height=100> </applet>
import
javax.swing.*;
import
java.awt.event.*;
import
java.awt.*;
import
com.bruceeckel.swing.*;
public
class ComboBoxes extends JApplet {
String[]
description = { "Ebullient",
"Obtuse",
"Recalcitrant","Brilliant",
"Somnescent",
"Timorous",
"Florid", "Putrescent" };
JTextField
t = new JTextField(15);
JComboBox
c = new JComboBox();
JButton
b = new JButton("Add items");
int
count = 0;
public
void init() {
for(int
i = 0; i < 4; i++)
c.addItem(description[count++]);
t.setEditable(false);
b.addActionListener(newActionListener()
{
public
void actionPerformed(ActionEvent e){
if(count
< description.length)
c.addItem(description[count++]);
}
});
c.addActionListener(newActionListener()
{
public
void actionPerformed(ActionEvent e){
t.setText("index:
"+ c.getSelectedIndex()
+"
"
+ ((JComboBox)e.getSource())
.getSelectedItem());
}
});
Container
cp = getContentPane();
cp.setLayout(newFlowLayout());
cp.add(t);
cp.add(c);
752
Thinking
in Java
cp.add(b);
}
public
static void main(String[] args) {
Console.run(newComboBoxes(),
200, 100);
}
}
///:~
TheJTextFielddisplaysthe
"selected index," which is
the sequence
number
of the currently
selectedelement, as well as
thelabel on the
radio
button.
List
boxes
Listboxes
are significantlydifferent
from JComboBox
boxes,and
not
just
in appearance. While a JComboBox
boxdrops
down whenyou
activateit,
a JListoccupiessome
fixed number of lines on a
screen all the
timeand
doesn't change. If youwant
to see the items in a list,
you simply
callgetSelectedValues(
), whichproduces
an array of Stringof
the
itemsthat
have beenselected.
A
JListallowsmultiple
selection: if youcontrol-click on
more thanone
item(holding
down the "control"key
while performingadditional
mouse
clicks)the
original item
stayshighlighted and you
canselect as many as
youwant.
If you select an item,then
shift-click on anotheritem,
all the
items
in the span between
thetwo are selected. To
remove an item from
a
groupyou
can control-clickit.
//:
c13:List.java
//
< applet code=List width=250
//
height=375> </applet>
import
javax.swing.*;
import
javax.swing.event.*;
import
java.awt.*;
import
java.awt.event.*;
import
javax.swing.border.*;
import
com.bruceeckel.swing.*;
public
class List extends JApplet {
String[]
flavors = {
"Chocolate","Strawberry",
"Vanilla
Fudge Swirl", "Mint Chip",
"Mocha
Almond Fudge", "Rum Raisin",
Chapter13:
Creating Windows &
Applets
753
"Praline
Cream", "Mud Pie" };
DefaultListModel
lItems=new DefaultListModel();
JList
lst = new JList(lItems);
JTextArea
t = new JTextArea(flavors.length,20);
JButton
b = new JButton("Add Item");
ActionListener
bl = new ActionListener() {
public
void actionPerformed(ActionEvent e) {
if(count
< flavors.length) {
lItems.add(0,flavors[count++]);
}
else {
//
Disable, since there are no more
//
flavors left to be added to the List
b.setEnabled(false);
}
}
};
ListSelectionListener
ll =
new
ListSelectionListener() {
public
void valueChanged(
ListSelectionEvent
e) {
t.setText("");
Object[]
items=lst.getSelectedValues();
for(int
i = 0; i < items.length; i++)
t.append(items[i]
+ "\n");
}
};
int
count = 0;
public
void init() {
Container
cp = getContentPane();
t.setEditable(false);
cp.setLayout(newFlowLayout());
//
Create Borders for components:
Border
brd = BorderFactory.createMatteBorder(
1,
1, 2, 2, Color.black);
lst.setBorder(brd);
t.setBorder(brd);
//
Add the first four items to the List
for(int
i = 0; i < 4; i++)
lItems.addElement(flavors[count++]);
//
Add items to the Content Pane for Display
cp.add(t);
754
Thinking
in Java
cp.add(lst);
cp.add(b);
//
Register event listeners
lst.addListSelectionListener(ll);
b.addActionListener(bl);
}
public
static void main(String[] args) {
Console.run(new
List(), 250, 375);
}
}
///:~
Whenyou
press the button it adds
items to the topof
the list (because
addItem(
)'s
second argument is
0).
Youcan
see that bordershave
also been added to the
lists.
If
you just want to put an
array of Strings
into a JList,
there's a much
simplersolution:
you pass thearray to
the JListconstructor,and
it builds
thelist
automatically. The
onlyreason for using
the"list model" in
the
aboveexample
is so that the listcould be
manipulated duringthe
execution
of the program.
JLists
do not automatically
providedirect support
forscrolling. Of
course,all
you need to do is wrapthe
JListin
a JScrollPaneandall
the
detailsare
automatically managedfor
you.
Tabbed
panes
TheJTabbedPaneallowsyou
to create a "tabbeddialog,"
which hasfile-
foldertabs
running across oneedge,
and all youhave to do is
press a tab
to
bring forward a
differentdialog.
//:
c13:TabbedPane1.java
//
Demonstrates the Tabbed Pane.
//
< applet code=TabbedPane1
//
width=350 height=200> </applet>
import
javax.swing.*;
import
javax.swing.event.*;
import
java.awt.*;
import
com.bruceeckel.swing.*;
public
class TabbedPane1 extends JApplet {
Chapter13:
Creating Windows &
Applets
755
String[]
flavors = {
"Chocolate","Strawberry",
"Vanilla
Fudge Swirl", "Mint Chip",
"Mocha
Almond Fudge", "Rum Raisin",
"Praline
Cream", "Mud Pie" };
JTabbedPane
tabs = new JTabbedPane();
JTextField
txt = new JTextField(20);
public
void init() {
for(int
i = 0; i < flavors.length; i++)
tabs.addTab(flavors[i],
new
JButton("Tabbed pane " + i));
tabs.addChangeListener(newChangeListener(){
public
void stateChanged(ChangeEvent e) {
txt.setText("Tab
selected: " +
tabs.getSelectedIndex());
}
});
Container
cp = getContentPane();
cp.add(BorderLayout.SOUTH,
txt);
cp.add(tabs);
}
public
static void main(String[] args) {
Console.run(newTabbedPane1(),
350, 200);
}
}
///:~
In
Java, the use of
somesort of "tabbed
panel"mechanism is
quite
importantbecause
in applet programmingthe use
of pop-up dialogs is
discouraged
by automatically adding a
littlewarning to any
dialogthat
pops
up out of an applet.
Whenyou
run the programyou'll
see that theJTabbedPane
automaticallystacks
the tabs if thereare
too many of them to fit on
one
row.You
can see this by resizing
the window whenyou
run theprogram
fromthe
console commandline.
Message
boxes
Windowingenvironments
commonly contain a standard
set of message
boxesthat
allow you to quicklypost
information to the user or to
capture
informationfrom
the user. In Swing,these
message boxes
arecontained
in
JOptionPane.
You have many
differentpossibilities
(somequite
756
Thinking
in Java
sophisticated),but
the ones you'llmost
commonly use areprobably
the
messagedialog
and confirmationdialog,
invoked using thestatic
JOptionPane.showMessageDialog(
) andJOptionPane.
showConfirmDialog(
).
The following example shows
a subset of the
messageboxes
available withJOptionPane:
//:
c13:MessageBoxes.java
//
Demonstrates JoptionPane.
//
< applet code=MessageBoxes
//
width=200 height=150> </applet>
import
javax.swing.*;
import
java.awt.event.*;
import
java.awt.*;
import
com.bruceeckel.swing.*;
public
class MessageBoxes extends JApplet {
JButton[]
b = { new JButton("Alert"),
new
JButton("Yes/No"), new
JButton("Color"),
new
JButton("Input"), new JButton("3 Vals")
};
JTextField
txt = new JTextField(15);
ActionListener
al = new ActionListener() {
public
void actionPerformed(ActionEvent e){
String
id =
((JButton)e.getSource()).getText();
if(id.equals("Alert"))
JOptionPane.showMessageDialog(null,
"There's
a bug on you!", "Hey!",
JOptionPane.ERROR_MESSAGE);
else
if(id.equals("Yes/No"))
JOptionPane.showConfirmDialog(null,
"or
no", "choose yes",
JOptionPane.YES_NO_OPTION);
else
if(id.equals("Color")) {
Object[]
options = { "Red", "Green" };
int
sel = JOptionPane.showOptionDialog(
null,
"Choose a Color!", "Warning",
JOptionPane.DEFAULT_OPTION,
JOptionPane.WARNING_MESSAGE,
null,
options,
options[0]);
if(sel
!= JOptionPane.CLOSED_OPTION)
Chapter13:
Creating Windows &
Applets
757
txt.setText(
"Color
Selected: " + options[sel]);
}
else if(id.equals("Input")) {
String
val = JOptionPane.showInputDialog(
"How
many fingers do you see?");
txt.setText(val);
}
else if(id.equals("3 Vals")) {
Object[]
selections = {
"First",
"Second", "Third" };
Object
val = JOptionPane.showInputDialog(
null,
"Choose one", "Input",
JOptionPane.INFORMATION_MESSAGE,
null,
selections, selections[0]);
if(val
!= null)
txt.setText(
val.toString());
}
}
};
public
void init() {
Container
cp = getContentPane();
cp.setLayout(newFlowLayout());
for(int
i = 0; i < b.length; i++) {
b[i].addActionListener(al);
cp.add(b[i]);
}
cp.add(txt);
}
public
static void main(String[] args) {
Console.run(newMessageBoxes(),
200, 200);
}
}
///:~
To
be able to write a
singleActionListener,
I've used
thesomewhat
riskyapproach
of checking theStringlabels
on the buttons.
Theproblem
withthis
is that it's easy to get
the label a littlebit
wrong, typically in
capitalization,and
this bug can be hard to
spot.
Notethat
showOptionDialog(
) andshowInputDialog( )
provide
returnobjects
that contain thevalue
entered by theuser.
758
Thinking
in Java
Menus
Eachcomponent
capable of holding a menu,
including JApplet,
JFrame,
JDialog,
and their descendants, has a
setJMenuBar(
)
methodthat
accepts a JMenuBar
(youcan
have only oneJMenuBar
on
a
particular component).
Youadd JMenus
to the JMenuBar,
and
JMenuItems
to the JMenus.
Each JMenuItem
canhave
an
ActionListenerattached
to it, to be fired when
thatmenu item is
selected.
Unlike
a system that
usesresources, with Java
andSwing you
musthand
assembleall
the menus in sourcecode.
Here is a very
simplemenu
example:
//:
c13:SimpleMenus.java
//
< applet code=SimpleMenus
//
width=200 height=75> </applet>
import
javax.swing.*;
import
java.awt.event.*;
import
java.awt.*;
import
com.bruceeckel.swing.*;
public
class SimpleMenus extends JApplet {
JTextField
t = new JTextField(15);
ActionListener
al = new ActionListener() {
public
void actionPerformed(ActionEvent e){
t.setText(
((JMenuItem)e.getSource()).getText());
}
};
JMenu[]
menus = { new JMenu("Winken"),
new
JMenu("Blinken"), new JMenu("Nod")
};
JMenuItem[]
items = {
new
JMenuItem("Fee"), new
JMenuItem("Fi"),
new
JMenuItem("Fo"), new
JMenuItem("Zip"),
new
JMenuItem("Zap"), new
JMenuItem("Zot"),
new
JMenuItem("Olly"), new
JMenuItem("Oxen"),
new
JMenuItem("Free") };
public
void init() {
for(int
i = 0; i < items.length; i++) {
items[i].addActionListener(al);
Chapter13:
Creating Windows &
Applets
759
menus[i%3].add(items[i]);
}
JMenuBar
mb = new JMenuBar();
for(int
i = 0; i < menus.length; i++)
mb.add(menus[i]);
setJMenuBar(mb);
Container
cp = getContentPane();
cp.setLayout(newFlowLayout());
cp.add(t);
}
public
static void main(String[] args) {
Console.run(newSimpleMenus(),
200, 75);
}
}
///:~
Theuse
of the modulus operator in
"i%3"
distributes the
menuitems
amongthe
three JMenus.
Each JMenuItem
musthave
an
ActionListenerattached
to it; here, the
sameActionListeneris
used
everywherebut
you'll usually need an
individual one
foreach
JMenuItem.
JMenuItem
inheritsAbstractButton,
so it has
somebuttonlike
behaviors.
By itself, it provides an
itemthat can be placed on a
drop-down
menu.There
are also threetypes
inherited fromJMenuItem:
JMenu to
holdother
JMenuItems
(so you can
havecascading menus),
JCheckBoxMenuItem,
which produces a checkmark to
indicate
whetherthat
menu item is selected,and
JRadioButtonMenuItem,
whichcontains
a radiobutton.
As
a more sophisticated
example,here are the
icecream flavors
again,
used
to create menus.
Thisexample also shows
cascadingmenus,
keyboardmnemonics,
JCheckBoxMenuItems,
and the way
youcan
dynamicallychange
menus:
//:
c13:Menus.java
//
Submenus, checkbox menu items, swapping menus,
//
mnemonics (shortcuts) and action commands.
//
< applet code=Menus width=300
//
height=100> </applet>
import
javax.swing.*;
import
java.awt.*;
760
Thinking
in Java
import
java.awt.event.*;
import
com.bruceeckel.swing.*;
public
class Menus extends JApplet {
String[]
flavors = {
"Chocolate","Strawberry",
"Vanilla
Fudge Swirl", "Mint Chip",
"Mocha
Almond Fudge", "Rum Raisin",
"Praline
Cream", "Mud Pie" };
JTextField
t = new JTextField("No flavor", 30);
JMenuBar
mb1 = new JMenuBar();
JMenu
f
= new JMenu("File"),
m
= new JMenu("Flavors"),
s
= new JMenu("Safety");
//
Alternative approach:
JCheckBoxMenuItem[]
safety = {
new
JCheckBoxMenuItem("Guard"),
new
JCheckBoxMenuItem("Hide")
};
JMenuItem[]
file = {
new
JMenuItem("Open"),
};
//
A second menu bar to swap to:
JMenuBar
mb2 = new JMenuBar();
JMenu
fooBar = new JMenu("fooBar");
JMenuItem[]
other = {
//
Adding a menu shortcut (mnemonic) is very
//
simple, but only JMenuItems can have them
//
in their constructors:
new
JMenuItem("Foo", KeyEvent.VK_F),
new
JMenuItem("Bar", KeyEvent.VK_A),
//
No shortcut:
new
JMenuItem("Baz"),
};
JButton
b = new JButton("Swap Menus");
class
BL implements ActionListener {
public
void actionPerformed(ActionEvent e) {
JMenuBar
m = getJMenuBar();
setJMenuBar(m
== mb1 ? mb2 : mb1);
validate();
// Refresh the frame
}
Chapter13:
Creating Windows &
Applets
761
}
class
ML implements ActionListener {
public
void actionPerformed(ActionEvent e) {
JMenuItem
target = (JMenuItem)e.getSource();
String
actionCommand =
target.getActionCommand();
if(actionCommand.equals("Open"))
{
String
s = t.getText();
boolean
chosen = false;
for(int
i = 0; i < flavors.length; i++)
if(s.equals(flavors[i]))
chosen = true;
if(!chosen)
t.setText("Choose
a flavor first!");
else
t.setText("Opening
"+ s +". Mmm, mm!");
}
}
}
class
FL implements ActionListener {
public
void actionPerformed(ActionEvent e) {
JMenuItem
target = (JMenuItem)e.getSource();
t.setText(target.getText());
}
}
//
Alternatively, you can create a different
//
class for each different MenuItem. Then you
//
Don't have to figure out which one it is:
class
FooL implements ActionListener {
public
void actionPerformed(ActionEvent e) {
t.setText("Fooselected");
}
}
class
BarL implements ActionListener {
public
void actionPerformed(ActionEvent e) {
t.setText("Barselected");
}
}
class
BazL implements ActionListener {
public
void actionPerformed(ActionEvent e) {
t.setText("Bazselected");
}
762
Thinking
in Java
}
class
CMIL implements ItemListener {
public
void itemStateChanged(ItemEvent e) {
JCheckBoxMenuItem
target =
(JCheckBoxMenuItem)e.getSource();
String
actionCommand =
target.getActionCommand();
if(actionCommand.equals("Guard"))
t.setText("Guard
the Ice Cream! " +
"Guarding
is " + target.getState());
else
if(actionCommand.equals("Hide"))
t.setText("Hide
the Ice Cream! " +
"Is
it cold? " + target.getState());
}
}
public
void init() {
ML
ml = new ML();
CMIL
cmil = new CMIL();
safety[0].setActionCommand("Guard");
safety[0].setMnemonic(KeyEvent.VK_G);
safety[0].addItemListener(cmil);
safety[1].setActionCommand("Hide");
safety[0].setMnemonic(KeyEvent.VK_H);
safety[1].addItemListener(cmil);
other[0].addActionListener(new
FooL());
other[1].addActionListener(new
BarL());
other[2].addActionListener(new
BazL());
FL
fl = new FL();
for(int
i = 0; i < flavors.length; i++) {
JMenuItem
mi = new JMenuItem(flavors[i]);
mi.addActionListener(fl);
m.add(mi);
//
Add separators at intervals:
if((i+1)
% 3 == 0)
m.addSeparator();
}
for(int
i = 0; i < safety.length; i++)
s.add(safety[i]);
s.setMnemonic(KeyEvent.VK_A);
f.add(s);
f.setMnemonic(KeyEvent.VK_F);
Chapter13:
Creating Windows &
Applets
763
for(int i = 0; i <
file.length; i++) {
file[i].addActionListener(fl);
f.add(file[i]);
}
mb1.add(f);
mb1.add(m);
setJMenuBar(mb1);
t.setEditable(false);
Container
cp = getContentPane();
cp.add(t,
BorderLayout.CENTER);
//
Set up the system for swapping menus:
b.addActionListener(new
BL());
b.setMnemonic(KeyEvent.VK_S);
cp.add(b,
BorderLayout.NORTH);
for(int
i = 0; i < other.length; i++)
fooBar.add(other[i]);
fooBar.setMnemonic(KeyEvent.VK_B);
mb2.add(fooBar);
}
public
static void main(String[] args) {
Console.run(new
Menus(), 300, 100);
}
}
///:~
In
this program I placed
themenu items into
arraysand then
stepped
througheach
array calling add(
) foreach
JMenuItem.
This makes
adding
or subtracting a menu
itemsomewhat
lesstedious.
Thisprogram
creates not onebut
two JMenuBars
to demonstrate that
menubars
can be actively swappedwhile
the program is running. You
can
seehow
a JMenuBar
is
made up of JMenus,
and each JMenu
is
made
up
of JMenuItems,
JCheckBoxMenuItems,
or even other JMenus
(whichproduce
submenus). When a JMenuBar
is
assembled it can be
installedinto
the current programwith
the setJMenuBar(
) method.
Notethat
when the button is pressed,
it checks to seewhich menu
is
currentlyinstalled
by calling getJMenuBar(
),
then it puts
theother
menubar
in its place.
Whentesting
for "Open," noticethat
spelling andcapitalization
are
critical,but
Java signals no error if
there is no match
with"Open." This
kind
of string comparison is a source of
programming errors.
764
Thinking
in Java
Thechecking
and unchecking of the menu
items is takencare of
automatically.The
code handling theJCheckBoxMenuItems
shows
twodifferent
ways to determinewhat was
checked:
stringmatching
(which,
as mentioned above, isn't a
very safe approach
althoughyou'll see
it
used) and matching on
theevent target object. As
shown, the
getState(
) methodcan
be used to reveal thestate.
You can
alsochange
thestate
of a JCheckBoxMenuItemwithsetState(
).
Theevents
for menus are a bit
inconsistent and canlead to
confusion:
JMenuItems
use ActionListeners,
but JCheckboxMenuItems
use
ItemListeners.
The JMenu
objectscan
also support ActionListeners,
butthat's
not usually helpful. In
general, you'll
attachlisteners to
each
JMenuItem,
JCheckBoxMenuItem,
or JRadioButtonMenuItem,
butthe
example shows ItemListeners
and ActionListeners
attached
to
the various
menucomponents.
Swingsupports
mnemonics, or "keyboardshortcuts," so
you canselect
anythingderived
from AbstractButton(button,menu
item, etc.)using
thekeyboard
instead of themouse. These
are quitesimple:
for
JMenuItem
youcan
use the
overloadedconstructor that
takes as a
secondargument
the identifier forthe
key. However,most
AbstractButtons
do not have
constructorslike this so the
moregeneral
way
to solve the problem is to
use the setMnemonic(
) method.The
exampleabove
adds mnemonics to thebutton
and some of
themenu
items;shortcut
indicators automaticallyappear on
thecomponents.
Youcan
also see theuse of
setActionCommand(
).
This seems a bit
strangebecause
in each case the"action
command" is exactlythe same
as
thelabel
on the menu component.Why
not just usethe
label instead of
thisalternative
string? Theproblem is
internationalization. If
youretarget
thisprogram
to another language,you want
to change onlythe label in
the
menu,and
not change thecode
(which would no
doubtintroduce new
errors).
So to make this easy
forcode that checks
thetext
stringassociated
with
a menu component,
the"action command" can be
immutable while
themenu
label can change.All
the code workswith
the "action
command,"
so it's unaffected by changes to
the menu labels.
Notethat in
thisprogram,
not all themenu
components are examinedfor
their action
commands,
so those that aren't
don'thave their action
commandset.
Chapter13:
Creating Windows &
Applets
765
Thebulk
of the work happens in the
listeners. BL
performsthe
JMenuBar
swapping.
In ML,
the "figure out
whorang" approach is
taken
by getting the source of
theActionEventandcasting
it to a
JMenuItem,
then getting the
actioncommand string to pass
it through a
cascadedif statement.
TheFL listener
is simple even though
it'shandling all the
differentflavors
in
the flavor menu.
Thisapproach is useful if
youhave
enoughsimplicity
in
your logic, but in
general,you'll want to take
theapproach used
with
FooL,
BarL, andBazL,
in which they are
eachattached to only a
single
menucomponent
so no extra detectionlogic is
necessary and
youknow
exactlywho
called the listener.Even
with the profusion of
classes
generatedthis
way, the codeinside
tends to be smallerand the
process is
morefoolproof.
Youcan
see that menucode
quickly getslong-winded and
messy. This is
anothercase
where the use of a GUI
builder is the
appropriatesolution. A
goodtool
will also handlethe
maintenance of
themenus.
Pop-up
menus
Themost
straightforward way to implement a
JPopupMenu
is
to create
an
inner class that
extendsMouseAdapter,
then add an object of
that
innerclass
to each component thatyou
want to produce
pop-upbehavior:
//:
c13:Popup.java
//
Creating popup menus with Swing.
//
< applet code=Popup
//
width=300 height=200></applet>
import
javax.swing.*;
import
java.awt.*;
import
java.awt.event.*;
import
com.bruceeckel.swing.*;
public
class Popup extends JApplet {
JPopupMenu
popup = new JPopupMenu();
JTextField
t = new JTextField(10);
public
void init() {
Container
cp = getContentPane();
cp.setLayout(newFlowLayout());
cp.add(t);
766
Thinking
in Java
ActionListener al = new
ActionListener() {
public
void actionPerformed(ActionEvent e){
t.setText(
((JMenuItem)e.getSource()).getText());
}
};
JMenuItem
m = new JMenuItem("Hither");
m.addActionListener(al);
popup.add(m);
m
= new JMenuItem("Yon");
m.addActionListener(al);
popup.add(m);
m
= new JMenuItem("Afar");
m.addActionListener(al);
popup.add(m);
popup.addSeparator();
m
= new JMenuItem("Stay Here");
m.addActionListener(al);
popup.add(m);
PopupListener
pl = new PopupListener();
addMouseListener(pl);
t.addMouseListener(pl);
}
class
PopupListener extends MouseAdapter
{
public
void mousePressed(MouseEvent e) {
maybeShowPopup(e);
}
public
void mouseReleased(MouseEvent e) {
maybeShowPopup(e);
}
private
void maybeShowPopup(MouseEvent e) {
if(e.isPopupTrigger())
{
popup.show(
e.getComponent(),
e.getX(), e.getY());
}
}
}
public
static void main(String[] args) {
Console.run(new
Popup(), 300, 200);
}
}
///:~
Chapter13:
Creating Windows &
Applets
767
Thesame
ActionListeneris
added to each JMenuItem,
so that it
fetchesthe
text from themenu
label and inserts it into
the JTextField.
Drawing
In
a good GUI framework,
drawingshould be reasonably
easy--and it is,
in
the Swing library.
Theproblem with any
drawingexample is that
the
calculationsthat
determine where things go
are typically a
lotmore
complicatedthat
the calls to thedrawing
routines, and
thesecalculations
areoften
mixed together withthe
drawing calls so it canseem
that the
interface
is more complicated than it
actually is.
Forsimplicity,
consider theproblem of
representing data on the
screen--
here,the
data will be provided by the
built-in Math.sin(
) methodwhich
is
a mathematical sine function. To
make things a little
moreinteresting,
and
to further demonstrate
howeasy it is to use
Swingcomponents, a
sliderwill
be placed at the bottom of
the form to
dynamicallycontrol
the
number
of sine wave cycles
thatare displayed. In
addition, if you
resize
thewindow,
you'll see thatthe
sine wave refitsitself to
the newwindow
size.
Althoughany
JComponent
may
be painted and thus used as
a canvas, if
youjust
want a straightforwarddrawing
surface you willtypically
inherit
from
a JPanel.
The only method
youneed to override
is
paintComponent(
),
which is called wheneverthat
component must be
repainted(you
normally don't need to worry
about this, as thedecision
is
managed
by Swing). When it is
called,Swing passes a
Graphicsobject
to
themethod,
and you canthen
use this object to draw or
paint on the
surface.
In
the following example,
allthe intelligence
concerningpainting is in
the
SineDraw
class;the
SineWave
classsimply
configures theprogram
andthe
slider control.
InsideSineDraw,
the setCycles(
) method
provides
a hook to allow
anotherobject--the slider
control, in this
case--
to
control the number of
cycles.
//:
c13:SineWave.java
//
Drawing with Swing, using a JSlider.
//
< applet code=SineWave
//
width=700 height=400></applet>
768
Thinking
in Java
import
javax.swing.*;
import
javax.swing.event.*;
import
java.awt.*;
import
com.bruceeckel.swing.*;
class
SineDraw extends JPanel {
static
final int SCALEFACTOR = 200;
int
cycles;
int
points;
double[]
sines;
int[]
pts;
SineDraw()
{ setCycles(5); }
public
void setCycles(int newCycles) {
cycles
= newCycles;
points
= SCALEFACTOR * cycles * 2;
sines
= new double[points];
pts
= new int[points];
for(int
i = 0; i < points; i++) {
double
radians = (Math.PI/SCALEFACTOR) * i;
sines[i]
= Math.sin(radians);
}
repaint();
}
public
void paintComponent(Graphics g) {
super.paintComponent(g);
int
maxWidth = getWidth();
double
hstep = (double)maxWidth/(double)points;
int
maxHeight = getHeight();
for(int
i = 0; i < points; i++)
pts[i]
= (int)(sines[i] * maxHeight/2 *
.95
+
maxHeight/2);
g.setColor(Color.red);
for(int
i = 1; i < points; i++) {
int
x1 = (int)((i - 1) * hstep);
int
x2 = (int)(i * hstep);
int
y1 = pts[i-1];
int
y2 = pts[i];
g.drawLine(x1,
y1, x2, y2);
}
}
}
Chapter13:
Creating Windows &
Applets
769
public class SineWave extends JApplet
{
SineDraw
sines = new SineDraw();
JSlider
cycles = new JSlider(1, 30, 5);
public
void init() {
Container
cp = getContentPane();
cp.add(sines);
cycles.addChangeListener(newChangeListener(){
public
void stateChanged(ChangeEvent e) {
sines.setCycles(
((JSlider)e.getSource()).getValue());
}
});
cp.add(BorderLayout.SOUTH,
cycles);
}
public
static void main(String[] args) {
Console.run(newSineWave(),
700, 400);
}
}
///:~
All
of the data members
andarrays are used in
thecalculation of
thesine
wavepoints:
cyclesindicatesthe
number of complete sinewaves
desired,
pointscontainsthe
total number of pointsthat
will be graphed,sines
containsthe
sine function values,and
ptscontainsthe
y-coordinates of
thepoints
that will be drawn on the
JPanel.
The setCycles(
) method
createsthe
arrays according to
thenumber of points needed
andfills the
sinesarraywith
numbers. By callingrepaint(
) ,
setCycles(
) forces
paintComponent(
) to
be called so the rest of
thecalculation and
redrawwill
take place.
Thefirst
thing you must do when
you override paintComponent(
) is
to
callthe
base-class version of the
method. Then youare
free to do
whateveryou
like; normally, thismeans
using the Graphicsmethods
thatyou
can find in thedocumentation
for java.awt.Graphics(inthe
HTMLdocumentation
from java.sun.com)
to draw and paint
pixelsonto
theJPanel.
Here, you can
seethat almost all
thecode is involved
in
performingthe
calculations; the onlytwo
method calls
thatactually
manipulatethe
screen are setColor(
) anddrawLine(
).
You will
probablyhave
a similar experiencewhen
creating your ownprogram
that
770
Thinking
in Java
displaysgraphical
data--you'll spendmost of
your time figuringout
what
it
is you want to draw,
butthe actual drawing
processwill be quite
simple.
When
I created this program,
thebulk of my time was
spent in getting the
sinewave
to display. Once I didthat,
I thought it would be nice to be
able
to
dynamically change thenumber
of cycles. My programming
experienceswhen
trying to do suchthings in
other languagesmade me
a
bitreluctant
to try this, but it turned
out to be theeasiest part of
the
project.
I created a JSlider(thearguments
are the left-mostvalue of
the
JSlider,
the right-most value,
andthe starting
value,respectively,
but
thereare
other constructors as well)
and dropped it intothe
JApplet.
Then
I looked at the
HTMLdocumentation and
noticedthat the
only
listenerwas
the addChangeListener,
which was triggered
wheneverthe
sliderwas
changed enough for it to
produce a different
value.The only
methodfor
this was theobviously
named stateChanged(
),
which
provided
a ChangeEventobject
so that I could
lookbackward to the
source
of the change and
findthe new value. By
callingthe sinesobject's
setCycles(
),
the new value
wasincorporated and
theJPanelredrawn.
In
general, you will
findthat most of your
Swingproblems can be
solved
by
following a similar
process,and you'll find
thatit's generally
quite
simple,even
if you haven't used a
particular
componentbefore.
If
your problem is morecomplex,
there are othermore
sophisticated
alternativesfor
drawing, includingthird-party
JavaBeanscomponents
andthe
Java 2D API. Thesesolutions
are beyond thescope of
this book,
butyou
should look them up if your
drawing code becomestoo
onerous.
Dialog
Boxes
A
dialog box is a window
thatpops up out of
anotherwindow. Its
purpose
is
to deal with some
specificissue without
cluttering theoriginal
window
withthose
details. Dialog boxesare
heavily used in
windowed
programmingenvironments,
but lessfrequently used in
applets.
To
create a dialog box,
youinherit from JDialog,
which is just another
kind
of Window,
like a JFrame.
A JDialoghas
a layout manager
(whichdefaults
to BorderLayout)
and you add
eventlisteners to
deal
withevents.
One significantdifference
when windowClosing(
) is
called
is
that you don't want to
shut down the
application.Instead, you
release
Chapter13:
Creating Windows &
Applets
771
theresources
used by thedialog's window
by callingdispose(
).
Here's a
verysimple
example:
//:
c13:Dialogs.java
//
Creating and using Dialog Boxes.
//
< applet code=Dialogs width=125
height=75>
//
< /applet>
import
javax.swing.*;
import
java.awt.event.*;
import
java.awt.*;
import
com.bruceeckel.swing.*;
class
MyDialog extends JDialog {
public
MyDialog(JFrame parent) {
super(parent,
"My dialog", true);
Container
cp = getContentPane();
cp.setLayout(newFlowLayout());
cp.add(new
JLabel("Here is my dialog"));
JButton
ok = new JButton("OK");
ok.addActionListener(newActionListener()
{
public
void actionPerformed(ActionEvent e){
dispose();
// Closes the dialog
}
});
cp.add(ok);
setSize(150,125);
}
}
public
class Dialogs extends JApplet {
JButton
b1 = new JButton("Dialog Box");
MyDialog
dlg = new MyDialog(null);
public
void init() {
b1.addActionListener(newActionListener()
{
public
void actionPerformed(ActionEvent e){
dlg.show();
}
});
getContentPane().add(b1);
}
public
static void main(String[] args) {
772
Thinking
in Java
Console.run(new
Dialogs(), 125, 75);
}
}
///:~
Oncethe
JDialogis
created, the show(
) methodmust
be called to
displayand
activate it. Forthe
dialog to close, it mustcall
dispose(
).
You'llsee
that anything thatpops up
out of an applet,including
dialog
boxes,
is "untrusted." That is,
youget a warning in the
windowthat's been
poppedup.
This is because, in theory, it
would be possible to fool
the user
intothinking
that they'redealing with a
regularnative application
and to
getthem
to type in their creditcard
number, which thengoes
across the
Web.
An applet is always attached to a
Web page and
visiblewithin your
Webbrowser,
while a dialog box is
detached--so in theory, it could
be
possible.
As a result it is not so common to
see an applet that uses a
dialog
box.
Thefollowing
example is morecomplex; the
dialog box is made up of
a
grid(using
GridLayout)
of a special kind of
buttonthat is defined
here
as
class ToeButton.
This button draws a
framearound itself
and,
depending
on its state, a blank, an
"x," or an "o" in themiddle.
It starts
outblank,
and then depending on whose
turn it is, changes to an
"x" or an
"o."However,
it will also flipback
and forth between"x"
and "o"
whenyou
click
on the button. (This
makesthe tic-tac-toe concept
onlyslightly more
annoyingthan
it already is.) In addition,
the dialog boxcan be
set up for
anynumber
of rows and columns by
changing numbers in
themain
applicationwindow.
//:
c13:TicTacToe.java
//
Demonstration of dialog boxes
//
and creating your own components.
//
< applet code=TicTacToe
//
width=200 height=100></applet>
import
javax.swing.*;
import
java.awt.*;
import
java.awt.event.*;
import
com.bruceeckel.swing.*;
public
class TicTacToe extends JApplet {
JTextField
rows
= new JTextField("3"),
Chapter13:
Creating Windows &
Applets
773
cols = new
JTextField("3");
static
final int BLANK = 0, XX = 1, OO = 2;
class
ToeDialog extends JDialog {
int
turn = XX; // Start with x's turn
//
w = number of cells wide
//
h = number of cells high
public
ToeDialog(int w, int h) {
setTitle("The
game itself");
Container
cp = getContentPane();
cp.setLayout(newGridLayout(w,
h));
for(int
i = 0; i < w * h; i++)
cp.add(new
ToeButton());
setSize(w
* 50, h * 50);
//
JDK 1.3 close dialog:
//#setDefaultCloseOperation(
//#
DISPOSE_ON_CLOSE);
//
JDK 1.2 close dialog:
addWindowListener(newWindowAdapter()
{
public
void windowClosing(WindowEvent e){
dispose();
}
});
}
class
ToeButton extends JPanel {
int
state = BLANK;
public
ToeButton() {
addMouseListener(new
ML());
}
public
void paintComponent(Graphics g) {
super.paintComponent(g);
int
x1 = 0;
int
y1 = 0;
int
x2 = getSize().width - 1;
int
y2 = getSize().height - 1;
g.drawRect(x1,
y1, x2, y2);
x1
= x2/4;
y1
= y2/4;
int
wide = x2/2;
int
high = y2/2;
if(state
== XX) {
g.drawLine(x1,
y1,
774
Thinking
in Java
x1 + wide, y1 +
high);
g.drawLine(x1,
y1 + high,
x1
+ wide, y1);
}
if(state
== OO) {
g.drawOval(x1,
y1,
x1
+ wide/2, y1 + high/2);
}
}
class
ML extends MouseAdapter {
public
void mousePressed(MouseEvent e) {
if(state
== BLANK) {
state
= turn;
turn
= (turn == XX ? OO : XX);
}
else
state
= (state == XX ? OO : XX);
repaint();
}
}
}
}
class
BL implements ActionListener {
public
void actionPerformed(ActionEvent e) {
JDialog
d = new ToeDialog(
Integer.parseInt(rows.getText()),
Integer.parseInt(cols.getText()));
d.setVisible(true);
}
}
public
void init() {
JPanel
p = new JPanel();
p.setLayout(newGridLayout(2,2));
p.add(new
JLabel("Rows", JLabel.CENTER));
p.add(rows);
p.add(new
JLabel("Columns", JLabel.CENTER));
p.add(cols);
Container
cp = getContentPane();
cp.add(p,
BorderLayout.NORTH);
JButton
b = new JButton("go");
b.addActionListener(new
BL());
Chapter13:
Creating Windows &
Applets
775
cp.add(b,
BorderLayout.SOUTH);
}
public
static void main(String[] args) {
Console.run(newTicTacToe(),
200, 100);
}
}
///:~
Becausestatics
can only be at the
outerlevel of the class,
innerclasses
cannothave
staticdata
or staticinnerclasses.
ThepaintComponent( )
methoddraws
the square aroundthe
panel,
andthe
"x" or the "o."This is
full of tediouscalculations,
butit's
straightforward.
A
mouse click is captured by
the MouseListener,
which first checks to
see
if the panel has
anythingwritten on it. If
not, theparent window
is
queried
to find out whose turn it
is and that is used to
establish the state
of
the ToeButton.
Via the inner
classmechanism, the
ToeButtonthen
reachesback
into the parentand
changes the turn. If the
button is already
displaying
an "x" or an "o" then that
is flopped. You can see in
these
calculationsthe
convenient use of theternary
if-else described in
Chapter
3.
After a state change,
theToeButtonis
repainted.
Theconstructor
for ToeDialogis
quite simple: it adds into
a
GridLayout
as
many buttons as you
request,then resizes it for
50 pixels
on
a side for
eachbutton.
TicTacToesets
up the whole application by
creating the JTextFields
(forinputting
the rows andcolumns of
the button grid)and
the "go"
buttonwith
its ActionListener.
When the button is
pressed,the data in
theJTextFields
must be fetched, and,
sincethey are in Stringform,
turnedinto
ints
using the staticInteger.parseInt(
) method.
Filedialogs
Someoperating
systems have a number of
special built-indialog boxes
to
handlethe
selection of things such as
fonts, colors, printers,and
the like.
Virtuallyall
graphical operatingsystems
support the openingand
saving
of
files, however, and so
Java's JFileChooserencapsulatesthese
for easy
use.
776
Thinking
in Java
Thefollowing
application exercisestwo
forms of JFileChooserdialogs,
onefor
opening and onefor
saving. Most of thecode
should by now be
familiar,and
all the
interestingactivities happen in
theaction listeners
for
thetwo
different
buttonclicks:
//:
c13:FileChooserTest.java
//
Demonstration of File dialog boxes.
import
javax.swing.*;
import
java.awt.*;
import
java.awt.event.*;
import
com.bruceeckel.swing.*;
public
class FileChooserTest extends JFrame {
JTextField
filename
= new JTextField(),
dir
= new JTextField();
JButton
open
= new JButton("Open"),
save
= new JButton("Save");
public
FileChooserTest() {
JPanel
p = new JPanel();
open.addActionListener(new
OpenL());
p.add(open);
save.addActionListener(new
SaveL());
p.add(save);
Container
cp = getContentPane();
cp.add(p,
BorderLayout.SOUTH);
dir.setEditable(false);
filename.setEditable(false);
p
= new JPanel();
p.setLayout(newGridLayout(2,1));
p.add(filename);
p.add(dir);
cp.add(p,
BorderLayout.NORTH);
}
class
OpenL implements ActionListener {
public
void actionPerformed(ActionEvent e) {
JFileChooser
c = new JFileChooser();
//
Demonstrate "Open" dialog:
int
rVal =
c.showOpenDialog(FileChooserTest.this);
Chapter13:
Creating Windows &
Applets
777
if(rVal ==
JFileChooser.APPROVE_OPTION) {
filename.setText(
c.getSelectedFile().getName());
dir.setText(
c.getCurrentDirectory().toString());
}
if(rVal
== JFileChooser.CANCEL_OPTION) {
filename.setText("You
pressed cancel");
dir.setText("");
}
}
}
class
SaveL implements ActionListener {
public
void actionPerformed(ActionEvent e) {
JFileChooser
c = new JFileChooser();
//
Demonstrate "Save" dialog:
int
rVal =
c.showSaveDialog(FileChooserTest.this);
if(rVal
== JFileChooser.APPROVE_OPTION) {
filename.setText(
c.getSelectedFile().getName());
dir.setText(
c.getCurrentDirectory().toString());
}
if(rVal
== JFileChooser.CANCEL_OPTION) {
filename.setText("You
pressed cancel");
dir.setText("");
}
}
}
public
static void main(String[] args) {
Console.run(newFileChooserTest(),
250, 110);
}
}
///:~
Notethat
there are manyvariations
you can apply to JFileChooser,
includingfilters
to narrow the filenames
that you
willallow.
For
an "open file" dialog,
youcall showOpenDialog(
),
and for a "save
file"dialog
you call showSaveDialog(
).
These commands
don'treturn
untilthe
dialog is closed.
TheJFileChooserobjectstill
exists, so youcan
778
Thinking
in Java
readdata
from it. Themethods
getSelectedFile(
) and
getCurrentDirectory(
) aretwo
ways you caninterrogate
the results of
theoperation.
If these returnnull
it
means the user
canceledout of the
dialog.
HTML
on Swing components
Anycomponent
that can taketext
can also takeHTML
text, which it will
reformataccording
to HTML rules.This means
you canvery easily
add
fancytext
to a Swing component.For
example,
//:
c13:HTMLButton.java
//
Putting HTML text on Swing components.
//
< applet code=HTMLButton width=200
height=500>
//
< /applet>
import
javax.swing.*;
import
java.awt.event.*;
import
java.awt.*;
import
com.bruceeckel.swing.*;
public
class HTMLButton extends JApplet {
JButton
b = new JButton("<html><b><font size=+2>"
+
"<center>Hello!<br><i>Press
me now!");
public
void init() {
b.addActionListener(newActionListener()
{
public
void actionPerformed(ActionEvent e){
getContentPane().add(newJLabel("<html>"+
"<i><font
size=+4>Kapow!"));
//
Force a re-layout to
//
include the new label:
validate();
}
});
Container
cp = getContentPane();
cp.setLayout(newFlowLayout());
cp.add(b);
}
public
static void main(String[] args) {
Console.run(newHTMLButton(),
200, 500);
}
}
///:~
Chapter13:
Creating Windows &
Applets
779
Youmust
start the textwith
"<html>," andthen you
can usenormal
HTMLtags.
Note that youare
not forced to includethe
normal closing
tags.
TheActionListeneradds
a new JLabelto
the form, which
alsocontains
HTMLtext.
However, this label is not
added during init(
) so
you must
callthe
container's validate(
) method
in order to force a re-layout of
the
components(and
thus the display of the
new label).
Youcan
also use HTMLtext
for JTabbedPane,
JMenuItem,
JToolTip,
JRadioButtonandJCheckBox.
Sliders
and progress bars
A
slider (which has
alreadybeen used in the
sinewave example)
allows
theuser
to input data by moving a
point back and
forth,which is
intuitive
in
some situations
(volumecontrols, for
example). A progress bar
displays
data
in a relative fashion
from"full" to "empty" so
theuser gets a
perspective.
My favorite example
forthese is to simply hook
theslider to
theprogress
bar so when youmove
the slider theprogress
bar changes
accordingly:
//:
c13:Progress.java
//
Using progress bars and sliders.
//
< applet code=Progress
//
width=300 height=200></applet>
import
javax.swing.*;
import
java.awt.*;
import
java.awt.event.*;
import
javax.swing.event.*;
import
javax.swing.border.*;
import
com.bruceeckel.swing.*;
public
class Progress extends JApplet {
JProgressBar
pb = new JProgressBar();
JSlider
sb =
new
JSlider(JSlider.HORIZONTAL, 0, 100, 60);
public
void init() {
Container
cp = getContentPane();
cp.setLayout(newGridLayout(2,1));
cp.add(pb);
780
Thinking
in Java
sb.setValue(0);
sb.setPaintTicks(true);
sb.setMajorTickSpacing(20);
sb.setMinorTickSpacing(5);
sb.setBorder(newTitledBorder("Slide
Me"));
pb.setModel(sb.getModel());
// Share model
cp.add(sb);
}
public
static void main(String[] args) {
Console.run(newProgress(),
300, 200);
}
}
///:~
Thekey
to hooking the twocomponents
together is in
sharingtheir
model,
in the line:
pb.setModel(sb.getModel());
Of
course, you could
alsocontrol the two
using a listener, but this
is more
straightforwardfor
simplesituations.
TheJProgressBaris
fairly straightforward,
butthe JSliderhas
a lot of
options,such
as the orientation andmajor
and minor tickmarks.
Notice
howstraightforward
it is to add a
titledborder.
Trees
Using
a JTreecan
be as simple as saying:
add(new
JTree(
new
Object[] {"this", "that", "other"}));
Thisdisplays
a primitive tree.The API
for trees is vast,
however--certainly
one
of the largest in Swing. It
appears that you can do
just about anything
withtrees,
but moresophisticated tasks
mightrequire quite a bit
of
researchand
experimentation.
Fortunately,there
is a middle groundprovided in
the library:the
"default"tree
components, whichgenerally do
what you need. So most
of
thetime
you can usethese
components, and only in
special cases
willyou
need
to delve in and
understandtrees more
deeply.
Chapter13:
Creating Windows &
Applets
781
Thefollowing
example uses the"default"
tree components to display
a
tree
in an applet. When you
pressthe button, a new
subtree is added
underthe
currently selected node(if
no node is selected, theroot
node is
used):
//:
c13:Trees.java
//
Simple Swing tree example. Trees can
//
be made vastly more complex than this.
//
< applet code=Trees
//
width=250 height=250></applet>
import
javax.swing.*;
import
java.awt.*;
import
java.awt.event.*;
import
javax.swing.tree.*;
import
com.bruceeckel.swing.*;
//
Takes an array of Strings and makes the first
//
element a node and the rest leaves:
class
Branch {
DefaultMutableTreeNode
r;
public
Branch(String[] data) {
r
= new DefaultMutableTreeNode(data[0]);
for(int
i = 1; i < data.length; i++)
r.add(new
DefaultMutableTreeNode(data[i]));
}
public
DefaultMutableTreeNode node() {
return
r;
}
}
public
class Trees extends JApplet {
String[][]
data = {
{
"Colors", "Red", "Blue", "Green" },
{
"Flavors", "Tart", "Sweet", "Bland" },
{
"Length", "Short", "Medium", "Long" },
{
"Volume", "High", "Medium", "Low" },
{
"Temperature", "High", "Medium", "Low" },
{
"Intensity", "High", "Medium", "Low" },
};
static
int i = 0;
DefaultMutableTreeNode
root, child, chosen;
782
Thinking
in Java
JTree tree;
DefaultTreeModel
model;
public
void init() {
Container
cp = getContentPane();
root
= new DefaultMutableTreeNode("root");
tree
= new JTree(root);
//
Add it and make it take care of scrolling:
cp.add(new
JScrollPane(tree),
BorderLayout.CENTER);
//
Capture the tree's model:
model
=(DefaultTreeModel)tree.getModel();
JButton
test = new JButton("Press me");
test.addActionListener(newActionListener()
{
public
void actionPerformed(ActionEvent e){
if(i
< data.length) {
child
= new Branch(data[i++]).node();
//
What's the last one you clicked?
chosen
= (DefaultMutableTreeNode)
tree.getLastSelectedPathComponent();
if(chosen
== null) chosen = root;
//
The model will create the
//
appropriate event. In response, the
//
tree will update itself:
model.insertNodeInto(child,
chosen, 0);
//
This puts the new node on the
//
currently chosen node.
}
}
});
//
Change the button's colors:
test.setBackground(Color.blue);
test.setForeground(Color.white);
JPanel
p = new JPanel();
p.add(test);
cp.add(p,
BorderLayout.SOUTH);
}
public
static void main(String[] args) {
Console.run(new
Trees(), 250, 250);
}
}
///:~
Chapter13:
Creating Windows &
Applets
783
Thefirst
class, Branch,
is a tool to take an array of
Stringandbuild
a
DefaultMutableTreeNodewiththe
first Stringas
the root
andthe
rest
of the Strings
in the array as leaves.
Thennode(
) can
be called to
producethe
root of this"branch."
TheTreesclasscontains
a two-dimensional array of Strings
from which
Branches
can be made and a static
int i to count
through thisarray.
TheDefaultMutableTreeNodeobjectshold
the nodes, butthe
physical
representation
on screen is controlled by
theJTreeandits
associated
model,the
DefaultTreeModel.
Note that when
theJTreeis
added to
theapplet,
it is wrapped in a JScrollPane--this
is all it takes to
provide
automaticscrolling.
TheJTreeis
controlled through
itsmodel.
When you make a change
to
themodel,
the model generates an
event that causes
theJTreeto
performany
necessary updates to
thevisible representation of
thetree. In
init(
),
the model is captured by
calling getModel(
).
When the button is
pressed,
a new "branch" is
created.Then the
currentlyselected
component
is found (or the root is
used if nothing is
selected)and the
model'sinsertNodeInto(
) methoddoes
all the work of changing
the
treeand
causing it to be updated.
An
example like the
oneabove may give
youwhat you need in a
tree.
However,trees
have the power to do just
about anything
youcan
imagine--everywhereyou
see the word"default" in
the exampleabove,
youcan
substitute your ownclass to
get differentbehavior. But
beware:
almostall
of these classes have a
large interface, so youcould
spend a lot
of
time struggling to
understandthe intricacies of
trees.Despite this, it's
a
gooddesign
and the alternativesare
usually muchworse.
Tables
Liketrees,
tables in Swing arevast
and powerful. Theyare
primarily
intended
to be the popular
"grid"interface to databases
viaJava Database
Connectivity(JDBC,
discussed in Chapter15) and
thus theyhave a
tremendousamount
of flexibility, whichyou pay
for in complexity.
There'seasily
enough here to be thebasis
of a full-blown
spreadsheetand
couldprobably
justify an entirebook.
However, it is alsopossible to
create
a
relatively simple JTableif
you understand
thebasics.
784
Thinking
in Java
TheJTablecontrolshow
the data is displayed,but
the TableModel
controlsthe
data itself. So to create a
JTableyou'lltypically
create a
TableModelfirst.You
can fully implementthe
TableModelinterface,
butit's
usually simpler to inherit
from the
helperclass
AbstractTableModel:
//:
c13:Table.java
//
Simple demonstration of JTable.
//
< applet code=Table
//
width=350 height=200></applet>
import
javax.swing.*;
import
java.awt.*;
import
java.awt.event.*;
import
javax.swing.table.*;
import
javax.swing.event.*;
import
com.bruceeckel.swing.*;
public
class Table extends JApplet {
JTextArea
txt = new JTextArea(4, 20);
//
The TableModel controls all the data:
class
DataModel extends AbstractTableModel {
Object[][]
data = {
{"one",
"two", "three", "four"},
{"five",
"six", "seven", "eight"},
{"nine",
"ten", "eleven", "twelve"},
};
//
Prints data when table changes:
class
TML implements TableModelListener {
public
void tableChanged(TableModelEvent e){
txt.setText("");
// Clear it
for(int
i = 0; i < data.length; i++) {
for(int
j = 0; j < data[0].length; j++)
txt.append(data[i][j]
+ " ");
txt.append("\n");
}
}
}
public
DataModel() {
addTableModelListener(new
TML());
}
public
int getColumnCount() {
Chapter13:
Creating Windows &
Applets
785
return
data[0].length;
}
public
int getRowCount() {
return
data.length;
}
public
Object getValueAt(int row, int col) {
return
data[row][col];
}
public
void
setValueAt(Object
val, int row, int col) {
data[row][col]
= val;
//
Indicate the change has happened:
fireTableDataChanged();
}
public
boolean
isCellEditable(int
row, int col) {
return
true;
}
}
public
void init() {
Container
cp = getContentPane();
JTable
table = new JTable(new DataModel());
cp.add(new
JScrollPane(table));
cp.add(BorderLayout.SOUTH,
txt);
}
public
static void main(String[] args) {
Console.run(new
Table(), 350, 200);
}
}
///:~
DataModel
contains
an array of data, but
youcould also get
thedata
fromsome
other source such as a
database. The
constructoradds a
TableModelListenerthatprints
the array everytime
the table is
changed.The
rest of the methodsfollow
the Beans
namingconvention,
andare
used by JTablewhen
it wants to present
theinformation in
DataModel.
AbstractTableModelprovidesdefault
methods for
setValueAt(
) andisCellEditable(
) thatprevent
changes to thedata,
so
if you want to be able to
edit the data,
youmust override
these
methods.
786
Thinking
in Java
Onceyou
have a TableModel,
you only need to hand it to
the JTable
constructor.All
the details of displaying,
editing, andupdating will
be
takencare
of for you. Thisexample
also puts theJTablein
a
JScrollPane.
Selecting
Look & Feel
One
of the very
interestingaspects of Swing is
the"Pluggable Look
&
Feel."This
allows your program to
emulate the look
andfeel of various
operatingenvironments.
You can even do all
sorts of fancy
thingslike
dynamicallychanging
the look andfeel
while the program is
executing.
However,you
generally just want to do
one of two things,
eitherselect the
"crossplatform"
look and feel(which is
Swing's "metal"), or select
the
lookand
feel for thesystem
you are currentlyon, so
your Javaprogram
lookslike
it was createdspecifically
for thatsystem. The
code to select
either
of these behaviors is
quitesimple--but you must
execute it before
youcreate
any visualcomponents,
because thecomponents will
be made
based
on the current look
andfeel and will
not be changed just
because
youhappen
to change the lookand
feel midway duringthe
program (that
process
is more complicated
anduncommon, and is
relegated to Swing-
specificbooks).
Actually,
if you want to use
thecross-platform ("metal")
lookand feel
that
is
characteristic of Swing
programs,you don't have to
do anything--it's
thedefault.
But if you wantinstead to
use the
currentoperating
environment'slook
and feel, youjust
insert the followingcode,
typically at
thebeginning
of your main(
) butsomehow
before anycomponents
are
added:
try
{
UIManager.setLookAndFeel(UIManager.
getSystemLookAndFeelClassName());
}
catch(Exception e) {}
Youdon't
need anything in
thecatchclausebecause
the UIManager
willdefault
to the cross-platformlook
and feel if yourattempts to
set up
any
of the alternatives
fail.However, during
debuggingthe exception
can
be
quite useful so you may at
least want to put a
printstatement in the
catchclause.
Chapter13:
Creating Windows &
Applets
787
Here is a
program that takes a
command-line argument to select a
look
andfeel,
and shows howseveral
different componentslook
under the
chosenlook
and feel:
//:
c13:LookAndFeel.java
//
Selecting different looks & feels.
import
javax.swing.*;
import
java.awt.*;
import
java.awt.event.*;
import
java.util.*;
import
com.bruceeckel.swing.*;
public
class LookAndFeel extends JFrame {
String[]
choices = {
"eeny",
"meeny", "minie", "moe", "toe", "you"
};
Component[]
samples = {
new
JButton("JButton"),
new
JTextField("JTextField"),
new
JLabel("JLabel"),
new
JCheckBox("JCheckBox"),
new
JRadioButton("Radio"),
new
JComboBox(choices),
new
JList(choices),
};
public
LookAndFeel() {
super("Look
And Feel");
Container
cp = getContentPane();
cp.setLayout(newFlowLayout());
for(int
i = 0; i < samples.length; i++)
cp.add(samples[i]);
}
private
static void usageError() {
System.out.println(
"Usage:LookAndFeel[cross|system|motif]");
System.exit(1);
}
public
static void main(String[] args) {
if(args.length
== 0) usageError();
if(args[0].equals("cross"))
{
try
{
788
Thinking
in Java
UIManager.setLookAndFeel(UIManager.
getCrossPlatformLookAndFeelClassName());
}
catch(Exception e) {
e.printStackTrace(System.err);
}
}
else if(args[0].equals("system")) {
try
{
UIManager.setLookAndFeel(UIManager.
getSystemLookAndFeelClassName());
}
catch(Exception e) {
e.printStackTrace(System.err);
}
}
else if(args[0].equals("motif")) {
try
{
UIManager.setLookAndFeel("com.sun.java."+
"swing.plaf.motif.MotifLookAndFeel");
}
catch(Exception e) {
e.printStackTrace(System.err);
}
}
else usageError();
//
Note the look & feel must be set before
//
any components are created.
Console.run(newLookAndFeel(),
300, 200);
}
}
///:~
Youcan
see that oneoption is to
explicitly specify a string
for a lookand
feel,
as seen with MotifLookAndFeel.
However, that one
andthe
default"metal"
look and feelare
the only onesthat
can legally be used
on
anyplatform;
even though thereare
strings for Windowsand
Macintosh
lookand
feels, those canonly be
used on theirrespective
platforms(these
areproduced
when you callgetSystemLookAndFeelClassName(
)
andyou're
on that
particularplatform).
It
is also possible to create a
custom look and
feelpackage, for example,
if
youare
building a framework for a
company that wants a
distinctive
appearance.This
is a big job and is far
beyond the scope of this
book (in
fact,you'll
discover it is beyondthe
scope of many
dedicatedSwing
books!).
Chapter13:
Creating Windows &
Applets
789
The
clipboard
TheJFC
supports limitedoperations
with the systemclipboard
(in the
java.awt.datatransferpackage).You
can copy Stringobjects
to the
clipboard
as text, and you
canpaste text from
theclipboard into String
objects.
Of course, the clipboard is
designed to hold any type of
data, but
howthis
data is represented on the
clipboard is up to theprogram
doing
thecutting
and pasting. TheJava
clipboard API providesfor
extensibility
throughthe
concept of a "flavor."When
data comes offthe
clipboard, it
has
an associated set of
flavorsthat it can be
converted to (for example,
a
graphmight
be represented as a string of numbers or
as an image)and
youcan
see if that
particularclipboard data
supports theflavor
you're
interestedin.
Thefollowing
program is a simpledemonstration of
cut, copy,and
paste
withStringdata
in a JTextArea.
One thing you'll notice is
that the
keyboardsequences
you normally usefor
cutting, copying,
andpasting
alsowork.
But if you look at any
JTextFieldor
JTextArea
in
any other
programyou'll
find that theyalso
automatically supportthe
clipboard key
sequences.This
example simply
addsprogrammatic control of
the
clipboard,and
you could usethese
techniques if you want to
capture
clipboardtext
into something otherthan a
JTextComponent.
//:
c13:CutAndPaste.java
//
Using the clipboard.
import
javax.swing.*;
import
java.awt.*;
import
java.awt.event.*;
import
java.awt.datatransfer.*;
import
com.bruceeckel.swing.*;
public
class CutAndPaste extends JFrame {
JMenuBar
mb = new JMenuBar();
JMenu
edit = new JMenu("Edit");
JMenuItem
cut
= new JMenuItem("Cut"),
copy
= new JMenuItem("Copy"),
paste
= new JMenuItem("Paste");
JTextArea
text = new JTextArea(20, 20);
Clipboard
clipbd =
790
Thinking
in Java
getToolkit().getSystemClipboard();
public
CutAndPaste() {
cut.addActionListener(new
CutL());
copy.addActionListener(new
CopyL());
paste.addActionListener(new
PasteL());
edit.add(cut);
edit.add(copy);
edit.add(paste);
mb.add(edit);
setJMenuBar(mb);
getContentPane().add(text);
}
class
CopyL implements ActionListener {
public
void actionPerformed(ActionEvent e) {
String
selection = text.getSelectedText();
if
(selection == null)
return;
StringSelection
clipString =
new
StringSelection(selection);
clipbd.setContents(clipString,clipString);
}
}
class
CutL implements ActionListener {
public
void actionPerformed(ActionEvent e) {
String
selection = text.getSelectedText();
if
(selection == null)
return;
StringSelection
clipString =
new
StringSelection(selection);
clipbd.setContents(clipString,clipString);
text.replaceRange("",
text.getSelectionStart(),
text.getSelectionEnd());
}
}
class
PasteL implements ActionListener {
public
void actionPerformed(ActionEvent e) {
Transferable
clipData =
clipbd.getContents(CutAndPaste.this);
try
{
String
clipString =
Chapter13:
Creating Windows &
Applets
791
(String)clipData.
getTransferData(
DataFlavor.stringFlavor);
text.replaceRange(clipString,
text.getSelectionStart(),
text.getSelectionEnd());
}
catch(Exception ex) {
System.err.println("Not
String flavor");
}
}
}
public
static void main(String[] args) {
Console.run(newCutAndPaste(),
300, 200);
}
}
///:~
Thecreation
and addition of themenu
and JTextArea
should
by now
seem
a pedestrian activity.
What'sdifferent is the
creation of the
Clipboardfieldclipbd,
which is done through
theToolkit.
Allthe
action takes place in the
listeners. The CopyL
andCutL listeners
arethe
same except forthe
last line of CutL,
which erases the
linethat's
beencopied.
The special twolines
are the creation of a
StringSelection
objectfrom
the Stringandthe
call to setContents(
) withthis
StringSelection.
That's all there is to
putting a Stringon
the clipboard.
In
PasteL,data
is pulled off the
clipboardusing getContents(
).
What
comesback
is a fairly anonymousTransferableobject,and
you don't
reallyknow
what it contains. Oneway to
find out is to call
getTransferDataFlavors(
),
which returns an array of
DataFlavor
objectsindicating
which flavors aresupported
by this
particularobject.
Youcan
also ask it directlywith
isDataFlavorSupported(
),
passing in
theflavor
you're interested in.Here,
however, the boldapproach is
taken:
getTransferData(
) is
called assuming that
thecontents supports
the
Stringflavor,and
if it doesn't theproblem is
sorted out in
theexception
handler.
In
the future you
canexpect more data
flavors to be supported.
792
Thinking
in Java
Packaging an applet into
a
JAR
file
An
important use of the
JARutility is to optimize
appletloading. In
Java
1.0,people
tended to try to cramall
their code into a single
applet class so
theclient
would need only a single
server hit to download the
appletcode.
Notonly
did this result in messy,
hard to read (andmaintain)
programs,
butthe
.classfilewas
still uncompressed so downloading
wasn't as fast
as
it could have been.
JARfiles
solve the problem by
compressing all of
your.classfilesinto
a
singlefile
that is downloaded by the
browser. Now youcan
create the
rightdesign
without worryingabout how
many .classfiles
it will
generate,and
the user willget a
much faster
downloadtime.
ConsiderTicTacToe.java.
It looks like a
singleclass, but in fact
it
containsfive
inner classes, so that's six
in all. Onceyou've compiled
the
program,you
package it into a JARfile
with theline:
jar
cf TicTacToe.jar *.class
Thisassumes
that the only.classfiles
in the current directory
arethe
onesfrom
TicTacToe.java(otherwiseyou'll
get extrabaggage).
Nowyou
can create an HTMLpage
with the newarchivetag
to indicate
thename
of the JAR file.Here is
the tag usingthe
old form of
theHTML
tag,
as an illustration:
<head><title>TicTacToeExample
Applet
</title></head>
<body>
<applet
code=TicTacToe.class
archive=TicTacToe.jar
width=200
height=100>
</applet>
</body>
You'llneed
to put it into thenew
(messy, complicated)form
shown earlier
in
the chapter in order to get
it to work.
Chapter13:
Creating Windows &
Applets
793
Programming
techniques
Because
GUI programming in Java
hasbeen an evolving
technologywith
somevery
significant changesbetween
Java 1.0/1.1 andthe
Swing library
in
Java 2, there have
beensome old programming
idiomsthat have
seepedthrough
to examples that youmight
see given forSwing.
In
addition,Swing
allows you to program in
more and better
waysthan were
allowed
by the old models. In
thissection, some of
theseissues will be
demonstrated
by introducing and
examiningsome
programmingidioms.
Bindingevents
dynamically
One
of the benefits of the
Swingevent model is
flexibility.You can
add
andremove
event behavior withsingle
method calls.
Thefollowing
exampledemonstrates
this:
//:
c13:DynamicEvents.java
//
You can change event behavior dynamically.
//
Also shows multiple actions for an event.
//
< applet code=DynamicEvents
//
width=250 height=400></applet>
import
javax.swing.*;
import
java.awt.*;
import
java.awt.event.*;
import
java.util.*;
import
com.bruceeckel.swing.*;
public
class DynamicEvents extends JApplet {
ArrayList
v = new ArrayList();
int
i = 0;
JButton
b1
= new JButton("Button1"),
b2
= new JButton("Button2");
JTextArea
txt = new JTextArea();
class
B implements ActionListener {
public
void actionPerformed(ActionEvent e) {
txt.append("A
button was pressed\n");
}
}
class
CountListener implements ActionListener
{
794
Thinking
in Java
int index;
public
CountListener(int i) { index = i; }
public
void actionPerformed(ActionEvent e) {
txt.append("Counted
Listener "+index+"\n");
}
}
class
B1 implements ActionListener {
public
void actionPerformed(ActionEvent e) {
txt.append("Button
1 pressed\n");
ActionListener
a = new CountListener(i++);
v.add(a);
b2.addActionListener(a);
}
}
class
B2 implements ActionListener {
public
void actionPerformed(ActionEvent e) {
txt.append("Button2pressed\n");
int
end = v.size() - 1;
if(end
> = 0) {
b2.removeActionListener(
(ActionListener)v.get(end));
v.remove(end);
}
}
}
public
void init() {
Container
cp = getContentPane();
b1.addActionListener(new
B());
b1.addActionListener(new
B1());
b2.addActionListener(new
B());
b2.addActionListener(new
B2());
JPanel
p = new JPanel();
p.add(b1);
p.add(b2);
cp.add(BorderLayout.NORTH,
p);
cp.add(new
JScrollPane(txt));
}
public
static void main(String[] args) {
Console.run(newDynamicEvents(),
250, 400);
}
}
///:~
Chapter13:
Creating Windows &
Applets
795
Thenew
twists in this
exampleare:
1.
There
is more than one
listenerattached to each
Button.
Usually,
componentshandle
events as multicast,
meaning that
youcan
registermany
listeners for a singleevent.
In the special
components
in which an event is handled as
unicast,
you'll get a
TooManyListenersException.
2.
Duringthe
execution of theprogram,
listeners
aredynamically
addedand
removed from theButton
b2.
Adding is accomplished
in
the way you've
seenbefore, but each
componentalso has a
removeXXXListener(
) method
to remove each type of
listener.
Thiskind
of flexibility providesmuch
greater power in your
programming.
Youshould
notice that eventlisteners
are not guaranteed to be
called in
theorder
they are added(although
most implementations do in fact
work
thatway).
Separatingbusiness
logic
from
UI logic
In
general you'll want to
design your classes so
thateach one
does"only
onething."
This is particularlyimportant
when user-interfacecode
is
concerned,since
it's easy to tie up "what
you're doing" with"how
you're
displayingit."
This kind of
couplingprevents code reuse.
It'smuch more
desirable
to separate your
"businesslogic" from the
GUI.This way,
you
cannot
only reuse thebusiness
logic more easily,it's
also easier to reuse
theGUI.
Anotherissue
is multitieredsystems,where
the "business
objects"reside
on
a completely separate
machine.This central
location of
thebusiness
rulesallows
changes to be instantlyeffective
for all newtransactions,
and
is
thus a compelling way to set
up a system. However,
thesebusiness
objectscan
be used in manydifferent
applications and so should
not be
tied
to any particular mode of
display. They should
justperform the
businessoperations
and nothingmore.
796
Thinking
in Java
Thefollowing
example shows howeasy it is
to separate thebusiness
logic
fromthe
GUI code:
//:
c13:Separation.java
//
Separating GUI logic and business objects.
//
< applet code=Separation
//
width=250 height=150> </applet>
import
javax.swing.*;
import
java.awt.*;
import
javax.swing.event.*;
import
java.awt.event.*;
import
java.applet.*;
import
com.bruceeckel.swing.*;
class
BusinessLogic {
private
int modifier;
public
BusinessLogic(int mod) {
modifier
= mod;
}
public
void setModifier(int mod) {
modifier
= mod;
}
public
int getModifier() {
return
modifier;
}
//
Some business operations:
public
int calculation1(int arg) {
return
arg * modifier;
}
public
int calculation2(int arg) {
return
arg + modifier;
}
}
public
class Separation extends JApplet {
JTextField
t
= new JTextField(15),
mod
= new JTextField(15);
BusinessLogic
bl = new BusinessLogic(2);
JButton
calc1
= new JButton("Calculation 1"),
Chapter13:
Creating Windows &
Applets
797
Table of Contents:
|
|||||