Search code examples
javaswingeventsjscrollpanecomponentlistener

java event for when the component becomes visible


I have a JScrollPane wrapped around a JPanel that contains potentially hundreds of JLabels that show thumbnail images (one thumbnail per JLabel). For memory reasons I don't want to build all the thumbnails. I want to build the thumbnails only for the JLabels that are visible and remove the thumbnails when their JLabels become not visible. They become visible/invisible when the user scrolls the JPanel. I tried to implement the loading/unloading the thumbnail by using the ComponentListener like this:

addComponentListener( new ComponentAdapter() {

    @Override
    public void componentShown( ComponentEvent e ) {
        setIcon( new ImageIcon( getThumb() ) );
    }

    @Override
    public void componentHidden( ComponentEvent e ) {
        setIcon( null );
    }
});

But this doesn't work. The JLabels are always empty. I could use the scroll event and calculate which thumbnails should be loaded but before I do that I would like to know if there is a simpler solution.


Solution

  • The "visible" property does not mean visible "on screen". It only indicates if the the component itself is to be displayed or not. Since components are visible by default and the listeners are only notified when a property changes your listener is never notified.

    To the best of my knowledge there is no dedicated event involved telling a component when it enters the visible region of the display. Also note that setting an icon on a label may alter its preferred size, breaking the entire layout. This can be worked around by manually giving the labels a fixed preferred size (which should be simple in case of thumbnails).

    A lazy approach would be to overwrite paintComponent on the labels and check if the thumb needs to be loaded in paintComponent:

     protected void paintComponent(Graphics g) {
         if (getIcon() == null) {
             // create thumbnail
         }
         super.paintComponent(g);
     }
    

    This isn't the best approach, as your code will run inside Swings event dispatch thread. This means any delay in loading the thumbnail will block rendering of your UI.

    A saner approach IMO would be to just request loading of the thumbnail and defer the actual loading to a background thread. When that thread completes loading, it can then use SwingUtilities.invoke (or invokeLater) to update the label (which triggers a repaint automatically if I'm not mistaken).

    The effect would be that the labels scrolled in briefly show empty, then update as soon as the thumb is available.