# JavaFX in mehrere Controller aufteilen



## Knoten (4. Jan 2018)

Hallo, das Thema gehört vermutlich eher zu den Anfängerfragen - aber hier ist es wegen der Verwendung von JavaFX auch angebracht.
Ich möchte für die bessere Übersicht ein JavaFX-Projekt so gestalten, dass es ein Haupfenster gibt (eine "Main"FXML mit dem "MainController"). 
In dieses kommt dann eine TabPane rein.
Jedes Tab soll seine eigene FXML und seinen eigenen Controller haben, da sich das meiste immer innerhalb dieser "Unterprogramme" abspielt.
Da es sich aber um eine einzige Gesamt-Applikation handelt, ist es teilweise notwendig, dass ein Controller auf Methoden eines anderen Controllers zugreifen kann und mit diesem Daten austauschen kann. 
Sowohl der "MainController" muss auf die einzelnen "TabController" zugreifen können (wie auch umgekehrt die TabController auf den MainController) und die TabController untereinander ebenso. 

Hier fehlt mir komplett der intellektuelle Zugang...
Ich habe den ganzen Tag verschiedene Ansätze aus anderen Post der weltweiten Intelligenz verfolgt, scheitere aber immer. Oft liest man auch den Vorschlag, eine "Factory" dafür zu verwenden - aber das ist dann sicher auch nicht banal und vor allem verstehe ich dann die tiefere Funktionsweise garantiert nicht. Und ein Verständnis wäre mir schon angenehm...

Ich habe ein simples Gerüst für so eine Anwendung erstellt, nur 2 Tabs, einmal mit einem Label und der andere mit einer CheckBox, das MainFormular hat außer der TabPane, welche mit diesen beiden Tabs gefüllt ist noch einen Button:

der Tab1Controller "FXMLTab1Controller.java":

```
package mytest;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.layout.AnchorPane;

/**
* FXML Controller class
*
* @author hth
*/
public class FXMLTab1Controller implements Initializable {

    @FXML
    private AnchorPane tab1;
    @FXML
    public Label label1;

    /**
     * Initializes the controller class.
     */
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        // TODO
    }   
   
}
```

und dazu das FXML-file "FXMLTab1.fxml":

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

<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane id="AnchorPane" fx:id="tab1" prefHeight="126.0" prefWidth="127.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="mytest.FXMLTab1Controller">
   <children>
      <Label fx:id="label1" layoutX="42.0" layoutY="46.0" prefHeight="17.0" prefWidth="50.0" text="Label" />
   </children>
</AnchorPane>
```


der Tab2Controller "FXMLTab2Controller.java":

```
package mytest;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.CheckBox;
import javafx.scene.layout.AnchorPane;

/**
* FXML Controller class
*
* @author hth
*/
public class FXMLTab2Controller implements Initializable {

    @FXML
    private AnchorPane tab2;
    @FXML
    private CheckBox checkbox1;

    /**
     * Initializes the controller class.
     */
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        // TODO
    }   
   
}
```

und dazu das FXML-file"FXMLTab2.fxml":

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

<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane id="AnchorPane" fx:id="tab2" prefHeight="135.0" prefWidth="118.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="mytest.FXMLTab2Controller">
   <children>
      <CheckBox fx:id="checkbox1" layoutX="23.0" layoutY="59.0" mnemonicParsing="false" text="CheckBox" />
   </children>
</AnchorPane>
```

dann der MainController "FXMLmainController.java":

```
package mycontrollertest;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;

/**
*
* @author hth
*/
public class FXMLmainController implements Initializable {

    @FXML private Button but_main;   
    @FXML private FXMLTab1Controller fXMLTab1Controller;
    @FXML private FXMLTab2Controller fXMLTab2Controller; 
   
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        // TODO
    }   

    @FXML
    private void do_something(ActionEvent event) {
       
    }
   
}
```

und dessen FXML-file "FXMLmain.fxml":

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

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane id="AnchorPane" prefHeight="329.0" prefWidth="308.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="mycontrollertest.FXMLmainController">
   <children>
      <TabPane layoutX="26.0" layoutY="10.0" prefHeight="200.0" prefWidth="200.0" tabClosingPolicy="UNAVAILABLE">
        <tabs>
          <Tab text="tab1">
            <content>
              <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="106.0" prefWidth="200.0">
                     <children>
                        <fx:include fx:id="tab1" source="FXMLTab1.fxml" />
                     </children>
                  </AnchorPane>
            </content>
          </Tab>
          <Tab text="tab2">
            <content>
              <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0">
                     <children>
                        <fx:include fx:id="tab2" source="FXMLTab2.fxml" />
                     </children>
                  </AnchorPane>
            </content>
          </Tab>
        </tabs>
      </TabPane>
      <Button fx:id="but_main" layoutX="126.0" layoutY="290.0" mnemonicParsing="false" onAction="#do_something" text="mainbutton" />
   </children>
</AnchorPane>
```

und die "Projekt-Chefdatei""MyControllerTest.java"::

```
package mycontrollertest;

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

/**
*
* @author hth
*/
public class MyControllerTest extends Application {
   
    @Override
    public void start(Stage stage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("FXMLmain.fxml"));
       
        Scene scene = new Scene(root);
       
        stage.setScene(scene);
        stage.show();
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }
   
}
```

Dieses Gerüst macht natürlich noch nichts - aber alle meine Versuche mit den zusammengelesenen Tips scheiterten.
Was muss ich z.B. tun, um in der "do_something()" Methode des MainControllers das Label des Tab1Controllers zu beeinflussen?
Die Existenz des Tab1Controllers und dessen Elementen muss doch irgendwie im MainController bekannt gemacht werden können (was ja mit "    @FXML private FXMLTab1Controller fXMLTab1Controller;" bereits möglich sein könnte? Aber irgendwelche Zuweisungen mit fXMLTab1Controller.label1 wie beiner einfachen Komponente funktionieren nicht, da das keine echte Instanz ist. Aber wie deklariere ich das dann?

Sorry, ich tue mir zugegebenermaßen schon mächtig schwer mit der Struktur dieser Hochsprache und hoffe auf größtmögliche Nachsicht.
Kann mich jemand erleuchten und meine Pein mildern?
Danke!

meine Entwicklungsumgebung:
*Product Version:* NetBeans IDE 8.2 (Build 201705191307)
*Updates:* NetBeans IDE is updated to version NetBeans 8.2 Patch 2
*Java:* 1.8.0_112; Java HotSpot(TM) 64-Bit Server VM 25.112-b15
*Runtime:* Java(TM) SE Runtime Environment 1.8.0_112-b15
*System:* Windows 7 version 6.1 running on amd64; Cp1252; de_DE (nb)


----------



## truesoul (4. Jan 2018)

Hallo. 

Also so wie ich dich verstanden habe willst du in den unterschiedlichen Controllern gleiche Datensätze beeinflussen (editieren, hinzufügen, löschen und lesen)?

Mal ein Beispiel wie es mit Factory funktionieren könnte:


```
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class UserdataFactory {

    private static UserdataFactory factory;
    private List<String> data = new ArrayList<>();

    private UserdataFactory() {
        // Soll von aussen nicht aufrufbar sein
    }

    public static UserdataFactory getFactory() {
        if (factory == null) {
            factory = new UserdataFactory();
        }
        return factory;
    }

    public List<String> getData() {
        return data;
    }

    public boolean removeData(String data) {
        return this.data.remove(data);
    }

    public void addData(String... values) {
        this.data.addAll(Arrays.asList(values));
    }

}

class MainController {
    private UserdataFactory userData = UserdataFactory.getFactory();
   
    private void do_something(ActionEvent event) {
        // Mach was mit userData
    }   
}

class TabController {
    private UserdataFactory userData = UserdataFactory.getFactory();
   
    private void do_something(ActionEvent event) {
        // Mach was mit userData
    }   
}
```

Da UserdataFactory nur einmal exisiteren kann, siehe getFactory hast du überall die Möglichkeit mit den gleichen Datenbestand zu arbeiten. 

Grüße


----------



## Knoten (4. Jan 2018)

Vielen Dank für die schnelle Antwort.
Das wäre eine Lösung, wenn es mir nur um einen überschaubaren zentralen reinen Datenbestand geht.
Ich möchte jedoch auf die Elemente der anderer Controller zugreifen. 
Zum Beispiel eben das label1 aus dem Tab1Controller.
Wie bekomme ich eine Einflussmöglichkeit darauf vom MainController oder vom Tab2Controller?


----------



## truesoul (4. Jan 2018)

Hallo.

Also der Wert der in label1 steckt muss ja irgendwo "abgespeichert/hinterlegt" werden. 
Ein Beispiel wäre:


```
class User {
        private String firstname;

        public String getFirstname() {
            return firstname;
        }

        public void setFirstname(String firstname) {
            this.firstname = firstname;
        }
    }
   
    class UserdataFactory {
       
        private static UserdataFactory factory;
        private User user;

        private UserdataFactory() {
            // Soll von aussen nicht aufrufbar sein
        }

        public static UserdataFactory getFactory() {
            if (factory == null) {
                factory = new UserdataFactory();
            }
            return factory;
        }
       
        public User getUser() {
            return user;
        }
       
    }
```

Wenn jetzt in label1 z. B der Vorname von einem User steckt hast du von überall den Einfluss darauf. 

Grüße


----------



## mrBrown (4. Jan 2018)

Die Unter-Controller werden in den Main-Controller injiziert, wenn du da den Namenskonventionen für die Attribute folgst. Für Controller ist das fx:id+"Controller"

In deinem Fall müsstest du zB `@FXML private FXMLTab2Controller fXMLTab2Controller;` ändern zu
`@FXML private FXMLTab2Controller tab2Controller;`


----------



## Knoten (4. Jan 2018)

Ihr bemüht Euch redlich, vielen Dank!
Um bei meinem Beispiel zu bleiben, habe ich diese Factory Klasse erstellt:

```
public class UserdataFactory {
   
        private static UserdataFactory factory;
        private FXMLTab1Controller user;

        private UserdataFactory() {
            // Soll von aussen nicht aufrufbar sein
        }

        public static UserdataFactory getFactory() {
            if (factory == null) {
                factory = new UserdataFactory();
            }
            return factory;
        }
      
        public FXMLTab1Controller getUser() {
            return user;
        }

}
```

und meinen Tab1Controller so angepasst:


```
public class FXMLTab1Controller implements Initializable {

    @FXML
    private AnchorPane tab1;
    @FXML
    public Label label1;

    private UserdataFactory userData = UserdataFactory.getFactory();
  
    /**
     * Initializes the controller class.
     */
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        // TODO
    }   
   
    public void setlabelcontent(String inhalt) {
        label1.setText(inhalt);
    }
}
```

und den MainController so:


```
public class FXMLmainController implements Initializable {

    @FXML private Button but_main;   
    //@FXML private FXMLTab1Controller fXMLTab1Controller;
    //@FXML private FXMLTab2Controller fXMLTab2Controller; 
    @FXML private FXMLTab1Controller tab1Controller;
    @FXML private FXMLTab2Controller tab2Controller; 
   
    private UserdataFactory userData = UserdataFactory.getFactory();
  
   
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        // TODO
    }   

    @FXML
    private void do_something(ActionEvent event) {
        tab1Controller=userData.getUser();
        // kompiliert fehlerfrei aber ich kann aber auf keinen Inhalt dieses Controllers zugreifen
    }
   
}
```


Aber wie kann ich nun z.B. im MainController auf die Methode setlabelcontent() des Tab1Controller zugreifen?
Das ist immer noch mein Eingangsproblem. 

Ich bringe Euch sicherlich zur Weissglut - aber wenn ich es raffe gebe ich Ruhe!


----------



## mrBrown (4. Jan 2018)

Knoten hat gesagt.:


> Aber wie kann ich nun z.B. im MainController auf die Methode setlabelcontent() des Tab1Controller zugreifen?


So wie man ganz Normal Methoden aufruft?
`tab1Controller.setlabelcontent("irgendein String")`


----------



## Knoten (4. Jan 2018)

Vielen Dank, das habe ich natürlich auch zuvor schon in allen Varianten ausprobiert, aber er kennt die Methoden des Tab1Controllers schlichtweg nicht:

_PopUp-Fehlermeldung:_
cannot find symbol
  symbol:   method setlabelcontent(String)
  location: variable tab1Controller of type FXMLTab1Controller
----
(Alt-Enter shows hints)

(ich wollte einen Screenshot einfügen aber da finde ich keine Funktion zum Einfügen für Bilder direkt vom PC)


----------



## Knoten (4. Jan 2018)

Also das ist mein Code:

```
public class FXMLmainController implements Initializable {

    @FXML private Button but_main;   
    //@FXML private FXMLTab1Controller fXMLTab1Controller;
    //@FXML private FXMLTab2Controller fXMLTab2Controller; 
    @FXML private FXMLTab1Controller tab1Controller;
    @FXML private FXMLTab2Controller tab2Controller; 
   
