Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaFX file listview with icon and file name

Tags:

javafx

In a JavaFX dialog, I would like to show a list of files with their icons and file names.

It was easy to find how to get an icon for a file extension:

File file = File.createTempFile("icon", ".doc");  
FileSystemView view = FileSystemView.getFileSystemView();      
java.swing.Icon icon = view.getSystemIcon(file);      
file.delete();

But, how can I draw that Swing Icon in a JavaFX ListView?

private static class AttachmentListCell extends ListCell<String> {
    @Override
    public void updateItem(String fileName, boolean empty) {
        if (item != null) {

            // Get file Icon for fileName as shown above.
            java.swing.Icon icon = 

            // Transform Icon to something that can be 
            // added to the box, maybe an ImageView.
            javafx.scene.image.ImageView image = ???

            // Label for file name
            Label label = new Label(item);

            HBox box = new HBox();
            box.getChildren().addAll(image, label);
            setGraphic(box);
        }
    }
}
like image 255
wimix Avatar asked Dec 12 '22 01:12

wimix


2 Answers

Here is an example for a list view with file icons:

enter image description here

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.embed.swing.SwingFXUtils;
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.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;

import javax.swing.filechooser.FileSystemView;

public class ListViewCellFactory2 extends Application {

    ListView<String> list = new ListView<String>();
    ObservableList<String> data = FXCollections.observableArrayList(
            "a.msg", "a1.msg", "b.txt", "c.pdf", 
            "d.html", "e.png", "f.zip",
            "g.docx", "h.xlsx", "i.pptx");

    @Override
    public void start(Stage stage) {
        VBox box = new VBox();
        Scene scene = new Scene(box, 200, 200);
        stage.setScene(scene);
        stage.setTitle("ListViewSample");
        box.getChildren().addAll(list);
        VBox.setVgrow(list, Priority.ALWAYS);

        list.setItems(data);

        list.setCellFactory(new Callback<ListView<String>, ListCell<String>>() {
            @Override
            public ListCell<String> call(ListView<String> list) {
                return new AttachmentListCell();
            }
        });

        stage.show();
    }

    private static class AttachmentListCell extends ListCell<String> {
        @Override
        public void updateItem(String item, boolean empty) {
            super.updateItem(item, empty);
            if (empty) {
                setGraphic(null);
                setText(null);
            } else {
                Image fxImage = getFileIcon(item);
                ImageView imageView = new ImageView(fxImage);
                setGraphic(imageView);
                setText(item);
            }
        }
    }

    public static void main(String[] args) {
        launch(args);
    }


    static HashMap<String, Image> mapOfFileExtToSmallIcon = new HashMap<String, Image>();

    private static String getFileExt(String fname) {
        String ext = ".";
        int p = fname.lastIndexOf('.');
        if (p >= 0) {
            ext = fname.substring(p);
        }
        return ext.toLowerCase();
    }

    private static javax.swing.Icon getJSwingIconFromFileSystem(File file) {

        // Windows {
        FileSystemView view = FileSystemView.getFileSystemView();
        javax.swing.Icon icon = view.getSystemIcon(file);
        // }

        // OS X {
        //final javax.swing.JFileChooser fc = new javax.swing.JFileChooser();
        //javax.swing.Icon icon = fc.getUI().getFileView(fc).getIcon(file);
        // }

        return icon;
    }

    private static Image getFileIcon(String fname) {
        final String ext = getFileExt(fname);

        Image fileIcon = mapOfFileExtToSmallIcon.get(ext);
        if (fileIcon == null) {

            javax.swing.Icon jswingIcon = null; 

            File file = new File(fname);
            if (file.exists()) {
                jswingIcon = getJSwingIconFromFileSystem(file);
            }
            else {
                File tempFile = null;
                try {
                    tempFile = File.createTempFile("icon", ext);
                    jswingIcon = getJSwingIconFromFileSystem(tempFile);
                }
                catch (IOException ignored) {
                    // Cannot create temporary file. 
                }
                finally {
                    if (tempFile != null) tempFile.delete();
                }
            }

            if (jswingIcon != null) {
                fileIcon = jswingIconToImage(jswingIcon);
                mapOfFileExtToSmallIcon.put(ext, fileIcon);
            }
        }

        return fileIcon;
    }

