Changing the mouse pointer to a hand cursor when the mouse moves over a JButton

Main.java:

import java.awt.event.*;
import javax.swing.*;
import java.awt.*;
 
public class Main extends JFrame
{
   private Cursor handCursor = new Cursor(Cursor.HAND_CURSOR);
   private Cursor defaultCursor = new Cursor(Cursor.DEFAULT_CURSOR);
 
   public static void main(String []args) {
      Main main = new Main();
      main.show();
   }
 
   public Main() {
      JButton button = new JButton("Move mouse over this button!");
      button.addMouseListener(new MouseAdapter() {
         public void mouseEntered(MouseEvent me) {
            setCursor(handCursor);
         }
         public void mouseExited(MouseEvent me) {
            setCursor(defaultCursor);
         }
      });
  
      getContentPane().setLayout(new FlowLayout(FlowLayout.CENTER));
      getContentPane().add(button);
 
      addWindowListener(new WindowAdapter() {
         public void windowClosing(WindowEvent e) {
            System.exit(0);
         }
      });
 
      setSize(300, 100);
   }  
}

Getting the selected item(s) from a JList

There are several functions to determine the selected value(s) of a JList:

  • getSelectedIndex() returns the first selected index
  • getSelectedIndices() returns all of the selected indices
  • getSelectedValue() returns the first selected value
  • getSelectedValues() returns all of the selected values

Here’s an example:

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<20; i++) {
         v.addElement("Item #" + i);
      }
 
      final JList list = new JList(v);
      getContentPane().add(new JScrollPane(list));
      JButton button = new JButton("Show selected items");
      getContentPane().add(button);
      button.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent ae) { 
            System.out.println("Selected values:");
            Object[] oarr = list.getSelectedValues();
            for (int i=0; i<oarr.length; i++) {
               System.out.println(oarr[i]);
            }
 
            System.out.println("Selected indices:");
            int[] iarr = list.getSelectedIndices();
            for (int i=0; i<iarr.length; i++) {
               System.out.println(iarr[i]);
            }
         }
      });
 
      addWindowListener(new WindowAdapter() {
         public void windowClosing(WindowEvent event) {
            System.exit(0);   
         }      
      });
 
      setSize(200, 250);
   }
   
   public static void main(String[] args) {
      (new Main()).show();
   }
}

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();
   }
}

Detecting a value change of my JSlider

Add a ChangeListener and add behavior to its method stateChanged.

Main.java:

import javax.swing.event.*;
import java.awt.event.*;
import javax.swing.*;
import java.awt.*;
  
public class Main extends JFrame { 
   JSlider slider;
 
   public Main() {
      addWindowListener(new WindowAdapter() {
         public void windowClosing(WindowEvent we) {
            System.exit(1);
         }
      });   
 
      slider = new JSlider(JSlider.HORIZONTAL, 0, 20, 1);
      slider.addChangeListener(new ChangeListener() {
         public void stateChanged(ChangeEvent ce) {
            repaint();
         }
      });
 
      slider.setMajorTickSpacing(1);
      slider.setPaintTicks(true);
      slider.setPaintLabels(true);
 
      getContentPane().setLayout(new BorderLayout());
      getContentPane().add(BorderLayout.NORTH, slider);
   } 
 
   public void paint(Graphics g) {
      super.paint(g);
  
      Graphics2D g2d = (Graphics2D) g;
 
      g2d.setFont(new Font("Serif", Font.BOLD, 36));
      g2d.drawString(""+slider.getValue(), 200, 130);
   }
 
   public static void main(String []args) {
      Main main = new Main();
      main.setSize(400, 150);
      main.setVisible(true);
   }
}

Creating a numeric-only JTextfield

Here’s the code for a Swing component that only accepts digits.

Main.java:

import javax.swing.text.*;
import java.awt.event.*;
import javax.swing.*;
import java.text.*;
import java.awt.*;
   
public class Main extends JFrame {
   public Main() {
      getContentPane().setLayout(new GridLayout(2, 1));
 
      final JPasswordField passwordField = new JPasswordField(20);
      final JTextField textField = new JTextField(20);
      getContentPane().add(new NumberTextField(5));
      getContentPane().add(new NumberTextField(123, 7));
  
      addWindowListener(new WindowAdapter() {
         public void windowClosing(WindowEvent event) {
            System.exit(0);   
         }      
      });
 
      pack();
   }
 
