The material in this lesson is extremely important. However, there is simply too much material to be covered in detail during lecture periods. Therefore, students in Prof. Baldwin's Advanced Java Programming classes at ACC will be responsible for studying this material on their own, and bringing any questions regarding the material to class for discussion.
This lesson was originally written on December 6, 1997 using the software and documentation in the JDK 1.1.3 download package.
It is also possible, to develop lightweight components which are in full compliance with the Source/Listener methodology. The availability of inner classes also makes this a neat and tidy process where the entire lightweight component can be developed inside a single outer class definition.
This lesson develops a lightweight 3D button class that replicates the functionality of the earlier one, but does so using the Source/Listener methodology with named inner classes.
Much of the class definition is still the same as the original. The following sections will discuss only those parts of the class definition which changed as a result of this change in methodology.
A test program will be provided at the end of the lesson that can be used to exercise this new version of the lightweight button class.
Please refer to the class named LWButton01 in a previous lesson for a complete functional description of the lightweight 3D button class.
The instance variable named mouseListener is a reference to the mouse listener object. This is needed so that code inside the key listener methods can invoke the mousePressed() method on the mouse listener object.
In both cases, the purpose is no different from before, but the mechanics
are slightly different.
LWButton02 refToThis; //ref to this object MyMouseListenerClass mouseListener; |
The only thing in this code that is in any way unusual is the last line
where a reference to the this object is stored in an instance variable
named refToThis to make it available to methods inside of the inner
classes. The purpose is as described in the discussion of the new instance
variables above.
mouseListener = new MyMouseListenerClass(); this.addMouseListener(mouseListener); this.addKeyListener(new MyKeyListenerClass()); this.addFocusListener(new MyFocusListenerClass()); refToThis = this; |
Recall that the purpose of the key event processor is to convert a keyPressed() event into a mousePressed() event, and to convert a keyReleased() event into a mouseReleased() event for those cases where the key event is the space bar. You should be able to recognize those actions in the two methods of this class.
A slightly different approach to servicing the keyboard was used in this version to see if it would help with the problem which results from holding the space bar down until it starts repeating. It didn't help.
Note that the code in this class uses the special instance variable
named mouseListener to gain access to the methods of the mouse listener
object.
class MyKeyListenerClass extends KeyAdapter{ public void keyPressed(KeyEvent e){ if(e.getKeyCode() == KeyEvent.VK_SPACE) mouseListener.mousePressed(new MouseEvent( refToThis,MouseEvent.MOUSE_PRESSED, 0,0,0,0,0,false)); }//end keyPressed() public void keyReleased(KeyEvent e){ if(e.getKeyCode() == KeyEvent.VK_SPACE) mouseListener.mouseReleased(new MouseEvent( refToThis,MouseEvent.MOUSE_RELEASED, 0,0,0,0,0,false)); }//end keyReleased() }//end MyKeyListenerClass |
The purpose of this class is to trap focusGained() and focusLost() events and to set an instance variable named gotFocus in the lightweight button class to true or false.
Code in the class also forces the lightweight button to be repainted when the focus is gained or lost in order to display the change in text style used as visual feedback of a change in focus.
Note that the code in this class uses the special instance variable
named refToThis to gain access to the methods of the lightweight
button class such as invalidate() and paint().
class MyFocusListenerClass implements FocusListener{ public void focusGained(FocusEvent e){ gotFocus = true; //set the gotFocus flag refToThis.invalidate(); refToThis.repaint(); }//end focusGained() public void focusLost(FocusEvent e){ gotFocus = false; //clear the gotFocus flag refToThis.invalidate(); refToThis.repaint(); }//end focusLost() }//end MyFocusListenerClass |
The purposes of the methods in this class are:
In addition to setting the instance variable named pressed to true or false, the code also forces a repaint so that the paint() method will be invoked to do something with the new value of the instance variable.
The code to accomplish the second two purposes is fairly obvious.
There is very little difference in the code in the methods of this class
and the code in the processMouseEvent() method of the previous version.
If you understood that version, you should have no trouble recognizing
corresponding code in this version.
class MyMouseListenerClass extends MouseAdapter{ public void mousePressed(MouseEvent e){ pressed = true; refToThis.invalidate(); refToThis.repaint(); }//end mousePressed() public void mouseReleased(MouseEvent e){ if(actionListener != null) { actionListener.actionPerformed(new ActionEvent( refToThis, ActionEvent.ACTION_PERFORMED, label)); }//end if on actionListener if(pressed == true) { pressed = false; refToThis.requestFocus(); refToThis.invalidate(); refToThis.repaint(); }//end if on pressed }//end mouseReleased() }//end MyMouseListenerClass |
Most of the new material in this listing has been highlighted in boldface.
After this lesson was published, it was pointed out by a reader named Kees Kuip that this version of the program contains code that could lead to a memory leak, because the object has an embedded reference to itself which could possibly prevent it from being garbage-collected. A solution to this potential problem (as recommended by Kees) is provided as an exercise for the student in the Review section at the end of the lesson. As it turns out, this is not a problem, and a demonstration program to that effect is also included in the Review section.
Even though this turns out to have been a false alarm, it was a very
useful exercise because it led to two additional programming exercises:
one to prevent the problem (if it is a problem), and another to demonstrate
that it is not a problem. Thanks go out to Kees for pointing out this potential
problem, and also for recommending a solution.
/* File LWButton02.java Copyright 1997, R.G.Baldwin This class replicates the functionality of the class named LWButton01. However, the LWButton01 class was implemented using the enableEvents()--process...Event() methodology whereas this class uses the source/listener methodology. The listener classes in this class are named inner classes. This gives them access to the necessary state variables without the requirement to pass references to those variables to the constructors of the listener classes. ======================== This class is used to instantiate a 3D lightweight button object that behaves quite a bit like a heavyweight Button object but is much more responsive than a heavyweight button under JDK 1.1.3 and Win95 on a 133 mhz Pentium processor. The color of the button is based on the background color of its container, but is one shade brighter than the color of the background. Normally, it appears to protrude slightly out of the screen with highlights on the left and top edges and shadows on the bottom and right edges. Note that the highlighting only works if the background color does not contain components with values of 255. When you click the button with the mouse, it appears to retreat into the screen and then pops back out. As with a heavyweight button, this causes it to gain the focus. When it appears to retreat into the screen, its color changes to match that of the background with heavy shadows on the left and top and a faint outline on the bottom and right. The visual indication of focus is that the text on the button is rendered in bold italics. When you click the button, it generates an action event. When the button has the focus and you press the space bar, it generates an action event. This class was tested using JDK 1.1.3 under Win95. */ //=======================================================// import java.awt.*; import java.awt.event.*; //=======================================================// class LWButton02 extends Component { //Save the raw label provided by the user here to make // it available to the getLabel() method. String rawLabel; //The following instance variable contains the raw // label with two spaces appended to each end to make // it easier to use for sizing the LWButton. String label; // The following instance variable is set to true if // the LWButton is pressed and not released. boolean pressed = false; //The following instance variable is set to true when // the LWButton has focus boolean gotFocus = false; //The following instance variable refers to a list of // registered ActionListener objects. ActionListener actionListener; //The following is a reference to this object that is // accessible by methods of the inner classes LWButton02 refToThis; //ref to this object //The following is a reference to the MouseListener // object that is used by methods of the KeyListener // class to make calls to mousePressed() and // mouseReleased() when keyPressed() and keyReleased() // events occur. MyMouseListenerClass mouseListener; //-----------------------------------------------------// //Constructor for an LWButton with no label. public LWButton02() { //Invoke the parameterized constructor with an // empty string this(""); }//end constructor //Constructor for an LWButton with a label. public LWButton02(String rawLabel) { this.rawLabel = rawLabel; //Add spaces on either end and save it that way this.label = " " + rawLabel + " "; //Instantiate a named listener object for the mouse // listener so that the methods of the object can be // accessed by the methods of the key listener to // convert key events into mouse events. mouseListener = new MyMouseListenerClass(); //Register the named mouse listener object along with // anonymous key listener and focus listener objects // on this lightweight button object. this.addMouseListener(mouseListener); this.addKeyListener(new MyKeyListenerClass()); this.addFocusListener(new MyFocusListenerClass()); //Store a reference to this lightweight button object // in an instance variable of the class so that it // is accessible to the code in the inner listener // classes for mouse, key, and focus. refToThis = this; }//end constructor //-----------------------------------------------------// //The following method uses the AWTEventMulticaster // class to construct a list of ActionListener objects // that are registered on the LWButton object public void addActionListener(ActionListener listener) { actionListener = AWTEventMulticaster.add( actionListener, listener); }//end addActionListener() //-----------------------------------------------------// //The following method removes ActionListener objects // from the list described above public void removeActionListener(ActionListener listener){ actionListener = AWTEventMulticaster.remove( actionListener, listener); }//end removeActionListener //-----------------------------------------------------// //The following two methods provide the preferred size // and the minimum size of the LWButton to be used by // the layout managers. //Override the getPreferredSize() method. Base the // preferred size on the size of the text in the // LWButton object. Recall that two spaces have been // appended to each end of the text in the LWButton. public Dimension getPreferredSize() { if(getFont() != null) { FontMetrics fm = getFontMetrics(getFont()); return new Dimension(fm.stringWidth(label), fm.getHeight() + 10); } else return new Dimension(10, 10);//no font }//end getPreferredSize() //Override the getMinimumSize() method and specify // an arbitrary minimum size for the LWButton. public Dimension getMinimumSize() { return new Dimension(10, 10); }//end getMinimumSize() //-----------------------------------------------------// //The following two methods are available to get and set // the label of an LWButton object. public String getLabel() {//gets the label return rawLabel; }//end getLabel() public void setLabel(String rawLabel) {//sets the label this.rawLabel = rawLabel; //save the raw label //Add spaces to each end of the rawLabel to make it // easier to center the label in the LWButton. this.label = " " + rawLabel + " "; this.invalidate(); this.repaint(); }//end setLabel() //-----------------------------------------------------// //The following overridden paint() method paints the // LWButton The appearance of the LWButton depends on // the pressed and gotFocus flags. When pressed is true, // the LWButton is rendered to appear that it has been // pressed into the screen. When it is false, the // LWButton is rendered to appear that it is protruding // from the screen. When gotFocus is true, the // text is rendered in bold italics as the visual // indication that the LWButton has the focus. When // gotFocus is false, the text is rendered plain. //Note also that the position of the text also depends // on the pressed flag. When pressed is false, the text // is rendered slightly up and to the left of its // pressed position. This enhances the illusion that // the button is being pressed into the screen. //The color of the LWButton object is tied to the // background color. When protruding, the button is // rendered one shade brighter than the background. // When pressed, the LWButton is rendered the same color // as the background. //The actual size of the LWButton object is determined // by the layout manager which may or may not honor the // preferred and minimum size specifications. public void paint(Graphics g) {//paints the LWButton //If LWButton has the focus, display the text in // bold italics. Otherwise display plain. if(gotFocus) g.setFont(new Font(getFont().getName(), Font.BOLD | Font.ITALIC,getFont().getSize())); else g.setFont(new Font(getFont().getName(), Font.PLAIN,getFont().getSize())); if(pressed){ //if the pressed flag is true g.setColor(getBackground()); g.fillRect(//fill rectangle with background color 0,0,this.getSize().width,this.getSize().height); //Draw shadows three shades darker than background g.setColor( getBackground().darker().darker().darker()); //Note that three offset rectangles are drawn to // produce a shadow effect on the left and top of // the rectangle. g.drawRect(// 0,0,this.getSize().width,this.getSize().height); g.drawRect( 1,1,this.getSize().width,this.getSize().height); g.drawRect( 2,2,this.getSize().width,this.getSize().height); //Now draw a faint outline on the bottom and right of // the rectangle. g.setColor(getBackground().darker()); g.drawRect( -1,-1,this.getSize().width,this.getSize().height); //Now center the text in the LWButton object FontMetrics fm = getFontMetrics(getFont()); g.setColor(getForeground()); g.drawString(label, getSize().width/2 - fm.stringWidth(label)/2, getSize().height/2 + fm.getAscent()/2); }//end if(pressed) else{//not pressed //Make the protruding LWButton object one shade // brighter than the background. g.setColor(getBackground().brighter()); g.fillRect(//and fill a rectangle 0,0,this.getSize().width,this.getSize().height); //Set the color for the shadows three shades darker // than the background. g.setColor( getBackground().darker().darker().darker()); //Draw two offset rectangles to create shadows on // the right and bottom. g.drawRect( -1,-1,this.getSize().width,this.getSize().height); g.drawRect( -2,-2,this.getSize().width,this.getSize().height); //Highlight the left and top two shades brighter // than the background, one shade brighter than the // color of the LWButton itself which is one shade // brighter than the background. g.setColor( getBackground().brighter().brighter()); g.drawRect(// 0,0,this.getSize().width,this.getSize().height); //Now place the text in the LWButton object shifted // by two pixels up and to the left. FontMetrics fm = getFontMetrics(getFont()); g.setColor(getForeground()); g.drawString(label, getSize().width/2 - fm.stringWidth(label)/2 - 2, getSize().height/2 + fm.getAscent()/2 - 2); }//end else }//end overridden paint() method //-----------------------------------------------------// class MyKeyListenerClass extends KeyAdapter{ /* This class is used to cause the LWButton to behave as if a mouse event occurred on it whenever it has the focus and the space bar is pressed and then released. Holding the space bar down generates repetitive events due to the repeat feature of the keyboard (this would need to be disabled in a real program). */ public void keyPressed(KeyEvent e){ //Generate mousePressed() event when the space bar // is pressed by invoking the mousePressed() method // of the MouseListener object, and passing an event // object that impersonates a mouse pressed event. if(e.getKeyCode() == KeyEvent.VK_SPACE) mouseListener.mousePressed(new MouseEvent( refToThis,MouseEvent.MOUSE_PRESSED, 0,0,0,0,0,false)); }//end keyPressed() public void keyReleased(KeyEvent e){ //Generate mouseReleased() event when the space bar // is released by invoking the mouseReleased() method // of the MouseListener object, and passing an event // object that impersonates a mouse released event. if(e.getKeyCode() == KeyEvent.VK_SPACE) mouseListener.mouseReleased(new MouseEvent( refToThis,MouseEvent.MOUSE_RELEASED, 0,0,0,0,0,false)); }//end keyReleased() }//end MyKeyListenerClass //-----------------------------------------------------// class MyFocusListenerClass implements FocusListener{ /* This methods of this class are invoked when a focus event occurs on the LWButton. This happens when the requestFocus() method is called inside the mouseReleased() event handler for the LWButton. This sets or clears the gotFocus flag that is used to cause the text renderer to modify the text to indicate that the LWButton has the focus. When the LWButton has the focus, the text is rendered in bold italics. */ public void focusGained(FocusEvent e){ gotFocus = true; //set the gotFocus flag refToThis.invalidate(); refToThis.repaint(); }//end focusGained() public void focusLost(FocusEvent e){ gotFocus = false; //clear the gotFocus flag refToThis.invalidate(); refToThis.repaint(); }//end focusLost() }//end MyFocusListenerClass //-----------------------------------------------------// class MyMouseListenerClass extends MouseAdapter{ /* The purpose of this methods in this class is threefold: 1. Modify the appearance of the LWButton object when the user clicks on it. 2. Invoke the actionPerformed() method in the Listener object that is registered to listen to this LWButton object. 3. Request focus for the LWButton. */ public void mousePressed(MouseEvent e){ //When the mouse is pressed on the LWButton object, // set the "pressed" state of the object to true // and force it to be repainted to change its // appearance. When pressed is true, the button is // rendered as though it has been pressed into the // screen. pressed = true; refToThis.invalidate(); refToThis.repaint(); }//end mousePressed() public void mouseReleased(MouseEvent e){ //When the mouse is released on the LWButton object: // 1. Invoke the actionPerformed() method in the // Listener objects that are registered to // listen to the LWButton object. // 2. Confirm that the "pressed" state is true and // if so, set it to false and force the object // to be repainted to change its appearance. // When pressed is false, the button is rendered // so as to appear to protrude out of the // screen. // 3. Request the focus for the LWButton object. //if an ActionListener is registered if(actionListener != null) { //Invoke the actionPerformed() method on the list // of listener objects registered on the // LWButton object. Instantiate and pass an // ActionEvent object as a parameter. actionListener.actionPerformed(new ActionEvent( refToThis, ActionEvent.ACTION_PERFORMED, label)); }//end if on actionListener if(pressed == true) { pressed = false; refToThis.requestFocus(); refToThis.invalidate(); refToThis.repaint(); }//end if on pressed }//end mouseReleased() }//end MyMouseListenerClass }//end class LWButton02 //=======================================================// |
/* File Lightweight07.java Copyright 1997, R.G.Baldwin This program was designed specifically to test the class named LWButton02 and requires access to that class. The classed named LWButton02 is a lightweight 3D button class which replicates the functionality of the LWButton01 class, but implements that functionality using source/listener methodology instead of enableEvents() -- process...Event() methodology. ==================== You will probably need to compile and run the program to really appreciate what it does. The purpose of the program is to exercise the lightweight button class named LWButton02 under a BorderLayout manager. The BorderLayout manager does not honor both dimensions of the preferred size of a component in any of its five positions. Components in the North and South positions probably have the vertical dimension of their preferred size honored. Similarly, components in the East and West positions probably have the horizontal dimension of their preferred size honored. Neither dimension of the preferred size is honored for components in the Center. Components in the center simply occupy all of the available space. This program places three lightweight buttons of type LWButton02, one heavyweight button of type Button, and a Label object in a Frame object with a ten-pixel gap between all components. The lightweight buttons occupy the East, North, and Center positions in the Frame. The heavyweight button occupies the West position. The Label occupies the South position. When you click on any of the four buttons, the color of the Label toggles between red and green. You can experiment with the manner in which the preferred size is or is not honored by resizing the Frame. Note that this program makes use of a named inner class. This program was tested using JDK 1.1.3 under Win95. */ //=======================================================// import java.awt.*; import java.awt.event.*; //=======================================================// public class Lightweight07 extends Frame{ Label myLabel; public static void main(String[] args){ new Lightweight07();//instantiate object of this type }//end main //-------------------------------------------------------// public Lightweight07(){//constructor this.setTitle("Copyright 1997, R.G.Baldwin"); //Set background to a dull yellow this.setBackground(new Color(128,128,0)); //Create a borderLayout object with gaps and apply // it to the Frame object. BorderLayout myLayout = new BorderLayout(); myLayout.setHgap(10); myLayout.setVgap(10); this.setLayout(myLayout); //Instantiate three lightweight buttons LWButton02 eastLWButton = new LWButton02("East"); LWButton02 northLWButton = new LWButton02("North"); LWButton02 centerLWButton = new LWButton02("Center"); //Instantiate a Label object and initialize it to green myLabel = new Label("Label Object"); myLabel.setBackground(Color.green); //Instantiate a heavyweight button object Button myButton = new Button("Heavyweight Button"); //Add all five components to the Frame object. this.add(eastLWButton,"East"); this.add(northLWButton,"North"); this.add(centerLWButton,"Center"); this.add(myButton,"West"); this.add(myLabel,"South"); //Instantiate an ActionListener object MyActionListener myActionListener = new MyActionListener(); //Register the ActionListener object on all four // of the buttons. eastLWButton.addActionListener(myActionListener); northLWButton.addActionListener(myActionListener); centerLWButton.addActionListener(myActionListener); myButton.addActionListener(myActionListener); this.setSize(300,200); this.setVisible(true); //Anonymous inner-class listener to terminate program this.addWindowListener(new WindowAdapter(){ public void windowClosing(WindowEvent e){ System.exit(0);}});//end addWindowListener }//end constructor //-----------------------------------------------------// //Inner Class to respond to action events. Make this an // inner class for easy access to myLabel. class MyActionListener implements ActionListener{ public void actionPerformed(ActionEvent e){ if(myLabel.getBackground() == Color.green) myLabel.setBackground(Color.red); else myLabel.setBackground(Color.green); }//end actionPerformed }//end class MyActionListener }//end class Lightweight07 //=======================================================// |
Thanks go to Kees for pointing out this potential problem, and also for providing a recommended solution.
Also, a reader named Myles Harding sent in the following suggestion:
Thanks very much for your excellent Java Tutorials. I recommend
them to all my students. The mind set your lessons project of keeping
it simple and testing everything from first principles sends the right
message.
May I make a suggestion regarding you Lighweight Components Lessons. The reftothis variable, or the getThis() method in the Inner classes can be replaced with LWButton.this so your getThis().requestFocus(), or refToThis.requestFocus() becomes LWButton01.this.requestFocus(); and so on. The benefits of this scheme becomes more marked when the Inner class has further Inner classes. I have not seen this way of accessing outer named this's documented anywhere but it seems to work OK. Regards
|
/* File SampProg143.java Copyright 1997, R.G.Baldwin From lesson 180. A reader has pointed out that the program named LWButton02 contains code that could lead to a memory leak. Without viewing the solution that follows, see if you can find the potential memory leak and modify the program to correct the problem. This modified program should replicate the functionality of the lightweight button named LWButton02. ======================== This class is used to instantiate a 3D lightweight button object. The color of the button is based on the background color of its container, but is one shade brighter than the color of the background. Normally, it appears to protrude slightly out of the screen with highlights on the left and top edges and shadows on the bottom and right edges. Note that the highlighting only works if the background color does not contain components with values of 255. When you click the button with the mouse, it appears to retreat into the screen and then pops back out. As with a heavyweight button, this causes it to gain the focus. When it appears to retreat into the screen, its color changes to match that of the background with heavy shadows on the left and top and a faint outline on the bottom and right. The visual indication of focus is that the text on the button is rendered in bold italics. When you click the button, it generates an action event. When the button has the focus and you press the space bar, it generates an action event. This class was tested using JDK 1.1.3 under Win95. */ //=======================================================// import java.awt.*; import java.awt.event.*; //=======================================================// class SampProg143 extends Component { //Save the raw label provided by the user here to make // it available to the getLabel() method. String rawLabel; //The following instance variable contains the raw // label with two spaces appended to each end to make // it easier to use for sizing the LWButton. String label; // The following instance variable is set to true if // the LWButton is pressed and not released. boolean pressed = false; //The following instance variable is set to true when // the LWButton has focus boolean gotFocus = false; //The following instance variable refers to a list of // registered ActionListener objects. ActionListener actionListener; //The following is a reference to the MouseListener // object that is used by methods of the KeyListener // class to make calls to mousePressed() and // mouseReleased() when keyPressed() and keyReleased() // events occur. MyMouseListenerClass mouseListener; //-----------------------------------------------------// //Constructor for an LWButton with no label. public SampProg143() { //Invoke the parameterized constructor with an // empty string this(""); }//end constructor //Constructor for an LWButton with a label. public SampProg143(String rawLabel) { this.rawLabel = rawLabel; //Add spaces on either end and save it that way this.label = " " + rawLabel + " "; //Instantiate a named listener object for the mouse // listener so that the methods of the object can be // accessed by the methods of the key listener to // convert key events into mouse events. mouseListener = new MyMouseListenerClass(); //Register the named mouse listener object along with // anonymous key listener and focus listener objects // on this lightweight button object. this.addMouseListener(mouseListener); this.addKeyListener(new MyKeyListenerClass()); this.addFocusListener(new MyFocusListenerClass()); }//end constructor //-----------------------------------------------------// //The following method replaces the instance variable // named refToThis in the version named LWButton02. It // was pointed out by a reader that the use of the // refToThis variable could lead to a memory leak, // because the object had an embedded reference to // itself which would prevent it from being // garbage-collected. SampProg143 getThis(){ return this; }//end getThis() //-----------------------------------------------------// //The following method uses the AWTEventMulticaster // class to construct a list of ActionListener objects // that are registered on the LWButton object public void addActionListener(ActionListener listener) { actionListener = AWTEventMulticaster.add( actionListener, listener); }//end addActionListener() //-----------------------------------------------------// //The following method removes ActionListener objects // from the list described above public void removeActionListener(ActionListener listener){ actionListener = AWTEventMulticaster.remove( actionListener, listener); }//end removeActionListener //-----------------------------------------------------// //The following two methods provide the preferred size // and the minimum size of the LWButton to be used by // the layout managers. //Override the getPreferredSize() method. Base the // preferred size on the size of the text in the // LWButton object. Recall that two spaces have been // appended to each end of the text in the LWButton. public Dimension getPreferredSize() { if(getFont() != null) { FontMetrics fm = getFontMetrics(getFont()); return new Dimension(fm.stringWidth(label), fm.getHeight() + 10); } else return new Dimension(10, 10);//no font }//end getPreferredSize() //Override the getMinimumSize() method and specify // an arbitrary minimum size for the LWButton. public Dimension getMinimumSize() { return new Dimension(10, 10); }//end getMinimumSize() //-----------------------------------------------------// //The following two methods are available to get and set // the label of an LWButton object. public String getLabel() {//gets the label return rawLabel; }//end getLabel() public void setLabel(String rawLabel) {//sets the label this.rawLabel = rawLabel; //save the raw label //Add spaces to each end of the rawLabel to make it // easier to center the label in the LWButton. this.label = " " + rawLabel + " "; this.invalidate(); this.repaint(); }//end setLabel() //-----------------------------------------------------// //The following overridden paint() method paints the // LWButton The appearance of the LWButton depends on // the pressed and gotFocus flags. When pressed is true, // the LWButton is rendered to appear that it has been // pressed into the screen. When it is false, the // LWButton is rendered to appear that it is protruding // from the screen. When gotFocus is true, the // text is rendered in bold italics as the visual // indication that the LWButton has the focus. When // gotFocus is false, the text is rendered plain. //Note also that the position of the text also depends // on the pressed flag. When pressed is false, the text // is rendered slightly up and to the left of its // pressed position. This enhances the illusion that // the button is being pressed into the screen. //The color of the LWButton object is tied to the // background color. When protruding, the button is // rendered one shade brighter than the background. // When pressed, the LWButton is rendered the same color // as the background. //The actual size of the LWButton object is determined // by the layout manager which may or may not honor the // preferred and minimum size specifications. public void paint(Graphics g) {//paints the LWButton //If LWButton has the focus, display the text in // bold italics. Otherwise display plain. if(gotFocus) g.setFont(new Font(getFont().getName(), Font.BOLD | Font.ITALIC,getFont().getSize())); else g.setFont(new Font(getFont().getName(), Font.PLAIN,getFont().getSize())); if(pressed){ //if the pressed flag is true g.setColor(getBackground()); g.fillRect(//fill rectangle with background color 0,0,this.getSize().width,this.getSize().height); //Draw shadows three shades darker than background g.setColor( getBackground().darker().darker().darker()); //Note that three offset rectangles are drawn to // produce a shadow effect on the left and top of // the rectangle. g.drawRect(// 0,0,this.getSize().width,this.getSize().height); g.drawRect( 1,1,this.getSize().width,this.getSize().height); g.drawRect( 2,2,this.getSize().width,this.getSize().height); //Now draw a faint outline on the bottom and right of // the rectangle. g.setColor(getBackground().darker()); g.drawRect( -1,-1,this.getSize().width,this.getSize().height); //Now center the text in the LWButton object FontMetrics fm = getFontMetrics(getFont()); g.setColor(getForeground()); g.drawString(label, getSize().width/2 - fm.stringWidth(label)/2, getSize().height/2 + fm.getAscent()/2); }//end if(pressed) else{//not pressed //Make the protruding LWButton object one shade // brighter than the background. g.setColor(getBackground().brighter()); g.fillRect(//and fill a rectangle 0,0,this.getSize().width,this.getSize().height); //Set the color for the shadows three shades darker // than the background. g.setColor( getBackground().darker().darker().darker()); //Draw two offset rectangles to create shadows on // the right and bottom. g.drawRect( -1,-1,this.getSize().width,this.getSize().height); g.drawRect( -2,-2,this.getSize().width,this.getSize().height); //Highlight the left and top two shades brighter // than the background, one shade brighter than the // color of the LWButton itself which is one shade // brighter than the background. g.setColor( getBackground().brighter().brighter()); g.drawRect(// 0,0,this.getSize().width,this.getSize().height); //Now place the text in the LWButton object shifted // by two pixels up and to the left. FontMetrics fm = getFontMetrics(getFont()); g.setColor(getForeground()); g.drawString(label, getSize().width/2 - fm.stringWidth(label)/2 - 2, getSize().height/2 + fm.getAscent()/2 - 2); }//end else }//end overridden paint() method //-----------------------------------------------------// class MyKeyListenerClass extends KeyAdapter{ /* This class is used to cause the LWButton to behave as if a mouse event occurred on it whenever it has the focus and the space bar is pressed and then released. Holding the space bar down generates repetitive events due to the repeat feature of the keyboard (this would need to be disabled in a real program). */ public void keyPressed(KeyEvent e){ //Generate mousePressed() event when the space bar // is pressed by invoking the mousePressed() method // of the MouseListener object, and passing an event // object that impersonates a mouse pressed event. if(e.getKeyCode() == KeyEvent.VK_SPACE) mouseListener.mousePressed(new MouseEvent( getThis(),MouseEvent.MOUSE_PRESSED, 0,0,0,0,0,false)); }//end keyPressed() public void keyReleased(KeyEvent e){ //Generate mouseReleased() event when the space bar // is released by invoking the mouseReleased() method // of the MouseListener object, and passing an event // object that impersonates a mouse released event. if(e.getKeyCode() == KeyEvent.VK_SPACE) mouseListener.mouseReleased(new MouseEvent( getThis(),MouseEvent.MOUSE_RELEASED, 0,0,0,0,0,false)); }//end keyReleased() }//end MyKeyListenerClass //-----------------------------------------------------// class MyFocusListenerClass implements FocusListener{ /* This methods of this class are invoked when a focus event occurs on the LWButton. This happens when the requestFocus() method is called inside the mouseReleased() event handler for the LWButton. This sets or clears the gotFocus flag that is used to cause the text renderer to modify the text to indicate that the LWButton has the focus. When the LWButton has the focus, the text is rendered in bold italics. */ public void focusGained(FocusEvent e){ gotFocus = true; //set the gotFocus flag getThis().invalidate(); getThis().repaint(); }//end focusGained() public void focusLost(FocusEvent e){ gotFocus = false; //clear the gotFocus flag getThis().invalidate(); getThis().repaint(); }//end focusLost() }//end MyFocusListenerClass //-----------------------------------------------------// class MyMouseListenerClass extends MouseAdapter{ /* The purpose of this methods in this class is threefold: 1. Modify the appearance of the LWButton object when the user clicks on it. 2. Invoke the actionPerformed() method in the Listener object that is registered to listen to this LWButton object. 3. Request focus for the LWButton. */ public void mousePressed(MouseEvent e){ //When the mouse is pressed on the LWButton object, // set the "pressed" state of the object to true // and force it to be repainted to change its // appearance. When pressed is true, the button is // rendered as though it has been pressed into the // screen. pressed = true; getThis().invalidate(); getThis().repaint(); }//end mousePressed() public void mouseReleased(MouseEvent e){ //When the mouse is released on the LWButton object: // 1. Invoke the actionPerformed() method in the // Listener objects that are registered to // listen to the LWButton object. // 2. Confirm that the "pressed" state is true and // if so, set it to false and force the object // to be repainted to change its appearance. // When pressed is false, the button is rendered // so as to appear to protrude out of the // screen. // 3. Request the focus for the LWButton object. //if an ActionListener is registered if(actionListener != null) { //Invoke the actionPerformed() method on the list // of listener objects registered on the // LWButton object. Instantiate and pass an // ActionEvent object as a parameter. actionListener.actionPerformed(new ActionEvent( getThis(), ActionEvent.ACTION_PERFORMED, label)); }//end if on actionListener if(pressed == true) { pressed = false; getThis().requestFocus(); getThis().invalidate(); getThis().repaint(); }//end if on pressed }//end mouseReleased() }//end MyMouseListenerClass }//end class SampProg143 //=======================================================// |
/* File SampProg144.java Copyright 1997, R.G.Baldwin From lesson 180. This program was designed specifically to test the class named SampProg143 and requires access to that class. The classed named SampProg143 is a lightweight 3D button class which replicates the functionality of the LWButton02 class, but eliminates the possibility of a memory leak that exists with LWButton02. ==================== You will probably need to compile and run the program to really appreciate what it does. The purpose of the program is to exercise the lightweight button class named SampProg143 under a BorderLayout manager. The BorderLayout manager does not honor both dimensions of the preferred size of a component in any of its five positions. Components in the North and South positions probably have the vertical dimension of their preferred size honored. Similarly, components in the East and West positions probably have the horizontal dimension of their preferred size honored. Neither dimension of the preferred size is honored for components in the Center. Components in the center simply occupy all of the available space. This program places three lightweight buttons of type SampProg143, one heavyweight button of type Button, and a Label object in a Frame object with a ten-pixel gap between all components. The lightweight buttons occupy the East, North, and Center positions in the Frame. The heavyweight button occupies the West position. The Label occupies the South position. When you click on any of the four buttons, the color of the Label toggles between red and green. You can experiment with the manner in which the preferred size is or is not honored by resizing the Frame. Note that this program makes use of a named inner class. This program was tested using JDK 1.1.3 under Win95. */ //=======================================================// import java.awt.*; import java.awt.event.*; //=======================================================// public class SampProg144 extends Frame{ Label myLabel; public static void main(String[] args){ new SampProg144();//instantiate object of this type }//end main //-------------------------------------------------------// public SampProg144(){//constructor this.setTitle("Copyright 1997, R.G.Baldwin"); //Set background to a dull yellow this.setBackground(new Color(128,128,0)); //Create a borderLayout object with gaps and apply // it to the Frame object. BorderLayout myLayout = new BorderLayout(); myLayout.setHgap(10); myLayout.setVgap(10); this.setLayout(myLayout); //Instantiate three lightweight buttons SampProg143 eastLWButton = new SampProg143("East"); SampProg143 northLWButton = new SampProg143("North"); SampProg143 centerLWButton = new SampProg143("Center"); //Instantiate a Label object and initialize it to green myLabel = new Label("Label Object"); myLabel.setBackground(Color.green); //Instantiate a heavyweight button object Button myButton = new Button("Heavyweight Button"); //Add all five components to the Frame object. this.add(eastLWButton,"East"); this.add(northLWButton,"North"); this.add(centerLWButton,"Center"); this.add(myButton,"West"); this.add(myLabel,"South"); //Instantiate an ActionListener object MyActionListener myActionListener = new MyActionListener(); //Register the ActionListener object on all four // of the buttons. eastLWButton.addActionListener(myActionListener); northLWButton.addActionListener(myActionListener); centerLWButton.addActionListener(myActionListener); myButton.addActionListener(myActionListener); this.setSize(300,200); this.setVisible(true); //Anonymous inner-class listener to terminate program this.addWindowListener(new WindowAdapter(){ public void windowClosing(WindowEvent e){ System.exit(0);}});//end addWindowListener }//end constructor //-----------------------------------------------------// //Inner Class to respond to action events. Make this an // inner class for easy access to myLabel. class MyActionListener implements ActionListener{ public void actionPerformed(ActionEvent e){ if(myLabel.getBackground() == Color.green) myLabel.setBackground(Color.red); else myLabel.setBackground(Color.green); }//end actionPerformed }//end class MyActionListener }//end class SampProg144 //=======================================================// |
/*File SampProg145.java From lesson 180. A reader has pointed out that the program named LWButton02 contains code that could lead to a memory leak because the object contains an instance variable named refToThis that contains a reference to itself. Without viewing the solution that follows, write a program to demonstrate that this is not a problem. The question was whether having a reference to the object embedded in the object would prevent the object from becoming eligible for garbage collection and result in a memory leak. This program was designed to answer that question. In this program, an instance variable named refToThis is assigned the value of the this reference causing it to become a reference to itself. In addition, another instance variable is assigned a reference to an object of a different class creating an embedded reference to another object. After an object of this class is instantiated, the reference to the object is set to null, making the outer object eligible for garbage collection. Then an attempt to force garbage collection is made and a two-second time delay is executed. The finalize methods for both classes are overridden to display a message. This makes it possible to determine when garbage collection takes place. Several different scenarios are tested. This program illustrates that when the outer object becomes eligible for garbage collection, embedded objects automatically become eligible for garbage collection with no requirement on the part of the programmer to set the references to those embedded objects to null. In this program, when the outer object is garbage collected, garbage collection of the embedded object follows shortly thereafter (because gargage collection of all eligible objects was forced). Apparently if the object contains an embedded reference to itself, this reference is treated the same as any reference to an embedded object. The embedded reference to the object does not prevent the object from becoming eligible for garbage collection. Tested using JDK 1.1.3 under Win95. The output from running this program was: a = new SampProg145(); In Xstr: SampProg145@1cc742 Embedded@1cc744 Nmbr = 1 a = null; In SampProg145 finalize: SampProg145@1cc742 Number = 0 In Embedded finalize: Embedded@1cc744 a = new SampProg145(); In Xstr: SampProg145@1cc71b Embedded@1cc70d Nmbr = 1 b = a; a = null; b = null; In SampProg145 finalize: SampProg145@1cc71b Number = 0 In Embedded finalize: Embedded@1cc70d a = new SampProg145(); In Xstr: SampProg145@1cc707 Embedded@1cc704 Nmbr = 1 a = new SampProg145(); In Xstr: SampProg145@1cc75c Embedded@1cc75b Nmbr = 2 In SampProg145 finalize: SampProg145@1cc707 Number = 1 In Embedded finalize: Embedded@1cc704 a = null; In SampProg145 finalize: SampProg145@1cc75c Number = 0 In Embedded finalize: Embedded@1cc75b Based on a test program from Kees. **********************************************************/ import java.awt.*; import java.util.*; //=======================================================// //Class used to embed an object inside of another object class Embedded{ int data = 10; //-----------------------------------------------------// protected void finalize() throws Throwable{ System.out.println("In Embedded finalize: " + this); }//end finalize }//end class Embedded //=======================================================// public class SampProg145{ static int numberOfInstances;//object counter SampProg145 refToThis; //reference to this object Embedded embeddedObj; //reference to embedded object //-----------------------------------------------------// public SampProg145(){//constructor //Embed an object in this object embeddedObj = new Embedded(); refToThis = this;//create ref to this object numberOfInstances++;//increment object counter System.out.println("In Xstr: " + refToThis + " " + embeddedObj + " Nmbr = " + numberOfInstances); }//end constructor //-----------------------------------------------------// static void delayAndGC() { System.gc();//try to force garbage collection try {Thread.sleep(2000);}//delay two seconds catch (Exception e) {} }//end delayAndGC //-----------------------------------------------------// protected void finalize() throws Throwable{ numberOfInstances--; System.out.println("In SampProg145 finalize: " + this + " Number = " + numberOfInstances); }//end finalize() //-----------------------------------------------------// public static void main(String args[]){ SampProg145 a , b; // There is no memory leak if after every test the // number equals 0 ! // Test 1. System.out.println(" a = new SampProg145();"); a = new SampProg145(); SampProg145.delayAndGC(); System.out.println(" a = null;"); a = null; SampProg145.delayAndGC(); // Test 2. System.out.println(" a = new SampProg145();"); a = new SampProg145(); SampProg145.delayAndGC(); System.out.println(" b = a;"); b = a; SampProg145.delayAndGC(); System.out.println(" a = null;"); a = null; SampProg145.delayAndGC(); System.out.println(" b = null;"); b = null; SampProg145.delayAndGC(); // Test 3. System.out.println(" a = new SampProg145();"); a = new SampProg145(); SampProg145.delayAndGC(); System.out.println(" a = new SampProg145();"); a = new SampProg145(); SampProg145.delayAndGC(); System.out.println(" a = null;"); a = null; SampProg145.delayAndGC(); }//end main() //-----------------------------------------------------// }//end class SampProg145 |