editable JTable that only accepts digits in one column

I am trying to create an editable JTable that acts like a spreadsheet. The 2nd column, 揃ib #?is only supposed to accept digits. If you double click on a cell in column 2 it activates the correct DefaultCellEditor which was set to a custom JTextField. The custom JTextField is called JDigitTextField.java and only accepts digits. However if you hit enter or a arrow key and start editing column 2 you can type anything in the cell that you want. Is there anyway that I can force the JTable to always use the correct DefaultCellEditor all the time?

I know that this is alot of code, but I included it so that you could see how the JTable works. Most of the work is done in the class ResultSheetTablePanel.java. The method setUpBibNumbersColumn creates the custom DefaultCellEditor and then uses it to create a new TableColumn which the method returns.

Here is a short explanation of the classes:

ResultSheetFrame.java is a JFrame that holds the ResultSheetTablePanel. It also calls the method showFinishSheet in ResultSheetTablePanel that displays the rider list. Call this class to start the program.

ResultSheetTableModel.java is the custom table model used in the JTable. It extends AbstractTableModel.

JDigitTextField.javais the custom JTextField that only accepts digits. I extendedJTextField and implemented a KeyListener.

StaticCellEditor.java is a custom cell editor that does not allow editing. I extended AbstractCellEditor and implemented TableCellEditor to restrict editable to false.

SheetEntry.java is the data that is used by the JTable and the table model.

To compile the program cut and past the classes to their respective files. Compile the program with java 1.5 or 1.6.

To run the program type: 搄ava ResultSheetFrame?

Type any digit between 1 and 10 in any of the cells in column 2 and the riders information should show up. if you type a digit outside that range it should give you a message dialog.

Any help is much appreciated.

Thanks

Wayne Unruh

-

import java.awt.BorderLayout;

import java.awt.Container;

import java.awt.Dimension;

import java.util.ArrayList;

import javax.swing.JFrame;

publicclass ResultSheetFrameextends JFrame

{

privatestaticfinallong serialVersionUID = 100;

public ResultSheetFrame ()

{

super ("Editing Help Inside a JTable" );

Container container = getContentPane ();

container.setLayout (new BorderLayout () );

ResultSheetTablePanel rstp =new ResultSheetTablePanel ();

container.add ( rstp, BorderLayout.CENTER );

this.setDefaultCloseOperation ( JFrame.EXIT_ON_CLOSE );

this.setSize (new Dimension ( 500, 300 ) );

this.setVisible (true );

ArrayList < SheetEntry > startEntries =new ArrayList < SheetEntry > ();

startEntries.add (new SheetEntry ( 1,"Joe Smith" ) );

startEntries.add (new SheetEntry ( 2,"Amy Smith" ) );

startEntries.add (new SheetEntry ( 3,"John Smith" ) );

startEntries.add (new SheetEntry ( 4,"Bill Jones" ) );

startEntries.add (new SheetEntry ( 5,"Frank Jones" ) );

startEntries.add (new SheetEntry ( 6,"Chris Jones" ) );

startEntries.add (new SheetEntry ( 7,"Shawn Jones" ) );

startEntries.add (new SheetEntry ( 8,"Wayne Wilson" ) );

startEntries.add (new SheetEntry ( 9,"William Wilson" ) );

startEntries.add (new SheetEntry ( 10,"Jerry Wilson" ) );

rstp.showFinishSheet ( startEntries );

}

publicstaticvoid main ( String [] args )

{new ResultSheetFrame ();}

}// end of ResultSheetFrame

import java.awt.BorderLayout;

import java.awt.GridBagLayout;

import java.util.*;

import javax.swing.*;

import javax.swing.event.*;

import javax.swing.table.*;

publicclass ResultSheetTablePanelextends JPanel

implements ListSelectionListener,

CellEditorListener