   public static void main(String[] args) {
      (new Main()).show();
   }
}
 
class NumberTextField extends JTextField
{
   private NumberFormat nf;
 
   public NumberTextField(int cols) {
      super(cols);
      nf = NumberFormat.getNumberInstance();
   }
 
   public NumberTextField(int value, int cols) {
      this(cols);
      setValue(value);
   }
 
   public int getValue() {
      int n = 0;
      try {
         n = nf.parse(getText()).intValue();
      }
      catch(ParseException e) { }
      return n;
   }
 
   public void setValue(int value) {
      setText(nf.format(value));
   }
 
   protected Document createDefaultModel() {
      return new NumberDocument();
   }
 
   protected class NumberDocument extends PlainDocument {
      public void insertString(int offset, String string, AttributeSet as) 
            throws BadLocationException  {
         char [] src = string.toCharArray();
         char [] dest = new char[src.length];
         int count=0;
         for (int i=0; i < src.length; i++) {
            if (Character.isDigit(src[i])) 
               dest[count++] = src[i];
         }
         super.insertString(offset, new String(dest, 0, count), as);
      }
   }
}

Creating a multi-line text tooltip for a JLabel

An easy way is to have your tooltip text to be HTML:

   label.setToolTipText("<html>This is a two-line<br> tooltip text!</html>");

as in following example:

import java.awt.event.*;
import javax.swing.*;
import java.awt.*;
                            
public class Main extends JFrame
{ 
   public Main() {
      getContentPane().setLayout(new FlowLayout());
      JLabel label = new JLabel("Mouse-over me!");
      getContentPane().add(label);
                         
      label.setToolTipText("<html>This is a two-line<br> tooltip text!</html>");
                         
      addWindowListener(new WindowAdapter() {
         public void windowClosing(WindowEvent we) {
            System.exit(1);
         }
      });      
                           
      setSize(new Dimension(200, 200));
   } 
                         
   public static void main(String[] args) throws Exception {
      Main main = new Main();
      main.setVisible(true);
   }
}

Adding a JProgressBar inside a JTable cell

A JTable registers default renderers for a Number, Date, ImageIcon, Boolean and Object. If you want to render another component, you have to create a custom class that implements TableCellRenderer and register it with the method setDefaultRenderer on the JTable object.

The following example creates a JTable where every row displays data from a Download object. A download object is a thread that mimics the behavior of downloading a file by increasing the number of downloaded bytes (progress) with a random value. Using the Observable-Observer pattern, the download object notifies the table model whenever the progress of downloading has changed.

Main.java:

import javax.swing.table.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.*;
import java.awt.*;
 
public class Main extends JFrame {
   public Main() {
      super(&quot;TableModel JProgressBar Demonstration&quot;);
 
      // create our own custom TableModel
      DownloadTableModel downloadModel = new DownloadTableModel();
      JTable table = new JTable(downloadModel);
 
      // add rows to our TableModel, each row is represented as a Download object
      downloadModel.addDownload(new Download(&quot;linuxmandrake.zip&quot;, 1234567));
      downloadModel.addDownload(new Download(&quot;flash5.exe&quot;, 56450000));
      downloadModel.addDownload(new Download(&quot;jdk1.2.2-007.zip&quot;, 20000000));
 
      // render the columns with class JProgressBar as such
      ProgressBarRenderer pbr = new ProgressBarRenderer(0, 100);
      pbr.setStringPainted(true);
      table.setDefaultRenderer(JProgressBar.class, pbr);
   
      // increase the height of the rows a bit
      table.setRowHeight((int) pbr.getPreferredSize().getHeight());
 
      // create the scroll pane and add the table to it. 
      JScrollPane scrollPane = new JScrollPane(table);
 
      // add the scroll pane to this window.
      getContentPane().add(scrollPane, BorderLayout.CENTER);
 
      addWindowListener(new WindowAdapter() {
         public void windowClosing(WindowEvent e) {
            System.exit(0);
         }
      });
   }
 
