Interlude Writing a Custom Editor


Step 5 — Defining Your Own Events and EventListeners

In this step, we will create our own event and event listener types and write a mechanism similar to Swing’s mechanism for registering event listeners with components. In the process we will gain better insight into the purpose and value of Swing’s event/listener mechanism.

The CheckboxListCell Class

DressingBoard used to update as a response to a ListSelectionEvent but now it has to respond to a mouse click inside an imposter checkbox. Somehow, MainFrame needs to be notified when CheckboxListCell’s “checkbox” has been toggled just as it used to be notified of ListSelectionEvents. Since only CheckboxListCell knows when the “checkbox” has been toggled, it is the logical candidate for handling notification. We will create the new event type, ToggleEvent, to define the event of a click inside the checkbox, and we will define the new listener interface, ToggleListener, such that Objects that implement ToggleListener and are registered with CheckboxListCell will be notified whenever a ToggleEvent occurs. In this way, our code design will loosely mirror Swing’s own event/listener mechanism.

CheckboxListCell will generate a ToggleEvent when it determines that the user has clicked inside its checkbox image. Two bits of information that CheckboxListCell will have at this point are the JList reference and the index of the cell whose checkbox was toggled. We will package these into the ToggleEvent object and create a ToggleEvent constructor that requires them. The JList will be the source of the event and the index might prove to be useful to a ToggleListener. The ToggleEvent class is defined in lines 76 - 86 of example 14.10.

Example 14.10: chap14.gui4.CheckboxListCell.java

image from book
 1     package chap14.gui4; 2 3     import java.awt.BorderLayout; 4     import java.awt.Color; 5     import java.awt.Component; 6     import java.awt.Point; 7     import java.awt.Rectangle; 8     import java.awt.event.MouseEvent; 9     import java.awt.event.MouseListener; 10    import java.util.EventListener; 11    import java.util.EventObject; 12    import java.util.Iterator; 13    import java.util.Vector; 14 15    import javax.swing.JCheckBox; 16    import javax.swing.JLabel; 17    import javax.swing.JList; 18    import javax.swing.JPanel; 19    import javax.swing.ListCellRenderer; 20    import javax.swing.SwingUtilities; 21 22    public abstract class CheckboxListCell 23      implements ListCellRenderer, MouseListener { 24 25      private JLabel label; 26      private JCheckBox checkBox; 27      private JPanel panel; 28      private Vector listeners = new Vector(); 29 30      public CheckboxListCell() { 31        panel = new JPanel(new BorderLayout()); 32        label = new JLabel(); 33        checkBox = new JCheckBox(); 34        checkBox.setOpaque(false); 35        panel.add("Center", label); 36        panel.add("West", checkBox); 37      } 38      public Component getListCellRendererComponent( 39        JList list, 40        Object value, 41        int index, 42        boolean isSelected, 43        boolean cellHasFocus) { 44 45        panel.setBackground(isSelected ? Color.yellow : list.getBackground()); 46        label.setText(String.valueOf(value)); 47        checkBox.setSelected(getCheckedValue(value)); 48        return panel; 49      } 50      protected abstract boolean getCheckedValue(Object value); 51 52      public void mouseClicked(MouseEvent e) { 53        JList list = (JList)e.getSource(); 54        Point p = e.getPoint(); 55        int index = list.locationToIndex(p); 56        Rectangle rect = list.getCellBounds(index, index); 57        panel.setBounds(rect); 58        if (!rect.contains(p)) { 59          if (!e.isShiftDown() && !e.isControlDown()) { 60            list.clearSelection(); 61          } 62          return; 63        } 64        p = SwingUtilities.convertPoint(list, p, panel); 65        if (!checkBox.getBounds().contains(p)) { 66          return; 67        } 68        notifyListeners(new ToggleEvent(list, index)); 69        list.repaint(rect.x, rect.y, rect.width, rect.height); 70      } 71      public void mousePressed(MouseEvent e) {} 72      public void mouseReleased(MouseEvent e) {} 73      public void mouseEntered(MouseEvent e) {} 74      public void mouseExited(MouseEvent e) {} 75 76      public static class ToggleEvent extends EventObject { 77        private int index; 78 79        public ToggleEvent(JList list, int index) { 80          super(list); 81          this.index = index; 82        } 83        public int getIndex() { 84          return index; 85        } 86      } 87      public interface ToggleListener extends EventListener { 88        public void checkboxToggled(ToggleEvent event); 89      } 90      private void notifyListeners(ToggleEvent event) { 91        for (Iterator it = listeners.iterator(); it.hasNext();) { 92          ToggleListener rl = (ToggleListener)it.next(); 93          rl.checkboxToggled(event); 94            } 95          } 96          public void addToggleListener(ToggleListener rl) { 97            listeners.add(rl); 98          } 99          public void removeToggleListener(ToggleListener rl) { 100           listeners.remove(rl); 101        } 102     }