    private UserdataFactory userData = UserdataFactory.getFactory();
  
   
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        // TODO
    }   

    @FXML
    private void do_something(ActionEvent event) {
        tab1Controller=userData.getUser();
        tab1Controller.setlabelcontent("irgendein String");
        // kompiliert fehlerfrei aber ich kann aber auf keinen Inhalt dieses Controllers zugreifen
    }
   
}
```

und die Zeile
        tab1Controller.setlabelcontent("irgendein String");
wird rot unterstrichen mit dem Hinweis:

cannot find symbol
symbol: method setlabelcontent(String)
location: variable tab1Controller of type FXMLTab1Controller
----
(Alt-Enter shows hints)


----------



## mrBrown (4. Jan 2018)

Dann entspricht der Code hier nicht deine Projekt oder etwas an deinem System ist nicht in Ordnung.

Die Zeile über dem Aufruf ist btw ziemlicher Unsinn.


----------



## Knoten (4. Jan 2018)

Ja stimmt, aber über diese Zeile wird nicht gemeckert. Wenn ich sie weglasse, ist das auch kein Unterschied.
War nur zum Testen, ob das fehlerfrei angenommen wird.

Der Code ist tatsächlich 1:1 mein TestProjekt!
Aber dann werde ich jetzt alles mal auf einem anderen PC aufsetzen und berichten. Dauert ein wenig...

Übrigens: Die Vorschläge, die angezeigt werden, wenn ich tab1Controller. eintippe sind:

equals(Object obj)
getClass()
hashCode()
initialize (URL url, ResourceBundle rb)
notify()
notifyAll()
toString()
wait()
wait(long timeout)
wait (long timeout, int nanos)

also tab1Controller ist nicht unbekannt aber die Methoden sind nicht sichtbar.
Sonst müsste hier auch 
setlabelcontent(String)
aufgelistet sein


----------



## mrBrown (4. Jan 2018)

Knoten hat gesagt.:


> Der Code ist tatsächlich 1:1 mein TestProjekt!


Wenn ich deinen Code hier kopiere funktioniert er ^^



Knoten hat gesagt.:


> also tab1Controller ist nicht unbekannt aber die Methoden sind nicht sichtbar.


Ist die Methode public?


----------



## Knoten (4. Jan 2018)

Dank für die Info,
ja, die Methode ist public (wie oben im Code gezeigt).
Ich lade Netbeans gerade auf einem anderen PC runter und lade das Projekt dann rüber.


----------



## Knoten (4. Jan 2018)

Ich habe jetzt das System neu aufgesetzt und siehe da, die Methode wird erkannt .
Das ist schon mal der Hammer!

Aber beim Aufruf im MainController (über den Button des Main FXML) wird ein Laufzeitfehler generiert:

```
jfx-project-run:
Executing H:\_temp\test\MyControllerTest\dist\run2141105447\MyControllerTest.jar using platform C:\PROGRA~1\Java\JDK18~1.0_1\jre/bin/java
Exception in thread "JavaFX Application Thread" java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
    at javafx.fxml.FXMLLoader$MethodHandler.invoke(FXMLLoader.java:1774)
    at javafx.fxml.FXMLLoader$ControllerMethodEventHandler.handle(FXMLLoader.java:1657)
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
    at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
    at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:49)
    at javafx.event.Event.fireEvent(Event.java:198)
    at javafx.scene.Node.fireEvent(Node.java:8413)
    at javafx.scene.control.Button.fire(Button.java:185)
    at com.sun.javafx.scene.control.behavior.ButtonBehavior.mouseReleased(ButtonBehavior.java:182)
    at com.sun.javafx.scene.control.skin.BehaviorSkinBase$1.handle(BehaviorSkinBase.java:96)
    at com.sun.javafx.scene.control.skin.BehaviorSkinBase$1.handle(BehaviorSkinBase.java:89)
    at com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:218)
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
    at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
    at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
    at javafx.event.Event.fireEvent(Event.java:198)
    at javafx.scene.Scene$MouseHandler.process(Scene.java:3757)
    at javafx.scene.Scene$MouseHandler.access$1500(Scene.java:3485)
    at javafx.scene.Scene.impl_processMouseEvent(Scene.java:1762)
    at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2494)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:381)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:295)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$353(GlassViewEventHandler.java:417)
    at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:416)
    at com.sun.glass.ui.View.handleMouseEvent(View.java:555)
    at com.sun.glass.ui.View.notifyMouse(View.java:937)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$147(WinApplication.java:177)
    at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at sun.reflect.misc.Trampoline.invoke(MethodUtil.java:71)
    at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at sun.reflect.misc.MethodUtil.invoke(MethodUtil.java:275)
    at javafx.fxml.FXMLLoader$MethodHandler.invoke(FXMLLoader.java:1769)
    ... 48 more