   public static void main(String[] args) {
      Main main = new Main();
      main.pack();
      main.setVisible(true);
   }
}
 
// a simple object that holds data about a particular download
// it starts a thread and increases the progress of &quot;downloading&quot;
// in a random manner
class Download extends Observable implements Runnable {
   private Thread  thisThread;
 
   private String  filename;
   private int     filesize;
   private float   progress;
 
   public Download(String filename, int filesize) {
      this.filename = filename;
      this.filesize = filesize;
      progress = 0.0f;
      thisThread = new Thread(this);
      thisThread.start();
   }
 
   public String getFilename() { return filename; }
   public int    getFilesize() { return filesize; }
   public float  getProgress() { return progress; }
 
   public String toString() { 
      return &quot;[&quot; + filename + &quot;, &quot; + filesize + &quot;, &quot; + progress + &quot;]&quot;; }

   public void run() {
      Random r = new Random();
      int count = 0;
      while (count &lt; filesize) {
         int random = Math.abs(r.nextInt() % 100000);
         count += random;
         if (count &gt; filesize) count = filesize; 
         progress = ((float) count / filesize) * 100;
 
         // notify table model (and all other observers)
         setChanged();
         notifyObservers(this);       
 
         try { thisThread.sleep(500); } catch(InterruptedException e) { }
      }
   }
}
 
class DownloadTableModel extends AbstractTableModel implements Observer {
   // holds the strings to be displayed in the column headers of our table
   final String[] columnNames = {&quot;Filename&quot;, &quot;Filesize&quot;, &quot;Progress&quot;};
 
   // holds the data types for all our columns
   final Class[] columnClasses = {String.class, Integer.class, JProgressBar.class};
 
   // holds our data
   final Vector data = new Vector();
  
   // adds a row
   public void addDownload(Download d) {
      data.addElement(d);
   
      // the table model is interested in changes of the rows
      d.addObserver(this);
      fireTableRowsInserted(data.size()-1, data.size()-1);
   }
 
   // is called by a download object when its state changes
   public void update(Observable observable, Object o) {
      int index = data.indexOf(o);
      if (index != -1) 
         fireTableRowsUpdated(index, index);
   }
 
   public int getColumnCount() {
      return columnNames.length;
   }
         
   public int getRowCount() {
      return data.size();
   }
 
   public String getColumnName(int col) {
      return columnNames[col];
   }
 
   public Class getColumnClass(int c) {
      return columnClasses1;
   }
 
   public Object getValueAt(int row, int col) {
      Download download = (Download) data.elementAt(row);
      if (col == 0)      return download.getFilename();
      else if (col == 1) return new Integer(download.getFilesize());
      else if (col == 2) return new Float(download.getProgress());
      else return null;
   }
 
   public boolean isCellEditable(int row, int col) {
      return false;
   }
}
 
// a table cell renderer that displays a JProgressBar
class ProgressBarRenderer extends JProgressBar implements TableCellRenderer {
   public ProgressBarRenderer() {
      super();
   }
 
   public ProgressBarRenderer(BoundedRangeModel newModel) {
      super(newModel);
   }
 
   public ProgressBarRenderer(int orient) {
      super(orient);
   } 
 
   public ProgressBarRenderer(int min, int max) {
      super(min, max);
   }
 
   public ProgressBarRenderer(int orient, int min, int max) {
      super(orient, min, max);
   }
 
   public Component getTableCellRendererComponent(
      JTable table, Object value, boolean isSelected, boolean hasFocus,
      int row, int column) {
           
      setValue((int) ((Float) value).floatValue());
 
      return this;
   }
}

Localizing a JFileChooser

Change the default values using the UIManager class. The default map contains a number
of key-values that are used by the FileChooser component. The following example shows you
how to change the open-file JFileChooser component to contain some dutch values.

Main.java:

import javax.swing.filechooser.*;
import javax.swing.event.*;
import java.awt.event.*;
import javax.swing.*;
import java.awt.*;
 