{

privatestaticfinallong serialVersionUID = 100;

private DefaultCellEditor bibNumberCellEditor;

private DefaultCellEditor comboBoxEditor;

privateint currentRow;

private SortedMap < Integer, SheetEntry > finishSheetEntries;

private SheetEntry previousEntry;

private JTable rsTable;

private ResultSheetTableModel rstModel;

public ResultSheetTablePanel ()

{

super (new BorderLayout () );

// create a new TreeMap in method showFinishSheet

this.finishSheetEntries =null;

ListSelectionModel lsm =null;

DefaultTableColumnModel dtcm =null;

this.rstModel =new ResultSheetTableModel ();

dtcm =new DefaultTableColumnModel ();

dtcm.addColumn ( getStaticTableColumn ( 0, SwingConstants.CENTER,

"Place" ) );

dtcm.addColumn ( setUpBibNumbersColumn ( 1, SwingConstants.CENTER,

"Bib #") );

dtcm.addColumn ( getStaticTableColumn ( 2, SwingConstants.LEFT,

"Name" ) );

dtcm.getSelectionModel ().addListSelectionListener (this );

this.rsTable =new JTable ( this.rstModel, dtcm );

this.rsTable.setRowSelectionAllowed (true );

this.rsTable.setColumnSelectionAllowed (true );

//this.resultSheetTable.setCellSelectionEnabled ( false );

// set the list selection model and add

// this to its list of ListSelectionListener's

lsm = this.rsTable.getSelectionModel ();

lsm.setSelectionMode ( ListSelectionModel.SINGLE_SELECTION );

lsm.addListSelectionListener (this );

this.add (new JScrollPane ( this.rsTable ), BorderLayout.CENTER );

}

publicvoid editingCanceled ( ChangeEvent ce )

{}

publicvoid editingStopped ( ChangeEvent ce )

{

Object source = ce.getSource ();

if ( source == this.bibNumberCellEditor )

doBibNumberEditingStopped ();

}

publicvoid showFinishSheet ( List < SheetEntry > startSheetEntries )

{

List < SheetEntry > entries =new ArrayList < SheetEntry > ();

int place = 1,

currPlace = 0,

remainder = 0;

// clear the current Result Sheet

this.rstModel.clearAll ();

this.finishSheetEntries =new TreeMap < Integer, SheetEntry > ();

for ( SheetEntry se : startSheetEntries )

{

this.finishSheetEntries.put (new Integer ( se.getBibNumber () ),

se );

if ( currPlace != 0 )

{

currPlace = se.getPlacing ();

remainder = currPlace - place;

if ( remainder > 0 )

{

while ( place < currPlace )

{

entries.add (new SheetEntry () );

place++;

}

}

entries.add ( se );

place++;

}

}

if ( entries.size () < startSheetEntries.size () )

{

remainder = startSheetEntries.size () - entries.size ();

for (int i = 0; i < remainder; i++ )

{

entries.add (new SheetEntry () );

}

}

this.rstModel.addSheetEntryList ( entries );

}

publicvoid valueChanged ( ListSelectionEvent lse )

{

ListSelectionModel lsm =null;

int selectedRow = 0,

selectedColumn = 0;

if ( ! lse.getValueIsAdjusting () )

{

lsm = this.rsTable.getColumnModel ().getSelectionModel ();

lsm.getAnchorSelectionIndex ();

lsm.getLeadSelectionIndex ();

selectedRow = this.rsTable.getSelectedRow ();

selectedColumn = this.rsTable.getSelectedColumn ();

if ( selectedRow >= 0 &&

this.rsTable.isCellEditable ( selectedRow, selectedColumn ) )

{

this.currentRow = selectedRow;

this.previousEntry = this.rstModel.getSheetEntryAt ( this.currentRow,

0 );

}

}

}

privatevoid doBibNumberEditingStopped ()

{

StringBuilder sbnrf = null,

sbdbn =null;

String bibText = null,

placeText =null;

int placeNumber = 0;

SheetEntry currEntry = null,

prevEntry =null;

Integer currBibNumber = null,

prevBibNumber =null;

bibText = this.bibNumberCellEditor.getCellEditorValue ().toString ().trim ();

prevEntry = this.previousEntry;

prevBibNumber =new Integer ( prevEntry.getBibNumber () );

if ( ! bibText.equals ("" ) )

{

currBibNumber = Integer.valueOf ( bibText );

currEntry = this.finishSheetEntries.get ( currBibNumber );

if ( currEntry !=null )

{

if ( currEntry.getPlacing () == 0 )

{

if ( prevBibNumber.intValue () != currBibNumber.intValue () )

{

placeText = ( String ) this.rstModel.getValueAt ( this.currentRow,

0 );

try

{

placeNumber = Integer.valueOf ( placeText ).intValue ();

}

catch ( NumberFormatException nfe )

{

placeNumber = 0;

}

if ( prevBibNumber.intValue () > 0 )

{

// reset previous entry placing to 0

prevEntry = this.finishSheetEntries.get ( prevBibNumber );

prevEntry.setPlacing ( 0 );

}

// set placing the place or 0

currEntry.setPlacing ( placeNumber );

this.rstModel.addSheetEntry ( this.currentRow, currEntry );

}

}

else

{

if ( prevBibNumber.intValue () != currBibNumber.intValue () )

{

sbdbn =new StringBuilder ();

sbdbn.append ("Bib number " );

sbdbn.append ( bibText );

sbdbn.append (" is already in results at placing " );

sbdbn.append ( currEntry.getPlacing () ).append ("." );

JOptionPane.showMessageDialog ( this, sbdbn.toString (),

"Dublicate Bib Number",

JOptionPane.WARNING_MESSAGE );

}

}

}

else

{

if ( prevBibNumber.intValue () > 0 )

{

// Reset the previous entry to a placing to 0

prevEntry = this.finishSheetEntries.get ( prevBibNumber );

prevEntry.setPlacing ( 0 );

}

this.rstModel.addSheetEntry ( this.currentRow,new SheetEntry () );

sbnrf =new StringBuilder ();

sbnrf.append ("No rider found with bib number " );

sbnrf.append ( bibText ).append ("." );

JOptionPane.showMessageDialog ( this, sbnrf.toString (),

"No Rider Found",

JOptionPane.WARNING_MESSAGE );

}

}

else

{

// a blank was entered

if ( prevBibNumber.intValue () > 0 )

{

// Reset the previous entry to a placing to 0

prevEntry = this.finishSheetEntries.get ( prevBibNumber );

prevEntry.setPlacing ( 0 );

// add an empty entry into the table model

this.rstModel.addSheetEntry ( this.currentRow,new SheetEntry () );

}

}

}

/**

* Returns a <CODE>TableColumn</CODE> that represents all the attributes of

* a column in a JTable that is not editable.

* @param modelIndex

*The index of the column in the model which will supply the data for

*this column in the table.

* @param alignment

*One of the following constants defined in <CODE>SwingConstants</CODE>:

*<CODE>LEFT</CODE>, <CODE>CENTER</CODE>, <CODE>RIGHT</CODE>.

* @return A <CODE>TableColumn</CODE> that represents all the attributes of

* a column in a JTable that is not editable.

*/

private TableColumn getStaticTableColumn (int modelIndex,int alignment,

String headerText )

{

TableColumn tc =null;

DefaultTableCellRenderer dtcr =new DefaultTableCellRenderer ();

dtcr.setHorizontalAlignment ( alignment );

tc =new TableColumn ( modelIndex, 75, dtcr,new StaticCellEditor () );

tc.setHeaderValue ( headerText );

return tc;

}

private TableColumn setUpBibNumbersColumn (int modelIndex,int alignment,

String headerText )

{

TableColumn tc =null;

JDigitTextField bibNumberTextField =null;

DefaultTableCellRenderer renderer =null;

bibNumberTextField =new JDigitTextField ( 3 );

bibNumberTextField.setHorizontalAlignment ( alignment );

this.bibNumberCellEditor =new DefaultCellEditor ( bibNumberTextField );

this.bibNumberCellEditor.addCellEditorListener (this );

this.bibNumberCellEditor.setClickCountToStart ( 2 );

// Set up tool tips for the status cells.

renderer =new DefaultTableCellRenderer();

renderer.setToolTipText("Enter bib number");

renderer.setHorizontalAlignment ( alignment );

tc =new TableColumn ( modelIndex, 75, renderer,

this.bibNumberCellEditor );

tc.setHeaderValue ( headerText );

return tc;

}

}// end of ResultSheetTablePanel

