Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-4765300

Use of JSpinners w/JTables & losing focus doesn't commit, but is supposed to

XMLWordPrintable

    • 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)
      ======================================================================

            svioletsunw Scott Violet (Inactive)
            svioletsunw Scott Violet (Inactive)
            Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

              Created:
              Updated:
              Resolved:
              Imported:
              Indexed: