Step 5 Defining Your Own Events and EventListeners


Step 4 —The Art of Illusion

Up until now, we have used a ListSelectionEvent to determine what garments the boy should wear. Now that we have plugged in our CheckboxListCell renderer complete with checkbox, we really need to make the checkbox functional and stop using a ListSelectionEvent. In this step, we improve the checkbox illusion by making the picture of the checkbox respond to mouseClicks as though it were real. We will also remove any code dealing with ListSelectionEvents and ListSelectionListeners since our “checkbox” is going to be responsible for the toggling of the Garment’s worn property.

If this were a normal checkbox, we could just give it an ActionListener that calls Garment.isWorn(JCheckbox.isSelected()). But this is not a real checkbox. When the user clicks on what he thinks is a checkbox, he is really only clicking a picture of a checkbox inside the JList. In other words, he is clicking in the JList itself. So we need to add a MouseListener to the JList and implement the mouseClicked() method. Theoretically speaking, if we can figure out whether or not the click was inside the checkbox, we’ll know whether or not we should toggle the Garment’s worn property.

Here is our mission: Starting with the JList, the CheckboxListCell renderer component and the location of the mouse click, navigate our way through the Swing API to determine whether or not the click was inside the imaginary checkbox. One of the marks of a good API is the availability of methods that support even unanticipated uses such as this one. Swing is a good API. Of course, it’s also a big API and there are lots and lots of methods and classes to look through when you’re trying to get from point A to point B. Here is the path I found:

Pass the location of the mouseclick into JList.locationToIndex() to get the index of the list cell that contained the mouseclick. Pass this index into JList.getCellBounds() to get the bounds of that list cell. Set the bounds of the renderer component to these bounds. (This is similar to what JList might do preparatory to rendering this list cell). From the resized renderer component, get the bounds of its checkbox. Remember that a component’s bounds are in its parent’s coordinate system, so this checkbox’s bounds are in the coordinate system of the renderer component. Now, pass the JList, the location of the mouseclick and the renderer component into SwingUtilities.convertPoint() to convert the mouseclick location from the JList’s coordinate system into the renderer component’s coordinate system. This converted point and the checkbox’s bounds are now in the same coordinate system. Pass the converted point into the contains() method of the checkbox’s bounds to find out if the mouseclick was inside the checkbox. Figure 14-7 illustrates this path graphically.

image from book
Figure 14-7: Maneuvering Through the Swing API

Whew! That’s a bunch of maneuvering through the Swing API! Anyway, once we determine that the click is in the checkbox, we can handle it however we want. Whatever class we register as a MouseListener to the JList will have access to the JList and the mouseClick location, since the JList will be the source of the event and the location will be an attribute of the MouseEvent. However, not every class knows how to get to the Checkbox. This makes CheckboxListCell the logical place to put this code for two reasons: 1) CheckboxListCell constructs the renderer component, so it has access to the checkbox, and 2) Keeping the checkbox-finding logic inside CheckboxListCell minimizes the chance that future modifications to the structure of CheckboxListCell’s renderer component could break external dependent classes.

So, we’ll have CheckboxListCell implement MouseListener and we’ll register it with the JList. For now, even though it introduces a dependency on the Garment class, we will put the code to toggle the clicked Garment’s worn property right inside CheckboxListCell’s mouseClicked method. We’ll remove this dependency in the next step. We’re not done yet, though. Since the checkbox isn’t real, it is our responsibility to update its appearance when it’s been toggled. But that’s simple. All we need to do is to tell the JList to repaint itself. When it paints this particular cell, the JList will obtain the CheckboxListCell renderer which will have already set the selected state of its checkbox to match whether or not the Garment is worn. Since we only need to repaint one list cell, we tell the JList to repaint only the rectangular area occupied by that list cell (line 77 of example 14.8).

Example 14.8: chap14.gui3.CheckboxListCell.java