--

import java.util.ArrayList;

import java.util.List;

import javax.swing.table.AbstractTableModel;

publicclass ResultSheetTableModelextends AbstractTableModel

{

privatestaticfinallong serialVersionUID = 100;

private List < SheetEntry > entryList;

public ResultSheetTableModel ()

{ this.entryList =new ArrayList < SheetEntry > ();}

publicvoid addSheetEntry (int rowIndex, SheetEntry entry )

{

this.entryList.set ( rowIndex, entry );

super.fireTableRowsUpdated ( rowIndex, rowIndex );

}

publicvoid addSheetEntryList ( List < SheetEntry > currEntryList )

{

SheetEntry currentEntry =null;

int size = currEntryList.size ();

for (int i = 0; i < size; i++ )

{

currentEntry = currEntryList.get ( i );

this.entryList.add ( i, currentEntry );

}

super.fireTableDataChanged ();

}

publicvoid clearAll ()

{

this.entryList.clear ();

super.fireTableDataChanged ();

}

publicint getColumnCount ()

{return 3;}

publicint getRowCount ()

{return this.entryList.size ();}

public SheetEntry getSheetEntryAt (int rowIndex,int columnIndex )

{

SheetEntry se =null;

if ( rowIndex < this.entryList.size () )

se = this.entryList.get ( rowIndex );

return se;

}

public Object getValueAt (int rowIndex,int columnIndex )

{

Object value =null;

int place = 0,

bibNumber = 0;

SheetEntry currentEntry = this.entryList.get ( rowIndex );

if ( currentEntry.getBibNumber () >= 0 )

{

switch ( columnIndex )

{

case 0:

{

place = currentEntry.getPlacing ();

value = Integer.valueOf ( place ).toString ();

break;

}

case 1:

{

bibNumber = currentEntry.getBibNumber ();

value = Integer.valueOf ( bibNumber ).toString ();

break;

}

case 2:

{

value = currentEntry.getFullName ();

break;

}

}

}

else

{

switch ( columnIndex )

{

case 0:

{

value = Integer.valueOf ( rowIndex + 1 ).toString ();

break;

}

default:

{

value ="";

break;

}

}

}

return value;

}

publicboolean isCellEditable (int row,int col )

{

boolean editable =false;

if ( col == 1 )

editable =true;

return editable;

}

}// end of ResultSheetTableModel

