Download presentation source

Document related concepts
no text concepts found
Transcript
GSAMS’
Undistinguished
Lecture Series
presents . . .
Java for the Impatient
Lecture Topics
•
•
•
•
Object Creation (a review)
Simple Animation; Double Buffering
Lightweight Components
Selected Swing/JFC Topics
• Notice: With the exception of the first
section, these slides begin a series of
more in-depth topics. Some proficiency
with Java is presumed.
Object Creation
• Consider why the following code will not
compile:
public class Test {
public void sayHello() {
System.out.println (“Hello”);
}
public static void main (String arg[]) {
sayHello(); // WRONG!
}
}
Classes and Objects
• The error produced is:
“Can't make static reference to method void
sayHello() in class test.”
• Why? The answer lies in the difference between objects and
classes. As noted in previous slides, classes are composed of
member objects, primitives and methods. When properly
designed, classes provide state and behavior for objects.
• As and OO language, Java emphasizes the use of objects to
manipulate data. Thus, we must make instances of a class in
order to drive a program.
An Another Perspective
t1
public class Test {
public void sayHello() {
t2
public
class Test { (“Hello”);
System.out.println
public void sayHello() {
}
public
class
Test
{
System.out.println
(“Hello”);
public static
void
main
(String
arg[]) {
public//void
sayHello() {
}
sayHello();
WRONG!
System.out.println
(“Hello”);
public static
void main (String
arg[]) {
}
}
// WRONG!
}//class Test sayHello();
public static void main (String arg[]) {
}
sayHello(); // WRONG!
} }//class Test
}
} }//class Test
When we make the class Test, there are potentially
an infinite number of instance available.
Test t1 = new Test();
Test t2 = new Test(); // etc.
t3
CLASS
public class Test {
int x;
static int y = 2;
}//class Test
New class
instances (objects)
get unique copies
of every member-except for static or
“class” members.
We can think of
static as meaning
“only one copy”
INSTANCES
Test t1 = new Test();
t1.x = 5; // should use accessor
public class Test {
int x; // now 5
static
y; // 22
int y; int
// still
}//class Test
Test t1 = new Test();
t1.x = 3; // should use accessor
public class Test {
int x; // now 3
int y; int
// still
static
y; // 22
}//class Test
t1
public class Test {
public void sayHello() {
t2
public
class Test { (“Hello”);
System.out.println
public void sayHello() {
}
System.out.println
(“Hello”);
public static
void main (String
arg[]) {
}
sayHello();
// WRONG!
public static void main (String arg[]) {
}
}//class Test sayHello(); // WRONG!
}
} }//class Test
So, when Java encounters our class, it sees:
* a potentially infinite number of Test instances, each
with their own behavior (sayHello() ), and
* only one main shared between all the classes
public class Test {
public void sayHello() {
System.out.println (“Hello”);
}
public static void main (String arg[]) {
sayHello(); // WRONG!
}
}//class Test
Potentially
many
Only one
Thus, the error of making a “static reference to a method”
might be understood as a problem of ambiguity.
We have not told Java which of the many possible
instances of class Test we intend to call “sayHello()” on.
Solution
One merely needs to resolve this ambiguity to correct the
program. Thus, just make an instance of the class, and
reference/invoke the object’s members.
public class Test {
public void sayHello() {
System.out.println (“Hello”);
}
public static void main (String arg[]) {
Test t = new Test();
t.sayHello(); // Corrected
}
}//class Test
Simple Animation
Double Buffering
Fun with paint();
Animation: The Problem
• Animation can be difficult in Java
because of the ‘flicker’ associated with
the repainting of components.
• Through a set of ‘trial and error’
experiments, we will consider
techniques for avoiding this problem.
• The discussion on double-buffering
presents concepts central to working
with light-weight components.
Animation: The Flicker
Problem
• Let’s write a simple applet to create
some animation.
• How would we animate a simple
bouncing ball?
• If we use the metaphor of a movie, we
can imagine the paint method rendering
a scene in a single slice of time. Each
call to the paint method therefore draws
an individual ‘frame’ of our animation.
import java.awt.*;
import java.applet.*;
public class buff extends Applet {
int x=10, y=10, dx=1, dy=2;
public void init(){super.init();}
public void paint (Graphics g){
g.setColor(Color.gray);
g.fillRect(0,0,getSize().width,
getSize().height);
g.setColor(Color.red);
g.fillOval(x,y, 10,10);
x+=dx;
y+=dy;
if (x>=getSize().width-10 || x <=10) dx*=-1;
if (y>=getSize().height-10 || y <=10)dy*=-1;
repaint();
}
}
Simple animation applet
Our simple applet follows this logic:
• At each call of the paint method, erase the entire canvas
area, then draw the ball in it’s new location, and add some
values to the x, y position of the ball.
• Some simple bounds checking is provided, and the
“repaint()” call from within paint create an animation loop
for us.
• The “repaint()” within paint eliminates the need for
threading (not yet covered), but may cause problems for
some browsers (e.g., Netscape). An appropriate sleep call
can help:
try{
Thread.sleep (10); // lets gc occur in Netscape
} catch (InterruptedException e) { … }
Problem: The Flicker
• As written, the applet works o.k., but has an
annoying flicker.
• What’s causing this? Well, each call to paint
causes Java to whipe out the entire canvas,
before our code even gets executed.
• Our canvas is therefore redrawn twice--once
with a white wash, and a second time with our
background.
Solution: Override
update()
• The culprit is update(), a method called by
Java with each call to paint().
• We can eliminate the flicker by merely
overriding update:
public void update (Graphics g){
paint(g);
}
• Note that we call paint() and don’t merely
create a no-op. This avoids premature
garbage collection of the Graphics object.
Overriding update . . .
import java.awt.*;
import java.applet.*;
public class buff extends Applet {
int x=10, y=10, dx=1, dy=2;
public void init(){super.init();}
public void paint (Graphics g){
g.setColor(Color.gray); g.fillRect(0,0,
getSize().width, getSize().height);
g.setColor(Color.red); g.fillOval(x,y, 10,10);
x+=dx; y+=dy; if (x>= getSize().width-10 || x <=10)
dx*=-1;
if (y>= getSize().height-10 || y <=10) dy*=-1;
repaint();
}
public void update(Graphics g)
{ paint (g); }
}
Problem solved? No.
• This eliminates most of the flicker, but what if
we increase the size and complexity of the
object we are drawing?
• Let’s change the dimensions of the ball and
see what happens.
• . . . On a slow system, the drawing of the
ball tends to flicker with our gray background.
This becomes more pronounced as we
increase the complexity of the paint methods
code.
Eliminating Flicker
• By overriding update, we’ve only addressed
half of the problem. We stop the flicker
associated with the white washing of our
canvas, but still must endure the ‘gray wash’
caused by our code.
• The heart of the problem: We draw gray
pixels on an area, and then replace them with
our red pixels. The video memory cannot
refresh these changes smoothly.
One Solution: Limited
Drawing Areas
• One temporary solution is to only erase
the portions of the canvas that have
changed.
(Repaint only the box bounding the changed area)
Limited updating: a limited solution
• Erasing only the areas that have changed
may provide some help, but does not solve
the essential problem.
• With many redrawn areas, we lose an
economy of scale, and redraw some areas
many times!
Complete Solution: Buffers
• Since the problem stems from the differing
rates at which memory is written and and
video is displayed, we can just use a buffer.
• Our drawing could take place in a buffered
graphics area. When we are done, the final
image is ’blitted’ (or block transfered) to the
screen all at once--no more piece meal
updating.
• We could implement buffers with clipping
areas to maximize performance.
Double-Buffering
• Draw off-screen, blit to on-screen:
“Graphics osG”
“Graphics g”
Mechanics of Double
Buffering
• To accomplish this, we’ll need some
tools:
– An off-screen image, upon which we draw
– A graphics context for that off-screen
image
– Some way of making sure the size of the
off-screen image matches the onscreen
drawing area--even if the applet is resized!
– Some way of making sure we create the
off-screen image the first time
Mechanics of DoubleBuffering
Some useful instance variables:
Image osImg;
// off-screen image
Graphics osG;
// off-screen graphics
boolean startOut=true; /*first time painting or not?*/
Dimension curD; // current size of off-screen stuff
Mechanics of DoubleBuffering
• Now, we can modify our paint method.
• First, let’s make sure we create the offscreen drawing area upon startup:
public void paint (Graphics g){
// is this the first time we are painting?
if (startOut){
curD = getSize();
osImg = createImage
(curD.width, curD.height);
osG = osImg.getGraphics();
startOut = false;
} . . . .
Mechanics of DoubleBuffering
• Now, let’s check to see if the applet has
been resized:
// has the component been resized?
if ((curD.width != size().width) ||
(curD.height != size().height)){
curD = size();
osImg = createImage (curD.width,
curD.height);
osG = osImg.getGraphics();
}
Mechanics of DoubleBuffering
• Now, we draw as usual, except that we
use the “osG” reference, not the more
familiar “g” reference to the Graphics
object.
• When we are done, the LAST line of our
paint (before repaint(), that is), should
blit the off-screen image to screen:
g.drawImage (osImg,0,0,this);
Summary Steps
-instance variables:
Image osImg; // off-screen image
Graphics osG; // off-screen graphics
boolean startOut = true; // first time painting or
not?
Dimension curD; // current size of off-screen stuff
-override update():
public void update (Graphics g) {
paint(g); }
-override paint()
public void paint (Graphics g) {
// is this the first time we are painting?
if (startOut) {
curD = size();
osImg = createImage (curD.width, curD.height);
osG = osImg.getGraphics();
startOut = false;
}
Summary Steps (Cont’d)
// has the component been resized?
if ((curD.width != size().width) || (curD.height !=
size().height)) {
curD = size();
osImg = createImage (curD.width, curD.height);
osG = osImg.getGraphics();
}
-in the remainder of paint(), draw the canvas/component as
normal, but use osG rather than g as the Graphics object on
which to draw. Then at the end of paint():
// make changes to off-screen graphics visible on screen
g.drawImage (osImg,0,0,this);
An example
• Let’s add complexity to our bouncing
ball example, to test the doublebuffering.
• Let’s add a Ball class, with random ball
properties.
• Then, let’s create an array of Balls,
each draw an updated in its own
manner . . .
Ball.java
import java.awt.Color;
class Ball{
Color c; int x, y, dx, dy;
public Ball (){
x = (int)(Math.random()*200);
y = (int)(Math.random()*200);
dx = (int)(Math.random()*3)+1;
dy = (int)(Math.random()*4)+1;
c = new Color
((int)(Math.random()*255),
(int) (Math.random()*255),
(int)(Math.random()*255));
}
}
Buff.java
import java.awt.*;
import java.applet.*;
public class buff extends Applet {
Image osImg; // off-screen image
Graphics osG; // off-screen graphics
boolean startOut = true;/* first time painting or
not?*/
Dimension curD;/* current size of off-screen
stuff*/
int x=10, y=10, dx=1, dy=2, MAX=15, i;
Ball[] b;
public void init(){super.init();
b = new Ball[MAX];
for (i=0; i<MAX; i++)
b[i]= new Ball();
}
Buff.java (cont’d)
public void paint (Graphics g) {
// is this the first time we are painting?
if (startOut){ curD = getSize();
osImg = createImage (curD.width, curD.height);
osG = osImg.getGraphics();
startOut = false;
}
// has the component been resized?
if ((curD.width != size().width) || (curD.height
!= size().height))
{curD = size();
osImg = createImage (curD.width, curD.height);
osG = osImg.getGraphics();
}
Buff.java (cont’d)
osG.setColor(Color.gray);
osG.fillRect(0,0,size().width, size().height);
for (i=0; i< MAX; i++){ osG.setColor(b[i].c);
osG.fillOval(b[i].x,b[i].y, 10,10);
b[i].x+=b[i].dx; b[i].y+=b[i].dy;
if (b[i].x>= getSize().width-10 || b[i].x <=10)
b[i].dx*=-1;
if (b[i].y>= getSize().height-10 || b[i].y <=10)
b[i].dy*=-1;
}
g.drawImage (osImg,0,0,this); repaint(); }
public void update(Graphics g)
{ paint (g); }
}
Improved Double Buffering
• Another method of handling double
buffering is to override invalidate():
public void invalidate() {
super.invalidate();
offscreen = null;
}
/* The offscreen variable refers to the
image, not the graphics context */
Improved Buffering (cont’d) . . .
Then, in paint(), perform a simple check on the offscreen
image prior to painting:
public void paint(Graphics g) {
if(offscreen == null) {
offscreen = createImage
(getSize().width,
getSize().height);
}
Graphics osG = offscreen.getGraphics();
/* paint to buffered graphics
object as usual */
This second approach is more efficient, but not as clear to read
(at first).
Lightweight Components
•
•
•
•
Motivation
Solution
Techniques for Creation
Uses
Lightweight Components--What?
• Let’s suppose you need a new widget in
Java--something not currently found in the
AWT.
• Perhaps one would use a Canvas or Panel
to create the new widget. (This was the only
option under JDK 1.02.) The paint()
method in either of these would provide the
mechanism for rendering the new
component.
Lightweight Components
• Suppose for example we wanted to
create a type of Button--something round,
holding an image or icon.
• We could subclass Canvas, resize the
object to the appropriate dimensions,
override the paint() method, and
implements appropriate listeners.
Lightweight Components
• What are the limitations of such an
approach?
– The components all have square, opaque backgrounds
(no transparent regions).
– The components all involve peer resources--a problem
if there are many, or if the operating system is slow.
– The use of native peers means that components vary
somewhat across platforms. This is acceptable for
some details in larger containers, but variation in our
components complicates our GUI design.
– Hard to change the look of existing components
Component
Architecture: Peers
• To better understand how lightweights work, one has
to appreciate what is involved in the creation of
heavyweight objects.
API
Button
MacOS
ButtonPeer
WinNT
ButtonPeer
ButtonPeer
With the exception of mouse clicks,
peers are at the end of the event
chain. Components get the events,
allowing users to write reactive code.
Then, the event is passed to the peer,
where it is processed. (E.g., a button
appears to be depressed when
clicked.)
Motif
ButtonPeer
Note the many indirections!
Problems with Component Creation:
Last-Minute Peer Creation
• When AWT components are created, but not yet made
visible, the associated peer resources are not allocated.
When the component object is made visible, the peers
are created ‘just-in-time’. (As a result, a component’s
size is not valid until it is displayed.)
• The same is found when a component is added to a
non-visible container. When the contain is made
visible, all its interior components get peer resources.
This may cause sudden pauses when large containers
become visible
Component Creation: Tips
• When adding a component to a visible container,
one needs to explicitly tell the AWT to create
peer resources.
• The method invalidate() method can be
invoked on the object, but is usually called on
the container.
• Invoking validate() has a ripple effect,
whereby every component in the container gets
validated as well.
• Example: when adding components to an Applet
object, you call validate() on the Applet,
which creates peers for all the components in
the Applet.
Lightweight Components
• How might one address these limitations in
component creation?
• Starting with JDK 1.1b3 provided support for the
Lightweight UI Framework.
• This enabled one to subclass Component and
Container directly (instead of just Canvas or Panel).
• Lightweights do not involve the use of native peer
resources. That is, Java (and not the OS) is
responsible for rendering the component. With less
indirection, there is less delay, and greater control.
Lightweights: No Mere Buzzword
• The migration away from peer-driven AWT
components is part of Sun’s overall design for
the Java 2 platform (a/k/a JDK 1.2.1 and
JFC/Swing for JDK 1.1.8).
• The “Java Foundation Classes” provide a
cross-platform look, flexibility, improved
speed, and better OO design.
• Even without JDK 1.2, you may create your
own lightweight components with many of
these features.
Lightweights--How?
• To create a lightweight component, you
need to create a class that directly
extends one of the following classes:
–
–
–
Component
Container
a class that implements a lightweight
component -- for example, a non-AWT
class that is a subclass of Component or
Container
Lightweights--How?
Your class will have to provide the ‘look and feel’ for the
component. Consider having:
One or more constructors:
If the component responds to any events, such
as mouse clicks, the constructor(s) should
invoke the enableEvents method.
The paint method, with your specified look.
Programmatic methods (state/behavior).
Resizing methods: If the component's size should
change, then call invalidate() before
repaint().
The contains() method if the component responds to
events fired in only part of its drawing area
Appropriate event methods
Lightweights--How?
• Container restrictions:
– Be sure to double-buffer the container, or suffer
the flicker
– The painting and event dispatching mechanism for
lightweight components is handled by the
Container class. This means that the painting of
lightweight components is triggered from
within the paint() method of its container.
Therefore, if a lightweight component is placed
inside of a Container instance where the paint()
method has been overridden but fails to call
super.paint(), the paint() method of the
lightweight component will never be called.
– Thus, be sure to call super.paint()!
Lightweights--example
• Let’s create a component using a
subclass of component.
• Suppose we have a lightweight
subclass that draws an image as part of
its paint method:
Lightweights--example
• The structure of our lightweight is fairly
simple:
– The constructor would takes in an image
(since image loading requires a toolkit,
most easily accessed from the Applet)
– The paint method just draws the image
– We include a getPreferredSize()
method to tell the container the size of the
component
Lightweights--example
import java.awt.*;
import java.awt.event.*;
class lwc extends Component{
Image im;
public lwc (Image im) {
this.im=im;
}
public void paint (Graphics g) {
g.drawImage (im, 0,0, this);
}
public Dimension getPreferredSize()
{
return new Dimension (im.getWidth(this),
im.getHeight(this));
}
}
Lightweights--Example
• Since we are using a lightweight, our
heavyweight container will have to call
super.paint(), or else the
component will never appear.
• (For comparison, we’ll also load a
background image, and add a nearly
identical heavyweight image.)
Lightweights--a heavyweight
for comparison purposes
import java.awt.*;
class hwc extends Canvas{
Image im;
public hwc (Image im){
this.im = im;
}
public void paint (Graphics g) {
g.drawImage (im, 0, 0, this);
}
public Dimension getPreferredSize(){
return new Dimension (im.getWidth(this),
im.getHeight(this));
}
}
import java.awt.*;
import java.applet.*;
public class lw extends Applet {
Image background;
public void init() {
super.init(); setBackground(Color.yellow);
background = getImage(getCodeBase(), "Back.gif");
lwc l = new lwc(getImage(getCodeBase(), "bishop.gif"));
add(l);
hwc h = new hwc(getImage(getCodeBase(), "bishop.gif"));
add(h);
}
public void paint (Graphics g)
{ /* uncomment these lines to experiment */
//g.drawImage (background, 0,0,this);
//super.paint(g);
}
}
News Flash: Lightweights
knock out Heavyweight!
• Notice what happens in our applet:
– When the background is a simple color, it
appears that all is well with the components;
both heavy and light components appear fine.
– But when we add a complex background, the
opaque background of the heavyweight
becomes evident.
– Notice also the crucial role played by the
super.paint() method for the lightweight
(without it, the lightweight isn’t drawn).
JFC/Swing
“Now it gets interesting . . . “
1.
Motivation
Historical Problems with AWT
• Runtime peer resources required
– slow on some platforms (e.g., windows)
– portability problems (slightly different look, some behaviors
different)
– least common denominator phenomenon; rigid look
• Limited widget library
– addressed somewhat by JDK 1.1b3+
java.awt.Button
ButtonPeer
AWT components required
native “peer” methods to
render and operate
WinNT
ButtonPeer
MacOS
ButtonPeer
Motif
ButtonPeer
File Edit
Help
CLICK ME
Slow & Inflexible!
Stop-gap remedies for JDK 1.1
• Developers avoided a few AWT limitations through:
–
–
–
–
Pervasive use of lightweights
(Layering of
e.g., tooltip simulation through threads/windows/components
components
extensive coding around rigid look
requires layout
use of layered gifs
subclasses)
File Edit
(Tooltips required
threads,
window
subclasses &
extensive
event handling)
Help
CLICK ME
Tooltip
(Menu bars limited;
no images possible
without *extensive*
simulation through
components)
(Image buttons required
component subclassing,
methods to handle ‘click’
look, as well as event
handlers)
Introducing Swing/JFC
• Sun addressed problems with Java Foundation Classes (JFC)
a/k/a Swing
• Key elements:
– No reliance on native peers; the JVM does the work
– Swing completely controls look and feel of all components:
PLAF
– Superior design: MVC-esque
File Edit
Help
CLICK ME
Fast, flexible, extensible!
javax.swing.JButton
Swing Packages
• All the new Swing components and classes need a
home. Where?
• A subject of great debate!
• For JDK 1.1, Sun used “com.sun.java.swing”
• Problem: developers complained this was not
appropriate for “core” class
Why “javax”?
Solution:
javax.swing.*
* logical grouping
* minimizes transition
costs
* most developers
happy with it
Denotes ‘extension’ package that
has migrated to core status
* helps maintain
existing JDK 1.1 code
(cf. MFC lib breaks)
Overview of JFC/Swing Packages
•
•
•
•
javax.swing.plaf
javax.swing.plaf.basic
javax.swing.plaf.metal
javax.swing.plaf.multi
• javax.swing
• javax.swing.table
• javax.swing.tree
• javax.swing.border
• javax.swing.colorchooser
• javax.swing.filechooser
•
•
•
•
javax.swing.text
javax.swing.text.html
javax.swing.text.html.parser
javax.swing.text.rtf
• javax.swing.event
• javax.swing.undo
Getting into the Swing
• Download from www.javasoft.com
• Swing works with JDK 1.1 and JDK 1.2
– JDK 1.1 requires “swingall.jar” file in the CLASSPATH
– For JDK 1.2, it’s in the soup (no CLASSPATH settings)
– JDK 1.2 will also run all JDK 1.1 code (~ 20% faster!)
import com.sun.java.swing.*;
import javax.swing.*;
AWT
AWT
JDK 1.1
Swing Classes
classpath
JDK 1.2
swingall.jar
2.
Short Examples
Short Example: Old AWT
import java.awt.*;
public class HelloWorld extends Frame {
public Button bOld;
//public Panel p;
public HelloWorld() {
bOld = new Button ("Good Bye");
//p = new Panel();
//p.add(bOld);
//this.add(p);
this.add(bOld);
/* note the addition directly to Frame*/
this.pack();
}
public static void main(String arg[]){
/* note lack of listener; this is a demo */
new HelloWorld().show();
}
}// class HelloWorld
Hello Swing, Good Bye AWT
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
Note Swing
public class HelloWorld extends JFrame {
components
Button bOld; public JButton bNew;
public JPanel p;
public HelloWorld() {
bNew = new JButton("Hello");
bOld = new Button ("Good Bye");
Note addition of
p = new JPanel();
components to
p.add(bNew); p.add(bOld);
JPanel’s ‘content
pane’
this.getContentPane().add(p);
this.pack();
}
public static void main(String arg[]){
new HelloWorld().show();
}
SEE CAUTIONARY NOTE RE: Mixing light
and heavyweight components!
}// class HelloWorld
What’s the Big Deal?
• HelloWorld looks similar to AWT. Why switch to
JFrame, JButton, JPanel, etc?
• Benefits:
–
–
–
–
–
speed
lightweight flexibility (transparency, non-rectangular shape)
pluggable look and feel
automatic double buffering with many J- components.
additional functionality (e.g., tooltips, etc.)
I’m lightweight,
pluggable, extensible
and fast. The VM
draws me.
I need Windows(tm)
to be visible and
‘toggle’. I’m slow.
Widget Example: JButtons
• The java.swing.JButton class implements a
state version of a java.swing.AbstractButton.
Many methods come from the abstract class:
– A variety of constructors (Strings, Icons, etc.);
– setRolloverEnabled(boolean);
– setIcon(Icon);
– setRolloverIcon(Icon);
– setActionCommand(String); -- an extra String tacked
onto the event that gets fired!
– setContentAreaFilled(boolean) -- transparency for
icons!
– setModel(ButtonModel); -- sets the ‘type’ of Button
(you can define your own Button behaviors!)
– setMnemonic(char/int); -- set mnemonics for button
Another Example
import javax.swing.*;
public class HelloWorld2 extends Jframe {
public JButton bNew;
public JPanel p;
public HelloWorld2() {
bNew = new JButton("New Document", new
ImageIcon("Document.gif"));
bNew.setRolloverEnabled(true);
bNew.setRolloverIcon
(new ImageIcon ("New.gif"));
p = new JPanel();
p.add(bNew);
getContentPane().add(p);
this.pack();
}
public static void main(String arg[ ]) {
new HelloWorld2().show(); }
}// class HelloWorld2
Images from disk:
Sets icon and
rollover Icon.
Note: Icon constructor
took String argument,
and automatically
loaded image
Why getContentPane()?
• The HelloWorld example required us to call
getContentPane() before “add()”ing an object to the
frame:
myFrameInstance.getContentPane().add(myComponent);
Usually
“this”
Required of
JFrame, JDialog
and JInternalFrame
instances
E.g.,
“myJButton”
• This differs from traditional AWT container additions, were
we simply call “add”, passing in the component.
• Let’s cut a JFrame open to find out why . . .
A JFrame Autopsy
“The Pop Tart / Sandwich Duality”
JLayeredPane
Menu
ContentPane
A java.awt.Frame is
composed of a single
container--the Frame object
itself
JPanel
A javax.swing.JFrame is
composed of a
transparent “glassPane”
surface, and an inner
Jpanel with Contents and
Menu
JFrame Class View
AWT
Component
GlassPane
Frame
Container
ContentPane
JComponent
contains
JFC
JRootPane
manages
JMenuBar
JFrame
The JRootPane is a container with
a JLayeredPane (holding the Menu
and ContentPane) and a
Component GlassPane. It serves
as base for numerous classes.
JLayeredPane
JRootPane: The Content Pane
The JRootPane contains only two components:
the JLayeredPane and
the Component GlassPane
Its layout manager ignores all attempts to add new components.
Instead, one must add to the JRootPane’s ContentPane, found
inside the JLayeredPane.
A call to getContentPane() returns an instance of the
ContentPane.
JRootPane: The Glass Pane
public Component getGlassPane();
public void setGlassPane(Component);
JFrame Blue Print
We can use the top “glassPane”
as a drawing area. Since it spans
the entire JFrame, we can draw on
top of menu bars, and every
component.
The JPanel has a remarkable
layering feature, allowing us to
stack and shuffle components.
(Demo alert!)
public JPanel getContentPane();
JFrame Disposal
•
JFrame allows you to configure how it responds to closure.
– Default: hides JFrame on closure attempt.
– To modify: invoke setDefaultCloseOperation().
– E.g.,:
MyJFrameInstance.setDefaultCloseOperation
(WindowConstants.DO_NOTHING_ON_CLOSE);
/* behaves just like java.awt.Frame */
– other constants in javax.swing.WindowConstants:
– HIDE_ON_CLOSE - invokes any registered WindowListener
objects, then hides. This is default behavior.
– DISPOSE_ON_CLOSE - invokes any reguster WindowListener
objects, and then disposes.
Glass Panel Example
/**
*
GlassExample -- draws an oval on the glassPane,
*
obscuring the widgets below until clicked
*/
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class GlassExample extends Jframe {
JButton start, end;
GlassCover cover;
public GlassExample(){
super ("Glass Pane Example");
this.setSize(100,100);
this.setDefaultCloseOperation
(WindowConstants.DISPOSE_ON_CLOSE);
start = new JButton ("Start");
end = new JButton ("End");
JPanel p = new JPanel();
p.add(start); p.add(end);
final JPanel glass = (JPanel) this.getGlassPane();
cover = new GlassCover();
cover.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {}
public void mouseEntered(MouseEvent e)
{
cover.setColor(Color.yellow.darker());
glass.repaint();
}
public void mouseExited(MouseEvent e)
{
cover.setColor(Color.yellow.brighter());
glass.repaint();
}
public void mousePressed(MouseEvent e)
{
glass.setVisible(false);
}
public void mouseReleased(MouseEvent e) {}});
glass.add(cover);
glass.setVisible(true);
getContentPane().add(p);
}// end of Default Constructor
class GlassCover extends Component {
Color color = Color.yellow.darker();
String strMsg = "Click Here";
public void setColor(Color color){this.color=color;}
public Color getColor(){ return color; }
public void paint(Graphics g){
g.setColor(getColor());
g.fillOval(0,0,getParent().getSize().width,
getParent().getSize().height);
g.setColor(Color.black);
g.setFont (new Font("Monospaced", Font.BOLD, 16));
FontMetrics fm = g.getFontMetrics();
g.drawString (strMsg,
(getParent().getSize().width
- fm.stringWidth(strMsg))/2,
getParent().getSize().height/2);
}//paint
public Dimension getPreferredSize(){
return new Dimension(getParent().getSize());
}
}//named inner class
public static void main(String arg[]){
new GlassExample().show();
}
}// class
JComponent: The Generic Widget
• The JComponent provides the basis for all Swing
components.
• JComponent extends java.awt.Container, making all
Swing components large, powerful widgets. (Also, all
Swing components are also containers--even if you
wouldn’t normally place things in them. E.g.,
JButton)
• In turn, JComponent is subclassed by numerous
widgets. Thus, composition is favored or inheritance
for widget manipulation.
JComponent
Since JComponent is the basis for most Swing components, all Swing
widgets have the following general features:
Borders
-- JComponent derived classes can have borders
Accessibility
-- JComponents use Swing’s accessibility features to provide
additional information about the widget.
Tooltips
-- JComponents can have time sensitive tooltips.
Double Buffering
-- By default, Swing components have double buffering built in
Serialization
-- CAUTION: This will change
3.
Design
Button Design
• JButtons are a good example of the Model, View
Control structure Java, *ahem*, borrowed and
modified from Smalltalk
public abstract interface
javax.swing.ButtonModel
extends ItemSelectable
java.lang.Object
|
+--java.awt.Component
|
+--java.awt.Container
hasA
|
+--javax.swing.JComponent
|
AbstractButton
+--javax.swing.AbstractButton
implements ItemSelectable,
|
SwingConstants
+--javax.swing.JButton
Defines the common behaviors
for the JButton, JToggleButton,
The ButtonModel provides the
JCheckbox, and the
state machine for Button behavior
JRadioButton classes.
MVC Paradigm
• The MVC paradigm breaks applications or interfaces
into three parts: the model, the view, and the
controller.
Users interact with a controller (e.g.,
buttons), and make changes to the
model (e.g., data), which is then
reflected in the view (e.g., graph).
Model
A --> 25 %
B --> 60 %
C --> 15 %
Control
View(s)
60
50
40
A
30
Percentage
20
B
B
10
0
A
B
B
Draw
Graph
MVC Relationships
The Controller has references to both the view
and the model; the controller is the base of the
triad.
Model
View
Controller
MVC Relationships
The Model might have a reference to the view,
to allow for notification of changes. The model
does not know who controls it, however.
The View has a reference to the model, for
gathering data. View also has reference to the
Controller, to notify of successful updates.
(References are minimal, perhaps to a base
class, to allow swapping of controls.)
MVC Paradigm, à la Swing
• Swing uses a simplified version of the MVC structure,
called the model-delegate
Swing
Component
View
Model
Controller
UI-delegate
MVC: Who Cares?
Helps us
understand
complex
Swing
packages
Guides future
package
development
Why does
it matter?
Allows user
customization of
model or view,
without rewriting
both
(Think of
something
else; need
four points)
4.
Events & Actions
Events & Actions In Swing
Swing uses the JDK 1.1 delegate event model, but adds a very
useful device with the javax.swing.AbstractAction class.
AbstractAction:
• implements the javax.swing.Action interface
• allows users to tie events to a single object
Swing also adds to the core JDK 1.1 event model with the
javax.swing.event.* package. There are several dozen events,
including:
TreeExpansionEvent
TreeModelEvent
TreeSelectionEvent
ChangeEvent
Efficient Event Registration: Motivation
Suppose we have a GUI that allows us to Cut and Paste
The “Cut” action can be performed by (1) a menu item and (2) a button.
Enabled
File Edit
Help
Cut
Paste
CUT
This is a
test of of
the cut
action
Disabled
Of course, we can only
cut when we’ve highlighted
an object or area of text,
so sometimes our
cut option is enabled,
sometimes it’s disabled
. . . We have to keep the
button and menu in sync.
If more objects are added
that perform “Cut”
operations, we have to
rewrite the program
File Edit
Help
Cut
Paste
CUT
This is a
test of of
the cut
action
Motivation (cont’d)
Often, we might like to have a single action handle events fired from
multiple components. The most common scenario is the menu bar
mapped to button events.
Swing containers can accept ‘action objects’ with their add() method,
allowing them to automatically create a GUI component, returned to
you upon calling add().
E.g.,:
JMenu menu = new JMenu();
JMenuItem item= menu.add(action);
Save
Ctrl+S
JToolBar toolbar = new JToolBar();
JButton button = toolbar.add(action);
Property
change
events
Action
event
Action
event
Save Action
Motivation (cont’d)
What does add() to a Swing
container do for us?
JMenu menu = new JMenu();
JMenuItem = menu.add(action);
Save
The method:
Ctrl+S
Action
event
JToolBar toolbar = new JToolBar();
JButton button = toolbar.add(action);
Property
change
events
Save Action
Action
event
(1) creates an instance of the appropriate
GUI component,
(2) registers it as a PropertyChangeListener for any changes in the
action object, and
(3) returns a reference to the GUI for customization
One creates the action object by interfacing with an appropriate
event handler, or subclassing a class that does this.
AbstractAction
Swing allows us to globally toggle the activation status of event handlers
through methods in the Action interface.
A convenient implementation technique is to merely subclass
javax.swing.AbstractAction, which merely requires us to override
actionPerformed(ActionEvent e)
to obtain the desired behavior.
public class MyAction extends AbstractAction {
public void actionPerformed(ActionEvent e){
. . .
}
}// MyAction
import
import
import
import
Example
javax.swing.*;
javax.swing.event.*;
java.awt.*;
java.awt.event.*;
public class EventDemo extends Jframe {
JSlider width = new JSlider(JSlider.HORIZONTAL, 1, 450, 75);
JSlider height = new JSlider(JSlider.VERTICAL, 1, 450, 75);
public EventDemo(){
/* load image */
Toolkit tk = Toolkit.getDefaultToolkit();
Icon icon = new DynamicIcon
(tk.getImage("goldenrod_motto_new.JPG"), width, height);
final JLabel dynamicLabel = new JLabel(icon);
/* anonymous inner class listener */
class Updater implements ChangeListener {
public void stateChanged(ChangeEvent ev) {
dynamicLabel.repaint();
}
};
Updater updater = new Updater();
width.addChangeListener(updater);
height.addChangeListener(updater);
this.addWindowListener
(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
Container c = getContentPane();
c.setLayout(new BorderLayout());
c.add(width, BorderLayout.NORTH);
c.add(height, BorderLayout.WEST);
c.add(dynamicLabel, BorderLayout.CENTER);
setSize(500,500);
setVisible(true);
}
public static void main(String[] args) {
new EventDemo();
}
}// class EventDemo
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
class DynamicIcon implements Icon {
Image im;
JSlider width, height;
public DynamicIcon (Image im, JSlider width, JSlider height){
this.im = im;
this.width = width;
this.height = height;
}
public int getIconWidth() { return width.getValue(); }
public int getIconHeight() { return height.getValue(); }
public void paintIcon(Component c, Graphics g, int x, int y) {
g.setClip(x, y, getIconWidth(), getIconHeight());
g.drawImage(im,0,0,c);
}
};
Simple Events: ChangeEvent
Some Swing components are relatively simple, and cannot generate
complex events.
A JSlider, for example, can only generate a single relevant event:
movements of the thumb.
Swing has a convenient event object for this type of simple lightweight: the ChangeEvent.
The corresponding interface for this object has a single method:
public void stateChange(ChangeEvent e);
import javax.swing.*; import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
public class JSliderTest extends JFrame implements ChangeListener{
JSlider slider; JLabel label; JPanel panel;
public JSliderTest() {
super("Test of Change Events");
this.setSize(400,100);
this.addWindowListener (new WindowAdapter(){
public void windowClosing(WindowEvent e){
System.exit(0); }});
slider = new JSlider(); panel = new JPanel();
label = new JLabel("Value: " + slider.getValue());
panel.add(label); panel.add(slider);
this.getContentPane().setLayout(new FlowLayout());
this.getContentPane().add(panel);
slider.addChangeListener(this);
}
public void stateChanged(ChangeEvent e){
label.setText("Value: " + slider.getValue());
}
public static void main (String arg[]){
new JSliderTest().show();
}}
ChangeListener: Simple Uses
import javax.swing.*; import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
public class JSliderTest extends JFrame implements ChangeListener{
JSlider slider; JLabel label; JPanel panel;
public JSliderTest() {
super("Test of Change Events");
this.setSize(400,100);
this.addWindowListener (new WindowAdapter(){
public void windowClosing(WindowEvent e){
System.exit(0); }});
slider = new JSlider(); panel = new JPanel();
label = new JLabel("Value: " + slider.getValue());
panel.add(label); panel.add(slider);
this.getContentPane().setLayout(new FlowLayout());
this.getContentPane().add(panel);
slider.addChangeListener(this);
}
public void stateChanged(ChangeEvent e){
label.setText("Value: " + slider.getValue());
}
public static void main (String arg[]){
new JSliderTest().show();
}}
The registration and handling of events is similar to JDK 1.1 events.
Note that the ChangeEvent Object is a direct descendant from
EventObject. It has very little useful information about state.
The ChangeListener interface is therefore useful for simple lightweight
components.
More Complex Events: State
Changes
Some Swing components are more complex than the JSlider.
Events generated from some of the atomic components such as JTree
and JTable, for example, could represent a variety of state changes.
One
Action:
You can ONLY
drag the thumb
You can edit a
cell, select a node,
drag a leaf, etc.
Many
Actions:
JTree
JSlider
Solution: PropertyChangeListener
Obviously, using a ChangeListener for complex widgets is not productive.
(How would you know what action was taken?)
Swing therefore provides a PropertyChangeListener interface to capture
state changes in complex widgets. (Note that its part of the beans
package)
import java.beans.*;
public void propertyChange(PropertyChangeEvent e);
Useful methods in the PropertyChangeEvent object include:
public
public
public
public
public
String getPropertyName();
Object getNewValue();
Object getOldValue();
void setPropagationId(Object propagationId);
Object getPropagationId();
Other Swing Action Classes
AncestorEvent
CaretEvent
ChangeEvent
DocumentEvent.EventType
EventListenerList
HyperlinkEvent
HyperlinkEvent.EventType
InternalFrameAdapter
InternalFrameEvent
ListDataEvent
ListSelectionEvent
MenuDragMouseEvent
MenuEvent
MenuKeyEvent
MouseInputAdapter
PopupMenuEvent
SwingPropertyChangeSupport
TableColumnModelEvent
TableModelEvent
TreeExpansionEvent
TreeModelEvent
TreeSelectionEvent
UndoableEditEvent
javax.swing.event.*
package
5.
Danger
Cautionary Notes: MVC &
Threads
• An artifact of the MVC (or model-delegate) structure
of Swing is the need to avoid separate threads
updating the model state
• To avoid a race condition, one should ONLY use the
event-dispatching queue.
View
myThread
Which thread
finishes last?
Event Dispatcher
Model
Controller
• The event-dispatching queue handles repaints, and
dispatches events to GUI component listeners
Cautionary Note: Mixing Types
• Avoid mixing light and heavy weight components.
• Heavy weights are “opaque” -- solid & rectangular
• Light weights are transparent, and draw themselves on top of
the heavyweight container they occupy (Frame, Jframe, Panel,
etc.)
• This can frustrate Z-order layering:
Lightweight
java.awt.Component
CLICK ME
CLICK ME
Heavy weight
java.awt.Button
As designed,
and as we coded it
What shows up
Possible Solution: Use Timers
We normally think of events as user-driven actions. But
the mere passage of time is an event that Swing
components can observe--all without the use of
additional threads.
Java provides the javax.swing.Timer object--something of
an ‘egg timer’ that allows one to have an event fired
periodically without user interaction:
delay
Timer t = new Timer(500, this);
t.start();
Event handler
import javax.swing.*; import java.awt.event.*;
import java.awt.*;
public class StopWatch extends JLabel implements ActionListener{
int count = 0;
public StopWatch(){
super("Elapsed Time: 0 secs", JLabel.CENTER);
Timer t = new Timer(1000, this); t.start();
}
public void actionPerformed(ActionEvent e){
this.setText("Elapsed Time: " + count++ + " secs");
}
public static void main (String arg[]){
JFrame f = new JFrame();
f.setSize(300,100);
f.addWindowListener (new WindowAdapter()
{ public void windowClosing(WindowEvent e){
System.exit(0);}});
StopWatch c = new StopWatch();
f.getContentPane()
.setLayout(new BorderLayout());
f.getContentPane()
.add(c, BorderLayout.CENTER);
f.show();
} }
6.
Trees in Swing
Trees in Swing
• A useful means of visualizing hierarchical information
is through the javax.swing.tree.JTree
• Some Key ingredients:
– JTree: The tree reference
– DefaultTreeModel -- how the tree works/looks/acts
– DefaultMutableTreeNodeClass -- information (leaves and
nodes) of the Tree
• Let’s see how these work in a simple tree
Hello Tree
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.tree.*;
public class TreeDemo extends Jframe {
JTree tree;
DefaultTreeModel treeModel;
String[] strLeaves =
{"Leaf 1.", "Leaf 2.", "Leaf 3.", "Subroot "};
public TreeDemo(){
super ("Demonstration of JTree");
setSize(200,400);
addWindowListener
(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
}
Hello Tree (cont’d)
public void makeTree(){
DefaultMutableTreeNode root = new DefaultMutableTreeNode("Root");
DefaultMutableTreeNode[] nodes = new DefaultMutableTreeNode[20];
treeModel = new DefaultTreeModel(root);
tree = new JTree(treeModel);
for (int i = 20; i-->0;)
nodes[i] = new DefaultMutableTreeNode
(strLeaves[i%strLeaves.length]+i);
DefaultMutableTreeNode temp=root;
for (int i=20; i-->0;) {
if (nodes[i].toString()
.substring(0,(strLeaves[strLeaves.length-1].length()))
.equals(strLeaves[strLeaves.length-1])) {
treeModel.insertNodeInto(nodes[i], root, 0);
temp = nodes[i];
continue;
}
treeModel.insertNodeInto(nodes[i], temp, 0);
}
getContentPane().add(tree, BorderLayout.CENTER);
}//makeTree
Hello Tree (cont’d)
public static void main (String arg[])
{
TreeDemo d = new TreeDemo();
d.makeTree();
d.setVisible(true);
}
}// class TreeDemo
Interface
Scrollable
JComponent
CellEditor
Abstract
JTree
Class
TreeCellRenderer
javax.swing
Tree Model
TreeSelectionModel
DefaultTreeCellRenderer
DefaultTreeCellEditor
ActionListener
TreeSelectionListener
javax.swing.event
TreePath
RowMapper
TreeModel
DefaultTreeModel
DefaultTreeSelectionModel
MutableTreeNode
TreeNode
DefaultMutableTreeNode
javax.swing.tree
TreeCellEditor
uses
contains
Tree Customizations
To change the look of our tree, we change the DefaultTreeCellRenderer:
DefaultTreeCellRenderer renderer =
new DefaultTreeCellRenderer();
renderer.setLeafIcon(new ImageIcon
("Document.gif"));
tree.setCellRenderer(renderer);
We can also directly change the Tree
through the UIManager:
UIManager.put("Tree.leafIcon",
new ImageIcon(”foo.gif"));
UIManager.put("Tree.openIcon",
new ImageIcon(”bar.gif"));
UIManager.put("Tree.closedIcon",
new ImageIcon(”baz.gif"));
UIManager.put("Tree.expandedIcon",
new ImageIcon(”frob.gif"));
UIManager.put("Tree.collapsedIcon",
new ImageIcon(”qux.gif"));
Image from disk:
Tree Customization: Cont’d
The javax.swing.tree class has a few awkward design
features:
Cell rendering is handled on a tree-wide basis;
making customization of icons more challenging.
Solution: interface TreeCellRender is available
Subclassing the DefaultTreeCellRenderer is difficult;
key variables used in the interfaced method are private.
Solution: jar -xf src.jar DefaultTreeCellRenderer.java
and rewrite the class
Tree Events
Selection Expansion
Structural
Model
Three types
of tree events
Tree Events: Model Changes
The TreeModelEvent Class lets us catch:
changes in path
changes in information on children in the path
This wrapped information is accessed through methods:
public int[ ] getChildIndices();
public Object[ ] getChildren();
public Object[ ] getPath();
public TreePath getTreePath();
Q: Should
Sun add
an Adapter?
A: No.
The TreeModelListener interface imposes four methods:
public void treeNodesChanged(TreeModelEvent e);
public void treeNodesInserted(TreeModelEvent e);
public void treeNodesRemoved(TreeModelEvent e);
public void treeStructureChanged(TreeModelEvent e);
?
Why
not?
Tree Events: Selection Changes
The TreeSelectionEvent Class lets us catch:
changes in Tree selection
Useful methods include:
public Object cloneWithSource(Object newSource);
public TreePath getPath();
/* also getPaths() */
public TreePath getNewLeadSelectionPath();
public TreePath getOldLeadSelectionPath();
public boolean isAddedPath();
public boolean isAddedPath(TreePath path);
The TreeModelListener interface imposes one methods:
public void valueChanged(TreeSelectionEvent e);
Useful where
a component
delegates
part of its
visual presence
to a tree; useful
in event adapter
to pass on the
event
Tree Events: View Changes
The TreeExpansionListener Class lets us catch:
changes in Tree expansion/collapse
Useful methods include:
public TreePath getPath();
The TreeModelListener interface
imposes two methods:
TreeWillExpandListener interface:
* new to JDK 1.2 Beta 4
public void treeExpanded
(TreeExpansionEvent e);
public void treeCollapsed
(TreeExpansionEvent e);
* allows access to events before
they take place
* allows one to prep the tree before
expansion/collapse
ExpandVetoException Class
-- blocks impending TreeExpansionEvent