Writing a simple file browser in JavaFX
Join the DZone community and get the full member experience.
Join For Free
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:
Opinions expressed by DZone contributors are their own.
Comments