JDK-4127936 : JTable Horizontal Scrollbar doesn't work
  • Type: Bug
  • Component: client-libs
  • Sub-Component: javax.swing
  • Affected Version: 1.1.5,1.2.0,1.2.1,1.4.0,6
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • OS: generic,windows_95,windows_nt
  • CPU: generic,x86
  • Submitted: 1998-04-10
  • Updated: 2017-05-23
Related Reports
Duplicate :  
Name: rk38400			Date: 04/10/98

If the table autoresizes and the minimum
column size is greater than the viewport area
the horizontal scroll bars don't show up or
allow you to scroll.

Below is a short example that will show the bug.
Run it and size the window so that a column
is cut off.  The horizontal scrollbar show up
but the slider has no where to go.

import com.sun.java.swing.*;
import com.sun.java.swing.table.*;
import com.sun.java.swing.event.*;
import com.sun.java.swing.border.*;

import java.awt.*;
import java.awt.event.*;
import java.util.*;

public class TableTest extends JFrame

  public static void main( String[] args)
      Frame f = new TableTest();

  public TableTest()
      setSize(new Dimension(200,200));
      public JScrollPane createTable() {

        // final
        final String[] names = {"First\nName", "Last\nName"};

	//ImageIcon burger = loadIcon("burger.gif","burger");
	//ImageIcon fries = loadIcon("fries.gif","fries");
	//ImageIcon softdrink = loadIcon("softdrink.gif","soft drink");
	//ImageIcon hotdog = loadIcon("hotdog.gif","hot dog");
	//ImageIcon pizza = loadIcon("pizza.gif","pizza");
	//ImageIcon icecream = loadIcon("icecream.gif","ice cream");
	//ImageIcon pie = loadIcon("pie.gif","pie");
	//ImageIcon cake = loadIcon("cake.gif","cake");
	//ImageIcon donut = loadIcon("donut.gif","donut");
	//ImageIcon treat = loadIcon("treat.gif","treat");
	//ImageIcon grapes = loadIcon("grapes.gif","grapes");
	//	ImageIcon banana = loadIcon("banana.gif","banana");
	//	ImageIcon watermelon = loadIcon("watermelon.gif","watermelon");
	//	ImageIcon cantaloupe = loadIcon("cantaloupe.gif","cantaloupe");
	//	ImageIcon peach = loadIcon("peach.gif","peach");
	//	ImageIcon broccoli = loadIcon("broccoli.gif","broccoli");
	//	ImageIcon carrot = loadIcon("carrot.gif","carrot");
	//	ImageIcon peas = loadIcon("peas.gif","peas");
	//	ImageIcon corn = loadIcon("corn.gif","corn");
	//	ImageIcon radish = loadIcon("radish.gif","radish");

        // Create the dummy data (a few rows of names)
        final Object[][] data = {
	  {"Mike", "Albers",        Color.green, "Soccer", new Integer(44)},
	  {"Mark", "Andrews",       Color.red, "Baseball", new Integer(2)},
	  {"Tom", "Ball",           Color.blue, "Football", new Integer(99)},
	  {"Alan", "Chung",         Color.green, "Baseball", new Integer(838)},
	  {"Jeff", "Dinkins",       Color.magenta, "Football", new Integer(8)},
	  {"Amy", "Fowler",         Color.yellow, "Hockey", new Integer(3)},
	  {"Brian", "Gerhold",      Color.green, "Rugby", new Integer(7)},
	  {"James", "Gosling",      Color.pink, "Tennis", new Integer(21)},
	  {"David", "Karlton",      Color.red, "Baseball", new Integer(1)},
	  {"Dave", "Kloba",         Color.yellow, "Football", new Integer(14),},
	  {"Peter", "Korn",         new Color(100, 100, 255), "Scuba Diving", new Integer(12)},
	  {"Dana", "Miller",        Color.blue, "Ice Skating", new Integer(8)},
	  {"Phil", "Milne",         Color.magenta, "Rugby", new Integer(3)},
	  {"Dave", "Moore",         Color.green, "Tennis", new Integer(88)},
	  {"Hans", "Muller",        Color.magenta, "Baseball", new Integer(5)},
	  {"Rick", "Levenson",      Color.blue, "Football", new Integer(2)},
	  {"Tim", "Prinzing",       Color.blue, "Baseball", new Integer(22)},
	  {"Chester", "Rose",       Color.black, "Hockey", new Integer(0)},
	  {"Chris", "Ryan",         Color.black, "None", new Integer(6)},
	  {"Ray", "Ryan",           Color.gray, "Football", new Integer(77)},
	  {"Georges", "Saab",       Color.red, "Hockey", new Integer(4)},
	  {"Tom", "Santos",         Color.blue, "Football", new Integer(3)},
	  {"Rich", "Schiavi",       Color.blue, "Hockey", new Integer(4)},
	  {"Nancy", "Schorr",       Color.blue, "Hockey", new Integer(8)},
	  {"Violet", "Scott",       Color.magenta, "Basketball", new Integer(44)},
	  {"Joseph", "Scheuhammer", Color.green, "Hockey", new Integer(66)},
	  {"Jeff", "Shapiro",       Color.black, "Skiing", new Integer(42)},
	  {"Willie", "Walker",      Color.blue, "Hockey", new Integer(4)},
	  {"Kathy", "Walrath",      Color.blue, "Baseball", new Integer(8)},
	  {"Arnaud", "Weber",       Color.green, "Football", new Integer(993)},
	  {"Steve", "Wilson",       Color.green, "Baseball", new Integer(7)}

        // Create a model of the data.
        TableModel dataModel = new AbstractTableModel() {
            public int getColumnCount() { return names.length; }
            public int getRowCount() { return data.length;}
            public Object getValueAt(int row, int col) {return data[row][col];}
            public String getColumnName(int column) {return names[column];}
            public Class getColumnClass(int c) {return getValueAt(0, c).getClass();}
            public boolean isCellEditable(int row, int col) {return getColumnClass(col) == String.class;} 
            public void setValueAt(Object aValue, int row, int column) { data[row][column] = aValue; }

        // Create the table
        JTable tableView = new JTable(dataModel);

        // Show colors by rendering them in their own color. 
        DefaultTableCellRenderer colorRenderer = new DefaultTableCellRenderer() {
	    public void setValue(Object value) { 
	        if (value instanceof Color) {
	            Color c = (Color)value; 
	            setText(c.getRed() + ", " + c.getGreen() + ", " + c.getBlue());


        //tableView.getColumn("Favorite Color").setCellRenderer(colorRenderer); 
	Enumeration columns = tableView.getColumnModel().getColumns();

        JScrollPane scrollpane = JTable.createScrollPaneForTable(tableView);
        return scrollpane;
  //    private ImageIcon loadIcon(String name, String description) {
  //	String path = "images/ImageClub/food/" + name;
  //return JPanel.sharedInstance().loadImageIcon(path, description);

(Review ID: 26070)
###@###.### 10/12/04 18:22 GMT

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

EVALUATION It's still not clear what the best way to fix this is in a backward compatible way. In the work-around section I've provided a work-around that very clearly solves the problem. It requires overriding both getScrollableTracksViewportWidth() AND getPreferredSize() to have them work together for the desired result. Ideally, we'd put this code into JTable, but it's much too dangerous. The problem is that the fix requires making getPreferredSize() call getMinimumSize() in some cases. It's quite possible that developers have done the opposite (having getMinimumSize() call getPreferredSize()). As such, this fix would result in their application crashing with a stack overflow. The investigation continues. This will be high priority for Dolphin.

EVALUATION It has been suggested during code review that having JTable.getScrollableTracksViewportWidth() returning true and then not honoring it when less than minimum size in JScrollPane may not be the right thing. More investigation is needed.

EVALUATION Unfortunately, the suggested fix and work-around do not take care of this. We want the user to be able to shrink the table down to it's minimum size, and at THAT POINT, then respect the minimum size and show the scrollbar. This has been fixed by having JScrollPane respect the minimum size even with auto resize turned on for JTables.

CONVERTED DATA BugTraq+ Release Management Values COMMIT TO FIX: mustang

WORK AROUND Override BOTH getScrollableTracksViewportWidth() AND getPreferredSize() to have them work together: // when the viewport shrinks below the preferred size, stop tracking the viewport width public boolean getScrollableTracksViewportWidth() { if (autoResizeMode != AUTO_RESIZE_OFF) { if (getParent() instanceof JViewport) { return (((JViewport)getParent()).getWidth() > getPreferredSize().width); } } return false; } // when the viewport shrinks below the preferred size, return the minimum size // so that scrollbars will be shown public Dimension getPreferredSize() { if (getParent() instanceof JViewport) { if (((JViewport)getParent()).getWidth() < super.getPreferredSize().width) { return getMinimumSize(); } } return super.getPreferredSize(); }

SUGGESTED FIX Safe fix is unknown at this time.

EVALUATION Yes, this is a reproducable bug that needs to be fixed before FCS with JDK1.2. sky 1998-05-28 The implementation of JTable's getScrollableTracksViewportWidth method does not take into account the minimum/preferred size of the table, so if autoresizing is turned on for any mode (ALL, LAST, NEXT, or SUBSEQUENT), it assumes that the table always fits in the viewport, and hence does not show horizontal scrollbars. It is possible that even with with autoresizing the table can be larger than the viewport. The suggested fix contains a better implementation of the method which bases the display of horizontal scrollbars solely on the comparison of the viewport and table sizes. ###@###.### 2001-10-29