Summary


Step 6 — A Good Component Gets Better

Swing’s JList is a very useful component for displaying lists of data. However, as noted in the previous section, it doesn’t provide a means for editing or manipulating its data model in any way. In this final step, we will write the new class, DragList, which extends JList to give it the ability to reorder its items by dragging one item at a time to a new position. We’ll deal with a bit of offscreen graphics and transparency along with a good dose of old-fashioned cleverness. Reapplying a pattern set by an earlier section, we will create the ReorderEvent and ReorderListener classes. We will also create a mechanism to notify objects that implement ReorderListener and are registered with the DragList whenever a ReorderEvent occurs.

The DragList Class — Overview

The DragList class (example 14.14) is more complex than other GUI classes we have written, so we will discuss its logic thoroughly.

Example 14.14: chap14.gui5.DragList.java

image from book
 1    package chap14.gui5; 2 3    import java.awt.AlphaComposite; 4    import java.awt.Component; 5    import java.awt.Composite; 6    import java.awt.Graphics; 7    import java.awt.Graphics2D; 8    import java.awt.Insets; 9    import java.awt.Point; 10   import java.awt.Rectangle; 11   import java.awt.event.MouseEvent; 12   import java.awt.image.BufferedImage; 13   import java.util.EventListener; 14   import java.util.EventObject; 15   import java.util.Iterator; 16   import java.util.Vector; 17 18   import javax.swing.DefaultListModel; 19   import javax.swing.JList; 20   import javax.swing.ListCellRenderer; 21   import javax.swing.ListModel; 22   import javax.swing.SwingUtilities; 23   import javax.swing.event.MouseInputListener; 24 25   public class DragList extends JList implements MouseInputListener { 26 27     private Object dragItem; 28     private int dragIndex = -1; 29     private BufferedImage dragImage; 30     private Rectangle dragRect = new Rectangle(); 31     private boolean inDrag = false; 32 33     private Point dragStart; 34     private int deltaY; 35     private int dragThreshold; 36 37     private boolean allowDrag; 38 39     private Vector listeners = new Vector(); 40 41     public DragList() { 42       this(new DefaultListModel()); 43     } 44     public DragList(DefaultListModel lm) { 45       super(lm); 46       addMouseListener(this); 47       addMouseMotionListener(this); 48     } 49     public void setModel(ListModel lm) { 50       super.setModel((DefaultListModel)lm); 51     } 52     public void setListData(Object[] listData) { 53       DefaultListModel model = (DefaultListModel)getModel(); 54       model.clear(); 55       for (int i = 0; i < listData.length; ++i) { 56         model.addElement(listData[i]); 57       } 58     } 59     public void setListData(Vector listData) { 60       DefaultListModel model = (DefaultListModel)getModel(); 61       model.clear(); 62       for (Iterator it = listData.iterator(); it.hasNext();) { 63         model.addElement(it.next()); 64       } 65     } 66     private void createDragImage() { 67       if (dragImage == null 68         || dragImage.getWidth() != dragRect.width 69         || dragImage.getHeight() != dragRect.height) { 70         dragImage = 71           new BufferedImage( 72             dragRect.width, 73             dragRect.height, 74             BufferedImage.TYPE_INT_RGB); 75       } 76       Graphics g = dragImage.getGraphics(); 77 78       ListCellRenderer renderer = getCellRenderer(); 79       Component comp = 80         renderer.getListCellRendererComponent( 81           this, 82           dragItem, 83           dragIndex, 84           true, 85           true); 86       SwingUtilities.paintComponent( 87         g, 88         comp, 89         this, 90         0, 91         0, 92         dragRect.width, 93         dragRect.height); 94     } 95     protected void paintComponent(Graphics g) { 96       super.paintComponent(g); 97       if (inDrag) { 98         Graphics2D g2 = (Graphics2D)g; 99         g2.setColor(getBackground()); 100        Rectangle r = getCellBounds(dragIndex, dragIndex); 101        g2.fillRect(r.x, r.y, r.width, r.height); 102        Composite saveComposite = g2.getComposite(); 103        g2.setComposite( 104          AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f)); 105        g2.drawImage(dragImage, dragRect.x, dragRect.y, this); 106        g2.setComposite(saveComposite); 107      } 108    } 109    public void mousePressed(MouseEvent e) { 110      allowDrag = false; 111      if (!SwingUtilities.isLeftMouseButton(e)) { 112        return; 113      } 114      dragStart = e.getPoint(); 115      dragIndex = locationToIndex(dragStart); 116      if (dragIndex < 0) { 117        return; 118      } 119      dragRect = getCellBounds(dragIndex, dragIndex); 120      if (!dragRect.contains(dragStart)) { 121        clearSelection(); 122        return; 123      } 124      allowDrag = true; 125      DefaultListModel model = (DefaultListModel)getModel(); 126      dragItem = model.getElementAt(dragIndex); 127      dragThreshold = dragRect.height / 4; 128      deltaY = dragStart.y - dragRect.y; 129    } 130    public void mouseDragged(MouseEvent e) { 131      if (!allowDrag) { 132        return; 133      } 134      Point mouse = e.getPoint(); 135      if (!inDrag && Math.abs(mouse.y - dragStart.y) < dragThreshold) { 136        return; 137      } 138      if (!inDrag) { 139        clearSelection(); 140        createDragImage(); 141        inDrag = true; 142      } 143      //remember dragRect.y 144      int oldTop = dragRect.y; 145 146      dragRect.y = mouse.y - deltaY; 147      //dragRect is now at the accurate vertical location 148      //shift dragRect up or down as necessary so that it doesn't 149      //spill over the top or bottom of the DragList 150      Insets insets = getInsets(); 151      dragRect.y = Math.max(dragRect.y, insets.top); 152      dragRect.y = 153        Math.min(dragRect.y, getHeight() - dragRect.height - insets.bottom); 154 155       //index is the index of the item located at the vertical center of dragRect 156       int index = 157         locationToIndex(new Point(mouse.x, dragRect.y + dragRect.height / 2)); 158       if (dragIndex != index) { 159         DefaultListModel model = (DefaultListModel)getModel(); 160         //move dragItem to the new location in the list 161         model.remove(dragIndex); 162         model.add(index, dragItem); 163         dragIndex = index; 164       } 165 166       int minY = Math.min(dragRect.y, oldTop); 167       int maxY = Math.max(dragRect.y, oldTop); 168       repaint(dragRect.x, minY, dragRect.width, maxY + dragRect.height); 169     } 170     public void mouseReleased(MouseEvent e) { 171       if (inDrag) { 172         setSelectedIndex(dragIndex); 173         inDrag = false; 174         repaint(dragRect); 175         notifyListeners(new ReorderEvent(this)); 176       } 177     } 178 179     public void mouseClicked(MouseEvent e) {} 180     public void mouseMoved(MouseEvent e) {} 181     public void mouseEntered(MouseEvent e) {} 182     public void mouseExited(MouseEvent e) {} 183 184     public static class ReorderEvent extends EventObject { 185       public ReorderEvent(DragList dragList) { 186         super(dragList); 187       } 188     } 189     public static interface ReorderListener extends EventListener { 190       public void listReordered(ReorderEvent e); 191     } 192     public void addReorderListener(ReorderListener rl) { 193       listeners.add(rl); 194     } 195     public void removeReorderListener(ReorderListener rl) { 196       listeners.remove(rl); 197     } 198     private void notifyListeners(ReorderEvent event) { 199       for (Iterator it = listeners.iterator(); it.hasNext();) { 200         ReorderListener rl = (ReorderListener)it.next(); 201         rl.listReordered(event); 202       } 203     } 204  }
image from book

Algorithm

When the user presses the mouse in a list item and begins to drag it, DragList sets the object, dragItem, to this item and creates an image of its corresponding list cell, dragImage. It erases that list cell (by painting a blank rectangle over it) suggesting that the dragged item has vacated its initial position. While the mouse is dragged, DragList tracks the mouse’s motion, drawing dragImage semi-transparently over the contents of the list at the position of the mouse. Each time dragImage encroaches on a neighboring list cell, DragList switches dragItem and that list cell’s corresponding value in the ListModel. This change of order is automatically and visibly manifested in the DragList. For as long as the mouse is dragged, the reordering process continues as necessary and DragList continues to erase the dragged list cell wherever it may be at the moment. When the mouse is finally released, DragList erases dragImage and stops erasing dragItem’s list cell, thereby creating the illusion that the dragged cell has finally come to rest. Registered ReorderListeners are then notified that the list has been reordered.

Use DefaultListModel (Lines 41 - 65)

Notice that the algorithm involves moving items’ positions within the list. While JList doesn’t provide methods for altering its data, it does provide access to its data model through the getModel() method which returns a ListModel. Unfortunately, the API for ListModel doesn’t provide methods for altering its data either, so we need to find a ListModel implementation that is editable. We could, of course, write our own class that implements ListModel and also supports editing, but we don’t need to. Starting with the javadocs for ListModel and searching via the “All Known Implementing Classes:” and “Direct Known Subclasses:” links we find that Swing provides the DefaultListModel class which provides a whole host of methods that support editing. The inheritance hierarchy for DefaultListModel is shown in Figure 14-10.

image from book

     javax.swing.ListModel        javax.swing.AbstractListModel           javax.swing.DefaultListModel

image from book

Figure 14-10: DefaultListModel Inheritance Hierarchy

Because having an editable ListModel is a requirement for DragList to be functional, we should ensure that DragList never uses anything but a DefaultListModel. We provide two constructors: one that takes no arguments and just creates a DefaultListModel for itself, and another that accepts a DefaultListModel argument to be used as its model. We also need to override any methods available from its superclass, JList, that might set the model to something other than a DefaultListModel. This requires us to override three methods. We override setModel(ListModel) to throw a ClassCastException if the argument is not a DefaultListModel, and we override setListData(Object[]) and setListData(Vector) to reuse DragList’s DefaultListModel by clearing it and adding the items in the array or Vector to it one by one.

ReorderEvent and ReorderListener (Lines 184 - 203)

Just as we did earlier with CheckboxListCell by creating a new event and listener type, we now create the ReorderEvent and ReorderListener for DragList. The ReorderEvent class defines the event of reordering the items in the list. The ReorderListener interface defines the one method listReordered(ReorderEvent). ReorderEvent’s only constructor takes a DragList as parameter so that it can set the event’s source as that DragList. DragList’s addReorderListener() method adds a ReorderListener to a private list of listeners and its removeReorderListener() method removes the specified ReorderListener from the list of listeners. DragList’s notifyListeners() method constructs a ReorderEvent and sends it to all registered ReorderListeners.

The DragList Class — Dragging

In the course of a mouse drag, three event types are always generated. These are in order:

  1. a mouse press event when the user presses the mouse to begin the drag

  2. a series of mouse drag events as the user drags the mouse

  3. a mouse release event when the user releases the mouse thereby ending the drag

DragList implements MouseListener and writes mousePressed(), mouseDragged() and mouseReleased() event handlers that maintain several DragList attributes needed to achieve the visual effect of dragging. DragList registers itself as its own MouseListener.

dragIndex, dragItem and dragRect Attributes

The mousePressed() method initializes three attributes needed to create an image of the dragged item. These are dragIndex, dragItem and dragRect as shown in table 14-12.

Table 14-12: dragIndex, dragItem and dragRect Attributes

Name

Meaning

How Obtained

dragIndex

The dragged item’s index in the list.

Obtained by passing the point of the mouse press into JList’s locationToIndex() method.

dragItem

The item being dragged.

Obtained by passing dragIndex into the ListModel’s getElementAt() method.

dragRect

The rectangular area that the DragList will use to paint the dragged item.

Obtained by passing dragIndex into JList’s getCell-Bounds() method.

dragStart, dragThreshold and allowDrag Attributes

The mousePressed() method initializes three attributes needed for the mouseDragged() method to determine whether an item is being dragged or not. These are dragStart, dragThreshold and allowDrag as shown in table 14-13.

Table 14-13: dragStart, dragThreshold and allowDrag Attributes

Name

Meaning

Initialization

dragStart

The location of the mousePress.

Set to MouseEvent.getPoint().

dragThreshold

The maximum vertical distance the mouse can be dragged before dragItem should be considered to be dragged. This eliminates unwanted drags due to inadvertent mouse jiggles.

Set to 1/4 the height of dragRect.

allowDrag

Whether or not dragging should be allowed.

Set to true if the mouse pressed event meets certain criteria.

deltaY and inDrag Attributes

Two other attributes are needed to enable the dragging process. These are deltaY and inDrag as shown in table 14-14.

Table 14-14: deltaY and inDrag Attributes
  

Interested Methods

Name

Meaning

mousePressed()

mouseDragged()

mouseReleased()

deltaY

Where, with respect to the mouse position, dragImage should be painted as the mouse is dragged.

Sets to the vertical distance between dragStart, and the top of the list item containing dragStart.

Uses deltaY to update dragRect’s position ensuring that the top of dragRect is always the same vertical distance from the mouse location.

N/A

inDrag

Whether or not an item is in the process of being dragged.

N/A

Sets to true only if allowDrag is true, the drag is with the left-mouse button and the mouse has moved at least dragThreshold pixels vertically away from dragStart.

Sets to false.

How the inDrag Attribute is Used

As shown in table 14-15, the paintComponent(), mouseDragged() and mouseReleased() methods do different things depending on the value of inDrag.

Table 14-15: How the inDrag Attribute is Used
 

Interested Methods

Value

paintComponent()

mouseDragged()

mouseReleased()

true

Calls super.paintComponent(), erases drag-Item from the list and paints dragImage at the current mouse location.

Updates dragRect’s position, updates the ListModel and calls repaint() when needed.

Sets inDrag to false, calls repaint() and notifies Reorder-Listeners.

false

Calls super.paintComponent().

Calls createDragImage if it determines that inDrag should become true.

Does nothing.

The DragList Class — Painting

Two methods handle the actual painting of the DragList. These are: createDragImage() which is called by mouseDragged() when mouseDragged() has just set inDrag to true; and paintComponent() which is called by the Swing painting mechanism when mouseDragged() and mouseReleased() call repaint().

The createDragImage() Method (Lines 66 - 94)

When called, createDragImage() creates an image of dragItem and paints it into dragImage. It does this by obtaining the component that would be used to “rubber stamp” dragItem’s image onto the list and using a handy SwingUtilities method to paint this component into dragImage. If dragImage is null or not the right size, createDragImage() first initializes dragImage to a new image of the correct size.

The paintComponent() Method (Lines 95 - 108)

The paintComponent() method is called by the Swing painting mechanism in response to calls to repaint(). If inDrag is true, paintComponent() erases the area where dragItem was painted by filling dragItem’s rectangular area with the DragList’s background color. Then it sets the graphics context to 50% transparency and paints dragImage at the location of dragRect.

The DragList Class — Code

The MainFrame Class

Changes to MainFrame are straightforward. MainFrame implements DragList.ReorderListener, codes listReordered(DragList.ReorderEvent) to call redrawBoy() and declares the garmentList attribute to be a DragList instead of a JList. Just because this is the final step, its constructor also creates a slightly fancier interface.

Example 14.15: chap14.gui5.MainFrame.java

image from book
 1     package chap14.gui5; 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.JLabel; 13    import javax.swing.JList; 14    import javax.swing.JPanel; 15    import javax.swing.ListModel; 16    import javax.swing.border.BevelBorder; 17 18    import utils.ResourceUtils; 19    import chap14.gui0.DressingBoard; 20    import chap14.gui0.Garment; 21    import chap14.gui4.CheckboxListCell; 22 23    public class MainFrame 24      extends JFrame 25      implements DragList.ReorderListener, CheckboxListCell.ToggleListener { 26 27      private DressingBoard dressingBoard; 28      private DragList garmentList; 29 30      public MainFrame() { 31        setTitle(getClass().getName()); 32        setSize(600, 400); 33        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 34 35        JPanel topPanel = new JPanel(new BorderLayout()); 36        topPanel.setBorder(BorderFactory.createEtchedBorder()); 37 38        Container contentPane = getContentPane(); 39        contentPane.setLayout(new BorderLayout()); 40        contentPane.add("Center", topPanel); 41 42        JPanel mainPanel = new JPanel(new BorderLayout()); 43        topPanel.add("Center", mainPanel); 44        String instructions = 45          "Click the checkboxes to put on or remove garments." 46            + " Drag the labels to determine order."; 47        topPanel.add("North", new JLabel(instructions, JLabel.CENTER)); 48 49        dressingBoard = new DressingBoard(); 50        mainPanel.add("Center", dressingBoard); 51 52        garmentList = new DragList(); 53        garmentList.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED)); 54        CheckboxListCell ccr = new CheckboxListCell() { 55          protected boolean getCheckedValue(Object value) { 56            return ((Garment)value).isWorn(); 57          } 58        }; 59        ccr.addToggleListener(this); 60        garmentList.addMouseListener(ccr); 61        garmentList.setCellRenderer(ccr); 62        garmentList.addReorderListener(this); 63        mainPanel.add("West", garmentList); 64 65        initList(); 66      } 67      private void initList() { 68        String[] names = 69          { 70            "T-Shirt", 71            "Briefs", 72            "Left Sock", 73            "Right Sock", 74            "Shirt", 75            "Pants", 76            "Belt", 77            "Tie", 78            "Left Shoe", 79            "Right Shoe" }; 80        Point[] points = 81          { 82            new Point(75, 125), 83            new Point(86, 197), 84            new Point(127, 256), 85            new Point(45, 260), 86            new Point(69, 118), 87            new Point(82, 199), 88            new Point(88, 203), 89            new Point(84, 124), 90            new Point(129, 258), 91            new Point(40, 268)}; 92 93        Vector garments = new Vector(names.length); 94        for (int i = 0; i < names.length; ++i) { 95          Image image = 96            ResourceUtils.loadImage("chap14/images/" + names[i] + ".gif", this); 97          Garment garment = new Garment(image, points[i].x, points[i].y, names[i]); 98          garments.add(garment); 99        } 100       Collections.shuffle(garments); 101       garmentList.setListData(garments); 102     } 103     public void listReordered(DragList.ReorderEvent e) { 104       redrawBoy(); 105     } 106     public void checkboxToggled(CheckboxListCell.ToggleEvent event) { 107       JList list = (JList)event.getSource(); 108       int index = event.getIndex(); 109       Garment garment = (Garment)list.getModel().getElementAt(index); 110       garment.setWorn(!garment.isWorn()); 111       redrawBoy(); 112     } 113     private void redrawBoy() { 114       ListModel lm = garmentList.getModel(); 115       int stop = lm.getSize(); 116       Vector order = new Vector(); 117       for (int i = 0; i < stop; ++i) { 118         Garment garment = (Garment)lm.getElementAt(i); 119         if (garment.isWorn()) { 120           order.add(garment); 121         } 122       } 123       dressingBoard.setOrder((Garment[])order.toArray(new Garment[0])); 124     } 125     public static void main(String[] arg) { 126       new MainFrame().setVisible(true); 127     } 128   }
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/gui5/MainFrame.java    java –cp classes chap14.gui5.MainFrame

We’re done! Run the program. Have fun. In the process of creating this application you have gained experience with several advanced features of the Swing API. This is just the tip of the iceberg, but I hope it gives you a good feeling for the power Swing offers that is just waiting to be harnessed by a creative and determined mind.




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