JavaFX - 我无法更改 TextField 的文本,除非我通过这种方法进行更改,而且它实际上只发生在这种方法中,在我的控制器中 class

JavaFX - I cannot change the text of a TextField unless I do it from this one method and it really only happens in this method, in my controller class

我的程序真的很简单,只有两个场景,第二个场景只是说“这是第二个场景”,在第一个场景中你必须 select 一个文件夹,然后它会出现在一个 un -可编辑的文本字段。您可以使用后退和下一步按钮在这两个场景之间来回切换。问题是,当我转到第二个场景然后返回第一个场景时,TextField 是空的,而它实际上应该显示我之前 select 编辑的文件夹的路径,但是当我重新 select 它再次显示该文件夹,但每次我更改场景时,它都会消失。 TextField 的FX:id 是textfield。我尝试这样做,以便它再次将文本设置为文件夹的路径,但这次是在后退按钮的“后退”方法中。但如果出于某种原因它不在 OpenFolder 方法中,这将永远不起作用,它在其他任何地方都不起作用。我想知道为什么它只适用于 OpenFolder 方法而不适用于其他任何地方。这是我的控制器 class:

package sample;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;
import javafx.stage.DirectoryChooser;
import javafx.stage.Stage;

import java.io.File;
import java.io.IOException;
import javafx.event.ActionEvent;

public class Controller {

private Stage stage;
private Scene scene;
private Parent root;

public Controller() {

}
@FXML
public void Next(ActionEvent event) throws IOException {

    Parent root = FXMLLoader.load(getClass().getResource("scene2.fxml"));
    stage = ((Stage)((Node)event.getSource()).getScene().getWindow()); 
    scene = new Scene(root);
    stage.setScene(scene);
    stage.show();
}
@FXML
public void Back(ActionEvent event) throws IOException{
    Parent root = FXMLLoader.load(getClass().getResource("scene1.fxml"));
    stage = ((Stage)((Node)event.getSource()).getScene().getWindow()); 
    scene = new Scene(root);
    stage.setScene(scene);
    stage.show();
}

@FXML
private TextField textfield = new TextField();
@FXML
private AnchorPane anchorid1;
@FXML
public static File thefolder;
@FXML
DirectoryChooser SongsOpener;
@FXML
public File OpenFolder(){
    final DirectoryChooser SongsOpener = new DirectoryChooser();
    stage = (Stage) anchorid1.getScene().getWindow();
    thefolder = SongsOpener.showDialog(stage);
    if (thefolder != null){
        textfield.setText(thefolder.getAbsolutePath());
    }
    return thefolder;
}
}

这是我的 FXML:

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

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Font?>
<?import javafx.scene.text.Text?>

<AnchorPane fx:id="anchorid1" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="411.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
   <children>
      <Text layoutX="326.0" layoutY="76.0" strokeType="OUTSIDE" strokeWidth="0.0" text="This is scene 1">
         <font>
            <Font size="33.0" />
         </font>
      </Text>
      <Button layoutX="939.0" layoutY="373.0" mnemonicParsing="false" onAction="#Next" text="Next" />
      <Label layoutX="63.0" layoutY="117.0" prefHeight="18.0" prefWidth="122.0" text="Songs folder :">
         <font>
            <Font size="20.0" />
         </font></Label>
      <Button layoutX="888.0" layoutY="121.0" mnemonicParsing="false" onAction="#OpenFolder">
         <graphic>
            <ImageView fitHeight="18.0" fitWidth="17.0" pickOnBounds="true" preserveRatio="true">
               <image>
                  <Image url="@../toppng.com-folder-icon-png-transparent-black-and-white-folder-ico-1589x1366.png" />
               </image>
            </ImageView>
         </graphic>
      </Button>
      <TextField fx:id="textfield" layoutX="190.0" layoutY="119.0" prefHeight="23.0" prefWidth="685.0" />
   </children>
</AnchorPane>

此处显示了我 select编辑文件夹后 TextField 中的路径 image

但是当我去下一个场景回来时,它就消失了:image

每次加载 FXML 文件时,FXMLLoader 都会创建一组与 FXML 文件中的元素相对应的新控件。所以如果你重新加载第一个视图,你会得到一个新的 TextField,它显然不包含与以前的 TextField 相同的文本,除非你明确设置文本。

因此您可以避免每次都重新加载 FXML 文件(下面的第一个解决方案),或者创建一种机制,在每次加载 FXML 时使用正确的值更新文本字段(下面的第二个解决方案,使用模型和绑定以更新文本字段)。

另请注意,绝对没有理由在每次更改视图时都创建新的 Scene。只需使用一个场景并替换其 root.

每个视图加载一次的解决方案

所以解决这个问题的一种方法是只加载每个 FXML 一次,然后安排在控制器中的两个视图之间切换:

App.java:

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 App extends Application {


    @Override
    public void start(Stage stage) throws IOException {
        FXMLLoader loader1 = new FXMLLoader(getClass().getResource("View1.fxml"));
        Parent view1 = loader1.load();
        
        FXMLLoader loader2 = new FXMLLoader(getClass().getResource("View2.fxml"));
        Parent view2 = loader2.load();
        
        Scene scene = new Scene(view1);
        
        View1Controller controller1 = loader1.getController();
        View2Controller controller2 = loader2.getController();
        
        controller1.setOnNext(() -> scene.setRoot(view2));
        controller2.setOnBack(() -> scene.setRoot(view1));
        
        stage.setScene(scene);
        stage.show();
    }


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

}

