# Wie bettet man Programmcode in JavaFX FXML ein?



## MiMa (2. Feb 2018)

Hallo,
ich habe mich seit einigen Tagen mit der GUI Programmierung von JavaFX beschäftigt und möchte nun meinen Programmcode in eine Oberfläche mit FXML Dateien einbetten.
Mein Aktuelles Problem ist Programmcode so ein zu betten, das erzeugte Daten in der GUI sichtbar und auch mit dem Model aktualisiert werden.
Aktuell habe ich mit dem Scene Builder ein Frame mit Split Screen erzeugt.
Auf der linken Seite soll eine Dateiliste angezeigt und auf der rechten Seite sollen beim selektieren einer Datei die Detailinformationen angezeigt werden.
Aktuell habe ich nun ein eine Dateiliste als Observal<File> Liste vorliegen und weis jetzt nicht wie ich diese in der FXML so einbinde, damit diese angezeigt werden kann.

Mit puren JavaFX ohne FXML Datei habe ich das schon erledigen können, aber jetzt geht es darum mit FXML Dateien zu arbeiten. 
Die Daten kommen aus der Hauptplasse (Methode "start"). Der Hautscreen hat eine fxml Datei und diese hat wiederum einen eigenen Controller. Ich bin mir mittlerweile nicht mehr sicher, wie der Datenfluss realisiert wird, damit informationen die erzeugt wurden in der GUI landen?
Jedes Fenster hat seinen eigenen Controller der auf das Model zu greift. Nur ist mir derzeit nicht klar wie auf das Model zugegriffen wird.
In der start Methode wird das Fenster aufgerufen mit

```
Parent root = FXMLLoader.load(getClass().getResource("Indexer.fxml"));
```
Muss ich da jetzt wie auch bei den Methoden Dateninformation mitgeben um im Fenster etwas aus zu werten?
Was nützt mir sonst ein Fenster welches ich zwar aufrufen kann aber das keine Werte erhält?
Oft habe ich gelesen das es eine Dreicksbeziehung gibt. Das Fenster arbeitet mit dem Controller und dieser hat Kontakt mit dem Model. Um den Kontakt aber zum Objekt nicht zu verlieren muss ich es doch übergeben oder zumindest einen Verweis.
Wäre super wenn mir da jemand helfen könnte.
Danke
Mi


----------



## olfibits (2. Feb 2018)

Kleiner Tipp: https://docs.oracle.com/javafx/scenebuilder/1/get_started/jsbpub-get_started.htm


----------



## andy82 (3. Feb 2018)

```
FXMLLoader loader = new FXMLLoader(getClass().getResource("Indexer.fxml"));
Parent root = loader.load();
//loader.getController() liefert Dir den Controller.
```


----------



## MiMa (4. Feb 2018)

Vielen Dank, 
ich habe das Problem jetzt gelöst.
Die Dateien werden im ListView einer FXML angezeigt, also komplett mit Pfad und Dateinamen.

```
N:\Dateien\Quelle\Microsoft Excel-Arbeitsblatt (neu).xlsx
N:\Dateien\Office\PowerPoint\Microsoft PowerPoint-Präsentation (neu).pptx
N:\Dateien\Office\Visio\Microsoft Visio Drawing (neu).vsdx
N:\Dateien\Office\Word\Microsoft Word-Dokument (neu).docx
N:\Quelle\Bilder\Neue Bitmap.bmp
```
Lieber wäre mir wenn nur die Dateinamen in der ListView angezeigt werden würden.

```
Microsoft Excel-Arbeitsblatt (neu).xlsx
Microsoft PowerPoint-Präsentation (neu).pptx
Microsoft Visio Drawing (neu).vsdx
Microsoft Word-Dokument (neu).docx
Neue Bitmap.bmp
```
Eine Liste so zu schreiben das nur die Dateinamen enthalten sind ist für mich kein Problem. 
Da die Dateien rekursiv gesammelt werden ist der Dateipfad unterschiedlich und kann dann beim selektieren nicht geladen/angezeigt werden. 
Weiss jemand wie man so etwas realisieren kann?
Vielen Dank
Mi