Caused by: java.lang.NullPointerException
    at mycontrollertest.FXMLmainController.do_something(FXMLmainController.java:33)
    ... 58 more
```

Schuld ist dann also offenbar der Methodenaufruf:
Caused by: java.lang.NullPointerException
    at mycontrollertest.FXMLmainController.do_something(FXMLmainController.java:33)

Auch wenn ich die Methode do_something im Tab1Controller komplett leer lasse, erfolgt der gleiche Fehler.
Der alleinige Aufruf dieser Methode zur Laufzeit macht also diesen Fehler.
Eine NullPointerException bedeutet dann doch, dass entweder tab1Controller oder setlabelcontent zur Laufzeit doch nicht bekannt sind?

Bin ich verrückt oder mein System?


----------



## Knoten (4. Jan 2018)

Damit die Zeilennummern zugeordnet werden können:
Der MainController sieht mittlerweile so aus:

```
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package mycontrollertest;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;

/**
*
* @author hth
*/
public class FXMLmainController implements Initializable {

    @FXML private Button but_main;   
    @FXML private FXMLTab1Controller tab1Controller; 
   
    private UserdataFactory userData = UserdataFactory.getFactory();
   
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        // TODO
    }   

    @FXML
    private void do_something(ActionEvent event) {
        tab1Controller.setlabelcontent("irgendein String");
    }
   
}
```

und der Tab1Controller so:

```
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package mycontrollertest;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.layout.AnchorPane;

/**
* FXML Controller class
*
* @author hth
*/
public class FXMLTab1Controller implements Initializable {

    @FXML
    private AnchorPane tab1;
    @FXML
    private Label label1;
   
    /**
     * Initializes the controller class.
     */
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        // TODO
    }   
    public void setlabelcontent(String inhalt) {
        label1.setText(inhalt);
    }   
}
```


----------



## mrBrown (4. Jan 2018)

Und die fxml dazu?


----------



## Knoten (4. Jan 2018)

Dank für die Geduld und unablässige Hilfe mrBrown!!!

die FXMLmain:


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

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane id="AnchorPane" prefHeight="329.0" prefWidth="308.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="mycontrollertest.FXMLmainController">
   <children>
      <TabPane layoutX="26.0" layoutY="10.0" prefHeight="200.0" prefWidth="200.0" tabClosingPolicy="UNAVAILABLE">
        <tabs>
          <Tab text="tab1">
            <content>
              <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="106.0" prefWidth="200.0">
                     <children>
                        <fx:include source="FXMLTab1.fxml" />
                     </children>
                  </AnchorPane>
            </content>
          </Tab>
          <Tab text="tab2">
            <content>
              <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0">
                     <children>
                        <fx:include source="FXMLTab2.fxml" />
                     </children>
                  </AnchorPane>
            </content>
          </Tab>
        </tabs>
      </TabPane>
      <Button fx:id="but_main" layoutX="126.0" layoutY="290.0" mnemonicParsing="false" onAction="#do_something" text="mainbutton" />
   </children>
</AnchorPane>
```

und die FXMLTab1.fxml:


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

<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane id="AnchorPane" fx:id="tab1" prefHeight="126.0" prefWidth="127.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="mytest.FXMLTab1Controller">
   <children>
      <Label fx:id="label1" layoutX="42.0" layoutY="46.0" prefHeight="17.0" prefWidth="50.0" text="Label" />
   </children>