public class Main extends JFrame {
   public Main() {
      super("JFileChooser Localization Demonstration");
 
      UIManager.put("FileChooser.filesOfTypeLabelText", "Bestanden van type:");
      UIManager.put("FileChooser.filesOfTypeLabelMnemonic", new Integer('t'));
      UIManager.put("FileChooser.fileNameLabelText", "Bestandsnaam:");
      UIManager.put("FileChooser.fileNameLabelMnemonic", new Integer('n'));
      UIManager.put("FileChooser.lookInLabelText", "Kijk in:");
      UIManager.put("FileChooser.lookInLabelMnemonic", new Integer('i'));
      UIManager.put("FileChooser.openButtonText", "Openen");
      UIManager.put("FileChooser.openButtonMnemonic", new Integer('o'));
      UIManager.put("FileChooser.cancelButtonText", "Annuleren");
      UIManager.put("FileChooser.cancelButtonMnemonic", new Integer('a'));
   
      getContentPane().setLayout(new FlowLayout()); 
      JFileChooser fileChooser = new JFileChooser(); 
 
      getContentPane().add(fileChooser); 
  
      addWindowListener(new WindowAdapter() {
         public void windowClosing(WindowEvent e) {
            System.exit(0);
         }
      });
   }
 
   public static void main(String[] args) {
      Main main = new Main();
      main.pack();
      main.setVisible(true);
   }
}

Using the UIManager.put call, you can change the values of the following default keys:

FileChooser.acceptAllFileFilterText
FileChooser.cancelButtonMnemonic
FileChooser.cancelButtonText
FileChooser.cancelButtonToolTipText
FileChooser.detailsViewButtonAccessibleName
FileChooser.detailsViewButtonToolTipText
FileChooser.detailsViewIcon
FileChooser.directoryDescriptionText
FileChooser.fileDescriptionText
FileChooser.fileNameLabelMnemonic
FileChooser.fileNameLabelText
FileChooser.filesOfTypeLabelMnemonic
FileChooser.filesOfTypeLabelText
FileChooser.helpButtonMnemonic
FileChooser.helpButtonText
FileChooser.helpButtonToolTipText
FileChooser.homeFolderAccessibleName
FileChooser.homeFolderIcon
FileChooser.homeFolderToolTipText
FileChooser.listViewButtonAccessibleName
FileChooser.listViewButtonToolTipText
FileChooser.listViewIcon
FileChooser.lookInLabelMnemonic
FileChooser.lookInLabelText
FileChooser.newFolderAccessibleNam
FileChooser.newFolderErrorSeparator
FileChooser.newFolderErrorText
FileChooser.newFolderIcon
FileChooser.newFolderToolTipText
FileChooser.openButtonMnemonic
FileChooser.openButtonText
FileChooser.openButtonToolTipText
FileChooser.saveButtonMnemonic
FileChooser.saveButtonText
FileChooser.saveButtonToolTipText
FileChooser.upFolderAccessibleName
FileChooser.upFolderIcon
FileChooser.upFolderToolTipText
FileChooser.updateButtonMnemonic
FileChooser.updateButtonText
FileChooser.updateButtonToolTipText

Adding a row to a JTable

The trick is to actaully create an instance of a new javax.swing.table.DefaultTableModel(). You will then have access to the .addRow and .addColumn methods of the TableModel which which will chage the data displayed in your table.

The initComponents() method in the code I pasted below was generated by Forte for Java. Since it does not let you edit the initComponents() method, I just overwrote the generated TableModel with an empty one.

public class TableTest extends javax.swing.JApplet {
 
   public TableTest() {
      initComponents ();
      javax.swing.table.DefaultTableModel t=new javax.swing.table.DefaultTableModel();
      jTable1.setModel (t);
      t.addColumn ((Object)"Test");
      t.addColumn ((Object)"Foo");
      t.addColumn ((Object)"Bar");
      t.addRow(new Object[] {"1","2","3"});
      t.addRow(new Object[] {"4","5","6"});
      t.removeRow(0);
   }
   
