Excel のようなデータ入力を可能にする JTable 機能のリクエストがありました。ユーザーがセルに入力すると、以前の値が新しい値に置き換えられ、矢印キーを使用して行と列の間を移動できます。
この問題に対するいくつかの解決策を見つけ、実装しました。
次に、追加の要件が与えられました。ユーザーがテーブルの外側をクリックすると、データが入力されていたセルが更新されます (編集が停止します)。
何かが欠けているだけだと思いますが、両方の要件を実装しようとして失敗しました。
focusLost をリッスンし、セルの編集を停止するカスタム セル エディターがあります。これは、テーブルをダブルクリックしてからテーブルの外側をクリックすると機能します。しかし、セルに数値を入力しただけでは、このメソッドは実行されません。
tableMe.setSurrendersFocusOnKeystroke(真); これにより、a) 最初のキーストロークが失われ、b) エディターに移動するため、右矢印キーと左矢印キーを使用してセルの外に移動することはできません。
テーブルに FocusListener を追加しました。次に、セルに数値を入力して別のセルに移動するだけですが、エディターにアクセスすることはできません。ダブルクリックすると、編集がすぐに停止します。「実際に」編集していたとき (エディター ウィンドウにカーソルを置いているとき) と、セルに数値を入力したときとの違いを見つけることができると思いました。私がこれまでに試したこと (isEditing() は常に true になり、hasFocus() は常に false になります) は機能しません。
誰でもこれに対する解決策を教えてもらえますか? 私の試みは以下のとおりです。誤った開始をコメントアウトしましたが、それらはそこにあるので、私が試したことを確認できます。
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();
}
}
以下に説明する簡単な解決策を次に示します。必要なのは1行だけです-
tableMe.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
セルへの入力を開始してからセル内でマウスをクリックすると、エディターが表示され、入力していた内容が失われます。他の場所をクリックすると、入力していた内容が保存され、エディターが停止します。
私が試したのは、フラグを定義することでした-
private boolean reallyInEditor = true;
次に、エディターを準備するのではなく、editCellAt をオーバーライドします
@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;
}
}
フラグを設定して、ユーザーがダブルクリックではなく入力によってエディターに入ったことを示す
テーブルに FocusListener を配置します
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;
}
}
}
}
この余分な作業との違いは、セルに入力を開始してセルをクリックすると、入力していた値でセルが更新され、エディターが停止することです。