Przekazywanie parametrów JavaFX FXML

Jak mogę przekazać parametry do dodatkowego okna w javafx? Czy istnieje sposób komunikacji z odpowiednim kontrolerem?

Na przykład: Użytkownik wybiera klienta z TableView i otwiera nowe okno, pokazujące informacje o kliencie.

Stage newStage = new Stage();
try 
{
    AnchorPane page = (AnchorPane) FXMLLoader.load(HectorGestion.class.getResource(fxmlResource));
    Scene scene = new Scene(page);
    newStage.setScene(scene);
    newStage.setTitle(windowTitle);
    newStage.setResizable(isResizable);
    if(showRightAway) 
    {
        newStage.show();
    }
}

newStage to będzie nowe okno. Problem w tym, że nie mogę znaleźć sposobu, aby powiedzieć kontrolerowi, gdzie szukać informacji o kliencie (przekazując id jako parametr).

Jakieś pomysły?
Author: RAnders00, 2013-01-07

8 answers

Zalecane Podejście

Ta odpowiedź wymienia różne mechanizmy przekazywania parametrów do kontrolerów FXML.

W przypadku małych aplikacji Gorąco polecam przekazywanie parametrów bezpośrednio od rozmówcy do kontrolera - jest to proste, proste i nie wymaga dodatkowych ram.

W przypadku większych, bardziej skomplikowanych aplikacji warto sprawdzić, czy chcesz użyć Dependency Injection lub Event Bus mechanizmy w Twojej aplikacji.

Przekazywanie parametrów bezpośrednio z wywołującego do kontrolera

Przekazanie własnych danych do kontrolera FXML poprzez pobranie kontrolera z instancji FXML loader i wywołanie metody na kontrolerze, aby zainicjować go wymaganymi wartościami danych.

Coś w stylu następującego kodu:

public Stage showCustomerDialog(Customer customer) {
  FXMLLoader loader = new FXMLLoader(
    getClass().getResource(
      "customerDialog.fxml"
    )
  );

  Stage stage = new Stage(StageStyle.DECORATED);
  stage.setScene(
    new Scene(
      (Pane) loader.load()
    )
  );

  CustomerDialogController controller = 
    loader.<CustomerDialogController>getController();
  controller.initData(customer);

  stage.show();

  return stage;
}

...

class CustomerDialogController {
  @FXML private Label customerName;
  void initialize() {}
  void initData(Customer customer) {
    customerName.setText(customer.getName());
  }
}

Nowy FXMLLoader jest skonstruowany tak, jak pokazano w przykładowym kodzie, tj. new FXMLLoader(location). Lokalizacja jest adresem URL i można generowanie takiego adresu URL z zasobu FXML przez:

new FXMLLoader(getClass().getResource("sample.fxml"));

uważaj, aby nie używać funkcji obciążenia statycznego w FXMLLoader, lub nie będzie w stanie pobrać kontrolera z instancji loader.

Same instancje FXMLLoader nigdy nie wiedzą nic o obiektach domeny. Nie przekazujesz bezpośrednio konkretnych obiektów domeny aplikacji do konstruktora FXMLLoader, zamiast tego:

  1. Zbuduj FXMLLoader na podstawie znaczników fxml na podanym lokalizacja
  2. Pobierz kontroler z instancji FXMLLoader.
  3. wywołuje metody na pobranym kontrolerze, aby dostarczyć kontrolerowi odniesienia do obiektów domeny.

Ten blog (przez innego pisarza) dostarcza alternatywnego, ale podobnego, przykład .

Ustawianie kontrolera na FXMLLoader

CustomerDialogController dialogController = 
    new CustomerDialogController(param1, param2);

FXMLLoader loader = new FXMLLoader(
    getClass().getResource(
        "customerDialog.fxml"
    )
);
loader.setController(dialogController);

Pane mainPane = (Pane) loader.load();

Możesz skonstruować nowy kontroler w kodzie, przekazując dowolne parametry od rozmówcy do kontrolera konstruktor. Po zbudowaniu kontrolera możesz ustawić go na instancji FXMLLoader zanim wywołasz load() instancja metoda.

aby ustawić kontroler na loaderze (w JavaFX 2.x) nie można również zdefiniować atrybutu fx:controller w pliku fxml.

Ze względu na ograniczenie definicji fx:controller w FXML, osobiście wolę pobranie kontrolera z Fxmlloadera niż ustawienie kontrolera w FXMLLoader.

Kontroler pobiera parametry z zewnętrznej metody statycznej

Ta metoda jest przykładem odpowiedzi Sergeya na JavaFX 2.0 How-to Application.getParameters () w kontrolerze.plik java .

Use Dependency Injection

FXMLLoader obsługuje systemy wtrysku zależności, takie jak Guice, Spring lub Java EE CDI, umożliwiając ustawienie niestandardowej fabryki kontrolera na Fxmlloaderze. Zapewnia to wywołanie zwrotne, które można użyć do utworzenia instancji kontrolera z wartościami zależnymi wprowadzanymi przez odpowiedni system wtrysku zależności. Istnieje próbka integracji FXML z systemem Spring dependency injection (niestety link nie działa, a zawartość zniknęła, jeśli ktoś zna podobny przykład, proszę edytować to pytanie, aby się do niego odwołać), choć jest to nieco bardziej clunkierne niż użycie nowych fabrycznych funkcji niestandardowego kontrolera udostępnionych w JavaFX 2.2.

A bardzo ładne, czyste podejście do iniekcji zależności jest przykładem dopalacza .FX framework z przykładową aplikacją air-hacks, która go używa. dopalacz.fx opiera się na JEE6 javax.inject do wykonania injekcji zależności.

Użyj szyny zdarzeń

Greg Brown, twórca i wykonawca oryginalnej specyfikacji FXML, często sugeruje użycie szyny zdarzeń do komunikacji między instancyjnymi kontrolerami FXML i inne logiki aplikacji.

EventBus jest prostym, ale potężnym API publikuj/Subskrybuj z adnotacjami, które pozwala POJOs komunikować się ze sobą w dowolnym miejscu w JVM bez konieczności odwoływania się do siebie.

Kolejne pytania i Odpowiedzi

W pierwszej metodzie, dlaczego powracasz etap? Metoda może być również void, ponieważ już podajesz polecenie show (); tuż przed return stage;. Jak zaplanować użycie zwracając Etap

Jest to funkcjonalne rozwiązanie problemu. Stage jest zwracany z funkcji showCustomerDialog tak, że odwołanie do niego może być przechowywane przez zewnętrzną klasę, która może chcieć coś zrobić, na przykład ukryć stage na podstawie kliknięcia przycisku w głównym oknie, w późniejszym czasie. Alternatywne, zorientowane obiektowo rozwiązanie może hermetyzować funkcjonalność i odniesienie do etapu wewnątrz obiektu CustomerDialog lub mieć moduł CustomerDialog extend Stage. Pełny przykład zorientowanego obiektowo interfejs do niestandardowego okna dialogowego zawierającego dane FXML, kontrolera i modelu wykracza poza zakres tej odpowiedzi, ale może stanowić wartościowy wpis na blogu dla każdego, kto chce go utworzyć.

Dodatkowe informacje dostarczone przez użytkownika StackOverflow o nazwie @ dzim

przykład dla Spring Boot Dependency Injection

Pytanie Jak to zrobić "w wiosenny sposób", była dyskusja na temat JavaFX 2, którą napisałem w dołączony permalink. Podejście jest nadal ważne i przetestowane w marcu 2016 roku, na Spring Boot v1. 3. 3.Uwolnienie: https://stackoverflow.com/a/36310391/1281217


Czasami możesz chcieć przekazać wyniki z powrotem do rozmówcy, w takim przypadku możesz sprawdzić odpowiedź na powiązane pytanie:

 220
Author: jewelsea,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-11 23:18:21

Javafx.miejsce.Klasa Node ma parę metod setUserData(obiekt) oraz Obiekt getUserData ()

Które można wykorzystać do dodania informacji do węzła.

Więc możesz zadzwonić do page ' a.setUserData (info);

I kontroler może sprawdzić, czy info jest ustawione. W razie potrzeby możesz również użyć ObjectProperty do przesyłania danych do tyłu.

Obserwuj dokumentację tutaj: http://docs.oracle.com/javafx/2/api/javafx/fxml/doc-files/introduction_to_fxml.html Przed phrase " w pierwszej wersji funkcja handleButtonAction() jest oznaczona @FXML, aby umożliwić wywołanie znaczników zdefiniowanych w dokumencie kontrolera. W drugim przykładzie pole przycisku jest adnotowane, aby umożliwić ładującemu ustawienie jego wartości. Metoda initialize() jest podobnie adnotowana."

Więc musisz powiązać kontroler z węzłem i ustawić dane użytkownika do węzła.

 8
Author: Alexander Kirov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-07 00:02:20

Oto przykład przekazywania parametrów do dokumentu fxml przez przestrzeń nazw.

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns="http://javafx.com/javafx/null" xmlns:fx="http://javafx.com/fxml/1">
    <BorderPane>
        <center>
            <Label text="$labelText"/>
        </center>
    </BorderPane>
</VBox>

Define value External Text For namespace variable labelText:

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

import java.io.IOException;

public class NamespaceParameterExampleApplication extends Application {

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

    @Override
    public void start(Stage primaryStage) throws IOException {
        final FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("namespace-parameter-example.fxml"));

        fxmlLoader.getNamespace()
                  .put("labelText", "External Text");

        final Parent root = fxmlLoader.load();

        primaryStage.setTitle("Namespace Parameter Example");
        primaryStage.setScene(new Scene(root, 400, 400));
        primaryStage.show();
    }
}
 6
Author: user1503636,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-12 04:05:07

To działa ..

Pamiętaj, że po pierwszym wydrukowaniu wartości passing otrzymasz null, Możesz go użyć po załadowaniu systemu windows, tak samo dla wszystkiego, co chcesz zakodować dla dowolnego innego komponentu.

Pierwszy Kontroler

try {
                                Stage st = new Stage();
                                 FXMLLoader loader = new FXMLLoader(getClass().getResource("/com/inty360/free/form/MainOnline.fxml"));

                                Parent sceneMain = loader.load();

                                MainOnlineController controller = loader.<MainOnlineController>getController();
                                controller.initVariable(99L);

                                Scene scene = new Scene(sceneMain);
                                st.setScene(scene);
                                st.setMaximized(true);
                                st.setTitle("My App");
                                st.show();
                            } catch (IOException ex) {
                                Logger.getLogger(LoginController.class.getName()).log(Level.SEVERE, null, ex);
                            }

Inny Kontroler

public void initVariable(Long id_usuario){
        this.id_usuario = id_usuario;
        label_usuario_nombre.setText(id_usuario.toString());

    }
 3
Author: diego matos - keke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-28 22:25:12

Zdaję sobie sprawę, że to bardzo stary post i ma już kilka świetnych odpowiedzi, ale chciałem zrobić prosty MCVE, aby zademonstrować takie podejście i pozwolić nowym programistom szybko zobaczyć koncepcję w działaniu.

W tym przykładzie użyjemy 5 plików:

  1. Main.java - służy do uruchomienia aplikacji i wywołania pierwszego kontrolera.
  2. Kontroler1.java - kontroler dla pierwszego FXML układ.
  3. Kontroler2.java - kontroler dla drugiego układu FXML.
  4. Layout1.fxml - układ FXML dla pierwszej sceny.
  5. Layout2.fxml - układ FXML dla drugiej sceny.

wszystkie pliki są wymienione w całości na dole tego postu.

Cel: zademonstrować przekazywanie wartości z Controller1 do Controller2 i odwrotnie.

Program Przepływ:

    Pierwsza scena zawiera a TextField, a Button i a Label. Po kliknięciu Button zostanie załadowane i wyświetlone drugie okno, w tym tekst wprowadzony w TextField. W drugiej scenie pojawia się również TextField, Button i Label. Label wyświetli tekst wprowadzony w TextField w pierwszej scenie.
  • Po wpisaniu tekstu w drugiej scenie TextField i kliknięciu jego Button, pierwsza scena Label jest aktualizowana, aby pokazać wprowadzony tekst.

Jest to bardzo prosta demonstracja i z pewnością mogłaby oznaczać pewną poprawę, ale powinna bardzo jasno przedstawić tę koncepcję.

Sam kod jest również komentowany z pewnymi szczegółami tego, co się dzieje i jak.

Na Kod

Main.java:

import javafx.application.Application;
import javafx.stage.Stage;

public class Main extends Application {

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

    @Override
    public void start(Stage primaryStage) {

        // Create the first controller, which loads Layout1.fxml within its own constructor
        Controller1 controller1 = new Controller1();

        // Show the new stage
        controller1.showStage();

    }
}

Kontroler1.java:

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.stage.Stage;

import java.io.IOException;

public class Controller1 {

    // Holds this controller's Stage
    private final Stage thisStage;

    // Define the nodes from the Layout1.fxml file. This allows them to be referenced within the controller
    @FXML
    private TextField txtToSecondController;
    @FXML
    private Button btnOpenLayout2;
    @FXML
    private Label lblFromController2;

    public Controller1() {

        // Create the new stage
        thisStage = new Stage();

        // Load the FXML file
        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("Layout1.fxml"));

            // Set this class as the controller
            loader.setController(this);

            // Load the scene
            thisStage.setScene(new Scene(loader.load()));

            // Setup the window/stage
            thisStage.setTitle("Passing Controllers Example - Layout1");

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Show the stage that was loaded in the constructor
     */
    public void showStage() {
        thisStage.showAndWait();
    }

    /**
     * The initialize() method allows you set setup your scene, adding actions, configuring nodes, etc.
     */
    @FXML
    private void initialize() {

        // Add an action for the "Open Layout2" button
        btnOpenLayout2.setOnAction(event -> openLayout2());
    }

    /**
     * Performs the action of loading and showing Layout2
     */
    private void openLayout2() {

        // Create the second controller, which loads its own FXML file. We pass a reference to this controller
        // using the keyword [this]; that allows the second controller to access the methods contained in here.
        Controller2 controller2 = new Controller2(this);

        // Show the new stage/window
        controller2.showStage();

    }

    /**
     * Returns the text entered into txtToSecondController. This allows other controllers/classes to view that data.
     */
    public String getEnteredText() {
        return txtToSecondController.getText();
    }

    /**
     * Allows other controllers to set the text of this layout's Label
     */
    public void setTextFromController2(String text) {
        lblFromController2.setText(text);
    }
}

Kontroler2.java:

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.stage.Stage;

import java.io.IOException;

public class Controller2 {

    // Holds this controller's Stage
    private Stage thisStage;

    // Will hold a reference to the first controller, allowing us to access the methods found there.
    private final Controller1 controller1;

    // Add references to the controls in Layout2.fxml
    @FXML
    private Label lblFromController1;
    @FXML
    private TextField txtToFirstController;
    @FXML
    private Button btnSetLayout1Text;

    public Controller2(Controller1 controller1) {
        // We received the first controller, now let's make it usable throughout this controller.
        this.controller1 = controller1;

        // Create the new stage
        thisStage = new Stage();

        // Load the FXML file
        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("Layout2.fxml"));

            // Set this class as the controller
            loader.setController(this);

            // Load the scene
            thisStage.setScene(new Scene(loader.load()));

            // Setup the window/stage
            thisStage.setTitle("Passing Controllers Example - Layout2");

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Show the stage that was loaded in the constructor
     */
    public void showStage() {
        thisStage.showAndWait();
    }

    @FXML
    private void initialize() {

        // Set the label to whatever the text entered on Layout1 is
        lblFromController1.setText(controller1.getEnteredText());

        // Set the action for the button
        btnSetLayout1Text.setOnAction(event -> setTextOnLayout1());
    }

    /**
     * Calls the "setTextFromController2()" method on the first controller to update its Label
     */
    private void setTextOnLayout1() {
        controller1.setTextFromController2(txtToFirstController.getText());
    }

}

Layout1.fxml:

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<AnchorPane xmlns="http://javafx.com/javafx/9.0.1" xmlns:fx="http://javafx.com/fxml/1">
    <VBox alignment="CENTER" spacing="10.0">
        <padding>
            <Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
        </padding>
        <Label style="-fx-font-weight: bold;" text="This is Layout1!"/>
        <HBox alignment="CENTER_LEFT" spacing="10.0">
            <Label text="Enter Text:"/>
            <TextField fx:id="txtToSecondController"/>
            <Button fx:id="btnOpenLayout2" mnemonicParsing="false" text="Open Layout2"/>
        </HBox>
        <VBox alignment="CENTER">
            <Label text="Text From Controller2:"/>
            <Label fx:id="lblFromController2" text="Nothing Yet!"/>
        </VBox>
    </VBox>
