7 Replies Latest reply: Apr 9, 2013 9:53 PM by James_D RSS

    Check if component is visible

    user8929955
      I implemented a gallery displaying some images. This worked fine for the few images, I had in my example. However as soon as there are more images (>100) memory becomes an issue, as all images are initialized when the gallery is loaded.
      Is there an easy way to implement lazy loading? I figure it would suffice to know which component (e.g. HBox) is visible, everything above and below could be loaded when it becomes visible (or I could load on or two rows that are not visible to improve scrolling)
        • 1. Re: Check if component is visible
          jsmith
          You could use the cell factory approach of a virtualized control such as a ListView or TableView which is designed for these kind of cases.
          • 2. Re: Check if component is visible
            KonradZuse
            There is the function isVisible() I believe for NODEs, but you could also implement 1 image/imageview and have that picture changing it's graphic /and move it if it's needed.. I'm not 100% sure what you're tryingto accomplish.
            • 3. Re: Check if component is visible
              user8929955
              Both options seem valid. While list approach seems the architectural "correcter" choice, the approach with isVisible() comes more naturally. I think I will try both approaches, to see where it leads.
              Thanks so far
              • 4. Re: Check if component is visible
                user8929955
                Giving this some more thoughts: The approach with Tree- or List-View might work with lazy loading. However this would only postpone the problem. While the UI will be reasonably fast loading, when scrolling down to image number 214 all the memory will be used up, as the rows no longer visible are not unloaded.
                Therefore I tried the other approach with the visibility property on the Node. I created a ImageView placeholder, which stores the image name and only displays a white are of the appropriate dimensions. Then bind a change listener to the visibility property of the row.
                The sole problem is that all the rows are visible. So I guess I would have to work with a viewport to figure out which row is currently shown, and then using the event listener for scroll finished to update the visibility of the row.
                • 5. Re: Check if component is visible
                  James_D
                  user8929955 wrote:
                  Giving this some more thoughts: The approach with Tree- or List-View might work with lazy loading. However this would only postpone the problem. While the UI will be reasonably fast loading, when scrolling down to image number 214 all the memory will be used up, as the rows no longer visible are not unloaded.
                  That's not true. The ListView, TreeView, and TableView controls will reuse the cells used for rows that are no longer on the screen.
                  • 6. Re: Check if component is visible
                    user8929955
                    I implemented a quick test with the ListView and a CellFactory:
                    package javafxtest.image;
                    
                    import java.io.File;
                    import java.net.MalformedURLException;
                    import java.util.ArrayList;
                    import java.util.logging.Level;
                    import java.util.logging.Logger;
                    import javafx.application.Application;
                    import javafx.collections.FXCollections;
                    import javafx.collections.ObservableList;
                    import javafx.scene.Scene;
                    import javafx.scene.control.ListCell;
                    import javafx.scene.control.ListView;
                    import javafx.scene.image.Image;
                    import javafx.scene.image.ImageView;
                    import javafx.scene.image.ImageViewBuilder;
                    import javafx.scene.layout.StackPane;
                    import javafx.stage.Stage;
                    import javafx.util.Callback;
                    
                    public class ImageGallery extends Application {
                        
                        @Override
                        public void start(Stage primaryStage) {
                            ListView<String> imageNames = new ListView<>();
                            File dir = new File("/home/andi/Pictures");
                            ArrayList<String> list = new ArrayList<>();
                            for (File f : dir.listFiles()) {
                                try {
                                    list.add(f.toURI().toURL().toExternalForm());
                                } catch (MalformedURLException ex) {
                                    Logger.getLogger(ImageGallery.class.getName()).log(Level.SEVERE, null, ex);
                                }
                            }
                            ObservableList<String> names = FXCollections.observableArrayList(list);
                            imageNames.setItems(names);
                            
                            imageNames.setCellFactory(new Callback<ListView<String>, 
                                ListCell<String>>() {
                                    @Override 
                                    public ListCell<String> call(ListView<String> list) {
                                        return new ImageListCell();
                                    }
                                }
                            );
                            
                            StackPane root = new StackPane();
                            root.getChildren().add(imageNames);
                            
                            Scene scene = new Scene(root, 500, 600);
                            
                            primaryStage.setTitle("Image Gallery");
                            primaryStage.setScene(scene);
                            primaryStage.show();
                        }
                    
                        public static void main(String[] args) {
                            launch(args);
                        }
                        
                        private static class ImageListCell extends ListCell<String> {
                    
                            @Override
                            protected void updateItem(String item, boolean empty) {
                                super.updateItem(item, empty);
                                if (item != null) {
                                    Image img = new Image(item);
                    System.out.println("Update timestamp: "+System.currentTimeMillis()+" load image "+item);
                                    ImageView imageView = ImageViewBuilder.create()
                                            .image(img).build();
                                    setGraphic(imageView);
                                }
                            }
                        }
                    }
                    Checking this piece of code with the profiler shows that the memory usage increases constantly the more I scroll through the list. Perhaps I'm missing here something? If the cell would be reused, the GC should be able to collect all the image instances that are no longer visible and thereby keep the memory usage more or less at the same level.
                    Taking a look at the memory results, I can see that there are some ImageListCell instances, but the big part of the memory is consumed by byte arrays. I suppose the image data goes into those arrays.
                    • 7. Re: Check if component is visible
                      James_D
                      I added this field and constructor to the ImageListCell class:
                          private static int instanceCount = 0 ;
                          ImageListCell() {
                            System.out.println("Image list cells created: "+(++instanceCount));
                          }
                      I was loading large images (so only one or two were displayed at a time), and I saw only five List Cells were constructed, even though I scrolled through ~ 100 images. The memory consumption increased steadily until I was pretty close to the max; then the memory was reclaimed.

                      Are you actually running out of memory? If not, I'd conclude that the gc just figured it didn't need to reclaim any. All the resources associated with these images will (most likely) be referenced long enough for them to graduate from the young generation, so they won't actually be collected until the gc runs a full sweep.