    private static Image jswingIconToImage(javax.swing.Icon jswingIcon) {
        BufferedImage bufferedImage = new BufferedImage(jswingIcon.getIconWidth(), jswingIcon.getIconHeight(),
                BufferedImage.TYPE_INT_ARGB);
        jswingIcon.paintIcon(null, bufferedImage.getGraphics(), 0, 0);
        return SwingFXUtils.toFXImage(bufferedImage, null);
    }

}

EDIT: On a HDPI monitor, the file icons are clipped.

enter image description here

  1. What has to be done to have the icons entirely displayed in the row?
  2. How can I retrieve the scale factor from Java?
like image 76
wimix Avatar answered Dec 13 '22 15:12

wimix


I came up with this code, which seems to work for converting the icon returned by getSystemIcon to a format which JavaFX can understand. It does this by using a combo of SwingUtilities.invokeLater with Platform.runLater to try to mitigate any potential threading issues between the two projects. The javax.swing.Icon is painted to java.awt.BufferedImage which is converted by SwingFXUtils to a JavaFX Image.

So the Swing Icon => JavaFX image portion of the code you were asking about seems to work. The only thing is, when I tested the application on OS X 10.7 running Oracle Java8u20, every file extension type I tried gave the exact same icon. So it would seem that your method for getting an icon from the system via the swing FileSystemView isn't really supported on OS X (as far as I can tell). I guess you could try it on another OS and see if it works for you there (presuming that supporting this icon lookup feature on OS X is not necessary for your task).

So googling around brought up the following question:

  • Access file type icons Mac OSX

And in that, Sam Barnum recommends a FileView works much better than a FileSystemView - and so it did for me. I switched your code to use a FileView and after that started getting different icons for different file types on OS X. The icons were still really small 16x16 icons (I wouldn't know how to retrieve the hi-res retina icons for instance), but at least the icons which were retrieved appeared correct and file type specific.

import javafx.application.Application;
import javafx.application.Platform;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

public class FileIconViewer extends Application {

    @Override
    public void start(Stage stage) throws IOException {
        Runnable fetchIcon = () -> {
            File file = null;
            try {
                file = File.createTempFile("icon", ".png");

                // commented code always returns the same icon on OS X...
                // FileSystemView view = FileSystemView.getFileSystemView();
                // javax.swing.Icon icon = view.getSystemIcon(file);

                // following code returns different icons for different types on OS X...
                final javax.swing.JFileChooser fc = new javax.swing.JFileChooser();
                javax.swing.Icon icon = fc.getUI().getFileView(fc).getIcon(file);

                BufferedImage bufferedImage = new BufferedImage(
                    icon.getIconWidth(), 
                    icon.getIconHeight(), 
                    BufferedImage.TYPE_INT_ARGB
                );
                icon.paintIcon(null, bufferedImage.getGraphics(), 0, 0);

                Platform.runLater(() -> {
                    Image fxImage = SwingFXUtils.toFXImage(
                        bufferedImage, null
                    );
                    ImageView imageView = new ImageView(fxImage);
                    stage.setScene(
                        new Scene(
                            new StackPane(imageView), 
                            200, 200
                        )
                    );
                    stage.show();
                });
            } catch (IOException e) {
                e.printStackTrace();
                Platform.exit();
            } finally {
                if (file != null) {
                    file.delete();
                }
            }
        };

        javax.swing.SwingUtilities.invokeLater(fetchIcon);
    }

    public static void main(String[] args) { launch(args); }
}

Note: there is an existing request in the JavaFX issue tracker to add this functionality to JavaFX (it is currently not scheduled for implementation, though you could log into the issue tracker and vote for the issue, comment on it, link it back to this StackOverflow question, etc):

  • RT-19583 Possibility to get native icons, on different sizes.
like image 34
jewelsea Avatar answered Dec 13 '22 13:12

jewelsea