   private void initComponents() {
      jScrollPane1 = new javax.swing.JScrollPane();
      jTable1 = new javax.swing.JTable();
      getContentPane().setLayout(null);
          
      jTable1.setModel(new javax.swing.table.DefaultTableModel (
          new Object [][] {
              {null, null, null, null},
              {null, null, null, null},
              {null, null, null, null},
              {null, null, null, null}
          },
          new String [] {
              "Title 1", "Title 2", "Title 3", "Title 4"
          }
          ) {
              Class[] types = new Class [] {
                  java.lang.Object.class,
                  java.lang.Object.class,
                  java.lang.Object.class,
                  java.lang.Object.class
              };
              
              public Class getColumnClass (int columnIndex) {
                 return types [columnIndex];
              }
          });
      jScrollPane1.setViewportView(jTable1);
           
      getContentPane().add(jScrollPane1);
      jScrollPane1.setBounds(70, 50, 290, 150);  
   }

   private javax.swing.JScrollPane jScrollPane1;
   private javax.swing.JTable jTable1;
}

Creating spanned headers in a JTable

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



MultiWidthHeaderExample.java:

/*
 *  (swing1.1beta3)
 * 
 * |-----------------------------------------------------|
 * |   1st  |      2nd        |          3rd             |
 * |-----------------------------------------------------|
 * |        |        |        |        |        |        |
 */
 
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.table.*;
 
/**
 * @version 1.0 11/09/98
 */
public class MultiWidthHeaderExample extends JFrame {
 
  MultiWidthHeaderExample() {
    super( "Multi-Width Header Example" );
 
    DefaultTableModel dm = new DefaultTableModel();
    dm.setDataVector(new Object[][]{
      {"a","b","c","d","e","f"},
      {"A","B","C","D","E","F"}},
    new Object[]{"1 st","","","","",""});
 
    JTable table = new JTable( dm ) {
      protected JTableHeader createDefaultTableHeader() {
        return new GroupableTableHeader(columnModel);
      }
    };
    TableColumnModel cm = table.getColumnModel();
    ColumnGroup g_2nd = new ColumnGroup("2 nd");
    g_2nd.add(cm.getColumn(1));
    g_2nd.add(cm.getColumn(2));
    ColumnGroup g_3rd = new ColumnGroup("3 rd");
    g_3rd.add(cm.getColumn(3));
    g_3rd.add(cm.getColumn(4));
    g_3rd.add(cm.getColumn(5));
    GroupableTableHeader header = (GroupableTableHeader)table.getTableHeader();
    header.addColumnGroup(g_2nd);
    header.addColumnGroup(g_3rd);
    JScrollPane scroll = new JScrollPane( table );
    getContentPane().add( scroll );
    setSize( 400, 100 );  
    header.revalidate(); 
  }
 
  public static void main(String[] args) {
    MultiWidthHeaderExample frame = new MultiWidthHeaderExample();
    frame.addWindowListener( new WindowAdapter() {
      public void windowClosing( WindowEvent e ) {
        System.exit(0);
      }
    });
    frame.setVisible(true);
  }
}

ColumnGroup.java:

import java.util.*;
import java.awt.*;
import javax.swing.*;
import javax.swing.table.*;
  
/** 
  * ColumnGroup
  *
  * @version 1.0 10/20/98
  * @author Nobuo Tamemasa
  */
 
public class ColumnGroup {
  protected TableCellRenderer renderer;
  protected Vector v;
  protected String text;
  protected int margin=0;
 
  public ColumnGroup(String text) {
    this(null,text);
  }
 
  public ColumnGroup(TableCellRenderer renderer,String text) {
    if (renderer == null) {
      this.renderer = new DefaultTableCellRenderer() {
        public Component getTableCellRendererComponent(JTable table, Object value,
                         boolean isSelected, boolean hasFocus, int row, int column) {
          JTableHeader header = table.getTableHeader();
          if (header != null) {
            setForeground(header.getForeground());
            setBackground(header.getBackground());
            setFont(header.getFont());
          }
          setHorizontalAlignment(JLabel.CENTER);
          setText((value == null) ? "" : value.toString());
          setBorder(UIManager.getBorder("TableHeader.cellBorder"));
          return this;
        }
      };
    } else {
      this.renderer = renderer;
    }
    this.text = text;
    v = new Vector();
  }
 
  
  /**
   * @param obj    TableColumn or ColumnGroup
   */
  public void add(Object obj) {
    if (obj == null) { return; }
    v.addElement(obj);
  }
 
  
  /**
   * @param c    TableColumn
   * @param v    ColumnGroups
   */
  public Vector getColumnGroups(TableColumn c, Vector g) {
    g.addElement(this);
    if (v.contains(c)) return g;    
    Enumeration enum = v.elements();
    while (enum.hasMoreElements()) {
      Object obj = enum.nextElement();
      if (obj instanceof ColumnGroup) {
        Vector groups = 
          (Vector)((ColumnGroup)obj).getColumnGroups(c,(Vector)g.clone());
        if (groups != null) return groups;
      }
    }
    return null;
  }
    