image from book
 1     package chap14.gui3; 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 11    import javax.swing.JCheckBox; 12    import javax.swing.JLabel; 13    import javax.swing.JList; 14    import javax.swing.JPanel; 15    import javax.swing.ListCellRenderer; 16    import javax.swing.ListModel; 17    import javax.swing.SwingUtilities; 18 19    import chap14.gui0.Garment; 20 21    public abstract class CheckboxListCell 22      implements ListCellRenderer, MouseListener { 23 24      private JLabel label; 25      private JCheckBox checkBox; 26      private JPanel panel; 27 28      public CheckboxListCell() { 29        panel = new JPanel(new BorderLayout()); 30        label = new JLabel(); 31        checkBox = new JCheckBox(); 32        checkBox.setOpaque(false); 33        panel.add("Center", label); 34        panel.add("West", checkBox); 35      } 36 37      public Component getListCellRendererComponent( 38        JList list, 39        Object value, 40        int index, 41        boolean isSelected, 42        boolean cellHasFocus) { 43 44        panel.setBackground(isSelected ? Color.yellow : list.getBackground()); 45        label.setText(String.valueOf(value)); 46        checkBox.setSelected(getCheckedValue(value)); 47        return panel; 48      } 49      protected abstract boolean getCheckedValue(Object value); 50 51      public void mouseClicked(MouseEvent e) { 52        JList list = (JList)e.getSource(); 53        Point p = e.getPoint(); 54        int index = list.locationToIndex(p); 55        Rectangle rect = list.getCellBounds(index, index); 56        panel.setBounds(rect); 57        if (!rect.contains(p)) { 58          if (!e.isShiftDown() && !e.isControlDown()) { 59            list.clearSelection(); 60          } 61          return; 62        } 63        p = SwingUtilities.convertPoint(list, p, panel); 64        if (!checkBox.getBounds().contains(p)) { 65          return; 66        } 67 68        /**********************************************************************/ 69        /* Not a good design. CheckboxCellHandler should be flexible.         */ 70        /* We will remove this dependence on Garment in the next iteration.   */ 71        /**********************************************************************/ 72        ListModel model = list.getModel(); 73        Garment garment = (Garment)model.getElementAt(index); 74        garment.setWorn(!garment.isWorn()); 75        /**********************************************************************/ 76 77        list.repaint(rect.x, rect.y, rect.width, rect.height); 78       } 79       public void mousePressed(MouseEvent e) {} 80       public void mouseReleased(MouseEvent e) {} 81       public void mouseEntered(MouseEvent e) {} 82       public void mouseExited(MouseEvent e) {} 83     }
image from book

Now that we know how to determine whether or not a mouseclick is in the checkbox, writing the code isn’t difficult. The mouseClicked method does exactly what we have laid out with one addition. I have never liked it that a click in the empty area below the last list cell of a JList acts like a click in the last cell. I think it would be better used as a method of unselecting all cells. So, if the mouseclick is not in the selected cell and the shift-key and control-keys weren’t down, lines 57 - 62clear the selection and return.

Example 14.8 gives the code for the modified version of the CheckboxListCell class.

The CheckboxListCell Class

The MainFrame Class

We only need to make minor changes to MainFrame. We “unimplement” ListSelectionListener and remove the valueChanged(ListSelectionEvent) method. Also, instead of registering MainFrame as a ListSelectionListener with the JList, we register CheckboxListCell as a mouseListener to the JList in line 42.

Example 14.9: chap14.gui3.MainFrame.java

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

Run the program. Great! Our checkboxes work. Unfortunately, they don’t affect the image in DressingBoard because we forgot to call redrawBoy. The previous step called redrawBoy() from MainFrame.valueChanged(), but when we unimplemented ListSelectionListener, we removed the valueChanged() method and the only call to redrawBoy(). Oh well, we’ve done enough for this step. In the next step, we’ll get rid of CheckboxListCell’s dependence on Garment and we’ll make sure to redraw the boy.

Quick Review

The Swing API can be overwhelming because of the sheer number of classes and methods to come to terms with. But, as this step illustrated, it has the flexibility to support even non-standard uses.




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