RSS feed Facebook profile Follow me on Twitter Google+ profile LinkedIn profile


Why am I writing this?
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:

Debigulator


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.


The code
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:

JavaFX file browser




Download
Source code for this demo




Support
Everything on this site is free. I'll never use pop-ups or randomly generated ads to support it. If you've found something here to be especially helpful or entertaining please consider making a small donation. This can be done through a secure PayPal transaction. Thanks for visiting my little web page!
Make a secure donation for any amount via PayPal.







index
feedback


Creative Commons License  This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License. All source code and software on this page is distributed under the terms of the GNU General Public License, version 2 (copyright 2000-2011 Hugues Johnson)