  public TableCellRenderer getHeaderRenderer() {
    return renderer;
  }
    
  public void setHeaderRenderer(TableCellRenderer renderer) {
    if (renderer != null) {
      this.renderer = renderer;
    }
  }
    
  public Object getHeaderValue() {
    return text;
  }
  
  public Dimension getSize(JTable table) {
    Component comp = renderer.getTableCellRendererComponent(
        table, getHeaderValue(), false, false,-1, -1);
    int height = comp.getPreferredSize().height; 
    int width  = 0;
    Enumeration enum = v.elements();
    while (enum.hasMoreElements()) {
      Object obj = enum.nextElement();
      if (obj instanceof TableColumn) {
        TableColumn aColumn = (TableColumn)obj;
        width += aColumn.getWidth();
        width += margin;
      } else {
        width += ((ColumnGroup)obj).getSize(table).width;
      }
    }
    return new Dimension(width, height);
  }
 
  public void setColumnMargin(int margin) {
    this.margin = margin;
    Enumeration enum = v.elements();
    while (enum.hasMoreElements()) {
      Object obj = enum.nextElement();
      if (obj instanceof ColumnGroup) {
        ((ColumnGroup)obj).setColumnMargin(margin);
      }
    }
  }
}

GroupableTableHeader.java:

import java.util.*;
import java.awt.*;
import javax.swing.*;
import javax.swing.table.*;
 
/**
  * GroupableTableHeader
  *
  * @version 1.0 10/20/98
  * @author Nobuo Tamemasa
  */
 
public class GroupableTableHeader extends JTableHeader {
  private static final String uiClassID = "GroupableTableHeaderUI";
  protected Vector columnGroups = null;
     
  public GroupableTableHeader(TableColumnModel model) {
    super(model);
    setUI(new GroupableTableHeaderUI());
    setReorderingAllowed(false);
  }
   
  public void setReorderingAllowed(boolean b) {
    reorderingAllowed = false;
  }
     
  public void addColumnGroup(ColumnGroup g) {
    if (columnGroups == null) {
      columnGroups = new Vector();
    }
    columnGroups.addElement(g);
  }
 
  public Enumeration getColumnGroups(TableColumn col) {
    if (columnGroups == null) return null;
    Enumeration enum = columnGroups.elements();
    while (enum.hasMoreElements()) {
      ColumnGroup cGroup = (ColumnGroup)enum.nextElement();
      Vector v_ret = (Vector)cGroup.getColumnGroups(col,new Vector());
      if (v_ret != null) { 
        return v_ret.elements();
      }
    }
    return null;
  }
   
  public void setColumnMargin() {
    if (columnGroups == null) return;
    int columnMargin = getColumnModel().getColumnMargin();
    Enumeration enum = columnGroups.elements();
    while (enum.hasMoreElements()) {
      ColumnGroup cGroup = (ColumnGroup)enum.nextElement();
      cGroup.setColumnMargin(columnMargin);
    }
  } 
}

GroupableTableHeaderUI.java:

import java.util.*;
import java.awt.*;
import javax.swing.*;
import javax.swing.table.*;
import javax.swing.plaf.basic.*;
 
public class GroupableTableHeaderUI extends BasicTableHeaderUI {
  
