Allowing multiple selections in a JList

A JList has three different selection modes:

1) SINGLE_SELECTION: allows only one item to be selected at a time

2) SINGLE_INTERVAL_SELECTION: allows contiguous items to be selected at a time

3) MULTIPLE_INTERVAL_SELECTION: (default) allows any combination of items to be selected at a time

Following example shows you how to apply them
Main.java:

import javax.swing.event.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.*;
import java.awt.*;
  
public class Main extends JFrame implements ActionListener {
   JList list; 
 
   public Main() {
      getContentPane().setLayout(new BorderLayout());
 
      final DefaultListModel listModel = new DefaultListModel();   
 
      // populate listmodel
      for (int i=0; i<10; i++) {
         listModel.addElement("list item #" + i);
      }
 
      list = new JList(listModel); 
      getContentPane().add(BorderLayout.CENTER, new JScrollPane(list));    
 
      JPanel panel = new JPanel(new GridLayout(3, 1));
      JRadioButton rb1 = new JRadioButton("SINGLE_SELECTION");
      rb1.addActionListener(this);
      JRadioButton rb2 = new JRadioButton("SINGLE_INTERVAL_SELECTION");
      rb2.addActionListener(this);
      JRadioButton rb3 = new JRadioButton("MULTIPLE_INTERVAL_SELECTION");
      rb3.addActionListener(this);
      ButtonGroup bg = new ButtonGroup();
      bg.add(rb1);
      bg.add(rb2);
      bg.add(rb3);
      panel.add(rb1);
      panel.add(rb2);
      panel.add(rb3);
      getContentPane().add(BorderLayout.EAST, panel);
  
      addWindowListener(new WindowAdapter() {
         public void windowClosing(WindowEvent event) {
            System.exit(0);   
         }      
      });
 
      pack();
   }
 
   public void actionPerformed(ActionEvent ae) {
      if (ae.getActionCommand().equals("SINGLE_SELECTION"))
         list.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
      else if (ae.getActionCommand().equals("SINGLE_INTERVAL_SELECTION"))
         list.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
      else if (ae.getActionCommand().equals("MULTIPLE_INTERVAL_SELECTION"))
         list.getSelectionModel().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
   }
   
   public static void main(String[] args) {
      (new Main()).show();
   }
}

Creating a JList with images

Create your own CellRenderer and change the default behavior as to show the ImageIcon:

import java.awt.event.*;
import javax.swing.*;
import java.util.*;
import java.awt.*;
  
public class Main extends JFrame {
 
   public Main() {
      getContentPane().setLayout(new FlowLayout());
 
      Vector v = new Vector();
      v.addElement(new ImageString("first", new ImageIcon("c:\first.gif")));
      v.addElement(new ImageString("second", new ImageIcon("c:\second.gif")));
      v.addElement(new ImageString("third", new ImageIcon("c:\third.gif")));
      v.addElement(new ImageString("fourth", new ImageIcon("c:\fourth.gif")));
      v.addElement(new ImageString("fifth", new ImageIcon("c:\fifth.gif")));
 
      final JList list = new JList(v);
      list.setCellRenderer(new ImageStringCellRenderer());
      getContentPane().add(new JScrollPane(list));
 
      addWindowListener(new WindowAdapter() {
         public void windowClosing(WindowEvent event) {
            System.exit(0);   
         }      
      });
 
      setSize(200, 300);
   }
   
   public static void main(String[] args) {
      (new Main()).show();
   }
}
 
class ImageStringCellRenderer extends DefaultListCellRenderer implements ListCellRenderer {
   public Component getListCellRendererComponent(
      JList list, Object value, int index, boolean isSelected, boolean cellHasFocus)    
   {
      ImageString is = (ImageString) value;
      setText(is.getString());
      setIcon(is.getIcon());
      if (isSelected) {
         setBackground(list.getSelectionBackground());
         setForeground(list.getSelectionForeground());
      }
      else {
         setBackground(list.getBackground());
         setForeground(list.getForeground());
      }
      setEnabled(list.isEnabled());
      setFont(list.getFont());
 
      return this;
   }
}
 