----------



## mrBrown (4. Feb 2018)

Wenn die Liste der Dateinamen kein Problem ist, was ist denn dann das Problem?


----------



## MiMa (4. Feb 2018)

Die observerList<Files> enthält die Dateien mit Pfad und Namen.
Wenn ich jetzt nur die Dateinamen ohne Pfad in die Liste speichere kann ich die Dateien nicht mehr aufrufen da der Pfad der Dateien durch die rekursive Sammelaktion immer anders ist.
Meine Idee wäre eine zweispaltige ObseverList. Eine Spalte nur für den Dateinamen und die zweite für den Dateinamen mit vollständigen Pfad. Ich weiß nicht ob zwei Spalten in einer ObserverList unterstützt werden.
Oder ob das noch anders gemacht werde kann ?


----------



## mrBrown (4. Feb 2018)

Erstell dir ein passendes Objekt, das Methoden für alles hat, was du brauchst, und nutz das statt File oder String


----------



## MiMa (4. Feb 2018)

Du meinst ein dateiObjekt, welches Inhalte wie Dateiname, PfadMitDateiname, usw. Beinhaltet? 
Eine Klasse dafür zu machen, wäre das nicht überdimensioniert?
Naja, wenn schon, dann würde mir noch einfallen, Endung, Dateigröße, Filecheck, usw, dann würde es sich zumindest Lohnen!?


----------



## mrBrown (4. Feb 2018)

Eine Klasse kann nicht zu klein sein 

Entspricht dem Adapter-Pattern, enthalten muss das nur ein File-Objekt oder einen Stringe, für alles andere gibts nur Methoden


----------



## andy82 (5. Feb 2018)

MiMa hat gesagt.:


> Die observerList<Files> enthält die Dateien mit Pfad und Namen.
> ...


Files ist Deine eigene Klasse oder meinst Du java.io.File?. Ich würde von java.io.File eine neue Klasse, z.B. File2ShortName, ableiten lassen und die Methode toString() überschreiben, denn ListView zeigt immer toString()-Werte von Object-Item an.


----------



## MiMa (5. Feb 2018)

Vielen Dank für den Tipp.
Ich habe es mal gleich umgesetzt.

```
public class Datei {

    // Erstellt ein LoggerObjekt und holt dessen Instanz
    private static final Logger LOG = (Logger) LogManager.getLogger(Indexer.class);

    // Variablendeklaration
    private File dateiQuelle;
    private String dateiName;

    // Konstruktor mit 2 Parameter
    public Datei(File datei, String dateiName) {
        this.dateiQuelle = datei;
        this.dateiName = dateiName;
    } // Konstruktor mit 2 Parameter

    // Methoden

    public File getDateiQuelle() {
        return dateiQuelle;
    }

    public void setDateiQuelle(File dateiQuelle) {
        this.dateiQuelle = dateiQuelle;
    }

    public String getDateiName() {
        return dateiName;
    }

    public void setDateiName(String dateiName) {
        this.dateiName = dateiName;
    }
   
    // Rekursives einlesen von Dateien in eine ArrayListe mit Dateiobjekten
    // In der Liste die Datei mit Pfad und der Dateiname gespeichert
    public static ArrayList<Object> einlesenDateiObjekteR(File quellVerzeichnis, ArrayList<Object> dateiObjekte, String endung) {
        if (quellVerzeichnis == null || dateiObjekte == null || !quellVerzeichnis.isDirectory()) {
            LOG.error("Es konnten keine Dateien vom Typ " + endung + " eingelesen werden");
            return null;
        }
        // Datei-Array Rekursiv verarbeiten
        LOG.info("Einlesen der Dateien - beginnt");
        File[] dateiArray = quellVerzeichnis.listFiles();
        for (File datei : dateiArray) {
            if (datei.isDirectory()) {
                einlesenDateiObjekteR(datei, dateiObjekte, endung);
            }
            // Nur Dateien mit der endung anhängen
            String dateiName = datei.getName();
            if (dateiName.toLowerCase().endsWith(endung)) {
                // Dateiobjekt Datei und Dateinamen hinzufügen
                dateiObjekte.add(new Datei(datei, datei.getName()));
            } // Nur Dateiobjekte mit der endung anhängen
        } // Datei-Array abarbeiten
        LOG.info("Einlesen der Dateien - beendet");
        return dateiObjekte;
    } // einlesenDateienR
} // Datei
```
Der Controller für das Fenster