View1Controller.java:

import java.io.File;

import javafx.fxml.FXML;
import javafx.scene.control.TextField;
import javafx.stage.DirectoryChooser;
import javafx.stage.Stage;

public class View1Controller {

    private Runnable onNext = () -> {};
    
    @FXML
    private TextField textfield ;
    
    public void setOnNext(Runnable onNext) {
        this.onNext = onNext ;
    }
    
    @FXML
    private void next() {
        onNext.run();
    }
    
    @FXML
    public void openFolder(){
        final DirectoryChooser songsOpener = new DirectoryChooser();
        Stage stage = (Stage) textfield.getScene().getWindow();
        File thefolder = songsOpener.showDialog(stage);
        if (thefolder != null){
            textfield.setText(thefolder.getAbsolutePath());
        }
    }
}

View2Controller.java

import javafx.fxml.FXML;

public class View2Controller {
    
    private Runnable onBack = () -> {} ;
    
    public void setOnBack(Runnable onBack) {
        this.onBack = onBack ;
    }
    
    @FXML
    private void back() {
        onBack.run();
    }

}

View1.fxml:

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

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Font?>
<?import javafx.scene.text.Text?>

<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="411.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.jamesd.examples.switchviews.View1Controller">
   <children>
      <Text layoutX="326.0" layoutY="76.0" strokeType="OUTSIDE" strokeWidth="0.0" text="This is scene 1">
         <font>
            <Font size="33.0" />
         </font>
      </Text>
      <Button layoutX="939.0" layoutY="373.0" mnemonicParsing="false" onAction="#next" text="Next" />
      <Label layoutX="63.0" layoutY="117.0" prefHeight="18.0" prefWidth="122.0" text="Songs folder :">
         <font>
            <Font size="20.0" />
         </font></Label>
      <Button layoutX="888.0" layoutY="121.0" mnemonicParsing="false" onAction="#openFolder" text="Open" />
  
      <TextField fx:id="textfield" layoutX="190.0" layoutY="119.0" prefHeight="23.0" prefWidth="685.0" />
   </children>
</AnchorPane>

View2.fxml:

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

<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.HBox?>


<HBox alignment="CENTER" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.jamesd.examples.switchviews.View2Controller">
   <children>
      <Button mnemonicParsing="false" onAction="#back" text="Back" />
   </children>
</HBox>

使用模型存储状态的解决方案

另一种方法是创建一个包含所需数据的模型,然后将文本字段中的文本绑定到模型中适当的 属性。然后将相同的模型实例传递给每个控制器。

例如:

ViewState.java:

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

public class ViewState {

    private final StringProperty selectedFolder = new SimpleStringProperty();

    public final StringProperty selectedFolderProperty() {
        return this.selectedFolder;
    }
    

    public final String getSelectedFolder() {
        return this.selectedFolderProperty().get();
    }
    

    public final void setSelectedFolder(final String selectedFolder) {
        this.selectedFolderProperty().set(selectedFolder);
    }
    
}

然后在 App.java:

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 App extends Application {


    @Override
    public void start(Stage stage) throws IOException {
        FXMLLoader loader1 = new FXMLLoader(getClass().getResource("View1.fxml"));
        Parent view1 = loader1.load();
        
        
        ViewState viewState = new ViewState();
        
        View1Controller controller = loader1.getController();
        controller.setViewState(viewState);
        
        Scene scene = new Scene(view1);

        
        stage.setScene(scene);
        stage.show();
    }


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

}

View1Controller.java:

import java.io.File;
import java.io.IOException;

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.control.TextField;
import javafx.stage.DirectoryChooser;
import javafx.stage.Stage;

public class View1Controller {

    
    @FXML
    private TextField textfield ;
    

    private ViewState viewState ;
    
    public void setViewState(ViewState viewState) {
        this.viewState = viewState ;
        textfield.textProperty().bindBidirectional(viewState.selectedFolderProperty());
    }
    
    @FXML
    private void next() throws IOException {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("View2.fxml"));
        Parent root = loader.load();
        textfield.getScene().setRoot(root);
        
        View2Controller controller = loader.getController();
        controller.setViewState(viewState);
    }
    
    @FXML
    public void openFolder(){
        final DirectoryChooser songsOpener = new DirectoryChooser();
        Stage stage = (Stage) textfield.getScene().getWindow();
        File thefolder = songsOpener.showDialog(stage);
        if (thefolder != null){
            textfield.setText(thefolder.getAbsolutePath());
        }
    }
}

View2Controller:

import java.io.IOException;

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;

public class View2Controller {
    
    private ViewState viewState ;
    
    @FXML
    private Parent root ;
    
    public void setViewState(ViewState viewState) {
        this.viewState = viewState;
    }
    
    @FXML
    private void back() throws IOException {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("View1.fxml"));
        Parent newRoot = loader.load();
        
        this.root.getScene().setRoot(newRoot);
        
        View1Controller controller = loader.getController();
        controller.setViewState(viewState);
    }

}

FXML 文件的唯一变化是向第二个视图中的根元素添加 fx:id

View2.fxml:

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

<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.HBox?>


<HBox fx:id="root" alignment="CENTER" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.jamesd.examples.switchviews.View2Controller">
   <children>
      <Button mnemonicParsing="false" onAction="#back" text="Back" />
   </children>
</HBox>