class ImageString
{
   private String s;
   private Icon icon;
 
   public ImageString(String s, Icon icon) {
      this.s = s;
      this.icon = icon;
   }
 
   public void setString(String s) {
      this.s = s;
   }
 
   public String getString() {
      return s;
   }
 
   public void setIcon(Icon icon) {
      this.icon = icon;
   }
 
   public Icon getIcon() {
      return icon;
   }
  
   public String toString() {
      return s;
   }
}

Selecting a JList item by double-clicking or pressing Enter

For detecting double-clicking, add a MouseListener and catch the MouseEvents where clickCount is equal to 2.
For detecting the enter key, add a KeyListener and check whether the KeyCode is KeyEvent.VK_ENTER.

Here’s an example:

import javax.swing.event.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.*;
import java.awt.*;
  
public class Main extends JFrame {
 
   public Main() {
      getContentPane().setLayout(new FlowLayout());
 
      Vector v = new Vector();
      for (int i=0; i<50; i++) {
         v.addElement("Item #" + i);
      }
      getContentPane().add(new JLabel("Double-clicked on: "));
      final JTextField dblTextField = new JTextField(15);
      getContentPane().add(dblTextField);
      getContentPane().add(new JLabel("Enter key on: "));
      final JTextField entTextField = new JTextField(15);
      getContentPane().add(entTextField);
      final JList list = new JList(v);
 
      // catch double-click events
      list.addMouseListener(new MouseAdapter() {
         public void mouseClicked(MouseEvent me) {
            if (me.getClickCount() == 2) {
               dblTextField.setText(""+list.getSelectedValue()); 
            }
         }
      });
 
      // catch enter-key events
      list.addKeyListener(new KeyAdapter() { 
         public void keyReleased(KeyEvent ke) { 
            if (ke.getKeyCode() == KeyEvent.VK_ENTER) { 
               entTextField.setText(""+list.getSelectedValue());
            }
         }
      });
 
      getContentPane().add(new JScrollPane(list));    
 
      addWindowListener(new WindowAdapter() {
         public void windowClosing(WindowEvent event) {
            System.exit(0);   
         }      
      });
 
      setSize(200, 300);
   }
   
   public static void main(String[] args) {
      (new Main()).show();
   }
}

Refreshing the contents of a JList

JListExample.java:

 
// Created with JBuilder (c) Philip Craiger
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
 
/**
 * Title:        A JList Programming Example
 * Description:
 * Copyright:    Copyright (c) 2001
 * Company:
 * @author Dr. Philip Craiger
 * @version 1.0
 */
 
public class JListExample extends JFrame {
  private JButton btnMove = new JButton();
  private JButton bthRemove = new JButton();
 
  // Model View Controller: We change contents of the JList through
  // manipulation of a MODEL, not the actual JList.
  
 
  private DefaultListModel model_1 = new DefaultListModel();
  private DefaultListModel model_2 = new DefaultListModel();
 
  private JList jList1 = new JList(model_2);
  private JList jList2 = new JList(model_1);
  
  // JLists do not scroll by default.  We need to add them to
  // an encompassing JScrollPane
  private JScrollPane jScrollPane1 = new JScrollPane();
  private JScrollPane jScrollPane2 = new JScrollPane();
 
  public JListExample() {     // our constructor
    super("A JList Example"); // call to super constructor
    try {
      jbInit();
    }
    catch(Exception e) {
      e.printStackTrace();
    }
  }
 
  public static void main(String[] args) {
    JListExample foo = new JListExample();
    foo.setSize(new Dimension(450,200));
    foo.show();
  }
 