</AnchorPane>
```

in der FXMLmain hatte ich zu Anfang noch die fx:id von Hand eingetragen, da das in vielen Internettips so beschrieben war, also anstatt
                        <fx:include source="FXMLTab1.fxml" />
das:
                        <fx:include fx:id="tab1" source="FXMLTab1.fxml" />

diese ID ist auch im Scenebuilder auch korrekt eigegeben und dort zu erkennen, aber wenn ich diese so einfüge, dann erhalte ich seit dem NetBean und Java refresh bereits beim Programmstart eine LaufzeitException. 
Also habe ich das wie nun gezeigt ohne die fx:id= Deklaration gemacht, so wie es "make controller" auch machten würde.


----------



## Knoten (4. Jan 2018)

Also Leute, ich gebe auf. Das waren jetzt sicher 20 Stunden ohne, dass ich irgendwie weitergekommen bin.
Ich werde mir wohl ein anderes Softwarekonzept überlegen, das dann mit einem einzigen MonsterController leben kann (Aufsplitten misslingt mir, wie ich hier breit ausgeführt habe).
Vielleicht kommt mir ja im Laufe der Jahre die Erleuchtung...
Danke allen, die sich bemüht haben!


----------



## mrBrown (4. Jan 2018)

Knoten hat gesagt.:


> in der FXMLmain hatte ich zu Anfang noch die fx:id von Hand eingetragen, da das in vielen Internettips so beschrieben war, also anstatt
> <fx:include source="FXMLTab1.fxml" />
> das:
> <fx:include fx:id="tab1" source="FXMLTab1.fxml" />


Die fx:id müsste nötig sein, setz die mal und Versuchs noch mal 

Ich kann’s später erst testen, auf den ersten Blick seh ich aber keinen weiteren Fehler


----------



## knilch (5. Jan 2018)

Hi
Nachfolgend stelle ich dir mal die Code-snippets dar, die das von dir gewünschte Verhalten darstellen:
MainController.java:

```
package test14;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;

public class MainController {
    @FXML
    private Button btnMain; 
    @FXML
    private Tab1Controller tab1Controller;
    @FXML
    private Tab2Controller tab2Controller;
   
    private int counter = 0;

    @FXML
    private void btnMainClicked(ActionEvent event) {
        counter++;
        String text = "Text: " + counter;
        tab1Controller.setLabelText(text);
    }
}
```

MainView.fxml

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

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane id="AnchorPane" prefHeight="329.0" prefWidth="308.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="test14.MainController">
   <children>
      <TabPane layoutX="26.0" layoutY="10.0" prefHeight="200.0" prefWidth="200.0" tabClosingPolicy="UNAVAILABLE">
        <tabs>
          <Tab text="tab1">
            <content>
              <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="106.0" prefWidth="200.0">
                     <children>
                        <fx:include fx:id="tab1" source="TabView1.fxml" />
                     </children>
                  </AnchorPane>
            </content>
          </Tab>
          <Tab text="tab2">
            <content>
              <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0">
                     <children>
                        <fx:include fx:id="tab2" source="TabView2.fxml" />
                     </children>
                  </AnchorPane>
            </content>
          </Tab>
        </tabs>
      </TabPane>
      <Button fx:id="btnMain" layoutX="126.0" layoutY="290.0" onAction="#btnMainClicked" text="mainbutton" />
   </children>
</AnchorPane>
```
Tab1Controller.java

```
package test14;

import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.layout.AnchorPane;

public class Tab1Controller  {
    @FXML
    private AnchorPane anchorPane1;
    @FXML
    private Label label1;
   
    public void setLabelText(String text) {
        label1.setText(text);
    }
}
```
TabView1.fxml

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

<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane fx:id="anchorPane1" prefHeight="126.0" prefWidth="127.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="test14.Tab1Controller">
   <children>
      <Label fx:id="label1" layoutX="42.0" layoutY="46.0" prefHeight="17.0" prefWidth="50.0" text="Label" />
   </children>
</AnchorPane>
```
Tab2Controller.java

```
package test14;

import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
import javafx.scene.layout.AnchorPane;
public class Tab2Controller {
    @FXML
    private AnchorPane anchorPane2;
    @FXML
    private CheckBox checkbox1;
}
```
TabView2.fxml

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

<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane fx:id="anchorPane2" prefHeight="135.0" prefWidth="118.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="test14.Tab2Controller">
   <children>
      <CheckBox fx:id="checkbox1" layoutX="23.0" layoutY="59.0" mnemonicParsing="false" text="CheckBox" />
   </children>
</AnchorPane>
```
MyControllerTest.java

```
package test14;

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

public class MyControllerTest extends Application {
  
    @Override
    public void start(Stage stage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("MainView.fxml"));
        Scene scene = new Scene(root);   
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}
```

