【问题标题】:MVC with multiple controllers具有多个控制器的 MVC
【发布时间】:2016-06-21 18:23:35
【问题描述】:

我正在为用 Java/JavaFX 制作应用程序而疯狂。

我有一个包含更多 fxml 文件的项目,每个文件都有一个控制器 (完全像这个例子JavaFX TabPane - One controller for each tab

这是主 fxml 文件 (screentab.fxml),有多个 fx:include

<TabPane fx:id="tabPane" BorderPane.alignment="CENTER">
    <tabs>
     <Tab text="Studenti">
           <content>
           <fx:include fx:id="Studenti" source="tabStudenti.fxml" />
            </content>
           </Tab>
      <Tab text="Percorsi &#10;formativi">
      <content>
      <fx:include fx:id="tabPercorsiFormativi" source="tabPercorsiFormativi.fxml" />
      </content>
           </Tab>
        <Tab text="Calendario &#10;delle&#10;lezioni">
          <content>
         <fx:include fx:id="tabCalendario" source="tabCalendario.fxml" />   
          </content>
           </Tab>
    </tabs>
       </TabPane>

它有效,但我认为模型有问题。

文件下方Main.java

public class Main extends Application {
    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("Manage your student");
        Model model = new Model();
        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("gui/screentab.fxml"));
            BorderPane root = (BorderPane)loader.load();
            SoftwareController controller = loader.getController();
            controller.setModel(model);
            Scene scene = new Scene(root,1000,600);
            scene.getStylesheets().add(getClass().getResource("gui/stylesheet1.css").toExternalForm());
            primaryStage.setScene(scene);
            primaryStage.show();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

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

这是主控制器(SoftwareController.java),方法为setModel()

public class SoftwareController {

    private Model model;

    @FXML private TabPane tabPane;

    @FXML private tabCalendarioController tabCalendarioController;
    @FXML private tabPercorsiFormativiController tabPercorsiFormativiController;
    @FXML private tabStudentiController tabStudentiController;


    public void setModel(Model model) {
        this.model = model;
    }

    @FXML
    void initialize() {
        assert tabPane != null : "fx:id=\"tabPane\" was not injected: check your FXML file 'screentab.fxml'.";
        tabStudentiController.init(this);
        tabCalendarioController.init(this);
        tabPercorsiFormativiController.init(this);
    }

}

model 调用 DAO 并包含从多个控制器调用的所有方法。

public class Model {

    StudentiDAO dao = new StudentiDAO();
    List<Studente> elencoStudenti = new ArrayList<Studente>();

    public List<Studente> elencaStudenti(){
        elencoStudenti= dao.listaStudenti();
        return elencoStudenti;

    }
}

这是控制器之一,它调用模型的方法。

public class tabStudentiController {
    private SoftwareController main;
    private Model model;

    @FXML
    private Button btnElencoStudenti;

    @FXML
    public void doVisualizzaStudenti(ActionEvent event) {

        model.elencaStudenti();

        txtStudenti.appendText("Elenco studenti: \n");
        for(Studente s: lista ){

            txtStudenti.appendText(s.getStud_NOME()+ " "+ s.getStud_COGNOME()+ "\n");
        } 

    }

 public void setModel(Model model) {
            this.model = model ;
        }

    public void init(SoftwareController softwareController) {
    main = softwareController;

}


    @FXML
    void initialize() {
        assert tabPane != null : "fx:id=\"tabPane\" was not injected: check your FXML file 'screentab.fxml'.";
        assert tabStudenti != null : "fx:id=\"tabStudenti\" was not injected: check your FXML file 'screentab.fxml'.";
    }

}

问题: 当我按下启动事件的按钮doVisualizzaStudenti 我有这个错误:

Exception in thread "JavaFX Application Thread" java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
at javafx.fxml.FXMLLoader$MethodHandler.invoke(Unknown Source)
at javafx.fxml.FXMLLoader$ControllerMethodEventHandler.handle(Unknown Source)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(Unknown Source)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(Unknown Source)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(Unknown Source)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(Unknown Source)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
at com.sun.javafx.event.EventUtil.fireEventImpl(Unknown Source)
at com.sun.javafx.event.EventUtil.fireEvent(Unknown Source)
at javafx.event.Event.fireEvent(Unknown Source)
at javafx.scene.Node.fireEvent(Unknown Source)
at javafx.scene.control.Button.fire(Unknown Source)
at com.sun.javafx.scene.control.behavior.ButtonBehavior.mouseReleased(Unknown Source)
at com.sun.javafx.scene.control.skin.BehaviorSkinBase$1.handle(Unknown Source)
at com.sun.javafx.scene.control.skin.BehaviorSkinBase$1.handle(Unknown Source)
at com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(Unknown Source)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(Unknown Source)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(Unknown Source)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(Unknown Source)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(Unknown Source)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)