```
public class IndexerCON implements Initializable {
   
    // Variablendeklaration
    @FXML public ListView<File> dateiListView;
   

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        // Einlesen der Dateiliste
        ArrayList<Object> dateiListe = new ArrayList<>();
        dateiListe = Datei.einlesenDateiObjekteR(new File("N:/Indexer/Quelle"), dateiListe, "");
        ObservableList<Object> dateien = FXCollections.observableArrayList(dateiListe);
        dateiListView.setItems(dateien.???);
    } // initialize
```
Mein Problem habe ich mit ein paar Fragezeichen gekennzeichnet.
Ich weiss nicht genau wie ich die Daten der entsprechenden Objekte in den FXCollections definieren kann. Habe zwar schon alle Möglichen Kombinationen die ich kenne ausprobiert. 
Vielleicht mache ich das ja auch völlig falsch?


----------



## MiMa (5. Feb 2018)

andy82 hat gesagt.:


> Files ist Deine eigene Klasse oder meinst Du java.io.File?. Ich würde von java.io.File eine neue Klasse, z.B. File2ShortName, ableiten lassen und die Methode toString() überschreiben, denn ListView zeigt immer toString()-Werte von Object-Item an.


Die Dateien in der ArrayListe waren vom Typ File aus java.io. Ich habe da keine eigene Klasse erstellt.
Wie mrBrown vorgeschlagen, habe ich jetzt eine eigene Klasse "Dateien" erstellt und nun Objekte in die ArrayList abgelegt. Weiss aber nicht genau ich ich nur den Dateianamen in die ListView bekomme.


----------



## andy82 (5. Feb 2018)

MiMa hat gesagt.:


> ... Weiss aber nicht genau ich ich nur den Dateianamen in die ListView bekomme.


Jeder kann eine eigene Stil haben, ich würde immer direkt Object benutzen(in diesem Fall eine FileCustome extends java.io.File als ListView-Item benuzen), denn auf lage Sicht String als ListView-Item hat vielen Nachteile - wieviel Aufwand muß Du Dich investieren, wenn in der List noch Image, Icon ... angezeigt werden sollen. Default zeigt ListView File.toString() (=getPath()) an, Du braucht nur die Methode toString() in FileCustome zu überschreiben, d.h. toString() liefert anstatt File.getPath() deine berechnete shortName-Werte.


----------



## mrBrown (5. Feb 2018)

andy82 hat gesagt.:


> Jeder kann eine eigene Stil haben, ich würde immer direkt Object benutzen(in diesem Fall eine FileCustome extends java.io.File) als ListView-Item benuzen


Du meinst damit aber jetzt hoffentlich nicht, Object als generischer Typ für die Liste nutzen?



MiMa hat gesagt.:


> Weiss aber nicht genau ich ich nur den Dateianamen in die ListView bekomme.


Du kannst eine CellFactory dafür angeben und musst der ListView dann nur die normale Liste übergeben (und solltest dann auch sinnvoll Generics nutzen) 

toString würde ich dafür nicht überschreiben - ist zwar eine schnelle, aber meistens nicht die sinnvollste Lösung.
Genauso würde ich auch auf Vererbung verzichten, das ist einer dieser Fälle, in denen man das "Composition over Inheritance" noch mal betonen muss. Mit zB einer simplen Umsetzung des Adapter-Patterns ist man hier besser bedient.


----------



## andy82 (5. Feb 2018)

mrBrown hat gesagt.:


> ...
> 
> toString würde ich dafür nicht überschreiben - ist zwar eine schnelle, aber meistens nicht die sinnvollste Lösung.
> Genauso würde ich auch auf Vererbung verzichten, das ist einer dieser Fälle, in denen man das "Composition over Inheritance" noch mal betonen muss. Mit zB einer simplen Umsetzung des Adapter-Patterns ist man hier besser bedient.


Wenn ich die Code von JavaFx lese, z.B. bei der Benutzung von CellFactory ... benutzt man heute sehr oft Inner- und und AnonymClass. Ich bin von der alten Schule, damals Inner- und AnonymClass als nicht sehr saubere Stil gesehen, anstatt werden Verbungen oft angewendet. Daran bin ich gewohnt. Im Grunde Inner- und AnonymClass sind auch Unterklasse.


----------



## mrBrown (5. Feb 2018)

andy82 hat gesagt.:


> Wenn ich die Code von JavaFx lese, z.B. bei der Benutzung von CellFactory ... benutzt man heute sehr oft Inner- und und AnonymClass. Ich bin von der alten Schule, damals Inner- und AnonymClass als nicht sehr saubere Stil gesehen, anstatt oft Verbungen angewendet. Daran bin ich gewohnt. Mehr habe ich nicht zu sagen.


Vererbung war schon lange vor Java der deutlich unsaubere Stil und hat in diesem Fall auch überhaupt nicht die inneren/anonymen Klassen. Das einzige was einem Vererbung hier erspart ist sauberes Design. 
Ob man eine CellFactory nutzt oder nicht ist davon unabhängig, in beiden Fällen kann man toString überschrieben. Ich würde toString aber nicht dafür nutzen, weil man es besser anders lösen kann und toString auch besser anders überschreiben kann - es ist nicht primär für die Darstellung, sondern für die Entwicklung gedacht, und sollte auch dafür nützliche Dinge enthalten, was mehr wäre, als nur der Dateiname.


("Ich bin von der alten Schule [...] Daran bin ich gewohnt" ruft bei mir immer die Vorstellung hervor, dass jemand seit 40  Jahren alles gleich macht und seit dem auch nichts neues gelernt hat)


----------



## MiMa (5. Feb 2018)

Habe ich das mit der Klasse und den Controller in etwa richtig gemacht?
Morgen werde ich noch mal schauen wie ich aus der ArrayList<Object> den richtigen Wert aus den Objekten erhalte.


----------



## mrBrown (5. Feb 2018)

Im Großen und Ganzen passt das 

Man könnte die Klasse noch einfacher gestalten, zB kann man den Dateinamen im Getter jedes mal aus dem File-Object abfragen, anstatt ihn als Feld anzulegen.

Und bisher werden ja noch nicht die richtigen Namen angezeigt, oder?


----------



## MiMa (6. Feb 2018)

Der richtige Name wird noch nicht angezeigt. Darum kümmere ich mich heute.


----------



## MiMa (6. Feb 2018)

Ich habe meine Methode in der Klasse Datei wie flgt geändert.

```
public static ArrayList<Datei> einlesenDateiObjekteR(File quellVerzeichnis, ArrayList<Datei> dateiObjekte, String endung) {
        if (quellVerzeichnis == null || dateiObjekte == null || !quellVerzeichnis.isDirectory()) {
            LOG.error("Es konnten keine Dateien vom Typ " + endung + " eingelesen werden");
            return null;
        }
        // Datei-Array Rekursiv verarbeiten
        LOG.info("Einlesen der Dateien - beginnt");
        File[] dateiArray = quellVerzeichnis.listFiles();
        for (File datei : dateiArray) {
            if (datei.isDirectory()) {
                einlesenDateiObjekteR(datei, dateiObjekte, endung);
            }
            // Nur Dateien mit der endung anhängen
            String dateiName = datei.getName();
            if (dateiName.toLowerCase().endsWith(endung)) {
                // Dateiobjekt Datei und Dateinamen hinzufügen
                dateiObjekte.add(new Datei(datei, datei.getName()));
            } // Nur Dateiobjekte mit der endung anhängen
        } // Datei-Array abarbeiten
        LOG.info("Einlesen der Dateien - beendet");
     
        return dateiObjekte;
    } // einlesenDateienR
```
Der Controller