  public void paint(Graphics g, JComponent c) {
    Rectangle clipBounds = g.getClipBounds();
    if (header.getColumnModel() == null) return;
    ((GroupableTableHeader)header).setColumnMargin();
    int column = 0;
    Dimension size = header.getSize();
    Rectangle cellRect  = new Rectangle(0, 0, size.width, size.height);
    Hashtable h = new Hashtable();
    int columnMargin = header.getColumnModel().getColumnMargin();
    
    Enumeration enumeration = header.getColumnModel().getColumns();
    while (enumeration.hasMoreElements()) {
      cellRect.height = size.height;
      cellRect.y      = 0;
      TableColumn aColumn = (TableColumn)enumeration.nextElement();
      Enumeration cGroups = ((GroupableTableHeader)header).getColumnGroups(aColumn);
      if (cGroups != null) {
        int groupHeight = 0;
        while (cGroups.hasMoreElements()) {
          ColumnGroup cGroup = (ColumnGroup)cGroups.nextElement();
          Rectangle groupRect = (Rectangle)h.get(cGroup);
          if (groupRect == null) {
            groupRect = new Rectangle(cellRect);
            Dimension d = cGroup.getSize(header.getTable());
            groupRect.width  = d.width;
            groupRect.height = d.height;    
            h.put(cGroup, groupRect);
          }
          paintCell(g, groupRect, cGroup);
          groupHeight += groupRect.height;
          cellRect.height = size.height - groupHeight;
          cellRect.y      = groupHeight;
        }
      }      
      cellRect.width = aColumn.getWidth() + columnMargin;
      if (cellRect.intersects(clipBounds)) {
        paintCell(g, cellRect, column);
      }
      cellRect.x += cellRect.width;
      column++;
    }
  }
 
  private void paintCell(Graphics g, Rectangle cellRect, int columnIndex) {
    TableColumn aColumn = header.getColumnModel().getColumn(columnIndex);
    TableCellRenderer renderer = aColumn.getHeaderRenderer();
    Component component = renderer.getTableCellRendererComponent(
      header.getTable(), aColumn.getHeaderValue(),false, false, -1, columnIndex);
    rendererPane.add(component);
    rendererPane.paintComponent(g, component, header, cellRect.x, cellRect.y,
                                cellRect.width, cellRect.height, true);
  }
 
  private void paintCell(Graphics g, Rectangle cellRect,ColumnGroup cGroup) {
    TableCellRenderer renderer = cGroup.getHeaderRenderer();
    Component component = renderer.getTableCellRendererComponent(
      header.getTable(), cGroup.getHeaderValue(),false, false, -1, -1);
    rendererPane.add(component);
    rendererPane.paintComponent(g, component, header, cellRect.x, cellRect.y,
                                cellRect.width, cellRect.height, true);
  }
 
  private int getHeaderHeight() {
    int height = 0;
    TableColumnModel columnModel = header.getColumnModel();
    for(int column = 0; column < columnModel.getColumnCount(); column++) {
      TableColumn aColumn = columnModel.getColumn(column);
      TableCellRenderer renderer = aColumn.getHeaderRenderer();
      Component comp = renderer.getTableCellRendererComponent(
        header.getTable(), aColumn.getHeaderValue(), false, false,-1, column);
      int cHeight = comp.getPreferredSize().height;
      Enumeration enum = ((GroupableTableHeader)header).getColumnGroups(aColumn);      
      if (enum != null) {
        while (enum.hasMoreElements()) {
          ColumnGroup cGroup = (ColumnGroup)enum.nextElement();
          cHeight += cGroup.getSize(header.getTable()).height;
        }
      }
      height = Math.max(height, cHeight);
    }
    return height;
  }
 
  private Dimension createHeaderSize(long width) {
    TableColumnModel columnModel = header.getColumnModel();
    width += columnModel.getColumnMargin() * columnModel.getColumnCount();
    if (width > Integer.MAX_VALUE) {
      width = Integer.MAX_VALUE;
    }
    return new Dimension((int)width, getHeaderHeight());
  }
 
  public Dimension getPreferredSize(JComponent c) {
    long width = 0;
    Enumeration enumeration = header.getColumnModel().getColumns();
    while (enumeration.hasMoreElements()) {
      TableColumn aColumn = (TableColumn)enumeration.nextElement();
      width = width + aColumn.getPreferredWidth();
    }
    return createHeaderSize(width);
  }
}