# JTree Ordner anstatt Blatt anzeigen



## rapthor (7. Okt 2005)

Hallo,

ich benutze einen JTree um die Dateistruktur meienr Festplatte anzuzeigen. Jedoch möchte ich immer nur die Ordner und Dateien in den Baum laden, die ich auch grad sehen kann.

Meine Lösung ist daher, dass ich einen TreeExpansionListener einsetze, der je nach Mausklick, den Teil der neu zu ladenden Unterverzeichnisse nachlädt. Nun habe ich aber das Problem, dass rein grafisch erstmal für jeden DefaultMutableTreeNode ein Blatt angezeigt wird ... sowohl für Dateien (was in Ordnung ist), als auch für Verzeichnisse (was schlecht ist).

Welche Vorgehensweise zur korrekten Anzeige des Knotens mit Unterscheidung nach Unterverzeichnis und Datei gibt es da? Die Verzeichnisse sollten natürlich auch das vorangestellte +-Zeichen zum Aufklappen erhalten. (Der Inhalt würde dann wie erwähnt vom Listener-Handler nachgeladen werden.)



Rapthor


----------



## Mag1c (7. Okt 2005)

Hi,

die Methode DefaultMutableTreeNode.isLeaf() liefert genau dann true, wenn getChildCount() == 0 ist. Versuch das mal so hinzubiegen, daß isLeaf() für Verzeichnisse immer false liefert.

Gruß
Mag1c


----------



## rapthor (8. Okt 2005)

Nagut aber selbst wenn ich unterscheiden kann zwischen Ordner und Datei, so stellt der JTree das Symbol immer als Blatt dar. Und das so lange bis ich einen weiteren Knoten anhänge. Dann erst wird es vom Blattsymbol zum Zweigsymbol und erhält das +-Zeichen vorangestellt.


----------



## Beni (8. Okt 2005)

Lad doch eine Ebene "zu tief". Also wenn du "C:/" nur schon anzeigen sollst (_nicht_ aufgeklappt), lädst du mal "Windows" und "Program Files". Und wenn du dann z.B. "Windows" anzeigen sollst (eine der getChildCount oder getChild oder sowas-Methoden wird aufgerufen), lädst du halt die "System" und "System32" (etc...) Knoten.


----------



## Mag1c (8. Okt 2005)

Hi,

also ich hab das schonmal gemacht. Ich kann da nur leider gerade nicht nachschauen. Evtl. müsstest du mal etwas Code posten. Es geht auf jeden Fall.

Gruß
Mag1c


----------



## rapthor (8. Okt 2005)

Das was "Beni" mir vorschlägt, wäre natürlich eine Lösung!

Hier hab ich nochmal den wichtigsten Teil zur Generierung des Baums aufgelistet:


```
private void baumErstellen(DefaultMutableTreeNode startKnoten, String startVerzeichnis)
	{
		File[] verzKnoten = new File(startVerzeichnis).listFiles( new FileFilter() {
			  public boolean accept( File f) {
				    return f.isDirectory();
				  } } );
		
		File[] dateiKnoten = new File(startVerzeichnis).listFiles( new FileFilter() {
			  public boolean accept( File f) {
				    return f.isFile();
				  } } );		
		
		Arrays.sort(verzKnoten);
		Arrays.sort(dateiKnoten);
		
		for (File f : verzKnoten)
		{
			startKnoten.add(new DefaultMutableTreeNode(f.getName(), true));
			startKnoten.getLastLeaf().add(new DefaultMutableTreeNode("dummy", false));
		}
		
		for (File f : dateiKnoten)
		{
			startKnoten.add(new DefaultMutableTreeNode(f.getName(), false));
		}
		
		verzBaum = new JTree(startKnoten);
}
```

Bisher habe ich jedem Ordner einfach noch einen DefaultMutableTreeNote namens "dummy" hinzugefügt aber ich befürchte, dass dieser Knoten vom Benutzer kurzzeitig gesehen wird, wenn der einen beliebigen Ordner aufblättert (auch wenn ich den Lsitener einsetze und den Dummy verschwinden lassen würde.)


----------



## Mag1c (9. Okt 2005)

Hi,

du benutzt ja noch den DefaultMutableTreeNode. Dort kannst du nicht "isLeaf() -> false" erzwinden. Ich habe gerade mal ein kleines Beispiel gezaubert:


```
import java.io.File;

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.WindowConstants;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeModel;

public class TreeTest extends JFrame {

	public TreeTest() {
		setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
		createTree();
	}

	public void createTree () {
		File root = new File("/");
		FileTreeNode rootNode = new FileTreeNode(root);
		TreeModel treeModel = new DefaultTreeModel(rootNode);
		JTree tree = new JTree(treeModel);
		tree.setShowsRootHandles(true);
		getContentPane().add(new JScrollPane(tree));
		setSize(300, 500);
	}

	private class FileTreeNode extends DefaultMutableTreeNode {
		public FileTreeNode (File f) {
			file = f;
			loaded = false;
			setUserObject(f.getName());
		}

		private void lazyLoad () {
			if (!loaded) {
				System.out.println("load dir: "+file);
				if (file.isDirectory()) {
					File[] files = file.listFiles();
					for ( int i=0; i < files.length; i++) {
						insert(new FileTreeNode(files[i]), i);
					}
				}
				loaded = true;
			}
		}

		public boolean isLeaf () {
			return file.isFile();
		}

		public int getChildCount () {
			lazyLoad();
			return super.getChildCount();
		}

		private File file;
		private boolean loaded;
	}

	public static void main (String[] args) {
		new TreeTest().show();
	}
}
```

EDIT: Ein kleiner Haken bleibt aber noch. Bei leeren Verzeichnissen verschwindet der "Lollipop", wenn man die aufklappen will. Ist zwar im Prinzip korrekt aber in der Bedienung unschön. Da müsste man noch etwas basteln.

Gruß
Mag1c


----------



## rapthor (9. Okt 2005)

Super Beispiel 

Also muss ich das selbst implementieren, indem ich von DefaultMutableTreeNode ableite... aha aha! Bekommst Punkte von mir für die Sache. Danke!


----------



## roddy (12. Dez 2005)

Hallo,

ich hätte hierzu auch noch ein paar Fragen:

- Ist es möglich, als "Startpfad" den Windows-Arbeitsplatz anzugeben, so dass es auch möglich ist, Pfade auf anderen Laufwerken auszuwählen?

- Ist es möglich, den kompletten Pfad auszulesen? Mit 

```
tree.getLastSelectedPathComponent()
```
bekomme ich ja den zuletzt angewählten Pfad, aber wie komme ich auf den gesamten Pfad? Ich finde nichts passendes in der API...

- Ist es möglich, nur Ordner anzeigen zu lassen und keine Dateien?

Danke für eure Hilfe...


----------



## Mag1c (12. Dez 2005)

Hi,



			
				roddy hat gesagt.:
			
		

> - Ist es möglich, als "Startpfad" den Windows-Arbeitsplatz anzugeben, so dass es auch möglich ist, Pfade auf anderen Laufwerken auszuwählen?



Dazu müsste man den Baum etwas umbauen, so daß er auf FileSystemView aufsetzt.



			
				roddy hat gesagt.:
			
		

> - Ist es möglich, den kompletten Pfad auszulesen? Mit
> 
> ```
> tree.getLastSelectedPathComponent()
> ...



getLastSelectedPathComponent() sollte ein FileTreeNode zurückliefern.  Tut man dort noch eine getFile()-Methode rein, hat man auch den kompletten Pfad.



			
				roddy hat gesagt.:
			
		

> - Ist es möglich, nur Ordner anzeigen zu lassen und keine Dateien?



Dazu mußt du im lazyLoad in der for-Schleife die Files überspringen, die kein Directory sind.

Gruß
Mag1c


----------



## roddy (12. Dez 2005)

Zu drittens: 

Ich hab das jetzt mal versucht, meine Datei (die start an deinen oberen Post hier angelehnt ist) sieht wie folgt aus:


```
import java.io.*;
import javax.swing.tree.*;