at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
    at com.sun.javafx.event.EventUtil.fireEventImpl(Unknown Source)
    at com.sun.javafx.event.EventUtil.fireEvent(Unknown Source)
    at javafx.event.Event.fireEvent(Unknown Source)
    at javafx.scene.Scene$MouseHandler.process(Unknown Source)
    at javafx.scene.Scene$MouseHandler.access$1500(Unknown Source)
    at javafx.scene.Scene.impl_processMouseEvent(Unknown Source)
    at javafx.scene.Scene$ScenePeerListener.mouseEvent(Unknown Source)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(Unknown Source)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$354(Unknown Source)
    at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(Unknown Source)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(Unknown Source)
    at com.sun.glass.ui.View.handleMouseEvent(Unknown Source)
    at com.sun.glass.ui.View.notifyMouse(Unknown Source)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$148(Unknown Source)
    at java.lang.Thread.run(Unknown Source)
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at sun.reflect.misc.Trampoline.invoke(Unknown Source)
    at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at sun.reflect.misc.MethodUtil.invoke(Unknown Source)
    ... 57 more
Caused by: java.lang.NullPointerException
    at software.tabStudentiController.doVisualizzaStudenti(tabStudentiController.java:36)
    ... 66 more

其中tabStudentiController.java:36model.elencaStudenti() 所在的行。

我哪里做错了? 请帮助我,对不起我的英语不好!

我使用回调更改了main,并在每个嵌套控制器中添加了setModel()。 但是现在我有这个错误:

`javafx.fxml.LoadException: 
/C:/software/gui/screentab.fxml
at javafx.fxml.FXMLLoader.constructLoadException(Unknown Source)
at javafx.fxml.FXMLLoader.loadImpl(Unknown Source)
at javafx.fxml.FXMLLoader.loadImpl(Unknown Source)
at javafx.fxml.FXMLLoader.load(Unknown Source)
at software.Main.start(Main.java:43)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$162(Unknown Source)
at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$175(Unknown Source)
at com.sun.javafx.application.PlatformImpl.lambda$null$173(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl.lambda$runLater$174(Unknown Source)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(Unknown Source)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$148(Unknown Source)
at java.lang.Thread.run(Unknown Source)
Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at sun.reflect.misc.Trampoline.invoke(Unknown Source)
at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at sun.reflect.misc.MethodUtil.invoke(Unknown Source)
... 13 more
Caused by: java.lang.NullPointerException
at software.SoftwareController.initialize(SoftwareController.java:42)
... 22 more`

其中Main.java:43BorderPane root = (BorderPane)loader.load() and SoftwareController.java:42 is: tabStudentiController.init(this);

我又哪里错了?

【问题讨论】:

  • 你能在tabStudentiController 中显示从SoftwareController.initialize() 调用的init(...) 方法吗? model 在哪里初始化 tabStudentiController
  • 当然,抱歉。最初我写了这个,但后来我删除了它,因为它不起作用。我在tabStudentiController 的初始帖子中添加了缺少的代码行。

标签: model-view-controller javafx controller


【解决方案1】:

我没有看到您为“嵌套控制器”调用setModel(...) 的任何地方(例如tabStudentiController)。由于您从未初始化模型,因此您会在 model.elencaStudenti() 处获得空指针异常。

您可能希望在 SoftwareController 上设置模型时初始化每个嵌套控制器的模型:

public class SoftwareController {

    private Model model;

    @FXML private TabPane tabPane;

    @FXML private tabCalendarioController tabCalendarioController;
    @FXML private tabPercorsiFormativiController tabPercorsiFormativiController;
    @FXML private tabStudentiController tabStudentiController;


    public void setModel(Model model) {
        this.model = model;
        tabStduentiController.setModel(model);
        tabCalendarioController.setModel(model);
        tabPercorsiFormativiController.setModel(model);
    }

    @FXML
    void initialize() {
        assert tabPane != null : "fx:id=\"tabPane\" was not injected: check your FXML file 'screentab.fxml'.";
        tabStudentiController.init(this);
        tabCalendarioController.init(this);
        tabPercorsiFormativiController.init(this);
    }

}

另一种方法

也许更简洁的方法是让所有控制器类都将模型的引用作为构造函数参数。这样,您就可以保证每个控制器在创建后立即拥有一个模型实例:

public class SoftwareController {

    private Model model;

    @FXML private TabPane tabPane;

    @FXML private tabCalendarioController tabCalendarioController;
    @FXML private tabPercorsiFormativiController tabPercorsiFormativiController;
    @FXML private tabStudentiController tabStudentiController;

    public SoftwareController(Model model) {
        this.model = model ;
    }

    @FXML
    void initialize() {
        assert tabPane != null : "fx:id=\"tabPane\" was not injected: check your FXML file 'screentab.fxml'.";
        tabStudentiController.init(this);
        tabCalendarioController.init(this);
        tabPercorsiFormativiController.init(this);
    }

}

public class tabStudentiController {
    private SoftwareController main;
    private Model model;

    @FXML
    private Button btnElencoStudenti;

    public tabStudentiController(Model model) {
        this.model = model ;
    }

    @FXML
    public void doVisualizzaStudenti(ActionEvent event) {

        model.elencaStudenti();

        txtStudenti.appendText("Elenco studenti: \n");
        for(Studente s: lista ){

            txtStudenti.appendText(s.getStud_NOME()+ " "+ s.getStud_COGNOME()+ "\n");
        } 

    }


    public void init(SoftwareController softwareController) {
        main = softwareController;

    }


    @FXML
    void initialize() {
        assert tabPane != null : "fx:id=\"tabPane\" was not injected: check your FXML file 'screentab.fxml'.";
        assert tabStudenti != null : "fx:id=\"tabStudenti\" was not injected: check your FXML file 'screentab.fxml'.";
    }

}

对于其他控制器也是如此。

默认情况下,FXMLLoader 调用控制器类的默认(无参数)构造函数来创建控制器实例。由于您不再有这样的构造函数,您需要告诉FXMLLoader 如何创建控制器实例,您可以使用控制器工厂来完成:

public class Main extends Application {
    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("Manage your student");
        Model model = new Model();

        // the controller factory is just a function mapping a Class 
        // to a controller instance:

        Callback<Class<?>, Object> controllerFactory = type -> {
            try {
                for (Constructor<?> c : type.getConstructors()) {
                    // look for a constructor taking a single parameter of type Model:
                    if (c.getParameterCount()==1 && c.getParameterTypes()[0]==Model.class) {
                        return c.newInstance(model);
                    }
                }
                // no suitable constructor found, just use default:
                return type.newInstance();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        };

        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("gui/screentab.fxml"));
            loader.setControllerFactory(controllerFactory);
            BorderPane root = (BorderPane)loader.load();
            Scene scene = new Scene(root,1000,600);
            scene.getStylesheets().add(getClass().getResource("gui/stylesheet1.css").toExternalForm());
            primaryStage.setScene(scene);
            primaryStage.show();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

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

【讨论】:

  • 非常感谢您的回答,但还不行!我在BorderPane root = (BorderPane)loader.load(); 上有一个错误(我用新的错误更新了第一篇文章。)
  • 您的fx:id 与控制器名称不匹配。你需要&lt;fx:include fx:id="tabStudenti" source="tabStudenti.fxml" /&gt; 而不是&lt;fx:include fx:id="Studenti" source="tabStudenti.fxml" /&gt;
【解决方案2】:

这是使用controllerFactory 的少数示例之一。这是一个负责创建控制器实例的工厂,您可以在这里使用它。为所有需要访问Model 的控制器添加一个接口,并在创建后使用该接口中的方法将模型传递给控制器​​实例。

优于Passing Parameters JavaFX FXML 中描述的其他方法

  • 您不需要通过控制器层次结构传递Model
  • 您可以决定 fxml 文件中使用的控制器
  • Model 实例在调用 initialize 时可用。

示例

modelinjection 包内的所有内容)

public class ModelInjectionControllerFactory implements Callback <Class<?>, Object> {

    private final Model model;

    public ModelInjectionControllerFactory(Model model) {
        this.model = model;
    }

    @Override
    public Object call(Class<?> param) {
        try {
            // create controller using default constructor
            Object controller = param.newInstance();

            // inject model, if needed
            if (controller instanceof ModelInjectionTarget) {
                ((ModelInjectionTarget) controller).injectModel(model);
            }
            return controller;
        } catch (IllegalAccessException | InstantiationException ex) {
            throw new IllegalArgumentException("Could not initialize "+ param.getSimpleName()+" using the default constructor", ex);
        }
    }

}
public interface ModelInjectionTarget {

    void injectModel(Model model);

}

public class Model {

    @Override
    public String toString() {
        return "I am your model";
    }

}
<AnchorPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="modelinjection.ParentController">
    <fx:include source="child.fxml"/>
</AnchorPane>
<AnchorPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="modelinjection.ChildController">
</AnchorPane>
public class ParentController {
}

public class ChildController implements Initializable, ModelInjectionTarget {

    /**
     * Initializes the controller class.
     */
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        System.out.println("this is my model: " +model);
    }    

    private Model model;

    @Override
    public void injectModel(Model model) {
        this.model = model;
    }

}

用法

Model model = new Model();
ModelInjectionControllerFactory controllerFactory = new ModelInjectionControllerFactory(model);

FXMLLoader loader = new FXMLLoader(ModelInjectionControllerFactory.class.getResource("parent.fxml"));
loader.setControllerFactory(controllerFactory);

Scene scene = new Scene(loader.load());

【讨论】:

  • 也许更好:在模型字段上使用注释(例如@Inject private Model model ;)而不是强制控制器实现接口......这基本上就是 afterburner.fx 的工作方式。不过,这与我的回答几乎没有什么不同?
  • @James_D: 一个新的注解还是一个 javaEE 的注解?就我个人而言,我会选择@FXML,因为目的足够相似,也就是说,如果我想使用反射来传递Model
  • 新的或javax.inject.Inject。您可以使用@FXML,但它可能会让人感到困惑,因为您希望看到模型定义为 FXML 元素(并且可以想象,您可能希望单独支持该使用)。或者,当然,只需使用 afterburner.fx,它已经实现了所有这些。
【解决方案3】:

出现错误是因为您尚未实例化模型。解决此问题的最简单方法是制作 Model Singleton

public class Model {

    private static final Model model;
    StudentiDAO dao;
    List<Studente> elencoStudenti;

    private Model(){
        dao = new StudentiDAO();
        elencoStudenti = new ArrayList<Studente>();
    }

    public static Model getInstance(){
        if(model == null){
            model = new Model();
        }

        return model;
    }

    public List<Studente> elencaStudenti(){
        elencoStudenti= dao.listaStudenti();
        return elencoStudenti;
    }
}

您还可以为每个变量提供 getter 和 setter 方法。如果您现在想使用模型,您只需要调用

Model model = Model.getInstance();

然后可以通过您的方法访问所有变量

model.elencaStudenti();

在我看来,这是一个更好的解决方案,而不是在每个班级都拉动模型。当使用单例时,您的模型可以从任何地方访问。我的应用程序总共有 50 多个控制器,而且效果很好。在模型中,我还使用了PropertyBindings,它非常方便。我觉得这种方式也符合MVC思想。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-07
    • 2021-04-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多