# javafx.scene.control.TreeView  als  dropdown Menü



## deemon84 (8. Apr 2016)

Hallo,

ich brauche wieder Eure Hilfe Im Rahmen eines Projektes muss ich einen Baumstruktur realisieren. Dies geht ganz gut mit TreeView.
Das Problem: ich möchte das TreeView als ein Dropdown-Fenster darstellen.
Das Dropdown soll sich öffnen, wenn man auf einem Jcombobox klickt. Es muss nicht unbedingt ein JComboBox Objekt mit einem TreeView eerweiteret werden. Es muss nur ein TextFeld  und  daneben ein "Pfeil nach unten" sichtbar sein. Wenn der Pfeil angeklickt wird, soll sich das Dropdown mit dem TreeView erscheinen.

Ich konnte bisher das TreeView nur in einem eigenem JFrame anzeigen. Wenn ich es zum Jcombobox (mitttels setEditor(); siehe Code) hinzufüge, dann erscheint es im TextFeld-Bereich, und nicht als dropdown.

Das Code kann man mit main() starten: TreeView erscheint im JFrame.
Ich möchte, dass durch  createCBox()  ein Jcombobox (oder ein ähnliches Objekt mit dropdown Menü) erstellt wird.
(Das Tree schaut aus, wie in Anhang.)

Kann mir jemand weiterhelfen?

Vielen Dank,
Daniel