Damit sollte das funktionieren.


----------



## Knoten (5. Jan 2018)

Oh Leute, Ihr seid echt die Helden.
Ich habe mir ein neues Projekt test14 angelegt und den Code so übernommen. Das hat jetzt auf Anhieb funktioniert.
Vielen Dank Knilch für die Mühe!
Ich habe dann auch nochmal an meinem vorigen Projekt rumgespielt und die fx:id in das fxml erneut eingetragen, wie von dem geduldigen mrBrown nochmal vorgeschlagen. Und das geht jetzt plötzlich auch, obwohl ich das vormals auch schon drin hatte...

Entweder habe ich immer genau einen anderen Fehler dringehabt und den nicht gesehen oder mein System hat echt nen Schuss (jetzt hängt z.B. der SceneBuilder fast immer... aber das ist eine andere Baustelle).

Egal, die Lösung ist top und ich danke Euch unendlich!!!
Ihr habt ein Bier frei, wenn Ihr mal im Allgäu seit!

Die Hauptschwierigkeit bei diesem eigentlich banalen Problem ist m.E. das Wissen über die Namenskonvention für injizierte Untercontroller (siehe Beitrag #5 von mrBrown, vielen Dank mrBrown!). 
Diesen Hinweis habe ich auch in anderen Forenbeiträgen (bei stackoverflow) gesehen - aber woher wisst Ihr das?
Das muss doch irgendwo in einer offiziellen Dokumentation stehen? Kennt Ihr die Oracle Dokus so genau?
Ich habe 2 aktuelle Java8 Bücher aber in keinem wird auf Interaktion zwischen Controllern eingegangen.

Vielen Dank nochmal, ein Knoten weniger im Hirn...


----------



## knilch (5. Jan 2018)

Danke für deinen Feedback.
Schön das wir helfen konnten.
Zu deinen Fragen betreffend der fx:id und dem Laden.
Wenn du in einem fxml mit fx:include ein Node von einem fxml lädst, dann nennt sich das nested controllers.
Hier ein Link: https://docs.oracle.com/javafx/2/ap.../introduction_to_fxml.html#nested_controllers
Diese Seite zeigt, wie das vorsich geht. Das Problem hierbei kann nun sein (so wie es bei dir der Fall war), wenn keine fx:id="" beim <fx:include..> vorhanden ist oder wenn die fx:id="" zwar da ist, aber nicht auf eine controller-Klasse "gemappt" werden kann (beim Start).
Bei nested controller muss ja keine Instanz vom nested controller erstellt werden. Dies wird beim start der Main-View gemacht. Da du aber evtl. auf die instanz vom nested controller zugreifen möchtest, muss die Controller- Instanz irgenwie erreichbar sein und dies geht nur dadurch, dass die Instanz genau so erstellt werden muss: fx:id + Controller.
Gruss
knilch


----------



## dzim (5. Jan 2018)

Es gibt ne Doku. Ab und an stolpert man mal drüber:
https://docs.oracle.com/javafx/2/api/javafx/fxml/doc-files/introduction_to_fxml.html#fxml_annotation
Die Seite wurde leider nicht auf Java 8 (oder gar 9) geupdated (daher die "2" in der URL), aber gültig ist sie immer noch.


----------



## Knoten (5. Jan 2018)

Super erklärt knilch!  
Diese Seite hätte ich eigentlich auch finden können...
Aber nachdem quasi alles, was mit Java zu tun hat, neu für mich ist, stochere ich halt immer noch größtenteils im Nebel herum.

Vielen Dank nochmal für Eure Hilfsbereitschaft!


----------



## Joob (5. Jan 2018)

Hallo 
bin gerade bei dem gleichen Thema.
Soweit ich das gesehen haben müssen im fxml file soure und include den gleichen Namen haben
und die id muss im obersten Element des subforms benannt werden

Bei dir.
<fx:include fx:id="tab1" source="FXMLTab1.fxml" />
so wäre es besser:
<fx:include fx:id="tab1" source="Tab1.fxml" />


----------



## mrBrown (5. Jan 2018)

Joob hat gesagt.:


> Soweit ich das gesehen haben müssen im fxml file soure und include den gleichen Namen haben
> und die id muss im obersten Element des subforms benannt werden


Häh?



Joob hat gesagt.:


> Bei dir.
> <fx:include fx:id="tab1" source="FXMLTab1.fxml" />
> so wäre es besser:
> <fx:include fx:id="tab1" source="Tab1.fxml" />


Nein - so würde es nicht funktionieren, da es die Datei "Tab1.fxml" nicht gibt.


----------



## Joob (5. Jan 2018)

Geht ja auch anders herum.
Hab ich aus einem youtube video




bei 6:24 kannst du es sehen, 
das erklärt auch besser was ich oben gemeint habe. (bei 3:50)


----------



## mrBrown (5. Jan 2018)

Keine Ahnung was mir das Video sagen soll, aber id und source müssen nicht zueinander passend benannt sein - das wäre auch ziemlich dämlich.

Und bei 3:50 ist rumgehampel mit dem Scenebuilder zu sehen, keine Ahnung wie das erklären soll, was du meinst.


----------



## Knoten (6. Jan 2018)

Ich vermute, Joob meint, dass man das fxml-file bereits so benennen sollte, dass es der fx:id mit Anfangsgroßbuchstaben entspricht.
Kann man sicherlich, ist aber nach meiner jetzigen Erkenntnis nicht notwendig. Ich habe das FXML bewusst vorangestellt, damit ich gleich weiß, um was es geht (macht ein Profi sicher nicht...).

Aber um das Thema abzuschließen für alle, die vor dem selben Problem stehen und auf diesen Thread stoßen:
In meinem Eingangspost hatte ich ja auch den Wunsch geäußert zwischen den Untercontrollern zu interagieren.
Mit dem obigen Ansatz kann der mainController auf die nested Controller zugreifen aber der Tab1Controller nicht auf den Tab2 Controller.
Das kann man jedoch ganz einfach erweitern. Im mainController fügt man in der initialize Methode 2 Zeilen ein:

```
package test14;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;

public class MainController {
    @FXML
    private Button btnMain;
    @FXML
    private Tab1Controller tab1Controller;
    @FXML
    private Tab2Controller tab2Controller;
  
    private int counter = 0;

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        tab1Controller.tab2Controller = tab2Controller; // um der tab2Controller Variablen im Tab1Controller den echten Tab2Controller zuzuweisen
        tab2Controller.tab1Controller = tab1Controller; // hier genauso andersrum
    } 

    @FXML
    private void btnMainClicked(ActionEvent event) {
        counter++;
        String text = "Text: " + counter;
        tab1Controller.setLabelText(text);
    }
}
```

und im Tab1Controller eine Zeile:


```
package test14;

import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.layout.AnchorPane;

public class Tab1Controller  {
    Tab2Controller tab2Controller;  //diese Variablendeklaration
    @FXML
    private AnchorPane anchorPane1;
    @FXML
    private Label label1;
  
    public void setLabelText(String text) {
        label1.setText(text);
    }
}
```

und im Tab2Controller auch eine Zeile:


```
package test14;

import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
import javafx.scene.layout.AnchorPane;
public class Tab2Controller {
    Tab1Controller tab1Controller;  //diese Variablendeklaration
    @FXML
    private AnchorPane anchorPane2;
    @FXML
    private CheckBox checkbox1;
}
```

damit erhält man in beiden Controllern die komplette Palette des anderen Controllers.
Das ist zwar überhaupt nicht im Sinne eines sauberen Programmierstils, da man bei übertriebener Nutzung schnell den Überblick verlieren kann - aber manchmal ist das m.E. doch notwendig und immens nützlich. Für mich zumindest...
Nochmal danke allen, die sich hier so fleißig um mich bemüht haben!


----------



## mrBrown (6. Jan 2018)

Knoten hat gesagt.:


> Ich vermute, Joob meint, dass man das fxml-file bereits so benennen sollte, dass es der fx:id mit Anfangsgroßbuchstaben entspricht.
> Kann man sicherlich, ist aber nach meiner jetzigen Erkenntnis nicht notwendig. Ich habe das FXML bewusst vorangestellt, damit ich gleich weiß, um was es geht (macht ein Profi sicher nicht...).


Das würde schon daran scheitern, dass man sie dann nur einmal einbinden kann, da die id eindeutig sein muss.



Knoten hat gesagt.:


> Das kann man jedoch ganz einfach erweitern. Im mainController fügt man in der initialize Methode 2 Zeilen ein:


Besser wäre es dabei, mit Settern statt direktem Zugriff zu arbeiten


----------