image from book

Because ToggleEvent and ToggleListener exist solely to support CheckboxListCell’s special needs, we define them as inner classes of CheckboxListCell. They could be defined as external classes but that would suggest that they had more universal utility. ToggleEvent is declared as a static class so that the existence of a ToggleEvent instance will not depend on the existence of an instance of its enclosing CheckboxListCell class. This would allow an external class to create a new instance of a ToggleEvent by the statement:

 CheckboxListCell.ToggleEvent event = new CheckboxListCell.ToggleEvent(list, index);

If it were not a static class, then in order for an external class to create a new instance of a ToggleEvent there would already have to be an instance of CheckboxListCell from which to create it as in:

 CheckboxListCell cell = new CheckboxListCell(); CheckboxListCell.ToggleEvent event = cell.new ToggleEvent(list, index);

This is a moot point for this application’s purposes since the only class that will be creating ToggleEvents is CheckboxListCell itself. Because CheckboxListCell is the enclosing class, it can use the simpler notation:

 ToggleEvent event = new ToggleEvent(list, index);

The ToggleListener interface is defined in lines 87 - 89 of example 14.10. It declares only one method: checkboxToggled(ToggleEvent). Interfaces are inherently “static” as they are not really objects at all. So an external class could create a new instance of ToggleListener like so:

 CheckboxListCell.ToggleListener tl = new CheckboxListCell.ToggleListener () {       public void checkboxToggled (ToggleEvent event) {} };

We will not, however, create one this way. Instead, we will simply have the existing class, MainFrame, implement ToggleListener.

In order for CheckboxListCell to maintain registered ToggleListeners, we copy the pattern set by many Swing classes before us and add the methods addToggleListener(ToggleListener) and removeToggleListener(ToggleListener). These methods update a Vector that contains all registered ToggleListeners. In lines 90 - 95, a third method called “notifyListeners()” handles listener notification by passing a ToggleEvent to the checkboxToggled method of all registered ToggleListeners. With this event-listener mechanism in place, we can remove the dependency on Garment and leave it to each particular ToggleListener to respond, as needed, to the ToggleEvent.

The MainFrame Class

The changes to MainFrame are minor. It implements CheckboxListCell.ToggleListener(), defines the checkboxToggled() method (lines 100 - 106) and registers itself as a ToggleListener with the instance of CheckboxListCell (line 45). Just as its predecessor two steps ago called redrawBoy() whenever it was notified of a ListSelectionEvent, this version of MainFrame will call redrawBoy() whenever it is notified of a ToggleEvent. But first, its checkboxToggled() method will use the ToggleEvent’s index to determine which Garment was affected and toggle that Garment’s worn property (lines 102 - 104).

Example 14.11: chap14.gui4.MainFrame.java

