【问题标题】:JavaFX: 2 independent windows at onceJavaFX:一次 2 个独立的窗口
【发布时间】:2019-04-13 18:45:15
【问题描述】:

我想一次创建 2 个独立的窗口。一个窗口将能够保存一个可观察列表,另一个窗口将显示所选列表对象的属性。我正在尝试将列表视图创建为通用列表,并将其与特定于对象的窗口(例如,客户属性、啤酒属性、商店属性)结合起来。

简而言之:如果用户单击“客户”,它将显示包含所有客户的列表视图,并且第一个客户的属性显示在单独的客户特定窗口中。

如果用户单击“商店”,它会显示相同的列表视图,但会显示商店。商店特定的窗口也会打开,其中包含第一家商店的属性。

我尝试使用 2 个 FXMLLoaders,但由于某种原因我不知道如何使用它们。我在 JavaFX 上相当平庸,所以我什至不知道从哪里开始。这就是我所拥有的,但它似乎是错误的。

 FXMLLoader loader = new FXMLLoader(getClass().getResource("List.fxml"));
 loader.setRoot(this);
 loader.setController(this);
 FXMLLoader loader2 = new FXMLLoader(getClass().getResource("StoreWindow.fxml"));
 loader2.setRoot(this);
 loader2.setController(this);
 try {
        loader.load();
        loader2.load(); 
     } catch (IOException ex) {
        throw new RuntimeException(ex);
     }

【问题讨论】:

  • 在两个不同的 FXML 文件之间共享同一个控制器是不好的做法。您应该创建一个在两个控制器之间共享的模型对象。每个控制器都会观察模型并在对其进行更改时做出反应(通过其他控制器或其他代码)。

标签: java javafx fxml


【解决方案1】:

您基本上必须遵循@Slaw 的说明。创建一个Model。在两个Controllers 之间共享Model。观察模型当前的Customer 并做出相应的反应。 MCVE 下面:

主类:(使用正确的场景加载两个阶段。创建模型并将其传递给两个控制器):

import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

/**
 *
 * @author sedri
 */
public class JavaFXApplication36 extends Application {

    @Override
    public void start(Stage stage) {
        try {
            FXMLLoader listViewFXMLLoader = new FXMLLoader(getClass().getResource("ListViewFXML.fxml"));
            Parent listViewRoot = listViewFXMLLoader.load();
            ListViewController listViewController = listViewFXMLLoader.getController();
            Scene scene1 = new Scene(listViewRoot);
            stage.setScene(scene1);           

            FXMLLoader detailsFXMLLoader = new FXMLLoader(getClass().getResource("DetailsFXML.fxml"));
            Parent detailsRoot = detailsFXMLLoader.load();
            DetailsController detailsController = detailsFXMLLoader.getController();
            Scene scene2 = new Scene(detailsRoot);
            Stage stage2 = new Stage();
            stage2.setScene(scene2);

            DataModel model = new DataModel();
            listViewController.initModel(model);
            detailsController.initModel(model);

            stage.show();
            stage2.show(); 
        } catch (IOException ex) {
            Logger.getLogger(JavaFXApplication36.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

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

}

模型类:(跟上当前的客户和客户的 ObservableList)

import javafx.beans.Observable;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

/**
 *
 * @author sedrick
 */
public class DataModel {
    private final ObservableList<Customer> customerList = FXCollections.observableArrayList(customer -> new Observable[]{customer.nameProperty(), customer.ageProperty()});
    private final ObjectProperty<Customer> currentCustomer = new SimpleObjectProperty();

    public ObjectProperty<Customer> currentCustomerProperty() {
        return currentCustomer;
    }

    public void setCurrentCustomer(Customer currentCustomer) {
        this.currentCustomer.set(currentCustomer);
    }

    public Customer getCurrentCustomer() {
        return this.currentCustomer.get();
    }  


    public ObservableList<Customer> loadCustomers()
    {        
        customerList.add(new Customer("John Doe", 21));
        customerList.add(new Customer("Jane Joe", 20));

        return customerList;
    }
}

客户类别:

import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/**
 *
 * @author sedrick
 */
public class Customer {
    private final StringProperty name = new SimpleStringProperty();
    private final IntegerProperty age = new SimpleIntegerProperty();
    public Customer(String name, int age) {
        this.name.set(name);
        this.age.set(age);
    }

    public String getName()
    {
       return this.name.get();
    }

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

    public StringProperty nameProperty()
    {
        return this.name;
    }

    public int getAge()
    {
       return this.age.get();
    }

    public void setAge(int age)
    {
        this.age.set(age);
    }

    public IntegerProperty ageProperty()
    {
        return this.age;
    }
}

ListView 控制器:(初始化模型,设置 ListView 并观察当前客户属性)

import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;

/**
 *
 * @author sedri
 */
public class ListViewController implements Initializable {
   @FXML private ListView<Customer> listView;

   private DataModel model;



    @Override
    public void initialize(URL url, ResourceBundle rb) {
        // TODO
    }    

    public void initModel(DataModel model)
    {
        // ensure model is only set once:
        if (this.model != null) {
            throw new IllegalStateException("Model can only be initialized once");
        }

        listView.getSelectionModel().selectedItemProperty().addListener((obs, oldCustomer, newCustomer) -> 
            model.setCurrentCustomer(newCustomer));

        model.currentCustomerProperty().addListener((obs, oldCustomer, newCustomer) -> {
            if (newCustomer == null) {
                listView.getSelectionModel().clearSelection();
            } else {
                listView.getSelectionModel().select(newCustomer);
            }
        });

        listView.setCellFactory(lv -> new ListCell<Customer>() {
            @Override
            public void updateItem(Customer customer, boolean empty) {
                super.updateItem(customer, empty);
                if (empty) {
                    setText(null);
                } else {
                    setText("Name: " + customer.getName() +  "  Age: " + customer.getAge());
                }
            }
        });

        listView.setItems(model.loadCustomers());
    }
}

ListView FXML:

<StackPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="javafxapplication36.ListViewController">
   <children>
      <ListView fx:id="listView" prefHeight="200.0" prefWidth="200.0" />
   </children>
</StackPane>

DetailsController:(初始化模型并观察当前客户属性)

import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TextField;

/**
 * FXML Controller class
 *
 * @author sedri
 */
public class DetailsController implements Initializable {
    @FXML TextField tfName, tfAge;

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

     public void initModel(DataModel model) {
        // ensure model is only set once:
        if (this.model != null) {
            throw new IllegalStateException("Model can only be initialized once");
        }

        this.model = model ;

        model.currentCustomerProperty().addListener((observable, oldCustomer, newCustomer) -> {
            if(newCustomer == null){
                tfName.setText("");
                tfAge.setText("");
            }
            else{
                tfName.setText(newCustomer.getName());
                tfAge.setText(Integer.toString(newCustomer.getAge()));
            }
        });
    }
}

详细信息 FXML:

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

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.VBox?>


<VBox alignment="CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="javafxapplication36.DetailsController">
   <children>
      <Label text="Name" />
      <TextField fx:id="tfName" />
      <Label text="Age" />
      <TextField fx:id="tfAge" />
   </children>
   <padding>
      <Insets left="20.0" right="20.0" />
   </padding>
</VBox>

更多信息:

@James D answer on Model-View-Controller(MVC).
GitHub code.

【讨论】:

    【解决方案2】:

    以下是共享同一模型的两个窗口(阶段)的演示。
    演示尽可能简单:一个窗口显示一个列表。第二个窗口动态显示在第一个窗口中选择的项目:

    共享模型保存两个窗口所需的信息。基本上是项目列表和选定项目列表:

    package two_windows;
    
    import javafx.collections.FXCollections;
    import javafx.collections.ObservableList;
    
    public class Model {
    
        private final ObservableList<String> list;
        private ObservableList<String> selected;
    
        Model(){
            list = FXCollections.observableArrayList();
        }
    
        void addMessage(String msg){
            list.add(msg);
        }
    
        ObservableList<String> getMessages(){
            return list;
        }
    
        ObservableList<String> getSelectedMessages(){
            return selected;
        }
    
        void setSelected(ObservableList<String> selected) {
            this.selected = selected;
        }
    }
    

    第一个窗口的内容由List.fxml及其控制器定义:

    <?xml version="1.0" encoding="UTF-8"?>
    
    <?import javafx.scene.layout.Pane?>
    <?import javafx.scene.control.ListView?>
    
    <Pane xmlns="http://javafx.com/javafx/10.0.1" xmlns:fx="http://javafx.com/fxml/1"
     fx:controller="two_windows.ListController">
       <children>
            <ListView fx:id="list" prefHeight="300.0" prefWidth="150.0" />
        </children>
    </Pane>
    

    控制器接受Model,在模型中设置选中项列表,监听并响应模型变化:

    package two_windows;
    
    import java.util.List;
    import javafx.collections.ListChangeListener;
    import javafx.fxml.FXML;
    import javafx.scene.control.ListView;
    import javafx.scene.control.SelectionMode;
    
    public class ListController {
    
        @FXML ListView<String> list;
    
        void setModel(Model model) {
    
            list.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);//allow multiple selection
    
            //sets the selected items of the list to the model 
            model.setSelected(list.getSelectionModel().getSelectedItems());
    
            //listen to changes in model, and respond
            model.getMessages().addListener(
                                            (ListChangeListener<String>) c -> {
                                                c.next();
                                                addElements(c.getAddedSubList());
                                            }
                                        );
        }
    
        private void addElements(List<? extends String> msgList){
    
            for(String msg : msgList){
                list.getItems().add(msg);
            }
        }
    }
    

    第二个窗口的内容与第一个非常相似,由Selected.fxml定义:

    <?xml version="1.0" encoding="UTF-8"?>
    
    <?import javafx.scene.layout.Pane?>
    <?import javafx.scene.control.ListView?>  
    
    <Pane xmlns="http://javafx.com/javafx/10.0.1" xmlns:fx="http://javafx.com/fxml/1"
     fx:controller="two_windows.SelectedController">
       <children>
            <ListView fx:id="selected" prefHeight="300.0" prefWidth="150.0" />
        </children>
    </Pane>
    

    它的控制器,就像另一个控制器一样接受Model 并响应它的变化:

    package two_windows;
    
    import java.util.List;
    import javafx.collections.ListChangeListener;
    import javafx.fxml.FXML;
    import javafx.scene.control.ListView;
    
    public class SelectedController {
    
        @FXML ListView<String> selected;
    
        void setModel(Model model) {
    
            //listen to changes in model, and respond
            model.getSelectedMessages().addListener(
                        (ListChangeListener<String>) c -> {
                            c.next();
                            removeElements(c.getRemoved());
                            addElements(c.getAddedSubList());
                        }
                    );
        }
    
        private void removeElements(List<? extends String> msgList){
    
            for(String msg : msgList){
                selected.getItems().remove(msg);
            }
        }
    
        private void addElements(List<? extends String> msgList){
    
            for(String msg : msgList){
                selected.getItems().add(msg);
            }
        }
    }
    

    将它们放在一起并进行测试:

    package two_windows;
    
    import javafx.application.Application;
    import javafx.fxml.FXMLLoader;
    import javafx.scene.Parent;
    import javafx.scene.Scene;
    import javafx.stage.Stage;
    
    public class TwoWindows extends Application {
    
        private Model model;
    
        @Override
        public void start(Stage primaryStage) throws Exception{
    
            model = new Model();
    
            FXMLLoader listLoader = new FXMLLoader(getClass().getResource("List.fxml"));
            Parent list = listLoader.load();
            ListController listController = listLoader.getController();
            listController.setModel(model);
    
            FXMLLoader selectedLoader = new FXMLLoader(getClass().getResource("Selected.fxml"));
            Parent selected = selectedLoader.load();
            SelectedController selectedController = selectedLoader.getController();
            selectedController.setModel(model);
    
            primaryStage.setScene(new Scene(list));
            primaryStage.setX(350); primaryStage.setY(300);
    
            Stage secondaryStage = new Stage();
            secondaryStage.setScene(new Scene(selected));
            secondaryStage.setX(550); secondaryStage.setY(300);
    
            addMessages();
            primaryStage.show();
            secondaryStage.show();
        }
    
        private void addMessages() {
    
            int counter = 0;
            while(counter < 15) {
                model.addMessage("message number "+ counter++);
            }
        }
    
        public static void main(final String[] args) {
            launch(args);
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-04-17
      • 2011-03-13
      • 2021-06-30
      • 2015-02-25
      • 2018-02-28
      • 2012-03-27
      • 1970-01-01
      相关资源
      最近更新 更多