```
public class IndexerCON implements Initializable {
 
    // Variablendeklaration
    @FXML public ListView<String> dateiListView;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        // Einlesen der Dateiliste
        ArrayList<Datei> dateiObjekte = new ArrayList<>();
        dateiObjekte = Datei.einlesenDateiObjekteR(new File("N:/Indexer/Quelle"), dateiObjekte, "");
     
        ObservableList<Datei> dateien = FXCollections.observableArrayList(dateiObjekte);
        for (int i = 0; i < dateien.size(); i++) {
        dateiListView.setItems(dateien.get(i).getDateiName());
        }
    } // initialize
} // IndexerCON
```
Allerdings funktioniert es immer noch nicht, weil es wohl ein Kompatibiltätsproblem mit den Typen der mit der ObseverList gibt?
Incompatible types: String can not be convert to ObservaLList<String>
Bei der Deklaration des ListView habe ich diesen auf String gesetzt, weil der Dateiname einen String zurückgibt.

```
// Variablendeklaration
    @FXML public ListView<String> dateiListView;
```


----------



## mrBrown (6. Feb 2018)

Deine ListView muss eine LstView<Datei> bleiben, und als Items setzt du dateien.

Du musst dann noch entweder toString passend überschreiben oder eine CellFactory setzten, damit das passende in der Liste angezeigt wird


----------



## MiMa (6. Feb 2018)

Es klappt leider immer noch nicht 
Bin langsam an verzweifeln. Muss ich dafür Property verwenden?

```
public class DateiListeController implements Initializable {
  
    // Variablendeklaration
    @FXML
    public ListView<Datei> dateiListView;

    @Override
    public void initialize(URL url, ResourceBundle rb) {
       // Einlesen der Dateiliste
        ArrayList<Datei> dateiObjekte = new ArrayList<>();
        dateiObjekte = Datei.einlesenDateiObjekteR(new File("N:/Indexer/Quelle"), dateiObjekte, "");

        ObservableList<Datei> dateien = FXCollections.observableArrayList(dateiObjekte);
        dateiListView.setCellFactory(new Callback<ListView<Datei>, ListCell<Datei>>() {
            @Override
            public ListCell<Datei> call(ListView<Datei> param) {
                return new Zelle();
            }
        });
        dateiListView.setItems(dateien);
    } // initialize

public class Zelle extends ListCell{
        protected void updateItem(Datei dateien, boolean empty) {
            super.updateItem(dateien, empty);
            setText(dateien.getDateiName());
        }
    } // Zelle  
} // DateiListeController
```


----------



## mrBrown (6. Feb 2018)

MiMa hat gesagt.:


> Es klappt leider immer noch nicht


Und was klappt nicht...?


----------



## MiMa (6. Feb 2018)

Die Liste mit den Dateinamen bleibt einfach leer.
Habe mich jetzt den ganzen Tag mit CellFactory und List cell auseinandergesetzt, aber anscheinend mach ich da immer noch Fehler. Wenn ich Ehrlich bin gefällt mir meine Lösung mit der zusätzlichen Zelle Klasse nicht besonders.
Mit den Strings hat es geklappt, mit den Objekten ist es aber ganz schön kompliziert geworden.


----------



## mrBrown (6. Feb 2018)

Wenn die Liste einfach leer bleibt, ist das Problem etwas anderes. zB, dass die Liste wirklich leer ist, mal mit dem Debugger überprüft?


----------



## MiMa (6. Feb 2018)

Ja habe ich überprüft.
Im dateiObjekt stimmen die Werte der Datei und Dateinamen.


----------



## mrBrown (6. Feb 2018)

Dann lass den Teil mit der CellFactory weg, sehen solltest du dann auf jeden Fall was - damit's schön ist, kannst du dann toString von Datei überschreiben


----------



## MiMa (6. Feb 2018)