image from book
 1    package chap14.gui4; 2 3    import java.awt.BorderLayout; 4    import java.awt.Container; 5    import java.awt.Image; 6    import java.awt.Point; 7    import java.util.Collections; 8    import java.util.Vector; 9 10   import javax.swing.BorderFactory; 11   import javax.swing.JFrame; 12   import javax.swing.JList; 13   import javax.swing.ListModel; 14   import javax.swing.border.BevelBorder; 15 16   import chap14.gui0.DressingBoard; 17   import chap14.gui0.Garment; 18   import utils.ResourceUtils; 19   import chap14.gui4.CheckboxListCell; 20 21   public class MainFrame 22     extends JFrame 23     implements CheckboxListCell.ToggleListener { 24 25     private DressingBoard dressingBoard; 26     private JList garmentList; 27 28     public MainFrame() { 29       setTitle(getClass().getName()); 30       setSize(600, 400); 31       setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 32 33       Container contentPane = getContentPane(); 34       contentPane.setLayout(new BorderLayout()); 35       dressingBoard = new DressingBoard(); 36       contentPane.add("Center", dressingBoard); 37 38       garmentList = new JList(); 39       garmentList.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED)); 40       CheckboxListCell cellHandler = new CheckboxListCell() { 41         protected boolean getCheckedValue(Object value) { 42           return ((Garment)value).isWorn(); 43         } 44       }; 45       cellHandler.addToggleListener(this); 46       garmentList.addMouseListener(cellHandler); 47       garmentList.setCellRenderer(cellHandler); 48       contentPane.add("West", garmentList); 49 50       initList(); 51     } 52     private void initList() { 53       String[] names = 54         { 55           "T-Shirt", 56           "Briefs", 57           "Left Sock", 58           "Right Sock", 59           "Shirt", 60           "Pants", 61           "Belt", 62           "Tie", 63           "Left Shoe", 64           "Right Shoe" }; 65       Point[] points = 66         { 67           new Point(75, 125), 68           new Point(86, 197), 69           new Point(127, 256), 70           new Point(45, 260), 71           new Point(69, 118), 72           new Point(82, 199), 73           new Point(88, 203), 74           new Point(84, 124), 75           new Point(129, 258), 76           new Point(40, 268)}; 77 78       Vector garments = new Vector(names.length); 79       for (int i = 0; i < names.length; ++i) { 80         Image image = 81           ResourceUtils.loadImage("chap14/images/" + names[i] + ".gif", this); 82         Garment garment = new Garment(image, points[i].x, points[i].y, names[i]); 83         garments.add(garment); 84       } 85       Collections.shuffle(garments); 86       garmentList.setListData(garments); 87     } 88     private void redrawBoy() { 89       ListModel lm = garmentList.getModel(); 90       int stop = lm.getSize(); 91       Vector order = new Vector(); 92       for (int i = 0; i < stop; ++i) { 93         Garment garment = (Garment)lm.getElementAt(i); 94         if (garment.isWorn()) { 95           order.add(garment); 96         } 97       } 98       dressingBoard.setOrder((Garment[])order.toArray(new Garment[0])); 99     } 100    public void checkboxToggled(CheckboxListCell.ToggleEvent event) { 101      JList list = (JList)event.getSource(); 102      int index = event.getIndex(); 103      Garment garment = (Garment)list.getModel().getElementAt(index); 104      garment.setWorn(!garment.isWorn()); 105      redrawBoy(); 106    } 107    public static void main(String[] arg) { 108      new MainFrame().setVisible(true); 109    } 110  }
image from book

Use the following commands to compile and execute the example. From the directory containing the src folder:

      javac –d classes -sourcepath src src/chap14/gui4/MainFrame.java      java –cp classes chap14.gui4.MainFrame

Run the program. Success! Checkboxes now affect the drawing of the boy.

Here’s what happens when we click in the checkbox of one of the JList’s cells: The click in the checkbox causes the JList to call CheckboxListCell.mouseClicked() because CheckboxListCell is a registered MouseListener of the list. CheckboxListCell.mouseClicked() determines that the click was in the checkbox and calls MainFrame.checkboxToggled() because MainFrame is a registered ToggleListener of CheckboxListCell. MainFrame.checkboxToggled() toggles the affected Garment’s worn property and then calls redrawBoy().

Meanwhile, CheckboxListCell.mouseClicked() calls repaint() for the affected cell which causes Swing to schedule the JList for repainting. When it is time to repaint the affected cells, JList calls CheckboxListCell.getListCellRendererComponent() for the affected cell, and CheckboxListCell.getListCellRendererComponent() sets the checkbox’s checked state to reflect whether or not the Garment is worn. Fortunately, the Garment’s worn state was just set by MainFrame’s checkboxToggled() method. Finally, the JList paints the affected cell with the correctly updated checkbox. Figure 14-8 details this process in a sequence diagram.

image from book
Figure 14-8: Sequence Diagram for a JList Using CheckboxListCell – Second Version

Quick Review

In this step, we created our own event and event listener types and wrote a mechanism similar to Swing’s mechanism for registering EventListeners with components.




Java For Artists(c) The Art, Philosophy, and Science of Object-Oriented Programming
Java For Artists: The Art, Philosophy, And Science Of Object-Oriented Programming
ISBN: 1932504052
EAN: 2147483647
Year: 2007
Pages: 452

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net