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

Java 6 Horizontal-Scroll and Column Reorder Incompatibility

XMLWordPrintable

      FULL PRODUCT VERSION :
      java version "1.6.0_02"
      Java(TM) SE Runtime Environment (build 1.6.0_02-b06)
      Java HotSpot(TM) Client VM (build 1.6.0_02-b06, mixed mode, sharing)

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

      A DESCRIPTION OF THE PROBLEM :
      When users scroll to the right in a JScrollPane containing a JTable with columns that do not auto resize, they cannot reorder the columns to the right properly. The scroll pane will jump back to the left, so the column can only be placed within the originally visible area of the scroll pane.

      This isn't a big issue if the scroll pane only needs to scroll to the right once. However, my application has a considerable number of columns. This makes it impossible to get left columns to the far right and columns that were dragged to the left back to the far right.

      I've tried this on a few different Java Web Start demo applications (including those from Sun's tutorial site) as well as my company's own Java Web Start application. Java 6 produces the same results on each. I've verified that this is an issue with multiple users, and in each instance switching back to a pre-Java 6 version (either 1.4.2 or 5) remedied the issue.

      I've included the TableRenderDemo file from the Java tutorial site to demonstrate the issue. The only changes needed to reproduce the bug are to turn on horizontal scrolling and turn off auto column resizing.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      1. The user must be running Java 6
      2. The JTable must be included within a JScrollPane
      3. The JScrollPane must allow horizontal scrolling
      4. The JTable must allow reordering of columns
      5. The JTable must not auto resize the columns
      6. Scroll the JTable to the right (resize the columns to make this happen if necessary)
      7. Drag one of columns only visible after scrolling either to the left or right

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      The column should be able to be dragged to any area of the JTable.
      ACTUAL -
      The scroll pane will jump back to the left, so the column can only be placed within the originally visible area of the scroll pane, making it impossible to place the column back in or near its original position to the far right.

      REPRODUCIBILITY :
      This bug can be reproduced always.

      ---------- BEGIN SOURCE ----------
      /*
       * This is the TableRenderDemo.java file from the following URL:
       * http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
       *
       * The additions needed to reproduce the bugs are the following two lines:
       * table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
       * scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
       */

      /*
       * TableRenderDemo.java requires no other files.
       */

      import javax.swing.DefaultCellEditor;
      import javax.swing.JComboBox;
      import javax.swing.JFrame;
      import javax.swing.JPanel;
      import javax.swing.JScrollPane;
      import javax.swing.JTable;
      import javax.swing.table.AbstractTableModel;
      import javax.swing.table.DefaultTableCellRenderer;
      import javax.swing.table.TableCellRenderer;
      import javax.swing.table.TableColumn;
      import java.awt.Component;
      import java.awt.Dimension;
      import java.awt.GridLayout;

      /**
       * TableRenderDemo is just like TableDemo, except that it
       * explicitly initializes column sizes and it uses a combo box
       * as an editor for the Sport column.
       */
      public class TableRenderDemo extends JPanel {
          private boolean DEBUG = false;

          public TableRenderDemo() {
              super(new GridLayout(1,0));

              JTable table = new JTable(new MyTableModel());
              table.setPreferredScrollableViewportSize(new Dimension(500, 70));
              table.setFillsViewportHeight(true);
              table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);

              //Create the scroll pane and add the table to it.
              JScrollPane scrollPane = new JScrollPane(table);
              scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);

              //Set up column sizes.
              initColumnSizes(table);

              //Fiddle with the Sport column's cell editors/renderers.
              setUpSportColumn(table, table.getColumnModel().getColumn(2));

              //Add the scroll pane to this panel.
              add(scrollPane);
          }

          /*
           * This method picks good column sizes.
           * If all column heads are wider than the column's cells'
           * contents, then you can just use column.sizeWidthToFit().
           */
          private void initColumnSizes(JTable table) {
              MyTableModel model = (MyTableModel)table.getModel();
              TableColumn column = null;
              Component comp = null;
              int headerWidth = 0;
              int cellWidth = 0;
              Object[] longValues = model.longValues;
              TableCellRenderer headerRenderer =
                  table.getTableHeader().getDefaultRenderer();

              for (int i = 0; i < 5; i++) {
                  column = table.getColumnModel().getColumn(i);

                  comp = headerRenderer.getTableCellRendererComponent(
                                       null, column.getHeaderValue(),
                                       false, false, 0, 0);
                  headerWidth = 150;

                  comp = table.getDefaultRenderer(model.getColumnClass(i)).
                                   getTableCellRendererComponent(
                                       table, longValues[i],
                                       false, false, 0, i);
                  cellWidth = comp.getPreferredSize().width;

                  if (DEBUG) {
                      System.out.println("Initializing width of column "
                                         + i + ". "
                                         + "headerWidth = " + headerWidth
                                         + "; cellWidth = " + cellWidth);
                  }

                  column.setPreferredWidth(Math.max(headerWidth, cellWidth));
              }
          }

          public void setUpSportColumn(JTable table,
                                       TableColumn sportColumn) {
              //Set up the editor for the sport cells.
              JComboBox comboBox = new JComboBox();
              comboBox.addItem("Snowboarding");
              comboBox.addItem("Rowing");
              comboBox.addItem("Knitting");
              comboBox.addItem("Speed reading");
              comboBox.addItem("Pool");
              comboBox.addItem("None of the above");
              sportColumn.setCellEditor(new DefaultCellEditor(comboBox));

              //Set up tool tips for the sport cells.
              DefaultTableCellRenderer renderer =
                      new DefaultTableCellRenderer();
              renderer.setToolTipText("Click for combo box");
              sportColumn.setCellRenderer(renderer);
          }

          class MyTableModel extends AbstractTableModel {
              private String[] columnNames = {"First Name",
                                              "Last Name",
                                              "Sport",
                                              "# of Years",
                                              "Vegetarian"};
              private Object[][] data = {
                  {"Mary", "Campione",
                   "Snowboarding", new Integer(5), new Boolean(false)},
                  {"Alison", "Huml",
                   "Rowing", new Integer(3), new Boolean(true)},
                  {"Kathy", "Walrath",
                   "Knitting", new Integer(2), new Boolean(false)},
                  {"Sharon", "Zakhour",
                   "Speed reading", new Integer(20), new Boolean(true)},
                  {"Philip", "Milne",
                   "Pool", new Integer(10), new Boolean(false)}
              };

              public final Object[] longValues = {"Sharon", "Campione",
                                                  "None of the above",
                                                  new Integer(20), Boolean.TRUE};

              public int getColumnCount() {
                  return columnNames.length;
              }

              public int getRowCount() {
                  return data.length;
              }

              public String getColumnName(int col) {
                  return columnNames[col];
              }

              public Object getValueAt(int row, int col) {
                  return data[row][col];
              }

              /*
               * JTable uses this method to determine the default renderer/
               * editor for each cell. If we didn't implement this method,
               * then the last column would contain text ("true"/"false"),
               * rather than a check box.
               */
              public Class getColumnClass(int c) {
                  return getValueAt(0, c).getClass();
              }

              /*
               * Don't need to implement this method unless your table's
               * editable.
               */
              public boolean isCellEditable(int row, int col) {
                  //Note that the data/cell address is constant,
                  //no matter where the cell appears onscreen.
                  if (col < 2) {
                      return false;
                  } else {
                      return true;
                  }
              }

              /*
               * Don't need to implement this method unless your table's
               * data can change.
               */
              public void setValueAt(Object value, int row, int col) {
                  if (DEBUG) {
                      System.out.println("Setting value at " + row + "," + col
                                         + " to " + value
                                         + " (an instance of "
                                         + value.getClass() + ")");
                  }

                  data[row][col] = value;
                  fireTableCellUpdated(row, col);

                  if (DEBUG) {
                      System.out.println("New value of data:");
                      printDebugData();
                  }
              }

              private void printDebugData() {
                  int numRows = getRowCount();
                  int numCols = getColumnCount();

                  for (int i=0; i < numRows; i++) {
                      System.out.print(" row " + i + ":");
                      for (int j=0; j < numCols; j++) {
                          System.out.print(" " + data[i][j]);
                      }
                      System.out.println();
                  }
                  System.out.println("--------------------------");
              }
          }

          /**
           * Create the GUI and show it. For thread safety,
           * this method should be invoked from the
           * event-dispatching thread.
           */
          private static void createAndShowGUI() {
              //Create and set up the window.
              JFrame frame = new JFrame("TableRenderDemo");
              frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

              //Create and set up the content pane.
              TableRenderDemo newContentPane = new TableRenderDemo();
              newContentPane.setOpaque(true); //content panes must be opaque
              frame.setContentPane(newContentPane);

              //Display the window.
              frame.pack();
              frame.setVisible(true);
          }

          public static void main(String[] args) {
              //Schedule a job for the event-dispatching thread:
              //creating and showing this application's GUI.
              javax.swing.SwingUtilities.invokeLater(new Runnable() {
                  public void run() {
                      createAndShowGUI();
                  }
              });
          }
      }
      ---------- END SOURCE ----------

      Release Regression From : 5.0
      The above release value was the last known release where this
      bug was not reproducible. Since then there has been a regression.

            alexp Alexander Potochkin (Inactive)
            ndcosta Nelson Dcosta (Inactive)
            Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

              Created:
              Updated:
              Resolved:
              Imported:
              Indexed: