Reputation: 324
I had a request for JTable functionality that would allow data entry more like in Excel. If the user types in a cell, the previous value is replaced by the new value and one can navigate between rows and columns using arrow keys.
I found several solutions to this problem and implemented one.
I was then given an additional requirement- if the user clicks outside the table, the cell that data was being entered into is updated (editing stops).
I suspect I am just missing something, but my attempts to implement both requirements have failed.
I have a custom cell editor that listens for focusLost ans stops cell editing. This works if I double click in the table and then click outside the table. But if I just typed a number in a cell, this method doesn't run.
tableMe.setSurrendersFocusOnKeystroke(true); This a) results in my losing the first keystroke and b) puts me in the editor, so I can;t used the right and left arrow keys to move out the cell.
I added a FocusListener on the table. Then, I can just type a number in a cell and navigate to another cell, but I can never get into the editor. A double-click immediately stops the edit. I thought I could find some difference between when I was 'really' editing (have a cursor in the editor window) and when I have just typed a number in the cell. What I have tried so far (isEditing() always comes up true, hasFocus() always comes up false) does not work.
Can anyone point me a solution for this? My attempts are below. I commented out the false starts, but they are there so you can see what I tried.
import java.io.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.table.*;
import java.text.NumberFormat;
import javax.swing.text.JTextComponent;
import javax.swing.border.*;
import javax.swing.BorderFactory;
import javax.swing.border.Border;
class ExcelTableTest extends JFrame
{
public static final Color activeDark = new Color(230, 185, 184);
public static final Color activeLight = new Color(244, 233, 233);
private MyJTable tableMe;
private MyTableModel modelMe;
private JTextField textMe;
private JScrollPane scrollTable;
private String[] tableHeaders = { "Column 1", "Column 2" };
private Object[][] tableData = new Object[ tableHeaders.length ][ 4 ];
ExcelTableTest()
{
setLayout( new BorderLayout() );
modelMe = new MyTableModel(tableData, tableHeaders );
tableMe = new MyJTable(modelMe);
tableMe.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
TableColumn column = null;
for (int i = 0; i < tableHeaders.length; i++)
{
column = tableMe.getColumnModel().getColumn(i);
column.setCellRenderer( new MyCellRenderer() );
column.setCellEditor( new NumberCellEditor() );
}
tableMe.setRowSelectionAllowed(false);
// tableMe.addFocusListener( new MyFocusListener() );
// tableMe.setSurrendersFocusOnKeystroke(true);
// modelMe.addTableModelListener(new MyTableModelListener());
scrollTable = new JScrollPane(tableMe);
scrollTable.setVerticalScrollBarPolicy(
ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
scrollTable.setHorizontalScrollBarPolicy(
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
scrollTable.setPreferredSize(new Dimension(500,150));
textMe = new JTextField( 6 );
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(500,210);
add(BorderLayout.CENTER, scrollTable );
add(BorderLayout.SOUTH, textMe );
setVisible(true);
}
class MyJTable extends JTable
{
MyJTable( TableModel model)
{
super(model);
}
//
// Trying to get cells selected when editing starts
@Override
public Component prepareEditor(TableCellEditor editor,
int row, int column)
{
Component editComponent = super.prepareEditor(editor, row, column);
if (editComponent instanceof JTextComponent)
{
System.out.println( "In prepare editor" );
((JTextComponent) editComponent).selectAll();
// System.out.println( "Requesting Focus" );
// System.out.println(
// ((JTextComponent) editComponent).isFocusable());
// System.out.println(
// ((JTextComponent) editComponent).requestFocusInWindow());
}
return editComponent;
}
//
// This also works, but end result is the same- when using this, the editor
// does not have focus, so I cannot update the cell just by switching focus
// to another part of the GUI.
// @Override
// public boolean editCellAt( int row, int col, EventObject e )
// {
// boolean result = super.editCellAt( row, col, e );
//
// Component editComponent = getEditorComponent();
// if ( editComponent == null ||
// !(editComponent instanceof JTextComponent) )
// return result;
// if (e instanceof KeyEvent)
// ((JTextComponent) editComponent).selectAll();
//
// return result;
// }
//
}
class MyFocusListener implements FocusListener
{
@Override
public void focusGained(FocusEvent e)
{
System.out.println("Table has focus");
}
@Override
public void focusLost(FocusEvent e)
{
System.out.println("Table lost focus");
MyJTable table = (MyJTable) e.getSource();
int[] columns = table.getSelectedColumns();
//
// I will not be allowing multiple column selection
//
// This is still a problem, as the table IS editing, even though the editor
// does not seem to have focus. This is true whether I am in the case where
// I really have the editor going (in which case the lostFocus of the editor
// nicely cleans things up) or the editor is not really going, and then the
// lostFocus of the editor never triggers.
// if ( columns.length > 0 && ! table.isEditing() )
if ( columns.length > 0 )
{
TableColumn tableColumn = table.getColumnModel()
.getColumn(columns[0]);
DefaultCellEditor cellEditor = (DefaultCellEditor)
tableColumn.getCellEditor();
// Does not have focus even when I double click to edit the cell
if (cellEditor != null && ! cellEditor.getComponent().hasFocus() );
{
System.out.println( "Editor focus: " +
cellEditor.getComponent().hasFocus() );
if (!cellEditor.stopCellEditing())
{
cellEditor.cancelCellEditing();
}
}
}
}
}
class MyTableModel extends DefaultTableModel
{
public MyTableModel(Object rowData[][], Object columnNames[]) {
super(rowData, columnNames);
}
@Override
public Class getColumnClass(int col) {
return Double.class;
}
@Override
public boolean isCellEditable(int row, int col)
{
return true;
}
}
class MyCellRenderer extends DefaultTableCellRenderer
{
public Component getTableCellRendererComponent(
JTable table, Object value, boolean selected, boolean focus,
int row, int col) {
Component renderComponent = super.getTableCellRendererComponent(
table, value, selected, focus, row, col);
//
// This is called constantly
// System.out.println( "In renderer" );
if ((row % 2) == 0) {
renderComponent.setBackground(activeDark);
} else {
renderComponent.setBackground(activeLight);
}
NumberFormat nf = NumberFormat.getInstance();
nf.setMinimumFractionDigits(0);
if (value != null)
System.out.println( "Value: " + value + "; " + value.getClass() );
super.setText((value == null) ? "" : nf.format(value));
super.setHorizontalAlignment( SwingConstants.RIGHT );
return renderComponent;
}
}
class NumberCellEditor extends DefaultCellEditor
{
private Double minimum = 0.0;
private Double value = null;
private final JTextField textField;
private final NumberFormat nf = NumberFormat.getInstance();
public NumberCellEditor()
{
super(new JTextField());
textField = (JTextField) getComponent();
nf.setMinimumFractionDigits(0);
textField.setHorizontalAlignment(JTextField.RIGHT);
textField.addFocusListener(new FocusListener()
{
@Override
public void focusGained(FocusEvent e)
{
System.out.println( "In editor" );
if (value != null)
textField.setText(nf.format(value.doubleValue()));
else
textField.setText("");
textField.setCaretPosition(0);
}
@Override
public void focusLost(FocusEvent e)
{
System.out.println( "Lost focus editing cell" );
if (!stopCellEditing()) cancelCellEditing();
}
});
}
public boolean stopCellEditing()
{
String s = (String)super.getCellEditorValue();
System.out.println( "Stop Cell editing: " + value );
if ("".equals(s))
{
value = null;
super.stopCellEditing();
}
try
{
value = nf.parse(s).doubleValue();
}
catch (Exception e)
{
textField.setBorder(new LineBorder(Color.red));
return false;
}
return super.stopCellEditing();
}
public Component getTableCellEditorComponent(JTable table, Object value,
boolean isSelected,
int row, int col)
{
this.value = (Double) value;
textField.setBorder(new LineBorder(Color.black));
return super.getTableCellEditorComponent(
table, value, isSelected, row, col);
}
public Object getCellEditorValue()
{
value = (Double) value;
return value;
}
}
public static void main( String[] args )
{
ExcelTableTest excelTest = new ExcelTableTest();
}
}
Here is the simple solution described below. It only requires one line-
tableMe.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
If I start typing in a cell, then mouse click in the cell, I go into the editor and lose what was being typed. If I click anywhere else, what was being typed is saved and the editor is stopped.
What I had tried was to define a flag-
private boolean reallyInEditor = true;
then override editCellAt rather than prepare editor
@Override
public boolean editCellAt( int row, int col, EventObject e )
{
boolean result = super.editCellAt( row, col, e );
Component editComponent = getEditorComponent();
if ( editComponent == null ||
!(editComponent instanceof JTextComponent) )
return result;
if (e instanceof KeyEvent)
{
((JTextComponent) editComponent).selectAll();
reallyInEditor = false;
}
return result;
}
}
to set my flag to indicate the user entered the editor by typing rather than a double-click
and put a FocusListener on the table
tableMe.addFocusListener( new MyFocusListener() );
class MyFocusListener implements FocusListener
{
@Override
public void focusGained(FocusEvent e)
{
System.out.println("Table has focus");
}
@Override
public void focusLost(FocusEvent e)
{
System.out.println("Table lost focus");
MyJTable table = (MyJTable) e.getSource();
int[] columns = table.getSelectedColumns();
//
// I will not be allowing multiple column selection
if ( columns.length > 0 )
{
TableColumn tableColumn = table.getColumnModel()
.getColumn(columns[0]);
DefaultCellEditor cellEditor = (DefaultCellEditor)
tableColumn.getCellEditor();
if (cellEditor != null && !reallyInEditor)
{
System.out.println( "Editor focus: " +
cellEditor.getComponent().hasFocus() );
if (!cellEditor.stopCellEditing())
{
cellEditor.cancelCellEditing();
}
reallyInEditor = true;
}
}
}
}
the difference from all this extra work, is that if I start typing in a cell, then click in the cell, the cell is updated with the value I was entering and the editor is stopped.
Upvotes: 2
Views: 1324
Reputation: 13066
Use the following line of code to achieve what you are looking for:
tableMe.putClientProperty("terminateEditOnFocusLost",Boolean.TRUE);
Upvotes: 4