public class FileTreeNode extends DefaultMutableTreeNode {
      public FileTreeNode (File f) {
         file = f;
         loaded = false;
         setUserObject(f.getName());
      }

      private void lazyLoad () {
         if (!loaded) {
            if (file.isDirectory()) {
               File[] files = file.listFiles();
               for ( int i=0; i < files.length; i++) {
                  if (files[i].isDirectory()){
                    insert(new FileTreeNode(files[i]), i);
                  }
                    System.out.println(i + " / " + files.length);
               }
            }
            loaded = true;
         }
      }

      public boolean isLeaf () {
         return file.isFile();
      }

      public int getChildCount () {
         lazyLoad();
         return super.getChildCount();
      }

      private File file;
      private boolean loaded;
   }
```

Ich erhalte beim Ausführen folgende Exception:


```
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 5>0
     at java.util.Vector.insertElementAt(Unknown source)
     at javax.swing.tree.DefaultMutableTreeNode.insert (Unknown source)
     at FileTreeNode.lazyLoad(FileTreeNode.java:17)
     at FileTreeNode.getChildCount(FileTreeNode.java:31)
     at javax.swing.tree.DefaultTreeModel.getChildCount(Unknown source)
...
```

Wie kann denn das sein? Der System.out.print zeigt 4/27 an, welcher Array hat da die Größe 0? Was mache ich falsch?


----------



## Mag1c (12. Dez 2005)

Hi,

du mußt einen zweiten Index mitführen, da nun der Index des Arrays nicht mehr synchron zum Index im Baum ist:


```
private void lazyLoad () { 
         if (!loaded) { 
            if (file.isDirectory()) { 
               File[] files = file.listFiles(); 
               for ( int i=0, j=0; i < files.length; i++) { 
                  if (files[i].isDirectory()){ 
                    insert(new FileTreeNode(files[i]), j++); 
                  } 
               } 
            } 
            loaded = true; 
         } 
      }
```

Gruß
Mag1c


----------



## roddy (12. Dez 2005)

Erstmal vielen Dank ;-)

Für die Nachwelt...

Lösung zweites Problem (etwas anders als von Mag1c beschrieben, so hab ichs nicht hinbekommen):




```
Object[] temp = tree.getLeadSelectionPath().getPath();
            String tempstring = "C:" + System.getProperty("file.separator");
            for (int i = 1; i<temp.length; i++){
                tempstring = tempstring.concat(temp[i].toString() + System.getProperty("file.separator"));
            }
            System.out.println(tempstring);
```

Bildschirmausgabe hierzu ist dann Beispielsweise: "C:\Programme\MeinSupiProgramm\"

Lösung drittes Problem:


```
private void lazyLoad () {
         int j = 0;
         if (!loaded) {
            if (file.isDirectory()) {
               File[] files = file.listFiles();
               for ( int i=0; i < files.length; i++) {
                 if (files[i].isDirectory()){
                    insert(new FileTreeNode(files[i]), j);
                    j++;
                 }
               }
            }
            loaded = true;
         }
      }
```

Zu ersterem schreib ich nachher noch was (wenn ichs verstanden hab)


----------



## roddy (12. Dez 2005)

Mit dem folgenden Initialisierungsblock für meinen JTree erhalte ich das ganze mit Arbeitsplatz als erstes Verzeichnis.


```
FileSystemView fsv = FileSystemView.getFileSystemView();
            File root = new File("/");
            root = fsv.getParentDirectory(root);
            FileTreeNode rootNode = new FileTreeNode(root);
            TreeModel treeModel = new DefaultTreeModel(rootNode);
            tree = new JTree(treeModel);
            tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
            tree.setShowsRootHandles(true);
