title: Displaying a row selector (cursor / pointer) in a JTable
body: <p>I'm migrating an MS-Access app to Java.
  My concern is about data grids. My first one is ok (JTable in a JScrollPane), but lacks the row selector (cursor) that you can see in MS-Access grids or OpenOffice Base grids. By "row selector", I mean the little black arrow at the left hand side of the row.
  Is there a standard way to achieve this visual effect with JTable.
  (I'd also like to know the rationale behind this missing feature : what problem (if any) was Sun trying to avoid by not implementing it ?).</p>
  
  <p>Thanks.  </p>
  
  <p>-- EDIT :  Camickr, I've used your snippet with the modifications you showed in your edit. It worked great ! Thank you !!!</p>
  
  <pre><code>import java.awt.*;
  import java.awt.event.*;
  import java.beans.*;
  import javax.swing.*;
  import javax.swing.event.*;
  import javax.swing.table.*;
  
  /*
   *    Use a JTable as a renderer for row numbers of a given main table.
   *  This table must be added to the row header of the scrollpane that
   *  contains the main table.
   */
  public class RowNumberTable extends JTable
      implements ChangeListener, PropertyChangeListener, TableModelListener
  {
      private JTable main;
  
      public static void main(String[] args) {
          JTable mainTable = new JTable(new MyTableModel());
          mainTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
          JScrollPane scrollPane = new JScrollPane(mainTable);
          RowNumberTable rowTable = new RowNumberTable(mainTable);
          rowTable.getSelectionModel()
              .addListSelectionListener(rowTable.new RowListener());
          scrollPane.setRowHeaderView(rowTable);
          scrollPane.setCorner(JScrollPane.UPPER_LEFT_CORNER, rowTable.getTableHeader());
  
          // Create a panel to hold all other components
          JPanel topPanel = new JPanel();
          topPanel.setLayout( new BorderLayout() );
          topPanel.add( scrollPane, BorderLayout.CENTER );
  
          // Set the frame characteristics
          JFrame frame = new JFrame();
          frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
          frame.setTitle( "Selector" );
          frame.setBackground( Color.gray );
          frame.getContentPane().add( topPanel );
          frame.pack();
          frame.setLocationRelativeTo(null);
          frame.setVisible(true);
      }
  
      public RowNumberTable(JTable table)
      {
          main = table;
          main.addPropertyChangeListener( this );
          main.getModel().addTableModelListener( this );
  
          setFocusable( false );
          setAutoCreateColumnsFromModel( false );
          setSelectionModel( main.getSelectionModel() );
  
          TableColumn column = new TableColumn();
          column.setHeaderValue(" ");
          addColumn( column );
          column.setCellRenderer(new RowNumberRenderer());
  
          getColumnModel().getColumn(0).setPreferredWidth(22);
          setPreferredScrollableViewportSize(getPreferredSize());
      }
  
      @Override
      public void addNotify()
      {
          super.addNotify();
  
          Component c = getParent();
  
          //  Keep scrolling of the row table in sync with the main table.
  
          if (c instanceof JViewport)
          {
              JViewport viewport = (JViewport)c;
              viewport.addChangeListener( this );
          }
      }
  
      /*
       *  Delegate method to main table
       */
      @Override
      public int getRowCount()
      {
          return main.getRowCount();
      }
  
      @Override
      public int getRowHeight(int row)
      {
          int rowHeight = main.getRowHeight(row);
  
          if (rowHeight != super.getRowHeight(row))
          {
              super.setRowHeight(row, rowHeight);
          }
  
          return rowHeight;
      }
  
      /*
       *  No model is being used for this table so just use the row number
       *  as the value of the cell.
       */
      @Override
      public Object getValueAt(int row, int column)
      {
          //return Integer.toString(row + 1);
          if (main.isRowSelected(row))
              return "\u25BA"; // Unicode Black Right-pointing Pointer
          else
              return " ";
      }
  
      /*
       *  Don't edit data in the main TableModel by mistake
       */
      @Override
      public boolean isCellEditable(int row, int column)
      {
          return false;
      }
  
      /*
       *  Do nothing since the table ignores the model
       */
      @Override
      public void setValueAt(Object value, int row, int column) {}
  
      //
      //  Implement the ChangeListener
      //
      public void stateChanged(ChangeEvent e)
      {
          //  Keep the scrolling of the row table in sync with main table
  
          JViewport viewport = (JViewport) e.getSource();
          JScrollPane scrollPane = (JScrollPane)viewport.getParent();
          scrollPane.getVerticalScrollBar().setValue(viewport.getViewPosition().y);
      }
  
      //
      //  Implement the PropertyChangeListener
      //
      public void propertyChange(PropertyChangeEvent e)
      {
          //  Keep the row table in sync with the main table
  
          if ("selectionModel".equals(e.getPropertyName()))
          {
              setSelectionModel( main.getSelectionModel() );
          }
  
          if ("rowHeight".equals(e.getPropertyName()))
          {
              repaint();
          }
  
          if ("model".equals(e.getPropertyName()))
          {
              main.getModel().addTableModelListener( this );
              revalidate();
          }
      }
  
      //
      //  Implement the TableModelListener
      //
      @Override
      public void tableChanged(TableModelEvent e)
      {
          revalidate();
      }
  
      /*
       *  Attempt to mimic the table header renderer
       */
      private static class RowNumberRenderer extends DefaultTableCellRenderer
      {
          public RowNumberRenderer()
          {
              setHorizontalAlignment(JLabel.CENTER);
          }
  
          public Component getTableCellRendererComponent(
              JTable table, Object value, boolean isSelected, boolean hasFocus,
              int row, int column)
          {
              if (table != null)
              {
                  JTableHeader header = table.getTableHeader();
  
                  if (header != null)
                  {
                      setForeground(header.getForeground());
                      setBackground(header.getBackground());
                      setFont(header.getFont());
                  }
              }
  
              setText((value == null) ? "" : value.toString());
              setBorder(UIManager.getBorder("TableHeader.cellBorder"));
  
              return this;
          }
      }
  
      private class RowListener implements ListSelectionListener {
          public void valueChanged(ListSelectionEvent event) {
              if (event.getValueIsAdjusting()) {
                  return;
              }
              int row =  main.getSelectedRow();
              System.out.println("selected row : "  +
                  main.getSelectionModel().getLeadSelectionIndex() + " - " +
                  main.getModel().getValueAt(row, 0) ); 
          }
      }
  }
  
  class MyTableModel extends AbstractTableModel {
      private String[] columnNames = {"First Name",
          "Last Name",
          "Sport",
          "# of Years",
          "Vegetarian"};
      private Object[][] data = {
          {"Kathy", "Smith",
              "Snowboarding", new Integer(5), new Boolean(false)},
          {"John", "Doe",
              "Rowing", new Integer(3), new Boolean(true)},
          {"Sue", "Black",
              "Knitting", new Integer(2), new Boolean(false)},
          {"Jane", "White",
              "Speed reading", new Integer(20), new Boolean(true)},
          {"Joe", "Brown",
              "Pool", new Integer(10), new Boolean(false)}
          };
  
      public int getColumnCount() {
          return columnNames.length;
      }
  
      public int getRowCount() {
          return data.length;
      }
  
      public String getColumnName(int col) {
          return columnNames[col];
      }
  
      public Object getValueAt(int row, int col) {
          return data[row][col];
      }
  
      public Class getColumnClass(int c) {
          return getValueAt(0, c).getClass();
      }
  }
  </code></pre>