From 1998-2001 I was a software engineer for Mercator Software, working mostly with Visual Basic 6. One massive stock crash later I found myself doing a mix of Visual Basic, C#, and Java development at a Fortune 100 company. From 2003-2006 I was lead C#/ASP.NET developer for a content hub there. When the company moved that site to WebSphere Portal I went along for the ride and was a Portal lead developer/team lead from 2007-2010. In 2011 I moved into a Solution Architect role. In 2004 I completed my M.S. in Computer Science from University of Illinois: Chicago. My area of focus was artificial intelligence. Prior to that I earned a B.S. in Computer Science from Elmhurst College. In my spare time I've written a few nerdy Android applications that are freely available on the market. Hugues has posted 3 posts at DZone. View Full User Profile

Writing a simple file browser in JavaFX

03.22.2012
| 12561 views |
  • submit to reddit

I want to like JavaFX, really I do. The return of the applet reminds me of the 90s which is nice. I also like the idea of being able to drag an applet into Windows, Ubuntu, and Mac to run it as a desktop application. It's a whole new take on their "write once, run anywhere" promise and breathing some life into a platform that needs it.

Java used to be so trendy and cool, it was the "Ruby on Rails of the 90s" now it's seemingly destined to be the "COBOL of the 20s". If JavaFX lives up to its promise it could turn things around. Well, I guess Android is technically leading a Java revival today unless Oracle's lawsuit forces Google to move to a different language.

So far though I've been a little disappointed with JavaFX. It's like an El Camino, a strange combination of AWT and Swing that doesn't quite feel natural. I'm going to keep trying it anyway and hope that one day it catches up to C# 1.0.

Look, I know this sounds terribly cynical so far but you have to believe me when I say I'm trying to like it.

The reality is, even if JavaFX is a little clunky now it's still a considerable improvement over Swing. Over the next few months I'm going to upgrade all my ugly Swing applications to JavaFX. The first one is something called Debigulator. It's a batch archive program that I wrote for myself but has been downloaded more than I expected.

It's also one of the ugliest programs ever created. Just look at this monstrosity:

 

Besides being unattractive it also doesn't resize well. JavaFX addresses both of those so I'm porting it. The first thing to go is that awful file browser in the top left region, I'm embarrassed to look at it. I think I'll replace it with a simple TreeView.

To create a TreeView we first have to create a TreeItem subclass to store in the tree. The API documentation for the JavaFX TreeItem class includes a partial implementation of a file browser. I looked at it but went a different direction because it recursively populates the entire tree up front and doesn't deal with things like folder & file icons. Instead I wanted to dynamically populate a node when it's expanded. The TreeItem also needs to store the path to the file represented by each item but only show the folder or file name.

Alright, let's get our TreeItem implementation started. The constructor and class members look a little something like:

public class FilePathTreeItem extends TreeItem<String>{
  public static Image folderCollapseImage=new Image(ClassLoader.getSystemResourceAsStream("com/huguesjohnson/javafxfilebrowsedemo/folder.png"));
  public static Image folderExpandImage=new Image(ClassLoader.getSystemResourceAsStream("com/huguesjohnson/javafxfilebrowsedemo/folder-open.png"));
  public static Image fileImage=new Image(ClassLoader.getSystemResourceAsStream("com/huguesjohnson/javafxfilebrowsedemo/text-x-generic.png"));
  
  //this stores the full path to the file or directory
  private String fullPath;
  public String getFullPath(){return(this.fullPath);}
  
  private boolean isDirectory;
  public boolean isDirectory(){return(this.isDirectory);}
    