  private void jbInit() throws Exception {
 
  // Note we are adding and removing JList items from the MODEL
  // NOT directly from the JList.  Because JList is associated with the
  // model, the JList contents are updated automatically
 
    model_1.addElement("Java");
    model_1.addElement("Visual Basic");
    model_1.addElement("C++");
    model_1.addElement("C");
    model_1.addElement("Common LISP");
    model_1.addElement("Fortran");
    model_1.addElement("Pascal");
    model_1.addElement("Python");
    this.getContentPane().setLayout(null);
 
    btnMove.setText("Move >>");
    btnMove.setBounds(new Rectangle(150, 35, 98, 36));
    btnMove.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        btnMove_actionPerformed(e);
      }
    });
 
    bthRemove.setText("<< Remove");
    bthRemove.setBounds(new Rectangle(152, 87, 97, 36));
    bthRemove.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        bthRemove_actionPerformed(e);
      }
    });
 
    jScrollPane1.setBounds(new Rectangle(286, 15, 109, 133));
    jScrollPane2.setBounds(new Rectangle(14, 15, 106, 136));
 
    // Allow user to select a single item from JList
    jList1.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    jList2.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
 
    this.getContentPane().add(jScrollPane2, null);
    jScrollPane2.getViewport().add(jList2, null); // Must add JList
    this.getContentPane().add(jScrollPane1, null); // to a JScrollPane
    this.getContentPane().add(btnMove, null);
    this.getContentPane().add(bthRemove, null);
    jScrollPane1.getViewport().add(jList1, null); // JList to JScrollPane
    jList2.setSelectedIndex(0);
  }
 
  void btnMove_actionPerformed(ActionEvent e) {
    model_2.addElement(jList2.getSelectedValue());  // change the MODEL
    model_1.removeElement(jList2.getSelectedValue());  // change the MODEL
    jList2.setSelectedIndex(0);       // Highlight first item in JList
  }
 
  void bthRemove_actionPerformed(ActionEvent e) {
    model_1.addElement(jList1.getSelectedValue());  // change the MODEL
    model_2.removeElement(jList1.getSelectedValue()); // change the MODEL
    jList2.setSelectedIndex(0);       // Highlight first item in JList
  }
}

Preventing the user from renaming files/directories in a JFileChooser

This is a hard task. I sat 3 hours to solve the problem for Jbuzzer. I do not know, if I solved the problem in a good way but it works.

Problem

The problem is, that deep inside of the UI of the JFileChooser – at the level of concrete implementation of the Look & Feel – the internal javax.swing.JList is instantiated without even keeping a member handle to it. It is done within a method that instantiates it, configures it (e.g.: with mouse listeners), adds it in a JPanel and returns that one.

The java jdk source code (1.4)

public class MetalFileChooserUI extends BasicFileChooserUI{
   private JPanel listViewPanel;
 
   public void installComponents(JFileChooser fc) {
      ...
      listViewPanel = createList(fc);
      ...
   }
 
   // A big don't: Hardcoded listeners 
   // in the UI implementation.
   protected JPanel createList(JFileChooser fc) {
      list = new JList() { <anonymous initialisation >};
      ...
      <b>list.addMouseListener(createSingleClickListener(fc,list));</b>
      ...
   }

   private MouseListener reateSingleClickListener(JFileChooser fc, JList list) 
   {
      return new SingleClickListener(list);
   }
 
