The JList class in Swing depends on a second model, this one to monitor the elements that have been selected by the user. As with the list data model, the programmer is given many places in which standard behavior can be altered or replaced when dealing with selections. Swing uses a simple interface for models that handle list selections (ListSelectionModel) and provides a default implementation (DefaultList-SelectionModel). 7.3.1 The ListSelectionModel InterfaceThe ListSelectionModel interface outlines the methods necessary for managing list selections. Selections are represented by a series of ranges, where each range is defined by its endpoints. For example, if the elements One, Two, Three, Six, Seven, and Nine were selected in the opening example of the chapter, the list selection model would contain three entries that specified the ranges {1,3}, {6,7}, and {9,9}. All selection indices are zero-based, and the ranges are closed, meaning both endpoint indices are included within the selection. If only one element is present in a range, such as with Nine, both endpoints are identical. 7.3.1.1 PropertiesTable 7-5 shows the properties of the ListSelectionModel interface. The first four properties of the list selection model can be used to retrieve various indices that are currently selected in the list. The anchorSelectionIndex and leadSelectionIndex properties represent the anchor and lead indices of the most recent range of selections, as illustrated in Figure 7-2. The maxSelectionIndex and minSelectionIndex properties return the largest and smallest selected index in the entire list, respectively.
The selectionMode property defines the type of selections that the user may make in the list. This property can take one of three constants representing a single selection, a single range of selections, or multiple ranges of selections. The default (since SDK 1.3) is multiple ranges of selections. (The selectionMode constants are outlined in greater detail in Table 7-6.) The selectionEmpty property is a boolean indicating whether there are any selections. If there are no selections anywhere in the list, the property is set to true. Setting the valueIsAdjusting property to true indicates that the object is sending a series of selection change events. For example, when the user is dragging the mouse across the list, the object can set this property to true, which indicates that the selection change events are part of a series. When the series has been completed, the property should be set to false. The receiver may wish to delay action until all events have been received.
7.3.1.2 ConstantsThe constants shown in Table 7-6 are used in conjunction with the selectionMode property of the ListSelectionModel interface.
7.3.1.3 Methods
While reading through the above interface, you may have been puzzled to find no way to get a list of all selected items. Even though you'd expect this to be a responsibility of the selection model, you must instead get this information from the JList itself. 7.3.1.4 EventsThe ListSelectionModel interface declares the addListSelectionListener( ) and removeListSelectionListener( ) event subscription methods for notifying other objects of selection changes. These selection changes come in the form of ListSelec-tionEvent objects.
7.3.2 The DefaultListSelectionModel ClassSwing provides a default implementation of the list selection interface called DefaultListSelectionModel. This class implements accessors for each of the ListSelectionModel properties and maintains an EventListenerList of change listeners. If you thought about how to implement all the behavior specified by the ListSelectionModel interface while reading about it on the last few pages, you probably realized that the code for all this is quite complex and tedious. We're glad Sun provides a default implementation! The DefaultListSelectionModel can chain ListSelectionEvent objects in a series to notify listeners of a change in the selection list. This is common, for example, when the user is dragging the mouse across the list. In this case, a series of selection change events can be fired off with a valueIsAdjusting property set to true, which indicates that this event is only one of many. The listener may wish to delay any activity until all the events are received. When the chain of selections is complete, an event is sent with the valueIsAdjusting property set to false, which tells the listener that the series has completed. (Relying on this final event prior to SDK 1.4 is safe only for lists that don't support selection ranges.) 7.3.2.1 PropertiesTable 7-7 lists the properties of the DefaultListSelectionModel. Almost all the properties are implementations of the properties defined by the ListSelectionModel interface. The only new property, leadAnchorNotificationEnabled, designates whether the class fires change events over leadSelectionIndex and anchorSelectionIndex each time it fires a series of notification events. (Recall that the anchor selection is at the beginning of the selection range while the lead selection is the most recent addition to the selection range.) If the property is false, only the elements selected or deselected since the last change are included in the series.
7.3.2.2 EventsThe DefaultListSelectionModel uses the ListSelectionEvent to signal that the list selection has changed. The event notifies interested listeners of a modification to the selection data and tells which elements were affected.
7.3.2.3 Constructor
7.3.2.4 Method
7.3.2.5 Working with the ListSelectionModelThe following example is a modified version of our earlier list example. This one has its own ListSelectionListener that reports each list selection event as it occurs. // SimpleList2.java // import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.*; public class SimpleList2 extends JPanel { String label[] = { "Zero","One","Two","Three","Four","Five","Six", "Seven","Eight","Nine","Ten","Eleven" }; JList list; public SimpleList2( ) { setLayout(new BorderLayout( )); list = new JList(label); JButton button = new JButton("Print"); JScrollPane pane = new JScrollPane(list); DefaultListSelectionModel m = new DefaultListSelectionModel( ); m.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); m.setLeadAnchorNotificationEnabled(false); list.setSelectionModel(m); list.addListSelectionListener(new ListSelectionListener( ) { public void valueChanged(ListSelectionEvent e) { System.out.println(e.toString( )); } }); button.addActionListener(new PrintListener( )); add(pane, BorderLayout.NORTH); add(button, BorderLayout.SOUTH); } public static void main(String s[]) { JFrame frame = new JFrame("List Example"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setContentPane(new SimpleList2( )); frame.pack( ); frame.setVisible(true); } // An inner class to respond to clicks of the Print button class PrintListener implements ActionListener { public void actionPerformed(ActionEvent e) { int selected[] = list.getSelectedIndices( ); System.out.println("Selected Elements: "); for (int i=0; i < selected.length; i++) { String element = (String)list.getModel( ).getElementAt(selected[i]); System.out.println(" " + element); } } } } Try running this code and selecting a couple of items in the list. If you drag the mouse from item 0 to item 5, you get the following output (the detailed contents of the JList have been omitted for readability since they don't change from line to line): javax.swing.event.ListSelectionEvent[ source=javax.swing.JList[...] firstIndex= 0 lastIndex= 1 isAdjusting= true ] javax.swing.event.ListSelectionEvent[ source=javax.swing.JList[...] firstIndex= 1 lastIndex= 2 isAdjusting= true ] javax.swing.event.ListSelectionEvent[ source=javax.swing.JList[...] firstIndex= 2 lastIndex= 3 isAdjusting= true ] javax.swing.event.ListSelectionEvent[ source=javax.swing.JList[...] firstIndex= 3 lastIndex= 4 isAdjusting= true ] javax.swing.event.ListSelectionEvent[ source=javax.swing.JList[...] firstIndex= 4 lastIndex= 5 isAdjusting= true ] javax.swing.event.ListSelectionEvent[ source=javax.swing.JList[...] firstIndex= 0 lastIndex= 5 isAdjusting= false ] Each entry describes a change in selection. The first five entries recognize that a change of selection has occurred between one element and the next as the mouse was dragged. In this case, the former was deselected, and the latter was selected. However, note that the isAdjusting property was true, indicating that this is potentially one in a series of changes. When the mouse button is released, the list knows that the drag has stopped and fires a ListSelectionEvent with the isAdjusting property set to false, repeating the last changed index. 7.3.3 ListSelectionEventMuch like the ListDataEvent, the ListSelectionEvent specifies a change by highlighting those elements in the selection list that have altered. Note that a ListSelectionEvent does not indicate the new selection state of the list element, only that some change has occurred. You should not assume that the new state is the opposite of the previous state; always check with the event source to see what the current selection state really is. 7.3.3.1 PropertiesThere are four properties in the ListSelectionEvent, as shown in Table 7-8.
7.3.3.2 Constructor
7.3.3.3 Methods
7.3.4 ListSelectionListenerThe ListSelectionListener interface, as the means of receiving ListSelectionEvents, consists of only one method: valueChanged( ). This method must be implemented by any listener object interested in changes to the list selection model.
7.3.4.1 Listening for ListSelectionEventsHere is a brief example that demonstrates how to use ListSelectionListener and the ListSelectionEvent. The example creates a series of checkboxes that accurately mirror the current selections in the list by listening for selection events. Some results from playing with the program are shown in Figure 7-6. Figure 7-6. Monitoring list selection events// SelectionMonitor.java // import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.*; public class SelectionMonitor extends JPanel { String label[] = { "Zero","One","Two","Three","Four","Five","Six", "Seven","Eight","Nine","Ten","Eleven","Twelve" }; JCheckBox checks[] = new JCheckBox[label.length]; JList list; public SelectionMonitor( ) { setLayout(new BorderLayout( )); list = new JList(label); JScrollPane pane = new JScrollPane(list); // Format the list and the buttons in a vertical box. Box rightBox = new Box(BoxLayout.Y_AXIS); Box leftBox = new Box(BoxLayout.Y_AXIS); // Monitor all list selections. list.addListSelectionListener(new RadioUpdater( )); for(int i=0; i < label.length; i++) { checks[i] = new JCheckBox("Selection " + i); checks[i].setEnabled(false); rightBox.add(checks[i]); } leftBox.add(pane); add(rightBox, BorderLayout.EAST); add(leftBox, BorderLayout.WEST); } public static void main(String s[]) { JFrame frame = new JFrame("Selection Monitor"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setContentPane(new SelectionMonitor( )); frame.pack( ); frame.setVisible(true); } // Inner class that responds to selection events to update the buttons class RadioUpdater implements ListSelectionListener { public void valueChanged(ListSelectionEvent e) { // If either of these are true, the event can be ignored. if ((!e.getValueIsAdjusting( )) || (e.getFirstIndex( ) == -1)) return; // Change the radio button to match the current selection state for each // list item that reported a change. for (int i = e.getFirstIndex( ); i <= e.getLastIndex( ); i++) { checks[i].setSelected(((JList)e.getSource( )).isSelectedIndex(i)); } } } } If you're running this example under SDK 1.4 or later, experiment with Swing's new support for keyboard-driven selection. Try typing the first letter, or few letters, of some of the list elements, and watch the selection jump around. Notice that if you type te, the selection starts by selecting Two and then jumps to Ten, but neither event reports an isAdjusting value of true. This feature is examined in more depth in the discussion of the getNextMatch( ) method. Remember that a ListSelectionEvent does not inform you of the new selection state of an element that has changed. You might be tempted to conclude that if you receive a ListSelectionEvent, the selection state for the target element would simply be the opposite of what it was before. This is not true. The selection state cannot be determined from the ListSelectionEvent; it must be determined by querying the event source. |