</AnchorPane>

Layout2.fxml:

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<AnchorPane xmlns="http://javafx.com/javafx/9.0.1" xmlns:fx="http://javafx.com/fxml/1">
    <VBox alignment="CENTER" spacing="10.0">
        <padding>
            <Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
        </padding>
        <Label style="-fx-font-weight: bold;" text="Welcome to Layout 2!"/>
        <VBox alignment="CENTER">
            <Label text="Text From Controller1:"/>
            <Label fx:id="lblFromController1" text="Nothing Yet!"/>
        </VBox>
        <HBox alignment="CENTER_LEFT" spacing="10.0">
            <Label text="Enter Text:"/>
            <TextField fx:id="txtToFirstController"/>
            <Button fx:id="btnSetLayout1Text" mnemonicParsing="false" text="Set Text on Layout1"/>
        </HBox>
    </VBox>
</AnchorPane>
 3
Author: Zephyr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-26 20:20:45

Musisz utworzyć jedną klasę kontekstową.

public class Context {
    private final static Context instance = new Context();
    public static Context getInstance() {
        return instance;
    }

    private Connection con;
    public void setConnection(Connection con)
    {
        this.con=con;
    }
    public Connection getConnection() {
        return con;
    }

    private TabRoughController tabRough;
    public void setTabRough(TabRoughController tabRough) {
        this.tabRough=tabRough;
    }

    public TabRoughController getTabRough() {
        return tabRough;
    }
}

Musisz po prostu ustawić instancję kontrolera w inicjalizacji za pomocą

Context.getInstance().setTabRough(this);

I możesz go używać z całej aplikacji tylko za pomocą

TabRoughController cont=Context.getInstance().getTabRough();

Teraz możesz przekazać parametr do dowolnego kontrolera z całej aplikacji.

 1
Author: CTN,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-22 14:14:44

Oto przykład użycia kontrolera wstrzykiwanego przez Guice.

/**
 * Loads a FXML file and injects its controller from the given Guice {@code Provider}
 */
public abstract class GuiceFxmlLoader {

   public GuiceFxmlLoader(Stage stage, Provider<?> provider) {
      mStage = Objects.requireNonNull(stage);
      mProvider = Objects.requireNonNull(provider);
   }

   /**
    * @return the FXML file name
    */
   public abstract String getFileName();

   /**
    * Load FXML, set its controller with given {@code Provider}, and add it to {@code Stage}.
    */
   public void loadView() {
      try {
         FXMLLoader loader = new FXMLLoader(getClass().getClassLoader().getResource(getFileName()));
         loader.setControllerFactory(p -> mProvider.get());
         Node view = loader.load();
         setViewInStage(view);
      }
      catch (IOException ex) {
         LOGGER.error("Failed to load FXML: " + getFileName(), ex);
      }
   }

   private void setViewInStage(Node view) {
      BorderPane pane = (BorderPane)mStage.getScene().getRoot();
      pane.setCenter(view);
   }

   private static final Logger LOGGER = Logger.getLogger(GuiceFxmlLoader.class);

   private final Stage mStage;
   private final Provider<?> mProvider;
}

Oto konkretna realizacja ładowarki:

public class ConcreteViewLoader extends GuiceFxmlLoader {

   @Inject
   public ConcreteViewLoader(Stage stage, Provider<MyController> provider) {
      super(stage, provider);
   }

   @Override
   public String getFileName() {
      return "my_view.fxml";
   }
}

Uwaga Ten przykład ładuje widok do środka płyty BoarderPane, która jest rdzeniem sceny w scenie. Jest to nieistotne dla przykładu (szczegóły implementacji mojego konkretnego przypadku użycia), ale postanowiłem zostawić go, ponieważ niektórzy mogą uznać go za przydatny.

 0
Author: jenglert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-20 00:48:45

Możesz zdecydować się na użycie publicznej listy obserwowalnej do przechowywania publicznych danych lub po prostu utworzyć metodę public setter do przechowywania danych i pobierania z odpowiedniego kontrolera

 0
Author: Nospaniol Noah,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-15 12:29:43