```
import java.awt.Component;
import java.util.Vector;

import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.plaf.basic.BasicComboBoxEditor;

import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.embed.swing.JFXPanel;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.CheckBoxTreeItem;
import javafx.scene.control.CheckBoxTreeItem.TreeModificationEvent;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.control.cell.CheckBoxTreeCell;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Text;


public class MyTreeView extends BasicComboBoxEditor {
   private Vector<String[]> nodelist;
   private boolean initted = false;
 
 
   /*
    * Gibt ein JComboBox mit einem custom dropdown zurück???
    */
   public JComboBox<String> createCBox() {
     JComboBox<String> kst = new JComboBox<String>();
     kst.setEditable(true);
     kst.setEditor( new MyTreeView( getTestData() ) );
     return kst;
   }
 
 
 
   //Create custom dropdown for JComboBox: http://www.codejava.net/java-se/swing/create-custom-gui-for-jcombobox
   //Interface - BasicComboBoxEditor - BEGIN
   public Component getEditorComponent() {
     final JFrame frame = new JFrame("Data");
  final JFXPanel fxPanel = new JFXPanel();
 
  frame.setSize(250, 350);
   
     Platform.runLater(new Runnable() {
  @Override
  public void run() {
  initFX(fxPanel, frame);
  initted = true;
  }
     });
   
     while(initted == false) {
       System.out.format("Waiting for init...\n");
     }
   
  return fxPanel;
  }
 
  public Object getItem() {
     System.out.println("\ngetItem() called...");
  return (String)"[SELECTED ITEM]";
  }
 
  public void setItem(Object item) {
  System.out.println("\nsetItem() called: " + item); 
  }
   //Interface - BasicComboBoxEditor - END
 
 
 
   public MyTreeView(Vector<String[]> nodes) {
     nodelist = nodes;
   }
 
   public void initAndShowGUI() {
  // This method should be invoked on the Swing-Event-Dispatch thread
  final JFrame frame = new JFrame("MyTreeView");
  final JFXPanel fxPanel = new JFXPanel();
  frame.add(fxPanel);
  frame.setSize(250, 350);
  frame.setVisible(true);
  frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

  Platform.runLater(new Runnable() {
  @Override
  public void run() {
  initFX(fxPanel, frame);
  }
  });
  }

  protected void initFX(JFXPanel fxPanel, JFrame container) {
  // This method is invoked on the JavaFX thread
  Scene scene = createScene(container.getWidth(), container.getHeight());
  fxPanel.setScene(scene);
  }
 
 
  /**
  * Füllt den Baum  root  mit Knoten spezifiziert in den Parameter  nodeStruct  auf. Wenn ein Knoten
  * im Baum bereits enthalten ist, wird dieser nicht überschrieben!
  * @param root Ist der Wurzelknoten vom Baum.
  * @param level Muss immer -1 sein!
  * @param nodeStruct Muss diesen Format haben: Node1/  oder  Node1/Node2  oder  Node1/Node2/Node3 ...
  * @param leafName Die Name vom Blattknoten. ZB wenn nodeStruct=Node1/Node2, dann wird der Knoten Node2
  * die Name  leafName  haben, alle anderen die Name null.
  */
  protected void populateTree(MyCheckBoxTreeItem<String> root, int level, String nodeStruct, String leafName) {
     String parts[] = nodeStruct.split("/");
     //System.out.println("\nparts.length: " + parts.length);
   
     if(level == -1) {level = 0;}
   
     boolean found=false;
     TreeItem<String> newRoot = null;
     for(TreeItem<String> elem: root.getChildren()) {
       //System.out.format("\n[%s]'s direkt children: %s\n\n",  root.getValue(),  elem.getValue());
       if(elem.getValue().equals(parts[level])) {
         found = true;
         newRoot = elem;
       }
     }
   
     if(found) {
       root = (MyCheckBoxTreeItem<String>) newRoot;
       if(level == parts.length - 1) {
         // do nothing
       } else {
         level++;
         populateTree(root, level, nodeStruct, leafName);
       }
     } else {
       MyCheckBoxTreeItem<String> node;
       if(level == parts.length - 1) {
         node = new MyCheckBoxTreeItem<String>(parts[level], leafName);
         root.getChildren().add(node);
       } else {
         node = new MyCheckBoxTreeItem<String>(parts[level], null);
         root.getChildren().add(node);
         root = node;
         level++;
         populateTree(root, level, nodeStruct, leafName);
       }
     }
  }
 

  protected Scene createScene(int width, int height) {
     MyCheckBoxTreeItem<String> rootItem = new MyCheckBoxTreeItem<String>("Root", null);
     rootItem.setExpanded(false); //expands the root node
 
  final TreeView tree = new TreeView(rootItem);
  tree.setEditable(false); //+++ true
  tree.setRoot(rootItem);
  tree.setShowRoot(true);

  //populate the tree
  tree.setCellFactory(CheckBoxTreeCell.<String>forTreeView());
  for(String node[]: nodelist) {
     populateTree(rootItem, -1, node[0], node[1]);
  }
 
 
     /*MyCheckBoxTreeItem<String> l2 = new MyCheckBoxTreeItem<String>("FIRST", "FIRST/2500/2500");
     rootItem.getChildren().add(l2);*/
   
   
     //[URL]https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/CheckBoxTreeItem.html#checkBoxSelectionChangedEvent--[/URL]
     //Für jedes Element im Baum wird dieses Event generieret, wenn das Root-Element aus/abgewählt wird.
     rootItem.addEventHandler(CheckBoxTreeItem.<String>checkBoxSelectionChangedEvent(), new EventHandler<TreeModificationEvent<String>>() {
      public void handle(TreeModificationEvent<String> event) {
          if(event.isConsumed()) {
            return;
          }
          MyCheckBoxTreeItem<String> cbi = (MyCheckBoxTreeItem<String>) event.getTreeItem();
          event.consume();
          System.out.println("Node: " + cbi.getName());
      }
     });
   
 
  StackPane root = new StackPane();
  root.getChildren().add(tree);
  Scene scene = new Scene(root, width, height);


  return (scene);
  }
 
 
  /*
  * Zum testen ...
  */
  public static Vector<String[]> getTestData() {
     Vector<String[]> data = new Vector<String[]>();
     String tdat[][] = new String[][] {
       {"FIRST/2500/2500", "FIRST/2500/2500"},
       {"SECOND/2800/2800", "SECOND/2800/2800"}
     };
   
     for(String node[]: tdat) {
       data.add(node);
     }
   
     return data;
  }
 
 
  /*
  * Zum testen...
  */
  public static void main(String[] args) {
  SwingUtilities.invokeLater(new Runnable() {
  @Override
  public void run() {
     MyTreeView mtree = new MyTreeView( getTestData() );
  mtree.initAndShowGUI();
  }
  });
  }
}


/**
* In dieser Klasse wird zusätzlich eine Name zu den jeweiligen CheckBoxTreeItem gespeichert.
*/
class MyCheckBoxTreeItem<T> extends CheckBoxTreeItem<T> {
   String name;
 
   public MyCheckBoxTreeItem(T arg0, String name) {
     super(arg0);
     this.name = name;
   }
 
   public String getName() {
     return this.name;
   }
}
```


----------



## deemon84 (10. Apr 2016)

Es könnte vielleicht auch hilfreich sein nur ein Popup-Fenster (was beim JComboBox nach dem Klick auf das Pfeil erscheint) zu programmieren...
Dort könnte ich das Tree anzeigen. Das Tree wird sowieso in einem java.awt.Container Objekt zurückgegeben (s. initAndShowGUI()).

Hat jemand schon soetwas gemacht?


----------



## dzim (11. Apr 2016)

Öhm... Kannst du bitte mal erläutern, welches UI-Framework du verwenden willst?

In deinem Test verwendest du im Prinzip nur Begriffe aus der Swing-Welt, dein Beispiel und der Titel des Threads dagegen sind JavaFX......... Was denn nun???

Dein Code sieht so aus, als wolltest du die Swing-JavaFX-Interoperability-Schicht nutzen. Schau mal bitte zuerst in dem folgenden Thread, ob du da ein paar Anregungen entnehmen kannst:
http://www.java-forum.org/thema/jframe-mit-jfxpanel-npe-bei-dispose.172437/

Ansonsten überlege, ob eine Vermischung dieser Technologien sinnvoll ist. Wenn du eine neue Anwendung von Grund auf neu programmierst: Nimm entweder Swing ODER JavaFX - verzichte in dem Fall auf eine Vermischung, damit tust du dir keinen Gefallen. Ich persönlich würde auf JavaFX setzen, aber das ist am Ende jedem selbst überlassen. Wenn du eine bestehende erweitern willst... Hm. Keine Ahnung. Sieht aber auch nicht so aus. Also entscheide dich lieber für eine Technologie.


----------



## deemon84 (12. Apr 2016)

Danke für deine Antwort.
Ich verwende hauptsächlich Swing. JavaFX habe ich für das TreeView verwendet. Deswegen wahrscheinlich die Verwirrung 
Das Link hat mich auf die Idee gebracht. Ich öffne das popup-window jedes mal vom JavaFX-Thread (Platform.runLater()) und erstelle es jedes mal neu. Jetzt funktioniert's.

Für das popup-window gibt's eigentlich eine einfachere Lösung:
einen JPanel erstellen, setVisible(true) setzen und setLocation() aufrufen...