   protected class SingleClickListener extends MouseAdapter {
      JList list;
      public void mouseClicked(MouseEvent e) {
         if (SwingUtilities.isLeftMouseButton(e)) {
            if (e.getClickCount() == 1) {
               JFileChooser fc = getFileChooser();
               int index = list.locationToIndex(e.getPoint());
               if ((!fc.isMultiSelectionEnabled() 
                   || fc.getSelectedFiles().length <= 1)
                   && index >= 0 
                   && list.isSelectedIndex(index)
                   && getEditIndex() == index 
                   && editFile == null) {
                      editFileName(index);
                   } 
               else {
                   ...
               }
            }

Solution

  1. Create your JFileChooser.

  • Run a search for it’s Component child of type JList.
  • Get the MouseListener of this JList whose class name contains “SingleClick” (I did not search other plaf packages: they might do the same bad thing but add the MouseListener for editing the JList in another way. This bugfix will only work for javax.swing.plaf.metal).
  • Remove that MouseListener from the retrieved JList.

    Code

    import javax.swing.JList;
    import java.awt.Component;
    import java.awt.Container;
    import javax.swing.JFileChooser;
    import java.awt.event.MouseListener;
     
    public class somename {
       ...
       /**
       * <p>
       * Hack to get the {@link List} of the {@link JFileChooser} 
       * instance used for loading sounds: 
       * We have to remove the "Single Click" listener 
       * that allows renaming files.
       * </p>
       */
       private JList searchJList(Container fileChooser)
       {
          JList ret = null;
          // First check, wether i am a JList:
          if (fileChooser instanceof JList){
              ret = (JList)fileChooser;
          }
          // Ok, me not: let's ask the children.
          else {
             Component[] children = fileChooser.getComponents();
             for(int i=children.length-1;i>=0;i--) {
                if (children[i] instanceof Container) {
                   ret = searchJList((Container)children[i]);
                   if(ret != null) {
                      break;
                   }
                }
             }
          }
    
          return ret;
       }
     
       // Just demo code! 
       public static void main(String[]args){
          JFileChooser load = new JFileChooser("/home/user/...");
          JList list = searchJList(this.openDialog);
          if (list!=null) {
             String listenerClassName;
             MouseListener[] listeners = list.getMouseListeners();
             for (int i=0;i< listeners.length;i++) {
                listenerCName = listeners[i].getClass().getName();
                if(listenerCName.indexOf("SingleClick")!= -1) {      
                   list.removeMouseListener(mouseListeners[i]);
                   break;
                }
             }
          }
          // Show the file chooser.
       }
    }
    

    Further examinations of other L&F packages could require changes. This has only be tested with javax.swing.plaf.metal.

    See the working example in Jbuzzer, a small fun-application for mapping sounds to keystrokes: http://sourceforge.net/projects/jbuzzer/.

  • A Swing drag and drop code example

    Drag and Drop is a powerful feature you should consider adding to your Java applications. When you check out the available tutorials on Java dnd, it may look fairly complex to you. Though it’s not all that hard, the hard part is remembering all the steps, objects and listeners involved. Here is a small example that shows you a basic drag and drop application (implemented with Swing).

    A custom Drag and Drop (source) JList appears to the right and contains a file list of your root directory. Files with .txt extensions can be dragged upon our Drag and Drop JTextArea (target). The object transferred (Transferable) is a filename. When the drop occurs (you release the mouse button) the public void drop(DropTargetDropEvent event) is called. You can extract the filename from the Transferable object and go from there.

    Main.java

    import javax.swing.event.*;
    import java.awt.event.*;
    import javax.swing.*;
    import java.awt.*;
    import java.io.*;
     
    public class Main extends JFrame
    {
       private DNDJList list;
       private DNDJTextArea textarea;
     
       public static void main(String []args) {
          Main frame = new Main();
          frame.show();
       }
     
       public Main() {
          getContentPane().setLayout(new BorderLayout());
          list = new DNDJList(new DefaultListModel());
          getContentPane().add(BorderLayout.EAST, new JScrollPane(list));
          textarea = new DNDJTextArea();
          getContentPane().add(BorderLayout.CENTER, new JScrollPane(textarea));
     
          addWindowListener(new WindowAdapter() {
             public void windowClosing(WindowEvent e) {
                System.exit(0);
             }
          });
     
          fillUpList(&quot;c:\&quot;);
     
          setSize(700, 300);
       }
    
       /**
        *  Fills up the JList with the entries in the specified directory
        */
       private void fillUpList(String directory) {
          File dir = new File(directory);
          File []files = dir.listFiles();
     
          DefaultListModel dlm = (DefaultListModel) list.getModel();
          for (int i=0; i&lt;files.length; i++) {
             dlm.addElement(directory + files[i].getName());
          }
       }
    }
    

    Our drag and drop JTextArea: DNDJTextArea.java

    import java.awt.*;
    import java.awt.dnd.*;
    import java.awt.datatransfer.*;
    
    import java.io.*;
    import java.io.IOException;
     
    import javax.swing.JTextArea;
     
    public class DNDJTextArea extends JTextArea implements DropTargetListener
    {
       DropTarget dropTarget = null;
      
       public DNDJTextArea() {
          // create a drop target
          dropTarget = new DropTarget(this, this);
       }
     
       public void dragEnter(DropTargetDragEvent event) { 
          event.acceptDrag(DnDConstants.ACTION_MOVE);
       }
     
       public void drop (DropTargetDropEvent event) {
          try {
             // get the object that is being transferred
             Transferable transferable = event.getTransferable();
           
             // DNDJTextArea accepts only Strings
             if (transferable.isDataFlavorSupported(DataFlavor.stringFlavor)) {
                event.acceptDrop(DnDConstants.ACTION_MOVE);
     
                String filename = (String) transferable.getTransferData(DataFlavor.stringFlavor);
                setText(readFile(filename));
    
                event.getDropTargetContext().dropComplete(true);
             } 
             else {
                event.rejectDrop();
             }
          }
          catch (UnsupportedFlavorException e) {
             setText(&quot;&quot;+e);
             event.rejectDrop();
          }
          catch (Exception e) {
             setText(&quot;&quot;+e);
             event.rejectDrop();
          } 
       }
     
       public void dragExit (DropTargetEvent event) { }
       public void dragOver (DropTargetDragEvent event) { }
       public void dropActionChanged (DropTargetDragEvent event) { }
     
       public String readFile(String filename) throws Exception {
          String LINEEND = System.getProperties().getProperty(&quot;line.separator&quot;);      
          StringBuffer sb = new StringBuffer();
          BufferedReader br = new BufferedReader(new FileReader(filename));
     
          String line;
          while ((line = br.readLine()) != null) {
             sb.append(line + LINEEND);
          }         
     
          return sb.toString();
       }
    }
    

    A drag and drop JList:DNDJList.java

    import java.awt.dnd.*;
    import java.awt.datatransfer.*;
    import java.io.IOException;
    import java.io.*;
     
    import javax.swing.*;
     
    public class DNDJList extends JList implements DragSourceListener, DragGestureListener    
    {
       DragSource dragSource = null;
     
       public DNDJList(ListModel lm) {
          super(lm);
     
          // create a dragsource
          dragSource = new DragSource();
     
          // create a drag gesture recognizer
          dragSource.createDefaultDragGestureRecognizer(this, DnDConstants.ACTION_MOVE, this);
       }    
     
       public void dragGestureRecognized( DragGestureEvent event) {  
          String fileSelected = (String) getSelectedValue();
     
          if (fileSelected != null) {
             if (fileSelected.endsWith(&quot;.txt&quot;)) {
                // StringSelection implements Transferable, wraps the data into a transferable object
                StringSelection text = new StringSelection(fileSelected.toString()); 
            
                // start the dragging
                dragSource.startDrag(event, DragSource.DefaultMoveDrop, text, this);
             }
             else {
                SwingUtilities.invokeLater(new Runnable() {
                   public void run() {
                      JOptionPane.showMessageDialog(SwingUtilities.getRootPane(DNDJList.this), 
                                                    &quot;Only .txt files can be dragged!&quot;, &quot;Error&quot;,
                                                    JOptionPane.ERROR_MESSAGE);
                   } 
                });
             }
          } else {
             System.out.println( &quot;nothing was selected&quot;);   
          }
       }
     
       public void dragDropEnd (DragSourceDropEvent event) { }
       public void dragEnter (DragSourceDragEvent event) { }
       public void dragExit (DragSourceEvent event) { }
       public void dragOver (DragSourceDragEvent event) { }
       public void dropActionChanged ( DragSourceDragEvent event) { }
    }
    

    Creating a sorted JList

    This example shows you a method sortList that gets the JList data, puts them in an array, sorts them using Arrays.sort and updates the list model:

    import javax.swing.event.*;
    import java.awt.event.*;
    import javax.swing.*;
    import java.util.*;
    import java.awt.*;
      
    public class Main extends JFrame {
     
       public Main() {
          getContentPane().setLayout(new BorderLayout());
     
          final DefaultListModel listModel = new DefaultListModel();   
     
          // populate listmodel
          Random r = new Random();
          for (int i=0; i<50; i++) {
             listModel.addElement("list item # " + (Math.abs(r.nextInt()) % 100));
          }
     
          final JList list = new JList(listModel); 
     
          getContentPane().add(BorderLayout.CENTER, new JScrollPane(list));    
          JButton sortButton = new JButton("Sort");
          sortButton.addActionListener(new ActionListener() {
             public void actionPerformed(ActionEvent ae) {
                sortList(list);
             }
          });
          getContentPane().add(BorderLayout.SOUTH, sortButton);
     
          addWindowListener(new WindowAdapter() {
             public void windowClosing(WindowEvent event) {
                System.exit(0);   
             }      
          });
     
          pack();
       }
     
       public void sortList(JList list) {
          ListModel model = list.getModel();
     
          int n = model.getSize();
          String[] data = new String[n]; 
     
          for (int i=0; i<n; i++) { 
             data[i] = (String) model.getElementAt(i); 
          }
     
          Arrays.sort(data); 
     
          list.setListData(data); 
       }
       
       public static void main(String[] args) {
          (new Main()).show();
       }
    }

    Creating a JTable with row headers

    Courtesy of Nobuo Tamemasa (http://www2.gol.com/users/tame/swing/examples/JTableExamples1.html)



    import java.awt.*;
    import java.awt.event.*;
    import javax.swing.*;
    import javax.swing.table.*;
     
     
    /**
     * @version 1.0 11/09/98
     */
      
    class RowHeaderRenderer extends JLabel implements ListCellRenderer {
       
      RowHeaderRenderer(JTable table) {
        JTableHeader header = table.getTableHeader();
        setOpaque(true);
        setBorder(UIManager.getBorder("TableHeader.cellBorder"));
        setHorizontalAlignment(CENTER);
        setForeground(header.getForeground());
        setBackground(header.getBackground());
        setFont(header.getFont());
      }
      
      public Component getListCellRendererComponent( JList list, 
             Object value, int index, boolean isSelected, boolean cellHasFocus) {
        setText((value == null) ? "" : value.toString());
        return this;
      }
    }
     
    public class RowHeaderExample extends JFrame {
     
      public RowHeaderExample() {
        super( "Row Header Example" );
        setSize( 300, 150 );
            
        ListModel lm = new AbstractListModel() {
          String headers[] = {"a", "b", "c", "d", "e", "f", "g", "h", "i"};
          public int getSize() { return headers.length; }
          public Object getElementAt(int index) {
            return headers[index];
          }
        };
     
        DefaultTableModel dm = new DefaultTableModel(lm.getSize(),10);
        JTable table = new JTable( dm );
        table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
        
        JList rowHeader = new JList(lm);    
        rowHeader.setFixedCellWidth(50);
        
        rowHeader.setFixedCellHeight(table.getRowHeight()
                                   + table.getRowMargin());
    //                             + table.getIntercellSpacing().height);
        rowHeader.setCellRenderer(new RowHeaderRenderer(table));
     
        JScrollPane scroll = new JScrollPane( table );
        scroll.setRowHeaderView(rowHeader);
        getContentPane().add(scroll, BorderLayout.CENTER);
      }
     
      public static void main(String[] args) {
        RowHeaderExample frame = new RowHeaderExample();
        frame.addWindowListener( new WindowAdapter() {
          public void windowClosing( WindowEvent e ) {
            System.exit(0);
          }
        });
        frame.setVisible(true);
      }
    }