-
Bug
-
Resolution: Fixed
-
P3
-
1.4.0
-
tiger
-
x86
-
windows_nt
Name: sv35042 Date: 10/18/2002
FULL PRODUCT VERSION :
java version "1.4.0_01"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.0_01-b03)
Java HotSpot(TM) Client VM (build 1.4.0_01-b03, mixed mode)
FULL OPERATING SYSTEM VERSION :
Windows NT Version 4.0
(Service Pack 6)
A DESCRIPTION OF THE PROBLEM :
When using a JSpinner (tested using the SpinnerNumberModel)
as a Cell Editor and Cell Renderer, a loss of focus does
not record the changed value (if the value was typed in).
This occurs even if the underlying JFormattedTextField had
setFocusLostBehavior set to JFormattedTextField.COMMIT.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Create a class that extends AbstractCellEditor and
implements TableCellEditor.
a. Create a JSpinner instance variable, and initialize
it in the constructor using the SpinnerNumberModel
b. Fill in the getTableCellEditorComponent to set the
JSpinner's value, and return the JSpinner
c. Override getCellEditorValue to return the JSpinner's
value
2. Create a class extending DefaultTableModel which
overrides getColumnClass to return "getValueAt(0,
<specified parameter>).getClass()"
3. Create a class that extends JSpinner (this will be the
Renderer) and implement the getTableCellRendererComponent
to return itself after setting the passed in value.
4. Create an instance of your custom table model, and add a
Column with any name for the header, and an array of
Integers as the data.
5. Create a JTable which uses your custom Table Model, and
then setDefaultEditor for Integer.class to your custom
Editor, and setDefaultRenderer for Integer.class to your
custom Renderer.
6. Add the JTable to a JScrollPane, and add the JScrollPane
to the CENTER of a BorderLayout in the ContentPane of a
JFrame.
7. Set the size of the JFrame to something reasonable, and
set Visible to true.
8. Compile and run.
9. Click inside the JSpinner, and type in a valid value.
Now, click in another cell. The value is not recorded.
10. Using the arrows of the JSpinner records the value
properly; Pressing an arrow after typing records the value
correctly; Pressing enter after typing records the value
correctly.
EXPECTED VERSUS ACTUAL BEHAVIOR :
I expected the value of the JSpinner to change with the
typed information even if focus was lost.
What really happened, is that the typed information was not
recorded (even if it was valid).
ERROR MESSAGES/STACK TRACES THAT OCCUR :
By adding a PropertyChangeListener to the Custom Cell Editor, I was able to
print out property change events and noticed that the ancestor is changed
before the value is recorded!:
<TRACE>
ancestor changed. Was: Now: javax.swing.JSpinner$NumberEditor[,0,0,0x0,inva
lid,layout=javax.swing.JSpinner$NumberEditor,alignmentX=null,alignmentY=null,bor
der=,flags=9,maximumSize=,minimumSize=,preferredSize=]
ancestor changed. Was: javax.swing.JSpinner$NumberEditor[,2,2,105x35,layout=jav
ax.swing.JSpinner$NumberEditor,alignmentX=null,alignmentY=null,border=,flags=9,m
aximumSize=,minimumSize=,preferredSize=] Now:
value changed. Was: 1 Now: 123
</TRACE>
<EXPLAINED TRACE>
***I clicked on the JSpinner:***
ancestor changed. Was: Now: javax.swing.JSpinner$NumberEditor[,0,0,0x0,inva
lid,layout=javax.swing.JSpinner$NumberEditor,alignmentX=null,alignmentY=null,bor
der=,flags=9,maximumSize=,minimumSize=,preferredSize=]
***I type in the number '123' and _then_ click on an adjacent cell (which was
not a JSpinner). The ancestor is changed before the value: ***
ancestor changed. Was: javax.swing.JSpinner$NumberEditor[,2,2,105x35,layout=jav
ax.swing.JSpinner$NumberEditor,alignmentX=null,alignmentY=null,border=,flags=9,m
aximumSize=,minimumSize=,preferredSize=] Now:
value changed. Was: 1 Now: 123
</EXPLAINED TRACE>
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
---->8----- Cut Here ----->8-----
import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import java.util.*;
import javax.swing.*;
import javax.swing.table.*;
public class QueryCellEditor extends AbstractCellEditor implements
PropertyChangeListener, TableCellEditor
{
Component component;
public QueryCellEditor()
{
JSpinner js = new JSpinner(new SpinnerNumberModel(1, 1, 999, 1));
((JSpinner.NumberEditor) js.getEditor()).getTextField
().addPropertyChangeListener(this);
component = js;
}
public Component getTableCellEditorComponent(JTable table, Object value,
boolean isSelected, int row, int column)
{
if (value instanceof String)
{
try
{
((JSpinner) component).setValue(new Integer((String) value));
}
catch (Exception e)
{
}
}
else if (value instanceof Number)
{
((JSpinner) component).setValue((Number) value);
}
return component;
}
public Object getCellEditorValue()
{
return ((JSpinner)component).getValue();
}
public void propertyChange(PropertyChangeEvent pce)
{
if (pce == null)
{
System.out.println("null PropertyChangeEvent!");
return;
}
String strName = (pce.getPropertyName() == null) ? "***no name***" :
pce.getPropertyName();
String strOld = (pce.getOldValue() == null) ? "" : pce.getOldValue
().toString();
String strNew = (pce.getNewValue() == null) ? "" : pce.getNewValue
().toString();
System.out.println(strName + " changed. Was: " + strOld + " Now: " +
strNew);
}
}
---->8----- Cut Here ----->8-----
import javax.swing.table.*;
public class QueryTableModel extends DefaultTableModel
{
public Class getColumnClass(int col)
{
return getValueAt(0, col).getClass();
}
}
---->8----- Cut Here ----->8-----
import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.table.*;
public class QueryCellRenderer extends JSpinner implements TableCellRenderer
{
private Color unselectedForeground;
private Color unselectedBackground;
protected static Border noFocusBorder = new EmptyBorder(1, 1, 1, 1);
public QueryCellRenderer()
{
this(1);
} // end of default constructor, QueryCellRenderer()
public QueryCellRenderer(String strNumber)
{
this(Integer.parseInt(strNumber));
} // end of constructor, QueryCellRenderer(String)
public QueryCellRenderer(Integer iObjNumber)
{
this(iObjNumber.intValue());
} // end of constructor, QueryCellRenderer(Integer)
public QueryCellRenderer(int iNumber)
{
this(iNumber, 1, 999);
} // end of constructor, QueryCellRenderer(int)
public QueryCellRenderer(String strNumber, int iMin, int iMax)
{
this(Integer.parseInt(strNumber), iMin, iMax, 1);
} // end of constructor, QueryCellRenderer(String, int, int)
public QueryCellRenderer(Integer iObjNumber, int iMin, int iMax)
{
this(iObjNumber.intValue(), iMin, iMax, 1);
} // end of constructor, QueryCellRenderer(Integer, int, int)
public QueryCellRenderer(int iNumber, int iMin, int iMax)
{
this(iNumber, iMin, iMax, 1);
} // end of constructor, QueryCellRenderer(int, int, int)
public QueryCellRenderer(String strNumber, int iMin, int iMax, int iStep)
{
this(Integer.parseInt(strNumber), iMin, iMax, iStep);
} // end of constructor, QueryCellRenderer(String, int, int, int)
public QueryCellRenderer(Integer iObjNumber, int iMin, int iMax, int iStep)
{
this(iObjNumber.intValue(), iMin, iMax, iStep);
} // end of constructor, QueryCellRenderer(Integer, int, int, int)
public QueryCellRenderer(int iNumber, int iMin, int iMax, int iStep)
{
if (iNumber < iMin)
{
iNumber = iMin;
}
else if (iNumber > iMax)
{
iNumber = iMax;
}
SpinnerNumberModel snm = new SpinnerNumberModel(iNumber, iMin, iMax,
iStep);
setModel(snm);
} // end of constructor, QueryCellRenderer(int, int, int, int)
public int getNumber()
{
return ((Integer) getValue()).intValue();
} // end of instance method, getNumber()
public void setForeground(Color c)
{
super.setForeground(c);
unselectedForeground = c;
}
public void setBackground(Color c)
{
super.setBackground(c);
unselectedBackground = c;
}
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column)
{
Component innerTextField = ((JSpinner.NumberEditor) getEditor
()).getTextField();
if (isSelected)
{
super.setForeground(table.getSelectionForeground());
super.setBackground(table.getSelectionBackground());
innerTextField.setBackground(table.getSelectionBackground());
innerTextField.setForeground(table.getSelectionForeground());
}
else
{
super.setForeground((unselectedForeground != null) ?
unselectedForeground : table.getForeground());
super.setBackground((unselectedBackground != null) ?
unselectedBackground : table.getBackground());
innerTextField.setForeground(UIManager.getColor
("Table.focusCellForeground"));
innerTextField.setBackground(UIManager.getColor
("Table.focusCellBackground"));
}
setFont(table.getFont());
if (hasFocus)
{
setBorder(UIManager.getBorder("Table.focusCellHighlightBorder"));
if (table.isCellEditable(row, column))
{
super.setForeground(UIManager.getColor
("Table.focusCellForeground"));
super.setBackground(UIManager.getColor
("Table.focusCellBackground"));
innerTextField.setForeground(UIManager.getColor
("Table.focusCellForeground"));
innerTextField.setBackground(UIManager.getColor
("Table.focusCellBackground"));
}
}
else
{
setBorder(noFocusBorder);
}
setValue(value);
Color back = getBackground();
boolean colorMatch = (back != null) && (back.equals(table.getBackground
())) && table.isOpaque();
setOpaque(!colorMatch);
return this;
}
} // end of class, QueryCellRenderer
---->8----- Cut Here ----->8-----
import java.applet.*;
import java.awt.*;
import java.util.*;
import javax.swing.*;
import javax.swing.table.*;
public class BugTable
{
public BugTable()
{
JFrame jf = new JFrame();
jf.setSize(640, 480);
jf.getContentPane().setLayout(new BorderLayout());
JScrollPane jsp = new JScrollPane(getJTable());
jf.getContentPane().add(jsp, BorderLayout.CENTER);
jf.setVisible(true);
}
public JTable getJTable()
{
QueryTableModel qtm = new QueryTableModel();
String[] column1 = new String[2];
Integer[] column2 = new Integer[2];
column1[0] = "B3221";
column1[1] = "B1234";
column2[0] = new Integer(1);
column2[1] = new Integer(10);
qtm.addColumn("Code", column1);
qtm.addColumn("Spinner", column2);
JTable jt = new JTable(qtm);
jt.setDefaultEditor(Integer.class, new QueryCellEditor());
jt.setDefaultRenderer(Integer.class, new QueryCellRenderer());
jt.setRowHeight(40);
return jt;
}
public static void main(String[] argv)
{
BugTable bt = new BugTable();
}
}
---------- END SOURCE ----------
CUSTOMER WORKAROUND :
Press Enter after typing... Doesn't really address the
issue, but the users will have to remember to do that to
save their changes.
(Review ID: 158818)
======================================================================