Wie soll ich das ohne CellFactory denn schreiben?
Mittlerweile ist mein Kopf völlig leer! 
Wenn ich den ganzen Factory Kram weg lasse, erhalte ich einen Eintrag.
Dieser sollte "Rechnung001.pdf sein
Angezeigt wird aber
de.mima.indexer.model.Datei@64b37b87


----------



## mrBrown (6. Feb 2018)

Wie gesagt: überschreib toString passend


----------



## MiMa (6. Feb 2018)

Die Richtige Ausgabe habe ich auf der Konsole erhalten mit

```
System.out.println(dateien.get(0).getDateiName());
```
Ich habe wie folgt toString() überschrieben

```
public String toString() {
       return getClass().getName();
   }
```
Ich erhalte einen Fehler der besagt:
	
	
	
	





```
incompatible types: String can not be converted to ObservableList<Datei>
```


```
ObservableList<Datei> dateien = FXCollections.observableArrayList(dateiObjekte);
        dateiListView.setItems(dateien.toString());
```
Habe auch probiert mal ein Ergebnis hiermit zu erzeugen

```
ObservableList<Datei> dateien = FXCollections.observableArrayList(dateiObjekte);
        dateiListView.setItems(dateien.getClass().getName());
```
Auch wieder den gleichen Fehler inkompatible Typen.
Mach ich da was falsch ??


----------



## MiMa (6. Feb 2018)

Das Überschreiben hat nun endlich geklappt 

```
public String toString() {
        return String.format("%s", getDateiName());
    }
```
Hab dafür einen ganzen Tag gebraucht 
Hoffe das die nächsten Probleme nicht so lange benötigen.
Als nächstes steht an, die weiteren Informationen eines selektierten objektes in einem anderen Fenster an zu zeigen.


----------



## mrBrown (6. Feb 2018)

MiMa hat gesagt.:


> Das Überschreiben hat nun endlich geklappt
> 
> ```
> public String toString() {
> ...


Ein `return getDateiName();` hätte dafür gereicht


----------



## MiMa (6. Feb 2018)

Danke, das werde ich noch korrigieren.


----------



## Flown (7. Feb 2018)

Nachdem es ja gelöst ist, einige Anmerkungen:
- Wenn du ListCell überschreibst, dann bitte aber mit Generics und kein Raw-type
- Das Interface Initializable ist quasi deprecated und kann durch @FXML annotation ersetzt werden
- Es gibt unglaublich hilfreiche API, um einen File-Tree zu durchlaufen

Ein kleines Beispiel

```
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.ListView?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.121"
            xmlns:fx="http://javafx.com/fxml/1" fx:controller="at.flown.controller.FilesController">
    <children>
        <ListView fx:id="listView" layoutX="206.0" layoutY="69.0" AnchorPane.bottomAnchor="0.0"
                  AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"/>
    </children>
</AnchorPane>
```


```
package at.flown;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class FileListViewer extends Application {
  
  public static void main(String... args) {
    launch(args);
  }
  
  public void start(Stage primaryStage) throws Exception {
    Parent files = FXMLLoader.load(getClass().getResource("/view/Files.fxml"));
    Scene scene = new Scene(files);
    primaryStage.setScene(scene);
    primaryStage.setTitle("FileListViewer");
    primaryStage.show();
  }
}
```


```
package at.flown.controller;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class FilesController {
  
  @FXML
  private ListView<Path> listView;
  
  @FXML
  private void initialize() {
    ObservableList<Path> paths = FXCollections.emptyObservableList();
    try (Stream<Path> pathStream = Files.walk(Paths.get("C:\\gradle-4.2.1\\lib"))) {
      paths = pathStream.filter(p -> !Files.isDirectory(p)).collect(Collectors.toCollection(FXCollections::observableArrayList));
    } catch (IOException e) {
      e.printStackTrace();
    }
    listView.setItems(paths);
    listView.setCellFactory(param -> new ListCell<>() {
      @Override
      protected void updateItem(Path item, boolean empty) {
        super.updateItem(item, empty);
        if (!empty) {
          setText(item.getFileName().toString());
        }
      }
    });
  }
}
```


----------



## MiMa (7. Feb 2018)

Danke an alle


----------

