JDK-6348946 : JSlider's thumb moves in the wrong direction when used as a JTable cell editor.
  • Type: Bug
  • Component: client-libs
  • Sub-Component: javax.swing
  • Affected Version: 5.0
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • OS: windows_xp
  • CPU: x86
  • Submitted: 2005-11-11
  • Updated: 2011-03-07
  • Resolved: 2011-03-07
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 7
7 b03Fixed
Related Reports
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.5.0_04"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_04-b05)
Java HotSpot(TM) Client VM (build 1.5.0_04-b05, mixed mode)


ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows XP [Version 5.1.2600]

A DESCRIPTION OF THE PROBLEM :
When I use a JSlider as a renderer and editor in a JTable, the first time I
click in a region to the left of the slider's thumb, it moves to the right,
instead of moving to the left towards the mouse pointer.

This happens only for the first click. After that the slider behaves normally.


STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Compile the attached code.  On the table in any of the slider cells, clickin a region to the left of any slider's thumb.  The first time you do this, the thumb moves to the right, instead of to the left.  After this first time, the slider moves in the correct direction towards the mouse click..


REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
/* Copyright 2005 The MathWorks, Inc. */
package Prototypes;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;

public class BlockParameterPanel2 extends JPanel {
  private static final long serialVersionUID = 24362462L;

  public BlockParameterPanel2() {
    JScrollPane scroll = new JScrollPane( new ParameterTable() );
    this.setLayout( new BorderLayout() );
    this.add(scroll, BorderLayout.CENTER);
  }

  private class SliderRenderer extends JSlider implements TableCellRenderer
{
    private static final long serialVersionUID = 24362462L;

    public SliderRenderer() {
      super(0,1000);
    }

    public Component getTableCellRendererComponent(JTable table, Object
value,
                                                   boolean isSelected,
                                                   boolean hasFocus,
                                                   int row, int col) {
      int val = ((Integer)value).intValue();
      setValue(val);

      // Needed to properly paint the slider.
      updateUI();

      return this;
    }
  }


  private class SliderEditor extends AbstractCellEditor implements
TableCellEditor {
    private static final long serialVersionUID = 24362462L;
    private SliderRenderer renderer = new SliderRenderer();

    public Component getTableCellEditorComponent(JTable table, Object value,
                                                 boolean isSelected,
                                                 int row, int col) {
      int val = ((Integer)value).intValue();
      renderer.setValue(val);
      return renderer;
    }

    public SliderEditor() {
      renderer.addChangeListener( new ChangeListener() {
          public void stateChanged(ChangeEvent e) {
            if (!renderer.getValueIsAdjusting()) {
              stopCellEditing();
            }
          }
        });
    }

    public Object getCellEditorValue() {
      return new Integer(renderer.getValue());
    }

  }


  private class ParameterTable extends JTable {
    private static final long serialVersionUID = 24362462L;

    public ParameterTable() {
      super( new Object[][] {
        { "A", new Integer(100)},
        { "B", new Integer(500)},
        { "C", new Integer(800)} } ,
             new String[]{ "Parameter", "Value"});

      SliderRenderer renderer = new SliderRenderer();
      SliderEditor   editor   = new SliderEditor();

      Dimension ps = ((JComponent)renderer).getPreferredSize();
      setRowHeight(ps.height);

      getColumnModel().getColumn(1).setCellRenderer(renderer);
      getColumnModel().getColumn(1).setCellEditor(editor);
    }
  }