import java.awt.event.KeyEvent;

import java.awt.event.KeyListener;

import javax.swing.JTextField;

publicclass JDigitTextFieldextends JTextFieldimplements KeyListener

{

privateint columns;

privatestaticfinallong serialVersionUID = 100;

public JDigitTextField (int columns )

{

super ( columns );

this.columns = columns;

super.addKeyListener (this );

}

publicvoid keyPressed ( KeyEvent e ){}

publicvoid keyReleased ( KeyEvent e ){}

publicvoid keyTyped ( KeyEvent event )

{

Object source = event.getSource ();

if ( source ==this )

{

char c = event.getKeyChar ();

if ( Character.isDigit ( c ) )

{

if ( super.getText ().length () < this.columns )

event.setKeyChar ( c );

else

event.consume ();

}

else

event.consume ();

}

}

}// end of JDigitTextField

import java.awt.Component;

import java.util.EventObject;

import javax.swing.AbstractCellEditor;

import javax.swing.JTable;

import javax.swing.table.TableCellEditor;

publicclass StaticCellEditorextends AbstractCellEditor

implements TableCellEditor

{

privatestaticfinallong serialVersionUID = 100;

public StaticCellEditor ()

{super ();}

publicboolean isCellEditable ( EventObject e )

{returnfalse;}

publicboolean shouldSelectCell ( EventObject anEvent )

{returnfalse;}

public Component getTableCellEditorComponent ( JTable table, Object value,

boolean isSelected,

int row,int column )

{returnnull;}

public Object getCellEditorValue ()

{returnnull;}

}// end of StaticCellEditor

publicclass SheetEntry

{

privateint bibNumber;

privateint placing;

private String fullName;

public SheetEntry ()

{this ( -1,"", 0 );}

public SheetEntry (int bibNumber, String fullName )

{this ( bibNumber, fullName, 0 );}

public SheetEntry (int bibNumber, String fullName,int placing )

{

this.bibNumber = bibNumber;

this.fullName = fullName;

this.placing = placing;

}

publicint getBibNumber ()

{return this.bibNumber;}

publicint getPlacing ()

{return this.placing;}

public String getFullName ()

{return this.fullName;}

publicvoid setPlacing (int placing )

{ this.placing = placing;}

}// end of SheetEntry

[32761 byte] By [WaynePeteUnruha] at [2007-11-27 9:56:31]
# 1
I feel that you are reinventing the wheel with JDigitTextField.Have a look at JFormattedTextField.
dwga at 2007-7-13 0:26:41 > top of Java-index,Desktop,Core GUI APIs...
# 2

I did try JFormattedTextField with a MaskFormatter, below is the code that I used, but, I did not behave the way I wanted. In the application, the column is supposed to accept numbers from 0 to 999. I did not want to force the users to enter 001 when they just wanted to type 1 and press enter. JFormattedTextField also did not allow me to enter a blank to clear the field entirely.

MaskFormatter mf = null;

try

{ mf = new MaskFormatter ( "###" ); }

catch ( ParseException pe ) { }

JFormattedTextField bibNumberField = new JFormattedTextField ( numberFormat );

WaynePeteUnruha at 2007-7-13 0:26:41 > top of Java-index,Desktop,Core GUI APIs...
# 3

> I know that this is alot of code, but I included it so that you could see how the JTable works.

In the future we don't want to see your entire application. we only care about the code directly related to the problem. So you create a table and attach your editor to it. We don't want to see your custom TableModel. The idea is to use the default classes so we can isolate the problem. Its called a SSCCE:

see http://homepage1.nifty.com/algafield/sscce.html,

> The custom JTextField is called JDigitTextField.java and only accepts digits.

Because you used a KeyListener, you made the assumption the text field always has focus. This is not the best approach as you have found out. If you read the Swing tutorial on "General Text Component Features" you will find that using a DocumentFilter is a better approach:

http://java.sun.com/docs/books/tutorial/uiswing/components/generaltext.html#filter

Or you can simply use a JFormattedTextField. The above tutorial also shows how this can be done.

> StaticCellEditor.java is a custom cell editor that does not allow editing.

Not sure what this is for. The isCellEditable(...) method is used to control which cells are editable.

camickra at 2007-7-13 0:26:41 > top of Java-index,Desktop,Core GUI APIs...