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

getPreferredSize() in BasicListUI reporting stale size

XMLWordPrintable

    • x86
    • windows_2000

      Name: jk109818 Date: 04/26/2002


      FULL PRODUCT VERSION :
      java version "1.4.0"
      Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.0-b92)
      Java HotSpot(TM) Client VM (build 1.4.0-b92, mixed mode)

      FULL OPERATING SYSTEM VERSION : n/a


      ADDITIONAL OPERATING SYSTEMS : n/a



      A DESCRIPTION OF THE PROBLEM :
      Getting the accurate preferred size of JList in a
      JScrollPane is not possible because the combination of
      behaviour in both the BasicListUI class and the
      AbstractListModel class.

      For instance, if you create a JList in a JScrollPane (no
      horiz scrollbar) with a single String "Foo", you would
      expect the horizontal size of the list to be enough to
      exactly accommodate the String "Foo". If you now add the
      string "FooBar" to the model, you would expect the
      preferred size to immediately report a size that
      accommodates "FooBar". This is not the case.

      This behaviour is because the preferred size of the JList
      is delegated to BasicListIU in the look and feel packages.
      The method BasicListUI.getPreferredSize() caches certain
      values like cell width when working out the preferred size
      of the JList. This cache is only marked stale on a
      ListDataEvent.

      In other words, the size of list is only fully recomputed
      when the list data changes. While the assumption is
      correct, the look and feel has no 'special' API for
      receiving these events and instead must add itself as an
      ordinary listener to the ListModel just like any other
      swing component. To make matters worse, the
      AbstractListModel iterates through its listeners in reverse
      order of insertion thereby guarenteeing that the
      BasicListUI is the last listener to receive the event
      (unless the user switches Look and Feel midway through the
      application - then the UI will get the event first!).

      The net effect is that it is impossible to determine a
      JList's new preferred size on any available event hook.
      This makes things like NarrowingComboBoxes (as seen in
      IntelliJ's IDEA) impossible to create with some serious
      hacking.

      See the code example for a demonstration of the bug.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      1. Run the application
      2. Check the checkbox.
      3. The list data changes, but the JList's size won't (until
      you resize the frame).
      4. Console output shows the difference between the reported
      preferred sizes when using an the event hook, and just
      after the event has taken place (the deferred output)

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      Expected:
      FlowLayout honours the preferred size of a component, so I
      expect the scrollpane to resize when I check the checkbox.
      I also expect the getPreferredSize method to reflect the
      size needed for the new layout.

      Actual:
      The scrollpane does not resize and the reported
      getPreferred size is wrong (it is using invalid cached
      data).

      The Swing worker thread is used to show that the preferred
      size is actually being properly recalculated, just too late
      to be of any use.

      This bug can be reproduced always.

      ---------- BEGIN SOURCE ----------

      import javax.swing.*;
      import javax.swing.event.ListDataEvent;
      import javax.swing.event.ListDataListener;
      import java.awt.*;
      import java.awt.event.ItemEvent;
      import java.awt.event.ItemListener;

      public class TestScrollPaneBug extends JPanel {
          public static void main(String[] args) {
              JPanel panel = new JPanel(new BorderLayout());

              final JList list = new JList();
              final JScrollPane scrollPane = new JScrollPane(list);
              scrollPane.setHorizontalScrollBarPolicy
      (JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);

              final DefaultListModel listModel = new DefaultListModel();
              list.setModel(listModel);
              listModel.add(0, "Short");
              listModel.addListDataListener(new ListDataListener() {
                  public void intervalAdded(ListDataEvent e) {
                      update();
                  }

                  public void intervalRemoved(ListDataEvent e) {
                      update();
                  }

                  public void contentsChanged(ListDataEvent e) {
                      update();
                  }

                  private void update() {
                      System.out.println("scrollPane preferred size = " +
      scrollPane.getPreferredSize());
                      SwingUtilities.invokeLater(new Runnable() {
                          public void run() {
                              System.out.println("scrollPane preferred size
      (deferred) = " + scrollPane.getPreferredSize());
                          }
                      });
                  }
              });


              // To preserve the preferred width contract
              JPanel flowLayoutPanel = new JPanel(new FlowLayout());
              flowLayoutPanel.add(scrollPane);
              panel.add(flowLayoutPanel, BorderLayout.CENTER);

              JCheckBox checkBox = new JCheckBox("Long description");
              checkBox.addItemListener(new ItemListener() {
                  public void itemStateChanged(ItemEvent e) {
                      if (e.getStateChange() == ItemEvent.SELECTED) {
                          listModel.set(0, "Very very very long description");
                      } else {
                          listModel.set(0, "Short");
                      }
                      list.setModel(listModel);
                  }
              });
              panel.add(checkBox, BorderLayout.SOUTH);

              JFrame frame = new JFrame();
              frame.setContentPane(panel);
              frame.pack();
              frame.show();
          }
      }

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

      CUSTOMER WORKAROUND :
      There are two hacks that may work and I've only tested one.

      The first hack is to use the SwingUtilities.invokeLater()
      service to run any code that may need a valid preferred
      size. This does seems to work while simulataneously
      seeming like a horrific abuse of Threading.

      The second hack is to get the collection of
      listDataListeners from the ListModel; find the UI delegate
      and send it the event yourself before requesting the
      preferred size. While this avoids the threading issues, it
      does require more inter-class knowledge which may have been
      avoided otherwise.
      (Review ID: 144875)
      ======================================================================

            malenkov Sergey Malenkov (Inactive)
            jkimsunw Jeffrey Kim (Inactive)
            Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

              Created:
              Updated:
              Resolved:
              Imported:
              Indexed: