JavaFX TreeView dynamisch aus Datenbank Tabelle erstellen

Bitte aktiviere JavaScript!
Hallo zusammen,

ich habe mal wieder ein Problem für das ich so recht keine Lösung finde, bzw. mir fehlt der Funke um zu verstehen wie ich das Problem angehen kann.
Hier die Problemstellung:

Ich habe eine Datenbank Tabelle mit folgender Struktur und mal 3 Test Zeilen:
SIDVERSIONPATCHSYSPWDTNSPORTSERVERROOTPWDSERVEROSIPADDRESSSSHPORT
ORCL1111.2.0.4123456abcdef1521server1xyzRed Hat192.168.0.9921
ORCL2112.1.0.2567898abcedf1521server1xyzRed Hat192.168.0.9921
ORCL2212.1.0.2452367ghdhjd1521server2xyzSUSE10.10.10.1021

Die Tabelle wir natürlich erweitert bzw. es können auch Zeilen gelöscht werden.

Aus dieser Tabelle möchte ich noch folgenden TreeView erstellen:

Code:
ROOT
 \__ 11.2.0.4
            \__ ORCL11:Patch#123456:SERVEROS#Red Hat
 \__ 12.1.0.2
            \__ ORCL21:Patch#567898:SERVEROS#Red Hat
            \__ ORCL22:Patch#452367:SERVEROS#SUSE
Den Root Node möchte ich nicht sehen, wenn ich es richtig verstanden habe benötigt aber jeder TreeView einen Root Node. Diesen kann man aber wohl über:
Java:
treeViewSID.setShowRoot(false);
ausblenden.

Dann sollte jeder Node aus dem TreeView der die SID enthält, also nicht die Parents (11.2.04 / 12.1.0.2 / etc.) "clickbar sein, da ich je nach Auswahl dann ein weiteres Fenster öffen möchte in dem ich dann die weiteren Details zu der SID darstelle und weiter Aktionen (Connect to SID, open putty to server, etc.) anbiete.

Das mit dem clicken der Nodes habe ich schon mal an Hand einer manuell erstellen TreeView getestet. Hier der Code dazu:
In der initialize() Methode einen EventHandler registrieren:
Java:
EventHandler<MouseEvent> mouseEventHandle = (MouseEvent event) -> {
            handleTreeMouseClicked(event);
        };

treeViewSID.addEventHandler(MouseEvent.MOUSE_CLICKED, mouseEventHandle);
Dann die Methode die augeführt wird wenn der Event erfolgt:
Java:
private void handleTreeMouseClicked(MouseEvent event) {
        Node node = event.getPickResult().getIntersectedNode();
        // Accept clicks only on node cells, and not on empty spaces of the TreeView
        if (node instanceof Text || (node instanceof TreeCell && ((TreeCell) node).getText() != null)) {
            String name = (String) ((TreeItem) treeViewSID.getSelectionModel().getSelectedItem()).getValue();
            if (!name.equals("11.2") && !name.equals("12.1") && !name.equals("12.2") && !name.equals("18.3"))
                System.out.println("Node click: " + name);
        }
    }
Frage:

Wie kann ich denn aus der Tabelle einen TreeView erstellen der die Versionen als Parent hat und alle SIDs je nach Version dem Parent zuordnet?

Gruß

Ralf
 
A

Anzeige


Vielleicht hilft dir dieser Kurs hier weiter: (hier klicken)
Hallo zusammen,

ich habe mal was versucht, das führt auch zu einem Ergebnis, ist aber bei weitem nicht dynamisch :eek:
Hier mal mein Versuch, mit der Hoffnung das Ihr noch bessere Ideen habt ;)

1. Ich babe in meinem Model pro Version, spricht Parent Node, eine List erstellt:
Java:
private List<TreeItem<String>> treeItemList11204;
private List<TreeItem<String>> treeItemList12102;
private List<TreeItem<String>> treeItemList12201;
private List<TreeItem<String>> treeItemList18300;
private List<TreeItem<String>> treeItemList18500;
Hierzu die entsprechenden Getter und Setter Methoden generiert, den Copde spare ich mir hier jetzt mal.

Im DAOService folgende Methode die mir eine List<TreeItem<String>> zurück gibt:
Java:
public List<TreeItem<String>> generateTreeItem(String version) {
        List<TreeItem<String>> treeItemList = new ArrayList<TreeItem<String>>();
        String querySidList;
        try {
            DataSourceSQLite sds = new DataSourceSQLite("oralabDdorf.db");
            conn = sds.getSQLiteDataSource().getConnection();
            querySidList = "select sid,patch,serveros from oralab where VERSION like '" + version + "%'";
            Statement stmt = conn.createStatement();
            ResultSet rset = stmt.executeQuery(querySidList);
            while (rset.next()) {
                String sidinfo = rset.getString(1) + ":Patch#" + rset.getString(2) + "@" + rset.getString(3);
                treeItemList.add(new TreeItem<String>(sidinfo));
            }
            
        } catch (Exception ex) {
            fireException(ex);
            return null;
        } finally {
            close();
        }
        
        return treeItemList;
    }
Und dann im MainController innerhalb eines Service Task folgender Code:

Java:
Service reloadInventoryService = new Service() {

        @Override
        protected Task createTask() {
            return new Task() {
                @Override
                protected Void call() throws Exception {
                    // Start entering execution code here ...
                    LOGGER.info("Creating SID TreeView.");
                    updateMessage("Creating SID Tree View. Please wait ...");
                    Platform.runLater(() -> {
                        model.setTreeItemList11204(dao.generateTreeItem("11.2"));
                        model.setTreeItemList12102(dao.generateTreeItem("12.1"));
                        model.setTreeItemList12201(dao.generateTreeItem("12.2"));
                        model.setTreeItemList18300(dao.generateTreeItem("18.3"));
                        model.setTreeItemList18500(dao.generateTreeItem("18.5"));
                        createTree();
                    });
                    return null;
                }

                @Override
                protected void succeeded() {
                    super.succeeded();
                    updateProgress(1.0, 1.0);
                    updateMessage("Done!");
                }

                @Override
                protected void cancelled() {
                    super.cancelled();
                    setFadeTransitionLabelErrorStatus(true);
                    updateMessage("Cancelled!");
                }

                @Override
                protected void failed() {
                    super.failed();
                    setFadeTransitionLabelErrorStatus(true);
                    updateMessage("Failed!");
                }
            };
        }
    };
Die Methode createTree():
Java:
public void createTree() {
        TreeItem<String> rootItem = new TreeItem<String>("Root Node");
        // Root Item
        rootItem.setExpanded(true);
        TreeItem<String> item11204 = new TreeItem<String>("11.2.0.4");
        TreeItem<String> item12102 = new TreeItem<String>("12.1.0.2");
        TreeItem<String> item12201 = new TreeItem<String>("12.2.0.1");
        TreeItem<String> item18300 = new TreeItem<String>("18.3.0.0");
        TreeItem<String> item18500 = new TreeItem<String>("18.5.0.0");
        
        model.getTreeItemList12102().forEach(item -> item12102.getChildren().add(item));
        model.getTreeItemList11204().forEach(item -> item11204.getChildren().add(item));
        model.getTreeItemList12201().forEach(item -> item12201.getChildren().add(item));
        model.getTreeItemList18300().forEach(item -> item18300.getChildren().add(item));
        model.getTreeItemList18500().forEach(item -> item18500.getChildren().add(item));

        // Add to Root
        rootItem.getChildren().addAll(item11204,item12102,item12201,item18300,item18500);

        // Set the Root Item
        treeViewSID.setRoot(rootItem);
        // Hide the Root Item
        treeViewSID.setShowRoot(false);
    }
Im Prinzip funktioniert das, hat aber folgende entscheidende Nachteile:
1. Gibt es in der Tabelle neue Versionen, z.B. 19.1, muss diese durch den Code (Model, MainController) durch implementiert werden.
2. Ein erneuter reload bei geänderter Tabelle (Tabelle extern) funktioniert auch nicht.

Ich hoffe Ihr könnt mir auf die Sprünge helfen.

Gruß

Ralf
 
Hi ralfb1105,

kannst du nicht einfach zuerst ein SQL Statment für die Versionen machen und die passenden TreeItems dazu und im zweiten Durlchlauf für die Kinder der Versionen? Die VersionsTreeItems eventuell in eine Map packen und beim Befüllen der Kinder die Eltern via Map holen.

Grüße
lam
 
Hallo lam,

Danke für Deine Idee/Deinen Tipp! Ich werde es mal probieren, mir ist noch nicht ganz klar wie das mit der Map der Eltern und dann in Beziehung zu den Kindern setzen funktinieren soll. Aber ich werde erst einmal versuchen eine DAOService Methode erstellen die mir die verfügbaren Versionen zurück gibt.

Gruß

Ralf
 
Kleines Beispiel wie man es machen könnte:
Java:
public class Main extends Application {
    @Override
    public void start( Stage primaryStage ) throws Exception {

        List<Entry> data = List.of(
                new Entry("ORCL11", "11.2.0.4", "123456", "Red Hat"),
                new Entry("ORCL21", "12.1.0.2", "567898", "Red Hat"),
                new Entry("ORCL22", "12.1.0.2", "452367", "SUSE"));

        // data würde bei dir aus der DB kommen
        Map<String, List<Entry>> map = data.stream().collect(Collectors.groupingBy(Entry::getVersion));

        TreeItem<Entry> root = new TreeItem<>(new Entry("", "", "", ""));

        map.values().forEach(list -> {
            TreeItem<Entry> parent = new TreeItem<>(new Entry("", list.get(0).getVersion(), "", ""));
            list.stream().map(TreeItem::new).forEach(parent.getChildren()::add);
            root.getChildren().add(parent);
        });

        TreeView<Entry> tree = new TreeView<>(root);
        tree.setShowRoot(false);
        tree.getSelectionModel().selectedItemProperty().addListener(( obs, oldValue, newValue ) -> {
            if ( newValue != null && !newValue.getValue().getSid().isEmpty() ) {
                Entry item = newValue.getValue();
                System.out.println(String.format("%s:Path#%s:SERVEROS#%s", item.getSid(), item.getPatch(), item.getServeros()));
            }
        });
        tree.setCellFactory(param -> new TreeCell<>() {
            @Override
            protected void updateItem( Entry item, boolean empty ) {
                super.updateItem(item, empty);
                if ( !empty ) {
                    String text = item.getSid()
                            .isEmpty() ? item.getVersion() : String.format("%s:Path#%s:SERVEROS#%s", item.getSid(), item
                            .getPatch(), item.getServeros());
                    setText(text);
                } else {
                    setText(null);
                    setGraphic(null);
                }
            }

        });
        primaryStage.setScene(new Scene(tree));
        primaryStage.show();

    }
}
Java:
public class Entry {
    private StringProperty sid;
    private StringProperty version;
    private StringProperty patch;
    private StringProperty serveros;


    public Entry( String sid, String version, String patch, String serveros ) {
        this.sid = new SimpleStringProperty(sid);
        this.version = new SimpleStringProperty(version);
        this.patch = new SimpleStringProperty(patch);
        this.serveros = new SimpleStringProperty(serveros);
    }

    public String getVersion() {
        return version.get();
    }

    public String getSid() {
        return sid.get();
    }

    public String getPatch() {
        return patch.get();
    }

    public String getServeros() {
        return serveros.get();
    }
}
Ergebnis:
11694
 

Anhänge

Hallo Robat,

vielen Dank schon mal :) ich schaue mir das Morgen mal an um zu sehen ob ich so weit verstehe um es bei mir zu implementieren. Sieht auf jeden Fall genauso aus wie ich es mir vorgestellt habe!!
Ich melde mich Morgen mit einem Status.

Gruß

Ralf
 
Hi Robat,

ich bin nur ein bisschen vertraut mit Lamda Ausdrücken. Dieses Beispiel ist an der Stelle sehr einfach, da das parent nie ändert. Wie würde der Lamda Code aussehen wenn ich rekursiv ein Node-Model mit Parent und Children in TreeItem mappen soll?

Code:
 map.values().forEach(list -> {
            TreeItem<Entry> parent = new TreeItem<>(new Entry("", list.get(0).getVersion(), "", ""));
            list.stream().map(TreeItem::new).forEach(parent.getChildren()::add);
            root.getChildren().add(parent);
        });
Grüße
lam
 
@Robat den Code kann man sicher noch schöner machen...

Code:
public class Node {

    private String name;

    private Node parent;

    private List<Node> children = new ArrayList<Node>();

    public Node(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Node getParent() {
        return parent;
    }

    public void setParent(Node parent) {
        this.parent = parent;
    }

    public List<Node> getChildren() {
        return children;
    }

    public void setChildren(List<Node> children) {
        this.children = children;
    }

}
Code:
public class Main {

    public void init() {
        Node parent = new Node("Root");
        Node a1Node = new Node("A1");
        Node a11Node = new Node("A1-1");
        Node a111Node = new Node("A1-1-1");
        a1Node.getChildren().add(a11Node);
        a11Node.getChildren().add(a111Node);
        a1Node.getChildren().add(new Node("A1-2"));
        a1Node.getChildren().add(new Node("A1-3"));
        parent.getChildren().add(a1Node);
    }
}
Und dieses Modell soll in TreeView dargestellt werden.
 
@lam_tr Jetzt weiß ich was du meinst. So richtig braucht man allerdings keine Lambda-Ausdruck. Zumindest nicht an der Stelle wie im ersten Beispiel:
Java:
public class Main extends Application {
    @Override
    public void start( Stage primaryStage ) throws Exception {

        Node parent = new Node("Root");

        Node a1Node = new Node("A1");
        Node a11Node = new Node("A1-1");
        a11Node.addChildren(new Node("A1-1-1"));
        a1Node.addChildren(a11Node);
        a1Node.addChildren(new Node("A1-2"));
        a1Node.addChildren(new Node("A1-3"));

        parent.addChildren(a1Node);

        Node a2Node = new Node("A2");
        a2Node.addChildren(new Node("A2-1"));
        parent.addChildren(a2Node);

        TreeItem<Node> parentItem = new TreeItem<>();
        addToTreeView(parentItem, parent);

        TreeView<Node> tree = new TreeView<>(parentItem);

        primaryStage.setScene(new Scene(tree));
        primaryStage.show();

    }

    private void addToTreeView( TreeItem<Node> parentItem, Node parent ) {
        parentItem.setValue(parent);
        parent.getChildren().forEach(node -> {
            TreeItem<Node> subParent = new TreeItem<>();
            addToTreeView(subParent, node);
            parentItem.getChildren().add(subParent);
        });

    }


    class Node {
        private String name;
        private List<Node> children;

        public Node( String name ) {
            this.name = name;
            this.children = new ArrayList<>();
        }

        @Override
        public String toString() {
            return name;
        }

        public void addChildren( Node node ) {
            children.add(node);
        }