  // main method for unit testing
  public static void main(String[] argv) {
    try {
      String lf = "com.sun.java.swing.plaf.windows.WindowsLookAndFeel";
      UIManager.setLookAndFeel( lf );
    } catch(Exception exc) { }

    BlockParameterPanel2 panel = new BlockParameterPanel2();
    JFrame f = new JFrame();
    f.setBounds(100, 100, 600, 300);
    f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
    f.getContentPane().add(panel);
    f.setVisible(true);

    f.addWindowListener(new WindowAdapter() {
        public void windowClosed(WindowEvent e) {
          System.exit(0);
        }
      });
  }
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
In the SliderEditor constructor, adding the following lines solves the problem.
.....
 public SliderEditor() {
    // Add these lines
    JFrame testframe = new JFrame();
     testframe.getContentPane().add(renderer);
     testframe.pack();
     testframe.getContentPane().removeAll();
     testframe.dispose();
......

The issue seems to be related to the JSlider not having properly set size/location properties.
The pack() command sets those properties and then this slider can be used as the editor component.

Comments
EVALUATION The better approach is simple: recalculate geometry just before calculation of thumb movement in mousePressed handler. See the webrev: http://javaweb.sfbay/jcg/1.7.0-dolphin/swing/6348946/
13-07-2006

SUGGESTED FIX The better approach is simple: recalculate geometry just before calculation of thumb movement in mousePressed handler: +++ BasicSliderUI.java Tue Aug 22 14:02:40 2006 @@ -1531,10 +1531,16 @@ public void mousePressed(MouseEvent e) { if (!slider.isEnabled()) { return; } + // We should recalculate geometry just before + // calculation of the thumb movement direction. + // It is important for the case, when JSlider + // is a cell editor in JTable. See 6348946. + calculateGeometry(); + currentMouseX = e.getX(); currentMouseY = e.getY(); if (slider.isRequestFocusEnabled()) { slider.requestFocus();
13-07-2006

EVALUATION Before cell editor is painted, it is given a size. But it does through usual AWT event, which is placed in the event queue. JTable has own algorithm of mouse event propagation to children components. When JTable gets MOUSE_PRESSED event, JTable reposts it to appropriate cell editor directly WITHOUT the event queue (see BasicTableUI.Handler.adjustSelection(), line 1086). Consequently, the editor gets the MOUSE_PRESSED event earlier then componentResized() is called (NB: JTable's MOUSE_PRESSED event handler works from EDT). The ancestor listener solves the problem because it is called earlier than the MOUSE_PRESSED event is propagated to the cell editor, see BasicTableUI.Handler.adjustSelection(), line 1084: table.editCellAt(...)
16-05-2006

EVALUATION To avoid the bug you can use same JSlider as cell renderer and as cell editor. I have the following working code: ---- CODE BEGIN ---- import java.awt.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.table.*; public class TestCase extends JPanel { public TestCase() { JScrollPane scroll = new JScrollPane(new ParameterTable()); this.setLayout(new BorderLayout()); this.add(scroll, BorderLayout.CENTER); } private class SliderEditor extends AbstractCellEditor implements TableCellEditor, TableCellRenderer { private JSlider slider = new JSlider(0, 1000); public JSlider getSlider() { return slider; } public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) { int val = (Integer) value; slider.setValue(val); // Needed to properly paint the slider. slider.updateUI(); return slider; } public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int col) { int val = (Integer) value; slider.setValue(val); return slider; } public SliderEditor() { slider.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { if (!slider.getValueIsAdjusting()) { stopCellEditing(); } } }); } public Object getCellEditorValue() { return slider.getValue(); } } private class ParameterTable extends JTable { public ParameterTable() { super(new Object[][]{{"A", 500}}, new String[]{"Parameter", "Value"} ); SliderEditor editor = new SliderEditor(); Dimension ps = editor.getSlider().getPreferredSize(); setRowHeight(ps.height); getColumnModel().getColumn(1).setCellRenderer(editor); getColumnModel().getColumn(1).setCellEditor(editor); } } public static void main(String[] args) throws Exception { String lf = "com.sun.java.swing.plaf.windows.WindowsLookAndFeel"; UIManager.setLookAndFeel(lf); TestCase panel = new TestCase(); JFrame f = new JFrame(); f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); f.getContentPane().add(panel); f.pack(); f.setLocationRelativeTo(null); f.setVisible(true); } } ---- CODE END ----
05-05-2006

SUGGESTED FIX +++ BasicSliderUI.java Fri May 5 16:11:14 2006 @@ -238,19 +238,21 @@ slider.addMouseMotionListener(trackListener); slider.addFocusListener(focusListener); slider.addComponentListener(componentListener); slider.addPropertyChangeListener( propertyChangeListener ); slider.getModel().addChangeListener(changeListener); + slider.addAncestorListener(getHandler()); } protected void uninstallListeners( JSlider slider ) { slider.removeMouseListener(trackListener); slider.removeMouseMotionListener(trackListener); slider.removeFocusListener(focusListener); slider.removeComponentListener(componentListener); slider.removePropertyChangeListener( propertyChangeListener ); slider.getModel().removeChangeListener(changeListener); + slider.removeAncestorListener(getHandler()); handler = null; } protected void installKeyboardActions( JSlider slider ) { InputMap km = getInputMap(JComponent.WHEN_FOCUSED, slider); @@ -1408,11 +1410,11 @@ return value; } private class Handler implements ChangeListener, - ComponentListener, FocusListener, PropertyChangeListener { + ComponentListener, FocusListener, PropertyChangeListener, AncestorListener { // Change Handler public void stateChanged(ChangeEvent e) { if (!isDragging) { calculateThumbLocation(); slider.repaint(); @@ -1460,10 +1462,18 @@ changeListener); calculateThumbLocation(); slider.repaint(); } } + + // Ancestor Handler + public void ancestorAdded(AncestorEvent e) { + calculateGeometry(); + slider.repaint(); + } + public void ancestorMoved(AncestorEvent e) { } + public void ancestorRemoved(AncestorEvent e) { } } ///////////////////////////////////////////////////////////////////////// /// Model Listener Class /////////////////////////////////////////////////////////////////////////
05-05-2006

EVALUATION The cause of the bug relates to the JTable cell painting and editing algorithm. In the test case from the bug description there are two JSliders: one for painting (cell renderer) and one for editing (cell editor). JSlider uses its viewable size to calculate its geometry (including thumb scrolling direction). The calculation occurs at the component creation time and when some events occur (componentResized and others). JTable shows cell editor only if user wants to edit a cell. When JSlider cell editor is created, it is not visible and the calculated direction is wrong. When user clicks at the cell, JSlider editor becomes visible, mousePressed event occurs (it scrolls the thumb), but the direction is not calculated yet. The componentResized event, which calculates thumb scrolling direction, occurs AFTER the mousePressed event. To fix the bug we should calculate the thumb scrolling direction in some event which occurs BEFORE the mousePressed event, but after the moment when JSlider obtains valid size. The AncestorListener.ancestorAdded() matches our demands.
05-05-2006

EVALUATION This looks odd, will try to fix it for Mustang
02-05-2006

EVALUATION Contribution-Forum:https://jdk-collaboration.dev.java.net/servlets/ProjectForumMessageView?messageID=12617&forumID=1463
11-04-2006

EVALUATION This sounds more like a slider problem than a table problem.
11-11-2005