```
package javaprinter;

import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.VBox;

import java.awt.BorderLayout;
import java.awt.Graphics;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

import com.jidesoft.swing.JidePopupMenu;


public class Javafx
{
  static JFXPanel fxPanel;
  static JidePopupMenu menu;
  static Scene scene;


  public static void initAndShowGUI()
  {
  // This method is invoked on Swing thread.
    // First init the JavaFX-Toolkit by creating a new JFXPanel.
    fxPanel = new JFXPanel();
    
  final JFrame frame = new JFrame("JavaFX / Swing Integrated");
  frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  frame.pack();
  frame.setSize(300, 100);
  frame.setVisible(true);

  setupUI(frame);
  }

  private static void setupUI(JFrame frame)
  {
  // create a panel, that will contain a label
  JPanel panel = new JPanel();
  
  /* add a label to the panel */
  JLabel label = new JLabel("Right click to get a popup");
  panel.add(label);
  panel.setSize(120, 100);
  
  /* add a mouse listener to handle the right click */
  /* a popup-window appears, that shows a JavaFX-TreeView
  * with some element inside it. Everything must be called
  * from Platform.runLater() */
  frame.addMouseListener(new MouseAdapter(){
  [USER=48687]@Override[/USER]
  public void mouseReleased(final MouseEvent e)
  {
  if (e.isPopupTrigger()) {
     Platform.runLater(new Runnable()
  {
  [USER=48687]@Override[/USER]
  public void run()
  {
      //Steps to open the popup-window:
  //create scene
  //setup (eventual recreate) fxpanel
  //create and show menu
      scene = createTextScene();
      fxPanel.removeAll();
      fxPanel.setScene(scene);
      menu = new MyPopupMenu();
      menu.add(fxPanel);
      menu.show(e.getComponent(), e.getX(), e.getY());
      System.out.format("\n\nOpening popup-window.\n\n");
  }
  });
  }
  }
  });
  
  frame.add(panel, BorderLayout.CENTER);
  frame.pack();
  frame.setSize(150, 100);
  }

  /**
  * Create the content of the TreeView.
  * [USER=49078]@Return[/USER] a Scene-instance containing the TreeView.
  */
  private static Scene createTextScene() {
    TreeItem<String> treeItemRoot = new TreeItem<> ("Root");
  
  TreeItem<String> nodeItemA = new TreeItem<>("Item A");
  TreeItem<String> nodeItemB = new TreeItem<>("Item B");
  TreeItem<String> nodeItemC = new TreeItem<>("Item C");
  treeItemRoot.getChildren().addAll(nodeItemA, nodeItemB, nodeItemC);
  
  for(int a=1; a<=10; a++) {
      TreeItem<String> nodeItemAsub = new TreeItem<>("Item A" + a);
      nodeItemA.getChildren().add(nodeItemAsub);
  }
  
  TreeView<String> treeView = new TreeView<>(treeItemRoot);
  
  VBox root = new VBox();
  root.setAlignment(Pos.CENTER);
  root.setStyle("-fx-border-style: solid;"
        + "-fx-border-width: 0;"
        + "-fx-border-color: white");
  root.getChildren().add(treeView);
  
  Scene scene = new Scene(root, 120, 250);
  // add CSS to the TreeView:
  // [URL]https://farrukhobaid.wordpress.com/2013/01/07/metro-theme-on-javafx-treeview/[/URL]
  // [URL]http://blog.ngopal.com.np/2012/07/11/customize-scrollbar-via-css/[/URL]
  // [URL]https://gist.github.com/maxd/63691840fc372f22f470[/URL]
  // [URL]http://www.guigarage.com/2015/11/styling-a-javafx-scrollbar/[/URL]
  // [URL]http://fxexperience.com/2013/01/modena-new-theme-for-javafx-8/[/URL]
  scene.getStylesheets().add("file:/C:/git/javafxtheme.css");
    return scene;
  }

  public static void main(String[] arguments)
  {
  SwingUtilities.invokeLater(new Runnable()
  {
  [USER=48687]@Override[/USER]
  public void run()
  {
     Platform.setImplicitExit(false);
  initAndShowGUI();
  }
  });
  
  //Platform.exit();
  }  
}


class MyPopupMenu extends JidePopupMenu {
   protected void paintBorder(Graphics g) {
     //JFXPanel has also an own border. Painting the
     //border of both looks annoying.
     //super.paintBorder(g);
   }
}
```


----------



## Flown (12. Apr 2016)

Setzt doch bitte das nächste mal deinen Code in Code-Tags: [code=java]//JAVA CODE HERE[/code]


----------



## deemon84 (12. Apr 2016)

Flown hat gesagt.:


> Setzt doch bitte das nächste mal deinen Code in Code-Tags: [code=java]//JAVA CODE HERE[/code]



Ja, werde ich machen 
Ich habe noch eine Frage bezüglich JavaFX. Ich habe gesehen, dass man das Scrollbar (vertikale/horizontale) beim TreeView drch CSS umstylen kann.
Kann man das herkömmliche Windows-Look von Scrollbalken (wie https://i.ytimg.com/vi/lgb7zm97RC0/maxresdefault.jpg) irgendwie einstellen?
Bei mir schauen die Scrollbalken so aus:
http://docs.oracle.com/javafx/2/ui_controls/img/tree-view-sample.png


Danke,
Daniel


----------



## dzim (13. Apr 2016)

Gut, das es dir geholfen hat. Irgendwie.
Bezüglich deiner CSS-Frage: Ja, es ist möglich. (Auch wenn ich nicht weiss, warum man die ollen hässlichen Scrollbalken des alten Windows haben möchte )

Schau mal bitte hier auf GitHub, da hat einer die dem JDK innewohnende modena.css (der Default-Theme von JavaFX) als Gist eingestellt:
https://gist.github.com/maxd/63691840fc372f22f470
Suche nach "ScrollBar" (ab Zeile 979 geht es los.)

Es dürfte aber recht aufwendig werden und setzt wahrscheinlich einiges an Trial-and-Error - und Zeit - voraus.
Zusätzlich gibt es schon ein paar Themes ausser den Defaults. Z.B. auf GuiGarage von Henrik Ebbers
http://www.guigarage.com/javafx-themes/
Dort könnte AeroFX (http://www.guigarage.com/aerofx/), der Win-7 nachamt interessant für dich sein. Wie vollständig der ist... Keine Ahnung. Wenn ich etwas gebraucht habe, hab ich i.d.R. selbst gestylt.


----------

