Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
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