```

Das ist nun allerdings sehr sehr plattformabhängig, ich schätze mal Linux dürfte es nicht mögen, wenn man das Parent Directory von root aufruft ;-). 

Jetzt hatte ich allerdings noch zwei Probleme

- In meiner Anzeige steht oben nicht "Arbeitsplatz", sondern ein Registrierungs-Schlüssel (unter dem find ich in der Windows-Registry Infos über den Arbeitsplatz
- Laufwerksnamen werden nicht angezeigt. Also statt z.B. "C:" steht dort "".

Wenn man in FileTreeNode aus:


```
setUserObject(f.getName());
```

das folgende macht:


```
setUserObject(f);
```

ist dieses Problem auch gelöst.


----------



## Illuvatar (12. Dez 2005)

Ich hab mal mit nem Explorer Abklatsch angefangen zu proggen, hab dann aber damit aufgehört, ich wollte die zip-Files "on the fly" mit einbinden, aber das war performancemäßig kacke :-/

Wenns dich interessiert, hier mal Node, Model und Renderer des JTrees, das zeigt vllt den Umgang mit dem FileSystemView und auch das dynamische nachladen:

--TreeNode--

```
package de.illu.jexplorer;

import javax.swing.tree.TreeNode;
import java.util.*;
import java.io.*;
import java.util.Comparator;
import javax.swing.filechooser.FileSystemView;
import java.util.zip.*;
import de.illu.util.IterableEnumeration;

class FileTreeNode implements TreeNode
{
	private File myFil;
	private File[] sub;
	private FileSystemView fsv;
	public FileTreeNode (File f, FileSystemView fsv)
	{
		myFil = f;
		this.fsv = fsv;
		if (myFil.isDirectory()){
			sub = myFil.listFiles();
			Arrays.sort(sub, new FileComparator());
		}
	}
	public TreeNode getChildAt(int childIndex)
	{
		if (myFil.isDirectory() && childIndex < getChildCount()){
			return new FileTreeNode (sub[childIndex], fsv);
		}else{
			return null;
		}
	}
	public int getChildCount()
	{
		return myFil.isDirectory() ? sub.length : 0;
	}
	public TreeNode getParent()
	{
		return new FileTreeNode (new File (fsv.isRoot(myFil) ? null : myFil.getParent()), fsv);
	}
	public int getIndex(TreeNode node)
	{
		File f = ((FileTreeNode)node).myFil;
		for (int i = 0; i < getChildCount(); ++i){
			if (sub[i].equals(f)){
				return i;
			}
		}
		return -1;
	}
	public boolean getAllowsChildren()
	{
		return myFil.isDirectory();
	}
	public boolean isLeaf()
	{
		return !myFil.isDirectory();
	}
	public Enumeration children()
	{
		java.util.Vector<TreeNode> v = new java.util.Vector<TreeNode>();
		for (int i = 0; i < getChildCount(); ++i){
			v.add (getChildAt (i));
		}
		return v.elements();
	}	
	public File getFile()
	{
		return myFil;
	}
	private class FileComparator implements Comparator<File>
	{
		public int compare (File f1, File f2)
		{
			if (f1.isDirectory() && !f2.isDirectory()){
				return -1;
			}else if (f2.isDirectory() && !f1.isDirectory()){
				return 1;
			}else{
				return f1.getName().compareTo(f2.getName());
			}
		}
		public boolean equals (Object o)
		{
			return o instanceof FileComparator;
		}
	}
}
```

--Renderer--

```
package de.illu.jexplorer;

import javax.swing.filechooser.*;
import javax.swing.tree.*;
import javax.swing.*;
import java.awt.*;

public class FileTreeCellRenderer extends DefaultTreeCellRenderer
{
	private FileSystemView fsv;
	public FileTreeCellRenderer (FileSystemView fsv)
	{
		this.fsv = fsv;
	}
	public Component getTreeCellRendererComponent
		(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus)
	{
		super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
		FileTreeNode ftn = (FileTreeNode)value;
		setText (fsv.getSystemDisplayName(ftn.getFile()));
		setIcon (fsv.getSystemIcon(ftn.getFile()));
		return this;
	}
}
```

--Model--

```
package de.illu.jexplorer;

import javax.swing.tree.*;
import javax.swing.filechooser.*;

class LeftTreeModel extends DefaultTreeModel
{
	public LeftTreeModel(FileSystemView fsv)
	{
		super (new FileTreeNode (fsv.getHomeDirectory(), fsv), true);
	}
}
```


----------

