views:

264

answers:

2

Hello, i'm implementing a Jlist populated with a lot of elements. Each element corresponds to a image so i'd like to show a resized preview of them inside each row of the list. I've implemented a custom ImageCellRenderer extending the Jlabel and on getListCellRendererComponent i create the thumbnail if there'snt any for that element. Each row corresponds to a Page class where i store the path of the image and the icon applied to the JLabel. Each Page object is put inside a DefaultListModel to populate the JList. The render code is something like this:

        public Component getListCellRendererComponent(
            JList list,
            Object value,
            int index,
            boolean isSelected,
            boolean cellHasFocus)
    {
        Page page = (Page) value;

        if (page.getImgIcon() == null)
        {
            System.out.println(String.format("Creating thumbnail of %s", page.getImgFilename()));
            ImageIcon icon = new ImageIcon(page.getImgFilename());

            int thumb_width = icon.getIconWidth() > icon.getIconHeight() ? 128 : ((icon.getIconWidth() * 128) / icon.getIconHeight());
            int thumb_height = icon.getIconHeight() > icon.getIconWidth() ? 128 : ((icon.getIconHeight() * 128) / icon.getIconWidth());
            icon.setImage(getScaledImage(icon.getImage(), thumb_width, thumb_height));

            page.setImgIcon(icon);
        }

        setIcon(page.getImgIcon());

    }

I was thinking that only a certain item is visibile in the List the cell renderer is called but i'm seeing that all the thumnails are created when i add the Page object to the list model. I've tried to load the items and after set the model in the JList or set the model first and after starting appending the items but the results are the same. Is there any way to load the data only when necessary or do i need to create a custom control like a JScrollPanel with stacked items inside where i check myself the visibility of each elements?

Thanks

+1  A: 

I believe the JList loops through all the items and invokes the renderer in order to determine the preferred size of the list.

You might be able to prevent this by using the setPrototypeCellValue(...) method. Of course the renderer will still be invoked for all items visible in the scroll pane.

camickr
i've assigned at fist an empty image with fixed size to all the item so the first render calculates the total height of the list. At the second paint request of a item i load the real image of the element. Thanks
elvencode
A: 

The culprit appears to be the BasicListUI in its updateLayoutState(). During a painting operation, it will iterate over each of the elements in your dataModel when the updateLayoutStateNeeded flag is set (and that is set after seemingly every property change, as well as when intervals are added and removed from the dataModel). On each of the cells, it will invoke the getListCellRendererComponent method and cause you to load your icon. You can stop this from happening if you set the fixedCellHeight and fixedCellWidth fields on your JList instance.

You can try to set the fixed height and width with the two public methods:

  • setFixedCellHeight(int)
  • setFixedCellWidth(int)

As an aside: in general, loading icons from the file system can be slow and cause your GUI to feel sluggish if performed in the Event Dispatch Thread. This is usually more a problem with large images, where loading the file can take time. You might want to, if you haven't already, consider loading the icons in a separate thread. This could be done by having a shared 'icon unavailable image' that you load up front, and when your Page is queried for an icon, if the page-specific icon is null, you can trigger the loading of the wanted file, and then return the 'icon unavailable image', and once the Page has loaded, assign it to the Page instance and trigger a repaint. Just a thought.

akf
I've created an icon object containing a dummy image which stands as a not loaded preview as you said. At the first call of getListCellRendererComponent i set the page icon with this object. In this way the list is populated with a correct scroll dimension. At the second call of the list element paint i load the resized image using a SwingWorker thread. In this way i can scroll the list without slowdowns but i'm unable to force the repaint of icon when the loading thread is done (i see the dummy image). Only resizing the window update the image and any repaint of the JLabel doesn't work.
elvencode
what you need to have happen is to trigger your ListModel to fire a `ListDataEvent`to its `ListDataListeners`, which will indicate to the UI to update its view. If you have subclassed `AbstractListModel`, it has a protected `fireContentsChanged` method which should do the trick for you. Remember that this should be run from within the EDT.
akf