        public List<Node> getChildren() {
            return children;
        }
    }
}
 
Hallo Robat,

auch von meiner Seite vielen Dank für das Beispiel, es funktioniert bei mir so wie von Dir dargestellt. Ich habe die Infos aus der DB in mein DAOService als Methode implementiert:

Java:
public List<Entry> generateTreeData() {
        List<Entry> dataEntries = new ArrayList<Entry>();

        String querySidList;
        try {
            DataSourceSQLite sds = new DataSourceSQLite("oralabDdorf.db");
            conn = sds.getSQLiteDataSource().getConnection();
            querySidList = "select sid,version,patch,serveros from oralab";
            Statement stmt = conn.createStatement();
            ResultSet rset = stmt.executeQuery(querySidList);
            while (rset.next()) {
                dataEntries.add(new Entry(rset.getString(1), rset.getString(2), rset.getString(3), rset.getString(4)));
            }

        } catch (Exception ex) {
            fireException(ex);
            return null;
        } finally {
            close();
        }
        return dataEntries;
    }
Im MainController in der initialize() Methode dann der Code zum erstellen der Map:
Java:
List<Entry> data = new ArrayList<Entry>(dao.generateTreeData());
        Map<String, List<Entry>> map = data.stream().collect(Collectors.groupingBy(Entry::getVersion));
Hier hätte ich jetzt noch eine Frage ;)

Frage:
Die Map ist ja von Haus aus unsortiert und ich habe keinen Weg gefunden die Map nach den Keys (Versionen) absteigend zu sortieren?

Gruß

Ralf
 
Hi Ralf,
schön, dass es geklappt hat.
Die Map ist ja von Haus aus unsortiert und ich habe keinen Weg gefunden die Map nach den Keys (Versionen) absteigend zu sortieren?
Das gilt für HashMaps. Wenn du eine TreeMap nimmst, kannst du einen Comparator mitgeben, nach welchem die Keys dann sortiert werden.
Java:
List<Entry> data = List.of(
        new Entry("ORCL11", "11.2.0.4", "123456", "Red Hat"),
        new Entry("ORCL21", "12.1.0.2", "567898", "Red Hat"),
        new Entry("ORCL22", "12.1.0.2", "452367", "SUSE"),
        new Entry("ORCL22", "12.1.0.3", "452367", "SUSE"));

Comparator<String> entryVersionComparator = ( entry, other ) -> {

    String[] entryNumbers = entry.split("\\.");
    String[] otherNumbers = other.split("\\.");

    for ( int i = 0; i < Math.max(entryNumbers.length, otherNumbers.length); i++ ) {
        int entryNumber = i < entryNumbers.length ? Integer.parseInt(entryNumbers[ i ]) : 0;
        int otherNumber = i < otherNumbers.length ? Integer.parseInt(otherNumbers[ i ]) : 0;

        if ( entryNumber < otherNumber ) {
            return 1;
        }
        if ( entryNumber > otherNumber ) {
            return -1;
        }
    }

    return 0;
};
Map<String, List<Entry>> map = data.stream().collect(Collectors.groupingBy(Entry::getVersion, () -> new TreeMap<>(entryVersionComparator), Collectors.toList()));
map.forEach(( key, value ) -> System.out.println(key));
Code:
12.1.0.3
12.1.0.2
11.2.0.4
Gruß Robert
 
Hallo Robert,

und nochmals VIELEN DANK.
Ich muss zugeben das ich das vermutlich ohne Hilfe nicht hinbekommen hätte. Ja, von Comperator habe ich schon gelesen, ob ich die Sortierung hinebkommen hätte .. na ja - großes ?

Die Zeile

Map<String, List<Entry>> map = data.stream().collect(Collectors.groupingBy(Entry::getVersion, () -> new TreeMap<>(entryVersionComparator), Collectors.toList()));
aber definitiv nicht - da fehlt mir noch ein großes Stück - daher vielen Dank für die Unterstützung.
Es hat wunderbar funktioniert:

11697

Gruß

Ralf
 
Ja, von Comperator habe ich schon gelesen, ob ich die Sortierung hinebkommen hätte .. na ja - großes ?
Wichtig ist nicht, ob du das ganze ad hoc alleine hinbekommen hättest. Viel wichtiger ist, dass du nachvollziehen kannst was der Code macht und es verinnerlichst.
da fehlt mir noch ein großes Stück - daher vielen Dank für die Unterstützung.
Es hat wunderbar funktioniert:
Hier das gleiche Spielchen. Wissen ist nicht alles auswendig zu können, sondern zu wissen, wo man nachschlagen kann ;)
 
Hallo Robat,

ich muss doch noch eine Frage zu dem TreeView stellen. Ich habe folgendes Verhalten, kein echtes Problem, eher "unschön" ;) und vielleicht hast Du eine Idee wie ich das lösen kann.

Ich habe den TreeView ja nach Deinem Beispiel implementiert und es funktioniert auch sehr gut. Das einzige Problem was ich sehe, wenn ich auf ein Item clicke wird die Aktion ausgeführt, getriggert durch den Listener. In meinem Fall öffne ich ein neues Fenster um weitere Aktionen zu der gewählten SID auszuführen. Schließe ich nach der Ausführung das Fenster und clicke erneut auf die SID in dem Tree *OHNE* den Baum vorher zu schließen und neu auf zu klappen, wird keine Aktion durchgeführt, ich vermute es liegt daran das sich der Wert nicht geändert hat und somit der Listener keine Veränderung von oldValue zu newValue feststellt.

Hier noch einmal der Code um den es geht:
Java:
// Add a listener to the items and store the clicked node value "sid" to the
        // Model StringProperty
        treeViewSID.getSelectionModel().selectedItemProperty().addListener((obs, oldValue, newValue) -> {
            if (newValue != null && !newValue.getValue().getSid().isEmpty()) {
                Entry item = newValue.getValue();
//                    System.out.println(
//                            String.format("%s:Path#%s:SERVEROS#%s", item.getSid(), item.getPatch(), item.getServeros()));
                model.setTreeItemSidClicked(item.getSid());
                proceedOperationActionBoxController();
            }
        });
Hast Du eine Idee wie ich eine Veränderung herbeiführe ohne den Baum schließen zu müssen?

Gruß

Ralf
 
Hallo Robat,

ich muss doch noch eine Frage zu dem TreeView stellen. Ich habe folgendes Verhalten, kein echtes Problem, eher "unschön" ;) und vielleicht hast Du eine Idee wie ich das lösen kann.

Ich habe den TreeView ja nach Deinem Beispiel implementiert und es funktioniert auch sehr gut. Das einzige Problem was ich sehe, wenn ich auf ein Item clicke wird die Aktion ausgeführt, getriggert durch den Listener. In meinem Fall öffne ich ein neues Fenster um weitere Aktionen zu der gewählten SID auszuführen. Schließe ich nach der Ausführung das Fenster und clicke erneut auf die SID in dem Tree *OHNE* den Baum vorher zu schließen und neu auf zu klappen, wird keine Aktion durchgeführt, ich vermute es liegt daran das sich der Wert nicht geändert hat und somit der Listener keine Veränderung von oldValue zu newValue feststellt.

Hier noch einmal der Code um den es geht:
Java:
// Add a listener to the items and store the clicked node value "sid" to the
        // Model StringProperty
        treeViewSID.getSelectionModel().selectedItemProperty().addListener((obs, oldValue, newValue) -> {
            if (newValue != null && !newValue.getValue().getSid().isEmpty()) {
                Entry item = newValue.getValue();
//                    System.out.println(
//                            String.format("%s:Path#%s:SERVEROS#%s", item.getSid(), item.getPatch(), item.getServeros()));
                model.setTreeItemSidClicked(item.getSid());
                proceedOperationActionBoxController();
            }
        });
Hast Du eine Idee wie ich eine Veränderung herbeiführe ohne den Baum schließen zu müssen?

Gruß

Ralf
Muss der TreeView eventuell nach dem Schließen des anderen Fensters refreshed werden?
 
Genau nach so etwas suche ich gerade ... der Baum an sich funktioniert ja einwandfrei, lediglich der Click auf das selbe Item triggered den Listener nicht. Wenn ich den Tree nach dem schließen des Fenster manuell zu klappe, dann wieder auf und auf das gleiche Item wie zuvor clicke geht es wieder ...
 
Passende Stellenanzeigen aus deiner Region:

Neue Themen

Oben