  public FilePathTreeItem(Path file){
    super(file.toString());
    this.fullPath=file.toString();

Next we want to set the icon, full path, and isDirectory members. This would be a good time to mention that all the icons in this demo come from the Tango library

    //test if this is a directory and set the icon
    if(Files.isDirectory(file)){
      this.isDirectory=true;
      this.setGraphic(new ImageView(folderCollapseImage));
    }else{
      this.isDirectory=false;
      this.setGraphic(new ImageView(fileImage));
      //if you want different icons for different file types this is where you'd do it
    }
    
    //set the value
    if(!fullPath.endsWith(File.separator)){
      //set the value (which is what is displayed in the tree)
      String value=file.toString();
      int indexOf=value.lastIndexOf(File.separator);
      if(indexOf>0){
        this.setValue(value.substring(indexOf+1));
      }else{
        this.setValue(value);
      }
    }

Now let's add the event handler for the node expanded event. That check for source.isExpanded() sure seems unnecessary. Man that was a fun piece of unexpected behavior to track down. 

    this.addEventHandler(TreeItem.branchExpandedEvent(),new EventHandler(){
      @Override
      public void handle(Event e){
        FilePathTreeItem source=(FilePathTreeItem)e.getSource();
        if(source.isDirectory()&&source.isExpanded()){
          ImageView iv=(ImageView)source.getGraphic();
          iv.setImage(folderExpandImage);
        }
        try{
          if(source.getChildren().isEmpty()){
            Path path=Paths.get(source.getFullPath());
            BasicFileAttributes attribs=Files.readAttributes(path,BasicFileAttributes.class);
            if(attribs.isDirectory()){
              DirectoryStream<Path> dir=Files.newDirectoryStream(path);
              for(Path file:dir){
                FilePathTreeItem treeNode=new FilePathTreeItem(file);
                source.getChildren().add(treeNode);
              }
            }
          }else{
            //if you want to implement rescanning a directory for changes this would be the place to do it
          }
        }catch(IOException x){
          x.printStackTrace();
        }
      }
    });

We'll wrap up this TreeItem implementation with an handler for the node collapsed event. Again the source.isExpanded() check really shouldn't be needed but just go ahead and remove it to see the goofiness that follows. 

    this.addEventHandler(TreeItem.branchCollapsedEvent(),new EventHandler(){
      @Override
      public void handle(Event e){
        FilePathTreeItem source=(FilePathTreeItem)e.getSource();
        if(source.isDirectory()&&!source.isExpanded()){
          ImageView iv=(ImageView)source.getGraphic();
          iv.setImage(folderCollapseImage);
        }
      }
    });

Now we can go to work on the main program. Here's all the basic stuff. 

public class JavaFXFileBrowseDemoApp extends Application{
  private TreeView<String> treeView;
   
  public static void main(String[] args){
    launch(args);
  }
  
  @Override
  public void start(Stage primaryStage){
    //create tree pane
    VBox treeBox=new VBox();
    treeBox.setPadding(new Insets(10,10,10,10));
    treeBox.setSpacing(10);

Now it's time to start populating the tree. We'll use the computer name as the root node. Although I might go back and hide the root node since it's kind of pointless for this application. It's really just showing off how to get the name from the InetAddress class which you either already knew or didn't care about. 

    //setup the file browser root
    String hostName="computer";
    try{hostName=InetAddress.getLocalHost().getHostName();}catch(UnknownHostException x){}
    TreeItem<String> rootNode=new TreeItem<>(hostName,new ImageView(new Image(ClassLoader.getSystemResourceAsStream("com/huguesjohnson/javafxfilebrowsedemo/computer.png"))));

One nifty addition to JDK7 is the ability to list all the drives on the system. That comes in handy for the next step where we need to add all the drives under the root node. 

    Iterable<Path> rootDirectories=FileSystems.getDefault().getRootDirectories();
    for(Path name:rootDirectories){
      FilePathTreeItem treeNode=new FilePathTreeItem(name);
      rootNode.getChildren().add(treeNode);
    }
    rootNode.setExpanded(true);

All that's left is to add the TreeView to the window and show it. 

    //create the tree view
    treeView=new TreeView<>(rootNode);
    //add everything to the tree pane
    treeBox.getChildren().addAll(new Label("File browser"),treeView);
    VBox.setVgrow(treeView,Priority.ALWAYS);
     
    //setup and show the window
    primaryStage.setTitle("JavaFX File Browse Demo");
    StackPane root=new StackPane();
    root.getChildren().addAll(treeBox);
    primaryStage.setScene(new Scene(root,400,300));
    primaryStage.show();

Here's what the final product looks like, much cleaner than the awful Swing version and less than half the code: 

 

Published at DZone with permission of its author, Hugues Johnson. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Carlos Hoces replied on Sat, 2012/03/24 - 3:48am

Please, have a look at some file browsers developed in plain old Swing...

For better viewing quality, check this url: 

http://www.javaforge.com/displayDocument/jplay-5.png?doc_id=153631 

jPlay 

Bhaskar Karambelkar replied on Mon, 2012/04/02 - 11:24am

One question ,

If the children 'FilePathTreeItem'(s) are added dynamically as part of the expand event. How do you get to show the "expand" arrow keys, upfront ?

I ask the question because, unless a TreeItem has children, JavaFX, won't show the 'expand' arrow key icon next to it.

Bhaskar Karambelkar replied on Mon, 2012/04/02 - 12:41pm in response to: Bhaskar Karambelkar

To answer my own question,

 To show the 'expand' arrow keys on directories upfront, override the 'isLeaf()' method  in FilePathTreeItem, and return 'false' for a file and 'true' for a directory.

Ahmad Hadidi replied on Tue, 2014/04/08 - 5:56pm in response to: Bhaskar Karambelkar

 Hello,


Will you please provide the source code on how you overrided the isLeaf() function for the FilePathTreeItem to correctly display directories inside each partition?

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.